Program 8

Program 8: Gaussian Blur in OpenGL

Due: Thursday, December 2nd, 2010 (before class begins)

For this programming assignment, you will utilize multipass rendering with fragment shaders to create a blurring effect. You may use whatever base code you want for this assignment, but you may not use code from outside the course. Your code must be able to read a PLY, so you may want to use the program 6 code as a starting point.

Previous advice about GLSL applies:

NOTE: GLSL can be very touchy, and the best way to save time debugging is to avoid mistakes in the first place. Some big stumbling blocks:

Without blur:

pgm_08/bunny_flat_noblur.png

With blur:

pgm_08/bunny_flat_blur.png

Gaussian Blur (The Theory)

Blurring an image is simple, in theory: in the new, blurred image, each pixel will be replaced with a weighted sum of that pixel's neighborhood in the original image. If the weights sum to 1, the overall brightness of an image won't be increased or decreased.

The choice of weights to use will affect the quality of the blur effect. The simplest choice is to give each pixel the same weight, but this has the (undesirable) property of weighting pixels at the edge of the neighborhood by the same amount as the center pixel. This is called a 'box filter', and in high frequency areas can result in artifacts after blurring. Intuitively, we want a set of weights that gives more weight to the center pixel and less weight to pixels as they are further from the center. The Gaussian probability density function (also known as the Normal PDF) does exactly this:

pgm_08/gaussian_2d_intensity.gif

image credit: http://ssip2003.info.uvt.ro/projects/teamE/abstract.htm

And the result of using a Gaussian filter on an image instead of a box filter can be seen below. From left to right, the original image, the image blurred with a box filter, and the image blurred with a Gaussian filter: (original images from http://www.nd.edu/campus-and-community/sights-sounds/)

Original Box Filter Gaussian Filter
pgm_08/irish_helmet.png pgm_08/irish_helmet_box_filter.png pgm_08/irish_helmet_gaussian.png
pgm_08/irish_helmet_detail.png pgm_08/irish_helmet_box_filter_detail.png pgm_08/irish_helmet_gaussian_detail.png

In theory, you compute the weight of each pixel in an N x N neighbor using the formula for the Gaussian PDF, where x and y would be the pixel distances of any of the neighbors from the center of that neighborhood:

pgm_08/gaussian_two_variable.gif

In practice, we precompute these weights to save time. For this assignment, implement a 5x5 blur filter; use the following weights for a 5x5 neighborhood:

0.0037 0.0147 0.0256 0.0147 0.0037
0.0147 0.0586 0.0952 0.0586 0.0147
0.0256 0.0952 0.1502 0.0952 0.0256
0.0147 0.0586 0.0952 0.0586 0.0147
0.0037 0.0147 0.0256 0.0147 0.0037

Blurring in OpenGL (Overview)

Normally, a fragment shader will only have access to information contained in its fragment, but performing a blur requires access to some information about the pixels in a nearby neighborhood. The solution to this problem is to use multipass rendering. Render the scene once, without the blur shader, and then copy the framebuffer into a texture. Then render the scene again, with the blur shader enabled: render a single quad covering the entire screen, and apply the texture to it. Then, inside your blur shader, you will be able to access the value of this texture at the current texture coordinate (which correspond to that pixel on the screen) as well as neighboring texture coordinates. Set the output color of the fragment to be the weighted sums of these samples.

Things You Will Need To Know

To copy the framebuffer into a texture, you can do one of two things. The first method is to read back the data from the framebuffer, like we did when making movies for the midterm project, and call glTexImage2D to transfer that data back onto a texture. This is slow, however, as it involves transferring the framebuffer from GPU memory into CPU memory (glReadPixels) and then back into GPU memory (glTexImage2D). Since we're not going to do any CPU-side processing on this data, the preferred method is to use glCopyTexImage2D (consult man pages for documentation).

Make sure to set the correct read buffer with glReadBuffer (GL_BACK in a double-buffered context as ours usually are), and make sure that the texture you want to affect has been enabled (glEnable(GL_TEXTURE_2D)) and bound (glBindTexture(...)) before you make the copy call. You may also want to call glFinish before copying, to ensure that all rendering has finished.

At this point, you know how to perform texturing within a fragment shader. In your blur fragment shader, you will need to access the texture at more than just the current fragment's texture coordinates. Since your texture coordinates should range from [0,0] to [1,1] over the entire quad/image, the pixel immediately left of the current pixel will be located at gl_TexCoord[0].st - vec2(1.0/windowWidth,0.0), and so on. You will need to pass the window's width and height into your fragment shader to have access to this information. Note that you don't necessarily have to worry about accessing the texture at values outside the range of [0,0] to [1,1] if your GL_TEXTURE_WRAP modes are set properly.

Tip: focus on rendering to texture and texture mapping the quad appropriately before you worry about the fragment shader. To test that you have multipass rendering and a basic fragment shader working, just try tinting the scene red, for instance: gl_FragColor = texture2D(tex, gl_TexCoord[0].st) + vec4(0.2,0,0,0);

Grading Rubric

Your submission will be graded on the following items (40 points):

Points Requirements
10
  • Basic program setup correctness
    • Program loads and displays a PLY
    • Camera can be moved around the scene
    • User can press 'b' to toggle blur shader on and off
25
  • Scene is correctly blurred
    • Frame buffer is read back to texture correctly
    • Quad is rendered to viewport appropriately (covers full screen)
    • Quad is given correct texture coordinates
    • Vertex shader / fragment shader compile correctly
    • Fragment shader indexes texture sampler correctly
    • Fragment shader uses appropriate weights
5
  • Code is well structured / commented
  • Submission includes README and Makefile, all with name.

Extra credit: Render a scene with multiple objects, and simulate depth-of-field: render objects in the distance with blur, but objects in the foreground without! (10 points).

Submission

Place the source code, a Makefile, and a README, all with your name, in your course dropbox:

/afs/nd.edu/coursefa.10/cse/cse40166.01/dropbox/<afsid>/pgm8