Table of Contents

Please enable javascript to display TOC.

Blurring on Graphics Cards without Shaders

published: 15 Oct 2007, last updated: 29 Dec 2012

Blurring a texture in OpenGL or DirectX applications is a common way to achieve various effects like glow in computer games and virtual reality. Implementing this with pixel shaders is easy, but sometimes applications have to run on older hardware, embedded devices or smart phones with no or limited shader support. The blurring method described in this article only requires the standard built-in bi-linear texture filter and looks similar to the well-known Gaussian blur filter.

Examples

The following images were rendered using the method described in this article. Click on them to see larger versions.

Download the Code

If you use this code, a link back to this website would be appreciated but is not required.

Fully working example (OpenGL) (3.5 kB)

This example requires the SDL library to compile. When running the programm, press 0-9 to blur the current image 2^{0-9} times and press r for resetting the view.

How the method works

  1. Bind the texture that should be blurred to one texture unit.
  2. Blend the texture four times to a back buffer (framebuffer/texture) without scaling, but with shifted texture coordinates (-0.25px and 0.25px in each direction).
  3. Copy the back buffer to the texture.
  4. Repeat steps 2+3 as often as you want. Steps 2+3 are one rendering pass.

Everything is done in orthographic mode. Your texture coordinates have to be clamped and the texture filtering has to be set to linear.

Example implementation

Here is an implementation in C and OpenGL.

void blur_tex(GLuint tex, int passes)
{
    int i, x, y;
    
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glBindTexture(GL_TEXTURE_2D, tex);
    while (passes > 0) {
        i = 0;
        for (x = 0; x < 2; x++) {
            for (y = 0; y < 2; y++, i++) {
                glColor4f (1.0f,1.0f,1.0f,1.0 / (i+1));
                glBegin(GL_TRIANGLE_STRIP);
                    glTexCoord2f(0 + (x-0.5)/WIDTH, 1 + (y-0.5)/HEIGHT); glVertex2f(0, 0);
                    glTexCoord2f(0 + (x-0.5)/WIDTH, 0 + (y-0.5)/HEIGHT); glVertex2f(0, HEIGHT);
                    glTexCoord2f(1 + (x-0.5)/WIDTH, 1 + (y-0.5)/HEIGHT); glVertex2f(WIDTH, 0);
                    glTexCoord2f(1 + (x-0.5)/WIDTH, 0 + (y-0.5)/HEIGHT); glVertex2f(WIDTH, HEIGHT);
                glEnd ();
            }
        }
        glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, WIDTH, HEIGHT, 0);
        passes--;
    }
    glDisable(GL_BLEND);
}

How Fast is It?

The test system is running Linux 2.6.20 on an Athlon XP 2200+ with an NVIDIA GeForce 6800GS and the official NVIDIA linux drivers.

The texture has a size of 512x512 and the rendering code always finishes with glFlush() and glFinish() before taking the times.

Calling the function 512 times takes 287ms. This is equal to 0.56ms/pass or 1783fps@1pass.

Variants

Zoom Blur

With a slight modification it is also possible to create a zoom effect.

void blur_tex_zoom(GLuint tex, int passes)
{
    int i;
    
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glBindTexture(GL_TEXTURE_2D, tex);
    while (passes > 0) {
        for (i = 0; i < 2; i++) {
            glColor4f(1.0f,1.0f,1.0f,1.0 / (i+1));
            glBegin(GL_TRIANGLE_STRIP);
                glTexCoord2f(0 - (i*0.5)/WIDTH, 1 + (i*0.5)/HEIGHT); glVertex2f(0, 0);
                glTexCoord2f(0 - (i*0.5)/WIDTH, 0 - (i*0.5)/HEIGHT); glVertex2f(0, HEIGHT);
                glTexCoord2f(1 + (i*0.5)/WIDTH, 1 + (i*0.5)/HEIGHT); glVertex2f(WIDTH, 0);
                glTexCoord2f(1 + (i*0.5)/WIDTH, 0 - (i*0.5)/HEIGHT); glVertex2f(WIDTH, HEIGHT);
            glEnd ();
        }
        glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, WIDTH, HEIGHT, 0);
        passes--;
    }
    glDisable(GL_BLEND);
}

Radial Blur

Radial blurring is also possible.

void blur_tex_radial(GLuint tex, int passes)
{
    int i;
    
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glBindTexture(GL_TEXTURE_2D, tex);
    while (passes > 0) {
        for (i = 0; i < 2; i++) {
            glPushMatrix();
            glLoadIdentity();
                if (i == 1) {
                    glTranslatef(WIDTH/2, HEIGHT/2,0);
                    glRotatef(1, 0, 0, 1);
                    glTranslatef(-WIDTH/2, -HEIGHT/2,0);
                    
                }
                glColor4f(1.0f,1.0f,1.0f,1.0 / (i+1));
                glBegin(GL_TRIANGLE_STRIP);
                    glTexCoord2f(0.0f, 1.0f); glVertex2f(0, 0);
                    glTexCoord2f(0.0f, 0.0f); glVertex2f(0, HEIGHT);
                    glTexCoord2f(1.0f, 1.0f); glVertex2f(WIDTH, 0);
                    glTexCoord2f(1.0f, 0.0f); glVertex2f(WIDTH, HEIGHT);
                glEnd();
            glPopMatrix();
        }
        glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, WIDTH, HEIGHT, 0);
        passes--;
    }
    glDisable(GL_BLEND);
}

Conclusion

The resulting image quality is nearly as good as a standard gaussian blur and the algorithm is more than fast enough for practical purposes.