Table of Contents
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) :
Silhouette edge detection is done, of course the model must be 2-manifold for this to work.
ManifoldEdges structure is prepared once during pre-processing step
(by PrepareRender call with
prShadowVolume, or simply on first call to
RenderShadowVolume).
This allows rendering shadow quads with silhouette detection in
O(n+m)
time, where n is a number of edges and m is a number of triangles
(these are roughly equal since on a perfect 2-manifold
3 * m = 2 * n).
Without calculated ManifoldEdges, this would have to take square time,
O(m2).
To account also models that are not completely 2-manifold, we have BorderEdges list with edges that have only one neighbor triangle. Actually, it lists edges with any odd number of neighbors (each neighbor pair makes one edge in ManifoldEdges, and then one left neighbor makes one BorderEdges item). All BorderEdges are always considered part of the silhouette. This is not a perfect solution, further in this chapter I present when this fails. When it fails, there are two solutions:
fix the model to be 2-manifold.
or use the much slower algorithm version that doesn't do silhouette edge detection.
Both Z-pass and Z-fail approaches are done. We automatically detect when Z-fail is needed, and in 99% of the cases we can use faster Z-pass approach.
Both positional and directional lights are supported.
Using homogeneous coordinates tricks: we render shadow quads vertexes in real infinity, and we can use perspective projection that has no far clipping plane.
We do shadow volume culling for scenes
(that is, we try to avoid rendering shadow quads when it's obvious
the scene shadow can't be visible within current camera frustum).
Implemented in TShadowVolumesHelper.InitScene.
It's not fully implemented, we could take more conservative convex hull
between light position and frustum. But it seems
that this wouldn't improve culling significantly, current approach gives
us almost as much as we can get from frustum culling.
More drastic improvements can only come from the use of portals.
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.