15-462 Shadow Mapping Tutorial Using GLSL

Back to Project 2

General Idea

The general idea is that we'll render from the light's point of view, save that to a texture, and then on each pixel, determine whether the pixel is in shadow by comparing the texture's depth value with a calculated depth value.

Rendering the Depth Buffer to a Texture

There are two common methods for doing this. One is to render into the window and copy that result to a texture. The other is to render directly into the texture using the Framebuffer Objects extension.

Render to Texture using glCopyTexImage

Render the scene into a window, and call glCopyTexImage with internal format set to GL_DEPTH_COMPONENT to copy the depth buffer to the texture. The rest of the details can be found in the man page. This function does the same thing as glTexImage2D, so apart from this you just have to generate the texture and set its filtering correctly during initialization.

Important notes:

Render to Texture using Framebuffer Objects

Note: the graphics cards in WeH 5336 do not support depth-texture framebuffer objects, so you'll have to stick with using glCopyTexImage2D

If you want to use FBOs to use do render-to-texture, the specification for the extension is available here. Just kidding, you don't have to read that. Since there aren't any good resources available online for this stuff, here is some sample code to set up a render-to-depth-texture loop.

Notes

Once this has been successfully completed, the contents of the depth buffer will look something like the following image.

Note that you should be pretty careful about how you select your z-near and z-far clipping planes. In this case, I've set z-near to be justin front of the guy's head and z-far to be just beyond the floor, which efficiently uses the full range of the z-buffer. While it isn't necessary to be this excessively precise, you should keep an eye on your range to make sure that you aren't wasting depth, since that will result in artifacts.

Calculating Texture Coordinates

To convert between world coordinates and texture coordinates, you need to do two steps.

  1. Multiply the world coordinate by the ModelView and Projection matrices that were used to set up rendering to the light.
  2. Scale and bias the result. Remember that the ModelView and Projection matrices will put all values intothe cube [-1 1]x[-1 1]x[-1 1]. However, remember that texture coordinates come from the range [0 1]x[0 1] and textures take on values over the range [0 1]. Therefore, you'll want do an two transformations after transforming by the ModelView and Projection matrices. The first of these will scale the result by 0.5 in all three directions, the second will translate the result by 0.5 in all directions.

In practice, probably the best way to do this is, each frame,

Note that just as before, multiplying by gl_ModelView/NormalMatrix will transform things into eye coordinates, and the lights will be in eye coordinates for lighting calculations.

If then, in your fragment program, you have the resulting varying texture coordinate as shadowTexCoord and the shadow texture map as a sampler2D called shadowMap then reading from the texture using texture2DProj (shadowMap, shadowTexcoord), the result will be the following image.

That is, the color that you read out of the texture will be exactly the depth of the nearest occluder of that pixel.

Comparing Depth Values Manually

To calculate each pixel's distance from the camera, you can just look at the value shadowTexCoord.z/shadowTexCoord.w If you render that, you'll end up with an image that looks like

And now, all you need to do is compare those two depth values to determine what is in shadow and what's not.

Comparing Depth Values Automatically in GLSL

GLSL makes this a bit easier for us. We can specify that the depth texture will be used for comparing depth values by setting the following texture parameters using glTexParameteri when the shadow texture is bound.

To tell GLSL that we're using texture comparison instead of texture mapping, define the shadow map texture as uniform sampler2DShadow shadowMap instead of uniform sampler2D shadowMap.

Finally, with all of this set up, calling the function shadow2DProj (shadowMap, shadowTexCoord) will return a vec4 that is 0 if the object is in shadow in 1 if it is not in shadow.

Final Results and Remarks

If you then combine these results with your normal mapping, you can create an image like this, and give John Carmack a run for his money (or something).

One thing that I mentioned earlier was this z-fighting thing. Calling glPolygonOffset (2, 2) pushes the scene back by a small epsilon in the z-buffer before rendering it. This corresponds to allowing a little bit of wiggle room for the depth values, so that if they're a little bit off, we err on the side of assuming that they're not in shadow. If you didn't use glPolygonOffset then you'll get a kind of gnarly looking image caused by z-fighting as seen below.


Last modified 2006.03.27 by Chris Cameron