OpenGL-based Real-Time Shadows

I've now updated the real-time shadow code on this page to use multiple processors if available to do the silhouette generation. This code works quite well on SGI's Onyx and Onyx2 systems, increasing the rendering speed. -mjk (6/30/97)

I think of OpenGL as a vocabulary of hardware-amenable building blocks for 3D graphics algorithms. By combining the "words" in this vocabulary in clever, often unexpected ways, you can implement all sorts of real-time graphics effects that would be difficult or impossible with an uninspired API such as Microsoft's Direct3D. Is it possible that the vocubulary provided by OpenGL is rich enough that it can generate high-quality shadows in real-time? You bet it can.

The source code and screen snapshots below demonstrate real-time shadows cast by object of arbitary shape onto arbitary geometry. The shadows are not the simple "planar shadow projections" so common in games and 3D graphics demos today. These shadows can be cast onto curved surfaces and can even be cast by objects with holes in them. The programs shown run at more than 15 frames per second on my SGI workstation. It could go faster with further tuning.

The technique uses stencil-based shadow volumes to determine where the shadow falls. OpenGL's feedback mode is used to capture the triangles from an arbitary OpenGL-rendered object so that the object's silhouette can be calculated with the OpenGL Utility (GLU) 1.2 library's boundary tessellation routines.

I've implemented the shadowing algorithm as a library layered on top of OpenGL so that OpenGL programmers just need to know where to position casting light sources and shadowing objects with their 3D scene and the library takes care of the details of generating correct shadows. The library is the Real-Time Shadow (RTS) library.

Here are some screen snapshots to see how nicely the technique works:

That last snapshot of the double torus is drawn from the point of view of the light source. The point of this snapshot is to demonstrate that if a scene is rendered as viewed by the light source, you can see no shadows (if you think about it, that makes sense). Also, look at the second to last snapshot. These two snapshots are showing the exact same scene. If you examine them carefully you can see that everything behind the red double torus in the last snapshot is shadowed in the second to last snapshot. These last two pictures should help you appreciate that the shadows are "correct".

Interested in the source code? The shadowfun.c example does not use the RTS library. It is implemented as a monolithic OpenGL Utility Toolkit (GLUT) program so that you can see the algorithm in the context of a single program.

The example above just has one light source and shadowing object. The algorithm actually generalizes to multiple light sources and multiple shadowing objects. Check out these snapshots:

Pretty cool, huh? Yes, this example runs in real-time on SGI machines with the torus and cube contiously spinning while the user can interactively adjust the lighting.

The last image is particularly interesting. Notice on the floor that the shadow from the cube caused by the greenish light has a redish tint since that area is also directly illuminated by the redish light. More importantly, where this shadow overlaps with the redish's light's shadow from the torus, you can see the shadow region is dark (neither redish or greenish) as you would expect since the region is double shadowed. This region is not directly illuminated by either light.

Still more interesting, you can see that on the cube, there is a shadow from the torus blocking the red light. The shadowing objects can be shadowed by other shadowing objects!

And also, don't miss how the smaller sphere has a shadow cast on it from the torus and the greenish light. All these effects have been captured in a single scene. Pretty nice.

The Real-Time Shadows library is implemented in rts.c and its interface is defined by rtshadow.h I'll mention that this library is still "under development", but it is functional enough right now to do some really cool stuff. The actual demo program is hello2rts.c.

I don't have time here to go into all the gory details of how the algorithm works. It uses OpenGL's stenciling capability (a rendering capability totally unsupported by Direct3D). The flow chart below shows a simplified view of the key points in the algorithm (for one object with one light source as in shadowfun.c).

Look up "shadow volumes" in a good graphics textbook for more understanding of what's going on. The key observation is that after you draw the front facing polygons of the shadow volume and then the back facing polygons of the shadow volume (without color buffer or depth buffer update, but with depth testing), shadowed pixels will be tagged in the stencil buffer.

In the future, I'll be making further performance enhancements and generalizations to the RTS library and supplying proper documentation for it. Actually, if you look in hello2rts.c, you'll only find 10 different RTS calls.

I hope that you find this technique interesting. Like any rich, powerful language, when you combine the vocabulary provided by OpenGL in the right way, you can get OpenGL to render amazing scenes very quickly.

If you are interested in more OpenGL rendering techniques, see the Way cool, way fast OpenGL rendering techniques page.

The older version of rts.c before the multiprocessor support.

- Mark Kilgard (mjk@nvidia.com)