Shadow Volumes


1. Intro

Fountain level model, with shadow volumes.
The same fountain level model, with shadow volumes. After some interactive fun with moving/rotating stuff around :)
Werewolves with shadows
Castle "fountain" level with shadows

Shadow volumes are another method of rendering dynamic shadows in our engine.

To test it in Castle Game Engine editor, just set the light's Shadows property to true. Right now this one click activates shadow volumes, described in this chapter!

Demo 3D models that use dynamic shadow volumes are inside our demo models, see subdirectory shadow_volumes/. Open them with view3dscene and play around.

2. Features

Featues of shadows by shadow volumes (especially when compared with shadow maps):

  • Shadow volumes produce hard shadows. That's both an advantage (they are as sharp as your geometry, no problems with texture resolution like in shadow maps) and disadvantage (when simulating large area lights, hard shadows may look unrealistic).

  • Shadow volumes are much easier to activate... To see the shadows, just choose one light in the scene (probably the brightest one) and set Shadows to true.

    (If you use X3D nodes, set fields shadowVolumes and shadowVolumesMain both to TRUE.)

    That's it. Compared to shadow maps, you don't need to tweak anything to deal with shadow maps resolution, bias etc.

  • ... but shadow volumes require the shadow casters to be 2-manifold. So you need to be more careful when modeling. Shadow volumes usually don't work if you try to activate them on a random 3D scene. More about this below.

  • It's difficult to compare the speed of "shadow volumes" vs "shadow maps". Both techniques are expensive in totally different ways. On one hand, shadow volumes are more expensive as they require extra rendering passes (additional rendering of "shadow quads" to the stencil buffer, and then additional render to color buffer, for each shadow-casting light). On the other hand, there is no need for a costly per-pixel shader over every shadow receiver (as in shadow maps).

  • Our current shadow volumes implementation allows for only one light casting shadow volumes. (This may be improved some day. Give us a shout at the forum if needed and support the engine development to make this happen sooner.)

  • Note that it's perfectly fine to use both shadow volumes and shadow maps in a single scene.

  • Shadow volumes do not take transparency by alpha-testing textures into account. E.g. you cannot use them to make shadows from leaves on a trees (where a leaf is typically modeled as a simple quad, covered with a leaf texture).

3. Usage

3.1. Make 3D models of shadow casters geometry to be 2-manifold

For shadow volumes, all shapes that cast shadows must be 2-manifold. This means that every edge has exactly 2 (not more, not less) neighbor faces, so the whole shape is a closed volume. Also, faces must be oriented consistently (e.g. CCW outside). This requirement is often quite naturally satisfied for natural objects. Note that the consistent ordering allows you to use backface culling (solid=TRUE in X3D), which is a good thing on it's own.

You can inspect whether your shapes are detected as a 2-manifold by view3dscene: see menu item Help -> Manifold Edges Information. To check which edges are actually detected as "border edges" use View -> Fill mode -> Silhouette and Border Edges, manifold silhouette edges are displayed yellow and border edges (you want to get rid of them!) are blue.

You can also check manifold edges in Blender: you can easily detect why the mesh is not manifold by Select non-manifold command (in edit mode). Also, remember that faces must be ordered consistently CCW — in some cases Recalculate normals outside (this actually changes vertex order in Blender) may be needed to reorder them properly.

Note that each shape must be 2-manifold. It's not enough (it's also not necessary) for the whole scene to be 2-manifold. This has advantages and disadvantages:

  • Advantage: We prepare and render shadow volumes per-shape, so we work efficiently with dynamic models. Transforming a shape (move, rotate...), or changing the active shapes (e.g. by changing Switch.whichChoice) has zero cost, no data needs to be recalculated.

  • Advantage: We can avoid rendering per-shape. We can reject shadow volume rendering for shape if we know shape's shadow will never be visible from the current camera view.

  • Advantage: Not the whole scene needs to be 2-manifold. If a shape is 2-manifold, it casts shadow. If your scene has both 2-manifold and non-2-manifold shapes, it will work OK, just only a subset of shapes (the 2-manifold ones) will cast shadows.

  • Disadvantage: The whole shape must be 2-manifold. You cannot create 2-manifold scenes by summing multiple non-2-manifold shapes.

