Gaussian Blur Filter Shader

Image Enhancements | Saturday 11 October 2008 9:01 pm

There are different ways to perform blur and this is one of the most common way to do it in a shader. It’s a two step method with first a horizontal blur and then a vertical blur. By splitting the work in two directions (two passes) you can save a lot of computation.

The method can be divided in the following parts:

  1. Render the scene you want to blur to a texture (could be downsampled)
  2. Render a screen aligned quad with the horizontal blur shader to a texture
  3. Render a screen aligned quad with the vertical blur shader to the screen or texture depending on what you want to use it for

The following image shows how the blur works when splitted up in two directions.

Separable blur filter

Here’s the horizontal blur shader.

Vertex Shader (GLSL) . This shader screen align a quad with width 1. Any method to render a screen aligned quad will work. So you’re free to use other shaders.

varying vec2 vTexCoord;
 
// remember that you should draw a screen aligned quad
void main(void)
{
   gl_Position = ftransform();;
  
   // Clean up inaccuracies
   vec2 Pos;
   Pos = sign(gl_Vertex.xy);
 
   gl_Position = vec4(Pos, 0.0, 1.0);
   // Image-space
   vTexCoord = Pos * 0.5 + 0.5;
}

Fragment Shader (GLSL) 

uniform sampler2D RTScene; // the texture with the scene you want to blur
varying vec2 vTexCoord;
 
const float blurSize = 1.0/512.0; // I've chosen this size because this will result in that every step will be one pixel wide if the RTScene texture is of size 512x512
 
void main(void)
{
   vec4 sum = vec4(0.0);
 
   // blur in y (vertical)
   // take nine samples, with the distance blurSize between them
   sum += texture2D(RTScene, vec2(vTexCoord.x - 4.0*blurSize, vTexCoord.y)) * 0.05;
   sum += texture2D(RTScene, vec2(vTexCoord.x - 3.0*blurSize, vTexCoord.y)) * 0.09;
   sum += texture2D(RTScene, vec2(vTexCoord.x - 2.0*blurSize, vTexCoord.y)) * 0.12;
   sum += texture2D(RTScene, vec2(vTexCoord.x - blurSize, vTexCoord.y)) * 0.15;
   sum += texture2D(RTScene, vec2(vTexCoord.x, vTexCoord.y)) * 0.16;
   sum += texture2D(RTScene, vec2(vTexCoord.x + blurSize, vTexCoord.y)) * 0.15;
   sum += texture2D(RTScene, vec2(vTexCoord.x + 2.0*blurSize, vTexCoord.y)) * 0.12;
   sum += texture2D(RTScene, vec2(vTexCoord.x + 3.0*blurSize, vTexCoord.y)) * 0.09;
   sum += texture2D(RTScene, vec2(vTexCoord.x + 4.0*blurSize, vTexCoord.y)) * 0.05;
 
   gl_FragColor = sum;
}

And here’s the vertical blur shader.

Vertex Shader (GLSL) (the same as for the blur in horizontal direction)

varying vec2 vTexCoord;
 
// remember that you should draw a screen aligned quad
void main(void)
{
   gl_Position = ftransform();;
  
   // Clean up inaccuracies
   vec2 Pos;
   Pos = sign(gl_Vertex.xy);
 
   gl_Position = vec4(Pos, 0.0, 1.0);
   // Image-space
   vTexCoord = Pos * 0.5 + 0.5;
}

Fragment Shader (GLSL) 

uniform sampler2D RTBlurH; // this should hold the texture rendered by the horizontal blur pass
varying vec2 vTexCoord;
 
const float blurSize = 1.0/512.0;
 
void main(void)
{
   vec4 sum = vec4(0.0);
 
   // blur in y (vertical)
   // take nine samples, with the distance blurSize between them
   sum += texture2D(RTBlurH, vec2(vTexCoord.x, vTexCoord.y - 4.0*blurSize)) * 0.05;
   sum += texture2D(RTBlurH, vec2(vTexCoord.x, vTexCoord.y - 3.0*blurSize)) * 0.09;
   sum += texture2D(RTBlurH, vec2(vTexCoord.x, vTexCoord.y - 2.0*blurSize)) * 0.12;
   sum += texture2D(RTBlurH, vec2(vTexCoord.x, vTexCoord.y - blurSize)) * 0.15;
   sum += texture2D(RTBlurH, vec2(vTexCoord.x, vTexCoord.y)) * 0.16;
   sum += texture2D(RTBlurH, vec2(vTexCoord.x, vTexCoord.y + blurSize)) * 0.15;
   sum += texture2D(RTBlurH, vec2(vTexCoord.x, vTexCoord.y + 2.0*blurSize)) * 0.12;
   sum += texture2D(RTBlurH, vec2(vTexCoord.x, vTexCoord.y + 3.0*blurSize)) * 0.09;
   sum += texture2D(RTBlurH, vec2(vTexCoord.x, vTexCoord.y + 4.0*blurSize)) * 0.05;
 
   gl_FragColor = sum;
}

