SGI Windows NT Toolbox
|Download Files |

OpenGL-based Real-time Rendering of Arbitary Objects Casting Shadows onto Arbitary Geometry

In the real-world, arbitarily shaped object cast shadows onto other arbitaryily shaped objects. This technical note describes how implement such shadows in real-time using OpenGL rendering. An easy-to-use API is described (including full source code) that will make it easy for you to incorporate such shadows into your OpenGL programs.

What am I really talking about? Consider holding a pretzel in front of a flashlight and letting the shadow fall on a basket ball. That's a bit contrived, but you get the idea. The result is going to be a fairly complex shadow (an irregular object casing a shadow onto a curved surface). If you look around you right now (and there is not too much ambient light), you'll probably see some fairly intricate shadows being cast all about you. Such shadows are vital clues to our understanding of the 3D reality around us.

Real-time computer generated 3D scenes often lack shadows. When they are supported, they are often very contrained. Blinn's planar projected shadow approach, described in a now famous article titled "Me and My (Fake) Shadow", makes it straightforward to project the shadow of an arbitary object onto a plane.

I've already show how to generate such planar projected shadows in a technical note titled "Shadows, Reflections, Lighting, Textures. Easy with OpenGL." This approach works and is very easy to incorporate into OpenGL programs. The problem with Blinn's approach is that real shadows aren't just cast onto planes. Real shadows block lighting of whatever is behind the shadowing object. Here's a simple image showing a planar projected shadow:

The shadow is reasonable, but if there was anything else behind the dinosaur such as a sphere or a wooden post, it would not be shadowed appropriately. Let's look at two interesting OpenGL-rendered images showing arbitary objects casting shadows on other arbitary geometry:

Notice that the light source in the red double torus is casting a shadow on both the orange sphere and on the wall behind the sphere. In the right image, the double torus (rotated from the previous image) is now casting a shadow on the cone and walls (the shadow moved because the light source location moved).

While in some sense, this is pretty simple stuff (the shadowing effects shown above are simplistic to what you see around you everyday), how you render such shadows is a bit involved. Fortunately, OpenGL supplies you with all the machinery you need to generate these shadows. The best part is that because almost all the rendering operations to generate these shadows are extremely amenable to hardware acceleration, you can generate such shadows in interactive rates. On my SGI workstation (admittedly more powerful than most of today's PCs), the program above runs at over 15 frames/second with both the light moving and the double torus spinning continously). Some straightforward optimizations could get the program running faster than that!

So how do you achieve such effects?

As I said, OpenGL supplies all the machinery necessary to render the scenes above in real-time. The critical features for the real-time shadows pictured above are:

  • OpenGL feedback. This mode lets you "capture" transformed vertices. By rendering the double torus above (and in pricinple, any object) as viewed from the light source with feedback mode enabled, you can find out all the 2D triangles making up the shape of the shadowing object as viewed from the light source.

  • The GLU 1.2 polygon tessellator. Once you get the 2D triangles of the shadowing object as viewed from the light source, you can feed these triangles into OpenGL's GLU polygon tessellator and request the silhouette (that is, the boundary) of the object.

  • Stencil testing. If you project out the silhouette in 3D from the object in the direction opposite of the light sources, you can build what is called the "shadow volume" for the shadowing object. With OpenGL stenciling (as described in more detail below), you can render the front and back facing polygons of the shadow volume in such a way that you update the stencil buffer to indicate which pixels in the scene are within the shadow volume (that is, which pixels should not be illuminated by the light source).

  • Dynamic lighting. OpenGL let's you enable and disable light sources quickly and efficiently so that you can re-render portions of the scene that should not be illuminated. Effectively, this lets you eliminate the lighting from shadowed pixels in the scene.

  • Reverse subtractive blending. An extension to OpenGL's blending functionality (very likely to be included in OpenGL 1.2, but available already on some OpenGL platforms) lets you subtract out the contribution of an OpenGL light source to pixels within the scene. This is useful to correctly handle the interactive of multiple light sources in a scene are casting shadows. You can get away with the default additive OpenGL blending, but reverse subtractive can be more efficient.

Now, I've described what features that OpenGL supports that makes the shadows shown above renderable and I've alluded to what each feature gets used for. Now is a good time to interject that Microsoft's competing Direct3D Immediate Mode API lacks all these features listed above except for dynamic lighting. While the last feature (reverse subtractive blending) is not required (it definitely helps though), the first three features are vital. The lack of these features in Direct3D (particularly the lack of stencil testing) mean the shadowing algorithm I'm describing really just isn't available to Direct3D programmers. Moreover, the shadowing algorithm discussed is what's known as a "multi-pass" graphics algorithms (something that will become very trendy as PC graphics hardware gets faster and faster). The problem is that Direct3D really has no strict requirements about whether multiple passes will update identical pixels (something very important for multi-pass techniques). This is because Direct3D implementations tend to cut corners (and in the process screw up programs that need to rely on reasonable rendering behaviors across multiple passes).

Now, let's continue looking at the real-time shadow algorithm in more detail. Here's a simple flow chart that should get you started: