Dynamic Depth Bias in DirectX Shadow Maps

Shadow mapping in DirectX often suffers from self-shadowing artifacts, commonly referred to as shadow acne or z-fighting, which occur due to finite depth buffer precision and resolution limits. This article explores how DirectX resolves these artifacts using dynamic depth bias parameters—specifically constant depth bias, slope-scaled depth bias, and depth bias clamp—within the rasterizer state. By adjusting depth calculations based on surface orientation relative to light sources, these parameters push shadow map comparisons beyond precision thresholds, eliminating false self-occlusion without causing detached shadows.

The Cause of Shadow Map Z-Fighting

When rendering a shadow map, the scene’s depth is captured from the perspective of the light source and stored in a discrete texture. During the main rendering pass, a surface fragment's distance to the light is compared against the sampled depth from the shadow map.

Because the shadow map has a finite pixel grid and numerical floating-point limits, multiple screen-space fragments map to the same shadow map texel. If a surface is not completely perpendicular to the light direction, some fragments on a smooth polygon will evaluate as slightly deeper than the stored texel value, while others evaluate as closer. This creates an alternating pattern of light and dark bands across the surface known as shadow acne or self-shadowing z-fighting.

Core Dynamic Depth Bias Parameters

DirectX addresses this issue at the hardware rasterization stage using the rasterizer state (D3D11_RASTERIZER_DESC or D3D12_RASTERIZER_DESC). Rather than applying manual offsets in custom pixel shaders, developers configure three primary rasterizer parameters:

  • DepthBias (Constant Bias): An integer value converted by hardware into a fixed depth offset. It applies a uniform shift to the z-value of every rasterized primitive.
  • SlopeScaledDepthBias: A dynamic multiplier that scales the offset proportionally to the primitive's slope (the rate of depth change across the polygon). Surfaces tilted sharply away from the light have a higher slope and receive a larger depth shift.
  • DepthBiasClamp: A floating-point upper bound that limits the total calculated bias. This prevents geometry viewed at extreme, grazing angles from receiving excessively large offsets.

How Dynamic Bias Eliminates Z-Fighting

A static, constant bias is rarely sufficient across an entire scene. If the constant bias is set high enough to fix z-fighting on steep surfaces, flat surfaces experience "Peter Panning"—an artifact where shadows detach from the base of objects because the depth has been pushed too far back. Conversely, if the constant bias is too low, steep surfaces will continue to show severe shadow acne.

The DirectX rasterizer dynamically evaluates total bias for each polygon using the following hardware formula:

\[\text{Total Bias} = (\text{Slope} \times \text{SlopeScaledDepthBias}) + \text{DepthBias}\]

The hardware calculates the slope as the maximum rate of change of depth with respect to screen-space coordinates:

\[\text{Slope} = \max\left(\left|\frac{\partial z}{\partial x}\right|, \left|\frac{\partial z}{\partial y}\right|\right)\]

When a polygon faces the light directly, \(\text{Slope}\) approaches zero, meaning the applied bias is almost purely determined by DepthBias. As the surface angles away from the light, the derivative of depth increases, automatically raising the total bias to match the larger depth discrepancies between adjacent texels.

Finally, if the computed total bias exceeds the DepthBiasClamp, the hardware caps the value. This ensures that near-parallel surfaces to the light vector do not generate an infinite or excessive depth offset that degrades shadow quality elsewhere.

By combining slope-aware scaling with hardware-level clamping, DirectX dynamically delivers the minimum depth offset required to push surfaces behind the shadow comparison threshold, cleanly eliminating z-fighting while preserving contact shadows.