And this is a scene without blur.

Scene before bluring

And this is the same scene but with gaussian blur.

Blured Scene

You can tweak the blur radius to change the size of the blur and change the number of samples in each direction.

Cost for separable blur shader : 9+9 = 18 (number of texture samples)
Cost for shader if blured in one pass: 9*9 = 81 (number of texture samples)
So splitting up in two directions saves a lot.

The gaussian weights are calculated accordingly to the gaussian function with standard deviation of 2.7. These calculations were done in the excel document found [2].

Here’s a description of blur shaders and other image processing shaders in DirectX:
[1] http://ati.amd.com/developer/shaderx/ShaderX2_AdvancedImageProcessing.pdf

More info about calculating weights for separable gaussian blur:
[2] http://theinstructionlimit.com/?p=40

Please share:
  • Print this article!
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Current
  • LinkedIn
  • Live
  • MySpace
  • Netvibes
  • StumbleUpon
  • Twitter
  • Reddit
  • Technorati
  • Yahoo! Bookmarks

14 Comments »

  1. Comment by Satoshi — July 17, 2009 @ 11:36 am

    Why vertex shader?
    Why vTexCoord = Pos * 0.5 + 0.5;

  2. Comment by Satoshi — July 17, 2009 @ 7:16 pm

    Picture is wrong!
    middle pixel is too dark. blur = 5/25

    Sorry for my bad english

  3. Comment by admin — October 28, 2009 @ 9:39 am

    Sorry for confusing you, this vertex shader might not be useful for everyone since it’s goal is to align a simple quad to the view and stretch it to cover the entire screen. If you’re already sending in a screen aligned quad, you can skip the vertex shader!

  4. Comment by Justin — November 10, 2009 @ 8:14 am

    Isn’t this just a box filter blur? Gaussian blurs are weighted based on their distance from the center. In your pixel shader you just take the average of all the pixels. This works fine in practice, but calling it a Gaussian blur is misleading.

  5. Comment by Justin — November 10, 2009 @ 8:17 am

    I take that back. You ARE weighting the neighbors based on their distance. Sorry about that. However, their weight is based on a linear decay, not a Gaussian function.

  6. Comment by admin — November 10, 2009 @ 9:53 pm

    Yes, I’m using the distance, but as you say, the weights are not really gaussian weights. But they are actually close enough to look good. I will add the “true” gaussian weights to this example since it’s more correct that way.

    Thanks for the feedback!

  7. Comment by Promit Roy — August 15, 2010 @ 11:19 pm

    Your filter weights are wrong somewhere.
    2 * (0.05 + 0.09 + 0.12 + 0.15) + 0.16 = 0.98
    This is probably the reason for #2’s darkness problem, and if you try to use this blur with VSM for example the results will be completely wrong.

  8. Comment by John — March 20, 2011 @ 5:19 pm

    Thanks for this, I’m looking for a good blur effect to blur a particle system so that I can make stuff like fire, fireworks, pulsating clouds etc …. I reckon this might do the trick!

  9. Comment by Luke — April 26, 2011 @ 9:25 pm

    I am a student and doing Image Processing Coursework

    Do you apply the Gaussian mask to the RGB components ot the YCbCr components of the image?

    I have tried and both seem to produce the same result.

  10. Comment by homer — September 8, 2011 @ 11:19 pm

    What if i need to use different radius size?

  11. Comment by Florian Märkl — November 2, 2011 @ 4:05 pm

    In both vertical and horizontal fragment shaders you have written:
    // blur in y (vertical)
    You should change that to avoid confusion

  12. Comment by Chris — November 24, 2011 @ 4:41 pm

    What happens at the borders when texture2D is trying to read pixels outside the image? For example when vTexCoord.x is 0 and you call texture2D to access pixels at vTexCoord.x – 4 * blursize? What is the texture2D return value in this case and how does it contribute to the filter kernel?

  13. Pingback by ND2D – Blur - nulldesign // lars gerckens — December 7, 2011 @ 3:53 pm

    [...] take the result and blur it vertically. This way, you have to take only 9 + 9 = 18 samples (see Article: Gaussian Blur Shader). Implementing it this way, means we have to do a horizontal blur, write the output to a texture [...]

  14. Comment by Arely Kamps — January 15, 2012 @ 1:58 pm

    I am so grateful for your blog post.Much thanks again. Cool.

RSS feed for comments on this post. TrackBack URI

Leave a comment