Chapter 7. Shadows

Table of Contents

7.1. Quick overview how to use shadow volumes in our engine
7.2. Inspecting models with shadow_volume_test
7.3. Ghost shadows
7.4. Problems with BorderEdges (models not 2-manifold)
7.4.1. Lack of shadows (analogous to ghost shadows)
7.4.2. Not closed silhouettes due to BorderEdges
7.4.3. Invalid capping for z-fail method

This chapter was not available in master's thesis version (September 2006), it was added later.

You can easily render shadow volume for any TVRMLFlatSceneGL by TVRMLFlatSceneGL.RenderShadowVolume method. Some features (see any article about shadow volumes to know what they mean) :

7.1. Quick overview how to use shadow volumes in our engine

  • Set your projection to have infinite far plane. This is simply a matter of passing ZFarInfinity a ZFar value to

    • ProjectionGLPerspective or

    • FrustumProjMatrix or

    • PerspectiveProjMatrixDeg or

    • PerspectiveProjMatrixRad

    (whichever you use).

    You can skip this part only if you are absolutely sure that you will never need z-fail technique. Z-fail is needed when camera near plane may be inside some shadow volume — if you're sure that it never happens (for example because your camera and scene layout is fixed), you may know in some situations that z-fail will never be needed. I advice to add assertion like Assert(not SVHelper.ZFail) to your drawing code in this case.

  • Use TShadowVolumesHelper class.

    • Create (by SVHelper := TShadowVolumesHelper.Create;) and destroy instance. This is typically done at Init / Close of TGLWindow, that is when GL context is initialized and closed .

    • Call SVHelper.InitGLContext when GL context is initialized.

    • Call SVHelper.InitFrustumAndLight(Frustum, LightPosition) at the beginning of your drawing callback. Frustum can usually be obtained from Navigator.Frustum if you use TMatrixWalker navigator.

  • Write drawing code for shadows like

      { ... render scene with lights off ... }
    
      glEnable(GL_STENCIL_TEST);
        { Note that stencil buffer should be set to all 0 now. }
    
        glPushAttrib(GL_ENABLE_BIT
          { saves Enable(GL_DEPTH_TEST), Enable(GL_CULL_FACE) });
    
          glEnable(GL_DEPTH_TEST);
    
          { Calculate shadows to the stencil buffer.
            Don't write anything to depth or color buffers. }
          glSetDepthAndColorWriteable(GL_FALSE);
          glStencilFunc(GL_ALWAYS, 0, 0);
    
          if SVHelper.StencilTwoSided then
          begin
            SVHelper.StencilSetupKind := ssFrontAndBack;
            RenderShadowVolumes;
          end else
          begin
            glEnable(GL_CULL_FACE);
    
            { Render front facing shadow volume faces. }
            SVHelper.StencilSetupKind := ssFront;
            glCullFace(GL_BACK);
            RenderShadowVolumes;
    
            { Render back facing shadow volume faces. }
            SVHelper.StencilSetupKind := ssBack;
            glCullFace(GL_FRONT);
            RenderShadowVolumes;
          end;
    
          glSetDepthAndColorWriteable(GL_TRUE);
        glPopAttrib;
    
      glDisable(GL_STENCIL_TEST);
    
      glPushAttrib(GL_DEPTH_BUFFER_BIT { for glDepthFunc });
        glDepthFunc(GL_LEQUAL);
    
        { setup stencil : don't modify stencil,
          stencil test passes only for = 0 }
        glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
        glStencilFunc(GL_EQUAL, 0, StencilShadowBits);
        glEnable(GL_STENCIL_TEST);
          { ... render scene with lights on ... }
        glDisable(GL_STENCIL_TEST);
      glPopAttrib();
    

    The details depend on what is shadow caster and what is shadow receiver. You may want everything to cast shadows on everything (including itself), but often you have more specific requirements (some objects are never in shadow, some objects do not cast shadows etc.). See "The Castle" source code (file castle/source/castleplay.pas) and shadow_volume_test code for examples.

    Rendering the scene with lights on / off may be done by one or more TVRMLFlatSceneGL.Render calls. You can turn on/off lights usage e.g. by LightSet.TurnLightsOffForShadows if you load lights from VRML files.

  • Finally, to implement RenderShadowVolumes mentioned above you want to call TVRMLFlatSceneGL.InitAndRenderShadowVolume for all your shadow casters. This will check whether given scene's shadow is visible at all, whether z-fail method is needed, setup stencil op parameters (using OpenGL glStencilOp or glStencilOpSeparate or something similar using OpenGL extensions), and render actual shadow volume.

    To avoid long initialization time in the middle of drawing routines, you can prepare your models for this in some preprocessing stage by PrepareRender(prShadowVolume).

    You can split TVRMLFlatSceneGL.InitAndRenderShadowVolume into two calls: SVHelper.InitScene and TVRMLFlatSceneGL.RenderShadowVolume, this may be useful if you already keep somewhere precalculated transformed bounding box of the scene.

The above approach is used throughout my whole engine, and it will readily use all implemented shadow volume optimizations under the hood. For example, in "The Castle" game, almost everything may have a shadow rendered by shadow volumes — creatures, level scene, level objects. And everything goes through this same approach, getting all optimizations.

The approach is designed to be quite flexible. For example, using only TShadowVolumesHelper class you can use shadow volumes with your own models storage, not our TVRMLFlatSceneGL. All you need to implement is to render shadow volumes for your models honouring TShadowVolumesHelper properties like SceneShadowPossiblyVisible, ZFail, ZFailAndLightCap.