Shadow Maps

CS 481 Lecture, Dr. Lawlor

In OpenGL, a fragment program's job is to render one pixel:
    gl_FragColor = ...;

The problem is that each fragment knows only about its triangle, and fragments don't know about each other.  For some stuff, like simple diffuse lighting, this works--every pixel needs to know its normal, and based on the normal it can figure out how much of the light source it can see.

But what about shadows?  Well, plain old ordinary OpenGL doesn't have any:
scene without shadows

We'd like to make some shadows.  One trick is to note that along a ray to the light source, only the first object that hits the ray will be lit; everything farther from the light source will be in shadow.

We could easily manage this in OpenGL, if we have a way to know how far we are from the light source (an easy computation) and how far the first-lit thing is from the light source (??):
	float lit=1.0;
if (our_dist > first_lit_dist) lit = 0.0; /* we are in shadow */
gl_FragColor = (a+d*lit)*reflectance + vec4(s*lit);
So the shadowing problem really boils down to: is somebody blocking my view of the light source?

One cool solution to this problem is called "shadow maps":
Here's what a shadow map texture looks like.  The colors code distance from the light source: black (0.0) is close to the light source, white (1.0) is farther away.
shadow map, with distances encoded as color
The big central spike is closest to the camera, and hence the darkest thing in the texture.

We can render a shadow map into a texture with a simple little chunk of code like this:
	"// GLSL Fragment shader\n"
"varying vec3 shadowCoords;\n"
"void main(void) {\n"
" gl_FragColor = vec4(shadowCoords.z);\n"
"}\n"
);

glUseProgramObjectARB(shadowProg);

glClearColor(1.0,1.0,1.0,0); /* set background color to white (farthest away) */
glClear(GL_COLOR_BUFFER_BIT + GL_DEPTH_BUFFER_BIT);

draw_the_world();

/* Read the rendered shadow-view distances into the shadow texture */
glBindTexture(GL_TEXTURE_2D,shadowTex);
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0,0, 0,0, texWid,texHt);
(Note: a framebuffer object could render directly to the texture, which would  be slightly faster than rendering to the screen and then copying.)

Now that we've built a shadow map, for each pixel we can look up the closest-lit distance:
shadow map values stretched over geometry
This is the shadow map, stretched across our geometry.  Note how the closest-lit geometry doesn't change along a light ray--shadow maps are 2D, although light is 3D.

We can compare the closest-lit value against our own distance from the light source:
geometry distance from light source
This is the distance from each piece of geometry from the light source.

If we just compare the distance of our geometry to the closest-lit geometry, we get this:
comparison between shadow map value and geometry
Note that this is actually pretty close--the big dark spots are where shadows really should be.  But where the surface should be lit, it's self-shadowing due to the low precision of our shadow map depths (note 8-bit color means there are just 256 planes of depth!).  This ugly self-shadowing is called "shadow acne", but luckily we can cure acne by adding a small tolerance to our depth comparison, like:
    if (shadowCoords.z > shadowMapPix.z + 1.0/256) lit = 0.0; /* we're in shadow */
If we do this, then we can distinguish light from shadow reliably:
Clean lighting by biasing shadow comparison

The final step is to fold the light/shadow determination into our lighting calculation.  Typically you just set the diffuse and specular contributions to zero for shadowed pixels.
Full phong lighting with shadows

The bottom line on shadow maps is that they're easy, and they generate real shadows.  You should use them in your programs!

The only downsides to shadow maps are: