Parallel-Split Shadow Maps on Programmable GPUs

Dan Rakušan

Screenshot #1 Screenshot #2 Screenshot #3

Assignment

Implement the method Parallel-Split Shadow Maps (PSSM) and compare it with the classic shadow map method. Depth maps must be generated in a single pass. Implement and compare both approaches recommended by the article:

  1. using instanced rendering,
  2. using the geometry shader and layered rendering.
The algorithm must be designed for fully dynamic scenes (objects, lights, camera can all move freely).

Implementation

Three shadow map methods are implemented, the "Regular" simple single shadow map method and two single pass PSSM methods described in the article ("PSSM Geometry shader" and "PSSM Instancing"). The two PSSM methods differ in the approach to rendering geometry into multiple shadow maps at once.

The first method uses the geometry shader to duplicate triangles and render them into an appropriate shadow map.

The second method avoids the geometry shader and instead uses instanced rendering to duplicate the geometry and render it into several layers of a layered texture at the same time. This method has been improved upon by using the "GL_AMD_vertex_shader_layer" extension that allows specifying the target layer of a layered texture using the "gl_Layer" built-in GLSL variable not only in the geometry shader, but also in the vertex shader, removing the need for a trivial geometry shader.

The practical split scheme described in the article is used for splitting the camera frustum into sub-frustums and only basic axis aligned bounding box collision testing is employed for finding objects intersecting with frustums as well as objects who can potentially project their shadows into a frustum (sweeping AABB test).

Each of the three methods can be compared by switching between them in the UI. Various parameters can be adjusted like the shadow map resolution and for the PSSM methods the number of splits and the split scheme weight can be adjusted.

The rendered shadow maps can be displayed using the "Show shadow maps" option and will be rendered as square textures in the bottom left corner of the screen. The shadow frustum split locations can be also visualized using an overlay by enabling the "Visualize shadow map" option.

A controllable secondary view of another camera is also provided which can visualize all the frustums, their bounding boxes as well as the actual frustums used to render individual shadow maps.
Objects that aren't rendered in the main view can also be hidden from the secondary view to display which objects are culled by the camera frustum.

Performance

Screenshot #2

In the images above, the three methods are compared. The PSSM methods (INST being the instancing method and GEO being the geometry shader method) are further compared using various numbers of splits. Four performance metrics were recorded: the FPS of the application, the time the CPU took to calculate shadow map frustums (CPU shadow), the total GPU frametime (GPU frame) and the time the GPU took to render the shadow maps (GPU shadow).

For the sake of rendering the same geometry, the "Regular" method runs a similar computation as the PSSM methods to determine where to place the shadow map frustum and to cull objects outside of the camera frustum. The shadow map frustum for the "Regular" method is placed such that it covers the entire camera frustum. It could very well be placed the exact same way as in the 1 split PSSM method in a real world application and thus the visual comparison is mainly relevant between the single and multiple split methods.

As mentioned, the "Regular" and "1 Split" cases use a single frustum that covers either the entire camera frustum, or all the potentially shadow casting objects. The example scene contains objects in a large area and thus the rendered shadow maps are very low resolution. Starting from the "2 Splits" case, multiple shadow maps are rendered each based on a sub-frustum of the main camera which are visualized using the color overlay.

The "Instancing" method managed to achieve similar performance in this scene as the "Regular" shadow map. The cost of the shadow frustum CPU computation does not rise significantly with a higher number of splits but its worth noting that its highly dependant on the scene geometry and in this case takes a tiny fraction of the rendering time, thus having a small impact on performance.

The "Geometry shader" method performed about 20% worse than the "Instancing" method while delivering the same exact visual results. This is likely due to the performance penalty of using a geometry shader. It is worth noting that originally, the "Instancing" method, as described in the article, used a simple passthrough geometry shader that it required to set the "gl_Layer" GLSL variable to specify the target layer of the shadow map layered texture. With this geometry shader present the "Instancing" method performed roughly the same as the "Geometry shader" method. Using the "GL_AMD_vertex_shader_layer" extension is therefore crucial for maintaining good performance.

Controls

UI

  • Control panel
    • Sun spin - Changes position of the sun
    • Shadow type - Method used for rendering shadows
      • PSSM Instancing - The single pass PSSM method using instanced drawing
      • PSSM Geometry shader - The single pass PSSM method using geometry shader to duplicate vertices
      • Regular - A simple single shadow map method, uses the "scene-independent" logic to fit the shadow map to the camera's frustum
    • Split scheme weight - The lambda parameter of the practical split scheme from the article
    • Visualize shadow maps - Color the render depending on which split frustum each fragment belongs to
    • Show shadow maps - Displays the individual square shadow maps in the bottom left corner of the screen
    • Limit FPS - Enable/Disable VSync
    • zNear - Main camera near plane position
    • zFar - Main camera far plane position
    • Shadow map cascades - Number of PSSM splits
    • Shadow map resolution - Resolution of the individual shadow maps
    • Shadow bias - Adjustable shadow bias used to counter shadow map aliasing artefacts
    • Enable profiling - Enable/Disable profiling timers
    • Reset timers - Reset profiling timers average, is done automatically when certain settings are changed
  • Secondary view - Shows the world from a second auxiliary camera, used to inspect the frustum of the main camera. The shadows in this view aren't meant to be correct.
    • Show frustums - Shows the individual split camera frustums
    • Show frustum AABBs - Shows the axis aligned bounding boxes of the camera frustums
    • Show shadow volumes - Shows the light frustums used to render individual shadow maps
    • Cull objects outside frustum - Hide objects which aren't rendered by the main camera
  • ImGui Demo window - Can be used to show FPS metrics (Tools > Metrics/Debugger)

Camera controls
RMB Drag
Rotate camera around the pivot point
MMB Drag
Move the pivot perpendicular to the camera's direction
Scroll
Zoom the camera in and out from the pivot point
Alt + LMB Drag
Zoom the camera smoothly with the mouse

Secondary view can be controlled in the same way when the mouse cursor is hovering over its window.

Download