How do I use a GLSL shader to apply a radial blur to an entire scene?

Patrick Oscity picture Patrick Oscity · Jan 2, 2011 · Viewed 12.6k times · Source

I have a radial blur shader in GLSL, which takes a texture, applies a radial blur to it and renders the result to the screen. This works very well, so far.

The problem is, that this applies the radial blur to the first texture in the scene. But what I actually want to do, is to apply this blur to the whole scene.

What is the best way to achieve this functionality? Can I do this with only shaders, or do I have to render the scene to a texture first (in OpenGL) and then pass this texture to the shader for further processing?

// Vertex shader

varying vec2 uv;

void main(void)
{
    gl_Position = vec4( gl_Vertex.xy, 0.0, 1.0 );
    gl_Position = sign( gl_Position );
    uv = (vec2( gl_Position.x, - gl_Position.y ) + vec2(1.0) ) / vec2(2.0);
}


// Fragment shader

uniform sampler2D tex;
varying vec2 uv;
const float sampleDist = 1.0;
const float sampleStrength = 2.2; 

void main(void)
{
    float samples[10];
    samples[0] = -0.08;
    samples[1] = -0.05;
    samples[2] = -0.03;
    samples[3] = -0.02;
    samples[4] = -0.01;
    samples[5] =  0.01;
    samples[6] =  0.02;
    samples[7] =  0.03;
    samples[8] =  0.05;
    samples[9] =  0.08;

    vec2 dir = 0.5 - uv; 
    float dist = sqrt(dir.x*dir.x + dir.y*dir.y); 
    dir = dir/dist; 

    vec4 color = texture2D(tex,uv); 
    vec4 sum = color;

    for (int i = 0; i < 10; i++)
        sum += texture2D( tex, uv + dir * samples[i] * sampleDist );

    sum *= 1.0/11.0;
    float t = dist * sampleStrength;
    t = clamp( t ,0.0,1.0);

    gl_FragColor = mix( color, sum, t );
}

alt text

Answer

Kos picture Kos · Jan 2, 2011

This basically is called "post-processing" because you're applying an effect (here: radial blur) to the whole scene after it's rendered.

So yes, you're right: the good way for post-processing is to:

  • create a screen-sized NPOT texture (GL_TEXTURE_RECTANGLE),
  • create a FBO, attach the texture to it
  • set this FBO to active, render the scene
  • disable the FBO, draw a full-screen quad with the FBO's texture.

As for the "why", the reason is simple: the scene is rendered in parallel (the fragment shader is executed independently for many pixels). In order to do radial blur for pixel (x,y), you first need to know the pre-blur pixel values of the surrounding pixels. And those are not available in the first pass, because they are only being rendered in the meantime.

Therefore, you must apply the radial blur only after the whole scene is rendered and fragment shader for fragment (x,y) is able to read any pixel from the scene. This is the reason why you need 2 rendering stages for that.