|
The idea of using the stencil buffer to generate shadows has been around
for over a decade, but only recently has 3D graphics hardware advanced
to the point where using the stencil algorithm on a large scale has become
practical. Not long ago, there existed some unsolved problems pertaining
to stencil shadows that prevented the algorithm from working correctly
under various conditions. Advances have now been made, however, so that
stencil shadows can be robustly implemented to handle arbitrarily positioned
point lights and infinite directional lights having any desired spatial
relationship with the camera. This article presents the intricacies of
the entire stencil shadow algorithm and covers every mathematical detail
of its efficient implementation.
Algorithm Overview
The basic concept of the stencil shadow algorithm is to use the stencil
buffer as a masking mechanism to prevent pixels in shadow from being drawn
during the rendering pass for a particular light source. This is accomplished
by rendering an invisible shadow volume for each shadow-casting object
in a scene using stencil operations that leave nonzero values in the stencil
buffer wherever light is blocked. Once the stencil buffer has been filled
with the appropriate mask, a lighting pass only illuminates pixels where
the value in the stencil buffer is zero.
As shown in Figure 1, an object’s shadow volume encloses the region
of space for which light is blocked by the object. This volume is constructed
by finding the edges in the object’s triangle mesh representing
the boundary between lit triangles and unlit triangles and extruding those
edges away from the light source. Such a collection of edges is called
the object’s silhouette with respect to the light source. The shadow
volume is rendered into the stencil buffer using operations that modify
the stencil value at each pixel depending on whether the depth test passes
or fails. Of course, this requires that the depth buffer has already been
initialized to the correct values by a previous rendering pass. Thus,
the scene is first rendered using a shader that applies surface attributes
that do not depend on any light source, such as ambient illumination,
emission, and environment mapping.
The original stencil algorithm renders the shadow volume in two stages.
In the first stage, the front faces of the shadow volume (with respect
to the camera) are rendered using a stencil operation that increments
the value in the stencil buffer whenever the depth test passes. In the
second stage, the back faces of the shadow volume are rendered using a
stencil operation that decrements the value in the stencil buffer whenever
the depth test passes. As illustrated in Figure 2, this technique leaves
nonzero values in the stencil buffer wherever the shadow volume intersects
any surface in the scene, including the surface of the object casting
the shadow.
There are two major problems with the method just described. The first
is that no matter what finite distance we extrude an object’s silhouette
away from a light source, it is still possible that it is not far enough
to cast a shadow on every object in the scene that should intersect the
shadow volume. The example shown in Figure 3 demonstrates how this problem
arises when a light source is very close to a shadow-casting object. Fortunately,
this problem can be elegantly solved by using a special projection matrix
and extruding shadow volumes all the way to infinity.
The second problem shows up when the camera lies inside the shadow volume
or the shadow volume is clipped by the near plane. Either of these occurrences
can leave incorrect values in the stencil buffer causing the wrong surfaces
to be illuminated. The solution to this problem is to add caps to the
shadow volume geometry, making it a closed surface, and using different
stencil operations. The two caps added to the shadow volume are derived
from the object’s triangle mesh as follows. A front cap is constructed
using the unmodified vertices of triangles facing toward the light source.
A back cap is constructed by projecting the vertices of triangles facing
away from the light source to infinity. For the resulting closed shadow
volume, we render back faces (with respect to the camera) using a stencil
operation that increments the stencil value whenever the depth test fails,
and we render front faces using a stencil operation that decrements the
stencil value whenever the depth test fails. As shown in Figure 4, this
technique leaves nonzero values in the stencil buffer for any surface
intersecting the shadow volume for arbitrary camera positions. Rendering
shadow volumes in this manner is more expensive than using the original
technique, but we can determine when it’s safe to use the less-costly
depth-pass method without having to worry about capping our shadow volumes.
The details of everything just described are discussed throughout the
remainder of this article. In summary, the rendering algorithm for a single
frame runs through the following steps.
A Clear the frame buffer and perform an ambient rendering
pass. Render the visible scene using any surface shading attribute that
does not depend on any particular light source.
B Choose a light source and determine what objects may
cast shadows into the visible region of the world. If this is not the
first light to be rendered, clear the stencil buffer.
C For each object, calculate the silhouette representing
the boundary between triangles facing toward the light source and triangles
facing away from the light source. Construct a shadow volume by extruding
the silhouette away from the light source.
D Render the shadow volume using specific stencil operations
that leave nonzero values in the stencil buffer where surfaces are in
shadow.
E Perform a lighting pass using the stencil test to mask
areas that are not illuminated by the light source.
F Repeat steps B through E for every light source that
may illuminate the visible region of the world.
For a scene illuminated by n lights, this algorithm requires at
least n+1 rendering passes. More than n+1 passes may be
necessary if surface shading calculations for a single light source cannot
be accomplished in a single pass. To efficiently render a large scene
containing many lights, one must be careful during each pass to render
only objects that could potentially be illuminated by a particular light
source. An additional optimization using the scissor rectangle can also
save a significant amount of rasterization work -- this optimization is
discussed in the last section of this article.
|