Shadow Volumes + Prcomputed Radiance Transfer


Shadow volumes are a real-time shadowing technique which uses the stencil buffer found on graphics hardware in conjunction with multiple rendering passes to determine if a pixel should be shadowed from a light source. I've implemented this technique using another new component of graphics hardware: the Geometry Shader. To generate shadowing information, volumes are extruded from geometry along shiloette edges, away from light sources. The visibility of the resulting geometry from the camera determines if that pixel is occluded or not. I've integrated this method with some other rendering techniques from Precomputed Radiance Transfer, to try to acquire soft self-shadowing over the surfaces of objects as well.

Shadow Volumes

While there are a couple of variations inside of Shadow Volumes, the fundamental approach is fairly regular:

  1. Render geometry in the scene with only ambient lighting, giving depth values to all pixels
  2. Turn off writes to the depth buffer and frame buffer
  3. For each light source, extrude shadow volumes away from it, into the scene (one at a time)
  4. For each pixel, increment or decrement the stencil buffer if a back-facing/front-facing volume element is sceen, which fails the depth test
  5. Render the geometry lit by the current light source, blending with the current contents of the framebuffer

The method described above is called the Z-Fail method, because it increments/decrements the stencil buffer when the depth test is failed -- meaning that the shadowing geometry is actually behind the object being scene. Because of incrementing/decrementing, it turns out to be the same as the alternative -- Z-Pass, except in one exceptional case: when the camera is inside a volume being shadowed. Z-Fail handles this situation gracefully, while Z-Pass generally inverts the shadowing in the scene.

Geometry Shading

Shadow Volumes present an interesting opportunity to leverage new capacity which is present on modern GPUs: geometry shaders. Geometry shaders allow the programmer to generate new geometry on the graphics card, without gathering more data from the host application. We can leverage the ability to produce new geometry there to offload the extrusion of the shadow volume geometry on to the graphics hardware.

In order for the graphics hardware to successfully generate shadow volumes, it needs to be able to find the shiloette edges for a mesh. To do this, it needs to know if the triangles attached to an edge face away from the light source. If they do, and the current one faces the light, then the edge between them must go from lit to shadow: the edge of a volume of shadow. To gather this information for the graphics hardware, we pass triangles down the pipeline with adjacency information: 3 additional vertices which specify the neighbor vertex of each edge of the original triangle.

After we have determined that a given edge is a shiloette edge defining a shadow volume, we compute the vector between the light and that edge's endpoints. We use each endpoint, projected to an infinite distance away along the light vector, to form a new triangle defining the side of the shadow volume. In addition, we cap shadow volumes by extruding some of the original geometry, and the original geometry projected to infinity, to make sure that our shadow volumes have "fronts and backs" to them when viewed from any angle. The shadow triangles generated are pass down the graphics pipeline, to be "rendered" to the screen. Of course, since framebuffer writes and depth-buffer writes are disabled, they are just used to do calculations on the stencil buffer while being rendered.