Skip to content

WREN Shadows Volume

David Mansolino edited this page Nov 5, 2018 · 1 revision

Outline

Rendering shadows using shadow volumes consists of the following steps (for each frame):

  1. For every shadow-casting mesh and light source, determine the silhouette edges of the mesh as seen from the light source. Store the edges with their triangles (one light-facing, one in shadow). Also store edges that only belong to a single triangle if the triangle faces the light source.
  2. Create a shadow volume by connecting the vertices of each edge with a copy of themselves that has been extruded to infinity in direction of the light. This creates a quad per edge for spot and point lights, or a triangle per edge for directional lights (both vertices of an edge meet at the same point when extruded to infinity). The winding order is determined by checking which of the triangles belonging to the edge face the light source. Also create light and dark caps from all the triangles of the mesh that are light-facing.
  3. Render the ambient and emissive contribution. This also fills the depth buffer.
  4. For each light:
    1. Enable depth clamping and disable depth writes, color writes and face culling. Set the depth test function to GL_LESS (depth-pass method) or GL_GEQUAL (depth-fail method). Enable the stencil test, set it to always pass and set the stencil operation for front-facing triangles to GL_INCR_WRAP (depth-pass) or GL_DECR_WRAP (depth-fail) if the geometry passes the depth and stencil tests. Similarily, set the stencil operation for back-facing triangles to GL_DECR_WRAP (depth-pass) or GL_INCR_WRAP (depth-fail).
    2. Render all the shadow volumes. If using the depth-fail method, also render light and dark caps.
    3. Render the scene geometry again, but set the stencil test to only pass if the stencil buffer is equal to 0. Re-enable color writes and face-culling, set the depth test to GL_LEQUAL and use additive blending.

Implementation

  • In order to quickly find the silhouette, an edge list is computed for each mesh upon creation. Also, an OpenGL buffer double the size of the mesh's original vertex buffer is created and filled with 2 copies of the mesh's vertices, one has its w coordinate set to 1, the other has it set to 0. This happens in StaticMesh::prepareGl. An associated index buffer is also created.
  • Each Renderable that casts shadows and has a mesh creates an instance of ShadowVolumeCaster, which will handle construction of the shadow volumes and can directly be rendered. This instance computes the silhouette for each active shadow-casting light when it needs to be rendered for the first time, and stores the indices necessary for rendering the shadow volume and its caps. The silhouette is recomputed if either the light or the renderable change position/orientation. See ShadowVolumeCaster::computeSilhouette.
  • When rendering the shadow volumes, and index buffer is filled with the indices of the vertices belonging to each edge of the silhouette plus one extruded vertex. Indices of edge vertices point to vertices in the shadow volume vertex buffer with a w coordinate of 1, while indices of extruded vertices point to vertices with a w coordinate of 0. By using an infinite perspective transform, extruded vertices are then automatically extruded to infinity in the vertex shader. Enabling depth clamping prevents them from being clipped. Shadow volume rendering takes place in Scene::renderStencilShadowVolumesDepthPass and Scene::renderStencilShadowVolumesDepthFail, and the vertex shader can be found in shadow_volume.vert.
  • When rendering the light caps of a shadow volume, there can be z-fighting between the capo and the original geometry (although this shouldn't be the case since the cap is composed of copies of triangles from the original mesh). To prevent this from happening, a small polygon offset is applied before rendering the light caps.
  • Whenever a light changes state, it notifies all instances of ShadowVolumeCaster. Next time the light's shadow volumes are rendered, all instances will re-compute the silhouettes/indices. When a Renderable changes position/orientation, it's ShadowVolumeCaster instance is also notified. See the ShadowVolumeCaster::notify* methods.
Clone this wiki locally