Shadow Mapping

23.1 Algorithm

Render the scene from the light into a depth texture (depth-only PSO, no PS needed).

In the main pass, transform each pixel into light clip space; compare its depth with the shadow map.

23.2 Shaders

// Shadow pass: depth only
cbuffer ShadowCB : register(b0) { float4x4 gLightViewProj; float4x4 gObjWorld; };
float4 ShadowVS(float3 pos : POSITION) : SV_Position
{
    return mul(gLightViewProj, mul(gObjWorld, float4(pos, 1)));
}

// Main pass
Texture2D<float>       gShadowMap : register(t6);     // R32_TYPELESS resource, R32_FLOAT SRV
SamplerComparisonState gShadowCmp : register(s2);     // COMPARISON_MIN_MAG_LINEAR_MIP_POINT, LESS_EQUAL, border 1

float ShadowFactor(float3 posW, float3 N, float3 L)
{
    float4 posL = mul(gLightViewProj, float4(posW + N * 0.02, 1));   // normal-offset bias
    float3 ndc  = posL.xyz / posL.w;
    float2 uv   = ndc.xy * float2(0.5, -0.5) + 0.5;                    // D3D: flip Y
    if (any(uv < 0) || any(uv > 1)) return 1.0;
    float  bias = max(0.002 * (1.0 - dot(N, L)), 0.0005);
    float  depth = ndc.z - bias;

    // 5×5 PCF using hardware 2×2 bilinear comparison
    uint w, h; gShadowMap.GetDimensions(w, h);
    float2 texel = 1.0 / float2(w, h);
    float sum = 0;
    [unroll] for (int y = -2; y <= 2; ++y)
    [unroll] for (int x = -2; x <= 2; ++x)
        sum += gShadowMap.SampleCmpLevelZero(gShadowCmp, uv + float2(x, y) * texel, depth);
 
…

23.3 Problems & solutions

slope-scaled depth bias (rasterizer DepthBias, SlopeScaledDepthBias), normal offset smaller bias; render back faces into the shadow map for closed meshes Aliasing / blocky edges

PCF, PCSS (soft shadows), higher resolution, texel snapping Cascaded shadow maps (CSM) in a Texture2DArray, select cascade by view depth; stabilize by snapping to texels cube shadow maps (TextureCube + SampleCmp with direction) or dual paraboloid shadow atlases, virtual shadow maps (Unreal), ray-traced shadows (Chapter 32) Cascade selection:

cbuffer CSM : register(b4) { float4x4 gCascadeVP[4]; float4 gCascadeSplits; };
Texture2DArray<float> gCascades : register(t7);

uint SelectCascade(float viewDepth)
{
    return (uint)dot(float4(viewDepth > gCascadeSplits), 1.0);   // count splits passed (0..3)
}
// sample: gCascades.SampleCmpLevelZero(gShadowCmp, float3(uv, cascade), depth)