Note that shadow casters may be transparent (have material with transparency > 0), this is handled perfectly.

3.2. Advanced: Control shadow volumes using X3D fields shadowVolumes and shadowVolumesMain

NOTE: This section is relevant only for X3D authors that directly edit X3D nodes. Most Castle Game Engine users can ignore it. Just control the shadows by toggling Shadows property.

To all X3D light nodes, we add two fields:

*Light {
  ... all normal *Light fields ...
  SFBool  [in,out]  shadowVolumes     FALSE
  SFBool  [in,out]  shadowVolumesMain  FALSE  # meaningful only when shadowVolumes = TRUE

The idea is that shadows are actually projected from only one light source (with shadow volumes, number of light sources is limited, since more light sources mean more rendering passes; for now, I decided to use only one light). The scene lights are divided into three groups:

  1. First of all, there's one and exactly one light that makes shadows. Which means that shadows are made where this light doesn't reach. This should usually be the dominant, most intensive light on the scene.

    This is taken as the first light node with shadowVolumesMain and shadowVolumes = TRUE. Usually you will set shadowVolumesMain to TRUE on only one light node.

  2. There are other lights that don't determine where shadows are, but they are turned off where shadows are. This seems like a nonsense from "realistic" point of view — we turn off the lights, even though they may reach given scene point ? But, in practice, it's often needed to put many lights in this group. Otherwise, the scene could be so light, that shadows do not look "dark enough".

    All lights with shadowVolumes = TRUE are in this group. (As you see, the main light has to have shadowVolumes = TRUE also, so the main light is always turned off where the shadow is).

  3. Other lights that light everything. These just work like usual X3D lights, they shine everywhere (actually, according to X3D light scope rules). Usually only the dark lights should be in this group.

    These are lights with shadowVolumes = FALSE (default).

Usually you have to experiment a little to make the shadows look good. This involves determining which light should be the main light (shadowVolumesMain = shadowVolumes = TRUE), and which lights should be just turned off inside the shadow (only shadowVolumes = TRUE). This system tries to be flexible, to allow you to make shadows look good — which usually means "dark, but not absolutely unrealistically black".

In view3dscene you can experiment with this using Edit -> Lights Editor.

If no "main" light is found (shadowVolumesMain = shadowVolumes = TRUE) then shadows are turned off on this model.

Trick: note that you can set the main light to have on = FALSE. This is the way to make "fake light" — this light will determine the shadows position (it will be treated as light source when calculating shadow placement), but will actually not make the scene lighter (be sure to set for some other lights shadowVolumes = TRUE then). This is a useful trick when there is no comfortable main light on the scene, so you want to add it, but you don't want to make the scene actually brighter.

3.3. Advanced: Control stencil buffer

In order for shadow volumes to work, we need a stencil buffer initialized.

NOTE: There is nothing you need to do now. We enable 8-bit stencil buffer by default.

To request stencil buffer explicitly, you need to set TCastleWindow.StencilBits or TCastleControl.StencilBits to something non-zero. The number of bits should be large enough to track all the possible objects that may cast a shadow at a given pixel. In practice, in most reasonable cases, using 8 bits (256 possible objects) is enough. Like this:

Window.StencilBits := 8;

You need to set this before the Window.Open call. In a typical cross-platform CGE application you would do this in your gameinitialize.pas initialization section.

4. Adjust what casts shadows

Everything by default is a shadow caster (as long as it's 2-manifold). To stop some objects from casting shadows, set TCastleTransform.CastShadows to false.

If you edit X3D nodes, you can even control it for each particular shape. Set the Appearance.shadowCaster field to false. See Appearance.shadowCaster documentation.