Shadow Masks |
This topic contains the following sections:
In DigitalRune Graphics shadow maps are used in two ways:
Forward rendering: In forward rendering models are drawn directly to the screen. The effect that renders the model samples the shadow map directly to see if a pixel is lit/shadowed. Shadows are disabled in the pre-built effects for forward rendering. But the code for directional light shadows is included in the source and can be enabled using preprocessor directives. If you want to add shadows for forward rendering, please have a look at the source code of Forward.fx.
Deferred shadow maps: In a deferred lighting pipeline models are first rendered into the G-buffer. Then the ShadowMaskRenderer reads the shadow maps and creates a shadow mask. The shadow mask indicates which pixels in the G-buffer are lit and which are shadowed. Shadow filtering is done when the shadow mask is created. In addition, the shadow mask can be blurred to create soft shadows. The shadow mask can also be rendered into a low-resolution render target to improve performance. Any shader that applies a light can simply read the shadow mask – it is not necessary to sample individual shadow maps.
In a typical game with deferred lighting, opaque meshes are rendered into the G-buffer, and transparent meshes are rendered using forward rendering. Therefore, deferred shadow maps only work for opaque meshes. In many games transparent meshes do not receive any shadows to keep the forward rendering simple.
The shadow mask is an image, as seen from the camera, where each pixel stores the shadow information of the scene. Each shadow-casting light is assigned to one 8-bit channel (R, G, B, or A). Lights that do not overlap can use the same channel!
Each channel stores the shadow term of the light source. A value of 0 means the pixel is shadowed; a value of 1 means the pixel is fully lit. The shadow mask is usually an RGBA render target, which means that it can store the shadow terms of up to four shadows per pixel. If a pixel is affected by more than 4 shadows at the same time, multiple render targets are required to store the shadow mask. – For performance reasons it is recommended that no more than 4 shadow-casting lights overlap in a scene. A single render target should be sufficient in most cases.
The following screenshot shows a scene with a directional light (sun) and a point light (colored sphere). The shadow mask is shown in the upper left. The shadows have to use different shadow mask channels because the directional light and the point light overlap. The directional light's shadow is stored in the R channel. The point light's shadow is stored in the G channel.
DigitalRune Graphics provides a ShadowMaskRenderer. The renderer supports StandardShadows, CubeMapShadows, CascadedShadows and CompositeShadows. Support for new shadow types can be implemented by creating a new renderer and adding it to the Renderers collection of the ShadowMaskRenderer.
In deferred rendering pipelines, the ShadowMaskRenderer is usually called after the shadow map renderer:
// Create shadow masks for the specified light nodes.
shadowMaskRenderer.Render(lightNodes, context);
The shadow mask renderer handles shadow filtering and biasing. The resulting shadow masks are stored in ShadowShadowMask. The assigned shadow mask channel is stored in ShadowShadowMaskChannel.
Shadow masks are also available via the ShadowMaskRendererShadowMasks property – which is often helpful for debugging.
The shadow masks are usually used by the deferred LightRenderer. When the shadow masks are no longer required, they can be recycled by calling RecycleShadowMasks.
shadowMaskRenderer.RecycleShadowMasks();
The shadow mask can be blurred as post-process to reduce shadow aliasing even further. This requires a special type of filter: A depth-scaled, cross-bilateral, anisotropic, pseudo-separable Gaussian filter.
This filter needs to be assigned to the ShadowMaskRenderer, for example:
var blur = new Blur(GraphicsService); blur.InitializeGaussianBlur(11, 3, true); // 11 taps blur.DepthScaling = 0.7f; blur.IsBilateral = true; blur.IsAnisotropic = true; shadowMaskRenderer.Filter = blur;
As can be seen in the screenshot below, this type of filter produces excellent results and is particularly efficient. It can blur all shadows in the shadow mask at once!
The next image compares shadow map filtering (PCF) with shadow mask filtering ("screen space blur").
The shadow mask can optionally be rendered at half resolution to improve performance.
ShadowMaskRenderer.UseHalfResolution = true;
The shadow mask is then upscaled using cross-bilateral ("depth-aware") upsampling.