Shadow Casting Effects

Click here to see other demos I've written.

Be sure to check out the update at the bottom of this page.

If you happened to go to GDC this year, and then happened to attend Mark Kilgard's session on Advanced Hardware Rendering, you probably saw his shadow casting demo.  That demo and a very helpful powerpoint presentation are available from nVidia's developer web pages.  The demo here and Mark's demo are based on a shadow mapping technique useful for consumer hardware described by Wolfgang Heidrich in his PhD thesis, which has a lot of other interesting material.

Between Mark's presentation and Wolfgang's thesis, there's more than enough description of how this technique works, so I will refer you to that material for how to implement this technique. Instead I'll show a couple of interesting effects that are possible using this technique.  This demo looks a lot like Mark's, but it is a completely independent implementation in C++ that allows for a slightly more compact representation of the algorithm.

The interesting effects are atmospheric effects and using a "slide projector" for the light source.  Here are a couple of pictures illustrating the effects.

The neat thing about these effects is that you get behavior that you expect.  That is, the atmospheric effects and the projective image are both occluded by the objects in the scene. The slide projector effect is just a straightforward combination of the shadow mapping technique with a projective texture.  The only wrinkle being that two texture units are used for the shadow determination, so the color buffer must be masked and the lit region is written to the stencil buffer.  In a subsequent pass, the projected image is applied only in the lit region.  The final pass using the illuminated region sets the stencil back to zero eliminating the need for a clear.

It is handy that the stencil can be unset in the final pass, because this is useful for the atmospheric effect.  For this effect, we turn off depth buffer writes and enable blending for the entire process.  Then we render a sequence of planes as follows:

So we repeat this process for each plane, which is an AWFUL lot of texture and other state reconfiguring per atmospheric plane.  But it looks nice.  With more than two texture units, all of these state changes could be avoided.

One final note: this technique requires GL_EXT_texture_env_combine, but that is covered in the other documentation.

Check out the source for the definitive description of what is happening.  Below are some other rendering modes that this demo can switch among.

Slide projector with shadows but no atmospheric effects range to light source from camera view slide projector depth map projected onto scene
atmospheric effects, but without shadows or occlusion of atmospheric effects view from the slide projector depth from the slide projector

Also, it should be pointed out that this demo makes no effort to eliminate the aliasing artifacts due to the 8-bit precision of the depth comparison.  There are some simple ways to improve this significantly.


In this second iteration I added a new rendering mode that eliminates the need for all that stenciling and state reconfiguration. It's worth putting an update out for this because using shadows this way is much more feasible for real-time rendering. The trick for this variation is that the slide projector image and the depth map can be combined into a single RGBA texture where the alpha channel holds the depth map. Since both the depth map and the slide image are projected on the scene exactly the same way, there's no extra cost. The trade-offs are that the depth map and slide image must be the same resolution, and though the slide image is typically static, it must be re-downloaded every time the depth map changes.

Other enhancments are that the depth map is only recomputed as-needed (which helps see a nice performance boost using this new rendering mode). And per Mark Kilgard's suggestion, the atmospheric effects target a certain "density" independent of the number of atmospheric planes being drawn (which can be adjusted with '+' and '-'). There are some numerical/precision issues with getting exactly the target density without being a little more clever with the blending.

You can get the updated source here.

The old versions of this demo, and are still available, but I would suggest getting the up-to-date version.