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)