[Feature] Directional Light Shadow Mapping β Complete Implementation
Status: π§ In Progress
Priority: High
Relates To: Rendering Pipeline, Lighting System
Started: 2026-06-21
Last Updated: 2026-06-25
Overview
Complete directional light shadow mapping system for deferred rendering pipeline.
Camera frustum β light space AABB β orthographic shadow map β PCF + slope-based bias β per-light shading.
Current Status:
- β
Phase 1 (Complete): Core shadow pass, projection setup, basic depth comparison
- β
Phase 2 (Complete): Slope-based dynamic bias, PCF (9-sample), boundary fade
- β
Phase 3 (Complete): Texel snapping for temporal stability
- β Phase 4 (Investigation): Depth bias origin (CPU vs GPU), Tangent ratio normalization
- π§ Phase 5 (Planned): Cascade Shadow Maps, Perspective Shadow Mapping
Implementation Details
Phase 1: Core Shadow Mapping β
Features:
- Orthographic projection from camera frustum perspective
- Shadow map render pass β depth texture
- Depth comparison with fragment depth
- Border color handling (GL_CLAMP_TO_BORDER)
Files:
Engine/Private/Renderer/Proxy/LightProxy.cpp β DirectionalLightProxy::UpdateView()
Engine/Public/Renderer/RenderPass/ShadowPass.cpp β Shadow rendering
asset/shader/shadow_directional.vs.glsl β Vertex transformation
Result: Basic shadow rendering functional.
Phase 2: Slope-Based Bias + PCF β
Features:
-
Dynamic Bias: shadowBias = texelRatio Γ tangentRatio
texelRatio: Shadow map texel size relative to projection
tangentRatio: Surface normal angle vs light direction
- Eliminates shadow acne on steep surfaces
-
PCF (Percentage Closer Filtering): 9-sample filter (3Γ3 neighborhood)
- Soft shadow edges without performance penalty
- Samples at texel boundaries
-
Boundary Fade: Smooth transition at shadow map edges
smoothstep() blending at clip boundaries
- Prevents hard shadow cutoff artifacts
Files:
asset/shader/lighting_directional.ps.glsl β Fragment shader bias + PCF
Result: High-quality shadows with soft edges, no acne on angled surfaces.
Phase 3: Texel Snapping for Temporal Stability β
Features:
- Texel Grid Quantization: Snap shadow map lookup position to discrete texel boundaries
- Snap Grid Size:
texelSize = frustumRadius * 2.f / shadowMapSize
- Implementation:
- Transform frustum center to light space
- Quantize position:
floor(pos / texelSize + 0.5) * texelSize
- Transform back to world space
- Reconstruct light view matrix from snapped position
Solves: Shadow flicker during camera movement (shadow grid no longer drifts relative to world).
Files:
Engine/Private/Renderer/Proxy/LightProxy.cpp β Lines 375-390
Result: Stable shadow placement across frames. β
Screenshot: asset/screenshot/fixed_shadow_shimmering.gif
Sub-Issues & Resolutions
Sub-Issue 1: Depth Bias Origin (CPU vs GPU Calculation) β
Status: Requires Investigation
Current Observation:
- CPU snaps at grid size:
texelSize = frustumRadius * 2.f / shadowMapSize β 0.02148
- GPU calculates texelRatio from:
1.0 / max(shadowSize.x, shadowSize.y) β 0.000977
- 22Γ difference but shadow blinking appears independent of bias magnitude
Question: Is the bias calculation actually the cause, or is this a symptom of deeper GPU/CPU synchronization issue?
Next Steps:
- Test with fixed bias value (bypass tangentRatio)
- Verify CPU and GPU use same snap grid for fragment position
- Check if issue persists with unified texelRatio
Candidate Fixes (if confirmed as root cause):
// Option A: Pass texelRatio from C++ to shader
m_directional->data.texelRatio = 1.0f / texelSize;
// Option B: Snap fragment position in shader using same grid
lightPos.xy = floor(lightPos.xy / texelRatio + 0.5) * texelRatio;
Sub-Issue 2: Tangent Ratio Magnitude β
Status: Requires Investigation
Current Observation:
tangentRatio = length(light.direction - worldNor * dot(light.direction, worldNor))
Ranges from 0 to ~2, producing bias variance of [0, 0.043] when multiplied by texelRatio.
Question: Is this variance causing depth comparison instability, or is it functioning as designed for slope-based bias?
Next Steps:
- Compare shadow quality with fixed tangentRatio (e.g., always 0.5)
- Visualize bias map to identify over/under-shadowing regions
- Determine if
saturate(tangentRatio) improves stability
Candidate Fix (if confirmed as root cause):
tangentRatio = saturate(tangentRatio); // Clamp to [0, 1]
Sub-Issue 3: Frustum Radius & Snap Grid Stability β
Status: Resolved (as of Phase 3)
Confirmed: Frustum radius is stable and correctly applies to texel size calculation.
frustumRadius = distance(frustumCenter, furthest_frustum_corner);
frustumRadius = floor(frustumRadius + 0.5f); // Snap to integer for stability
const float texelSize = frustumRadius * 2.f / shadowMapSize; // β Correct
Result: Snap grid is consistent and correctly scaled. β
Future Phases
Phase 5: Cascade Shadow Maps (Planned) π§
Goal: Multiple shadow maps at varying resolutions for different camera distances.
Implementation:
- Split camera frustum into N cascades (e.g., near/mid/far)
- Render each to separate shadow map at appropriate resolution
- Blend transitions at cascade boundaries via depth-based lerp
Expected Benefit: High shadow resolution near camera, lower at distance (less memory/bandwidth).
Phase 6: Perspective Shadow Mapping (Planned) π§
Goal: Shadow shimmering mitigation for perspective camera (not orthographic).
Note: PSM unsuitable for directional lights (infinite distance); consider alternative approaches:
- Re-projection techniques
- Temporal filtering (previous frames)
Testing Checklist
Performance Considerations
| Operation |
Cost |
Notes |
| Shadow Pass (1 directional light) |
~0.5ms |
Renders entire scene depth to 1024Γ1024 map |
| Frustum Calculation |
<0.1ms |
Per-frame, O(8) frustum corners |
| Texel Snapping |
<0.01ms |
Arithmetic only, no branching |
| PCF (9-sample) |
~0.1ms per pixel |
Modest cost; necessary for quality |
| Cascade Maps (if implemented) |
~1.5ms |
3 cascades Γ shadow pass cost |
Optimization Opportunities:
- Shadow pass early-exit culling (front-to-back rendering)
- Compute shader for frustum/sphere calculation
- Deferred light culling (only shade pixels lit by shadow-casting lights)
Decision Log
| Date |
Decision |
Rationale |
| 2026-06-21 |
Slope-based bias + PCF |
Industry standard; eliminates acne without custom bias logic |
| 2026-06-25 |
Texel snapping for stability |
Solves temporal drift; proven in major engines |
| 2026-06-25 |
Defer bias investigation |
Origin unclear (CPU? GPU? both?); requires systematic testing |
| 2026-06-25 |
Separate shadow blinking as distinct issue |
Bug is orthogonal to feature; tracked in Bug_ShadowBlinking.md |
Notes
- Frustum Stability: Confirmed that frustum radius and snap grid size are consistent; not the root cause of blinking.
- Bias Origin: Unclear whether bias divergence or tangent ratio variance is problematic. Both need investigation via controlled testing.
- Shadow Blinking: Separate tracked issue (see Docs/Bug_ShadowBlinking.md). May be GPU/CPU sync race unrelated to bias.
[Feature] Directional Light Shadow Mapping β Complete Implementation
Status: π§ In Progress
Priority: High
Relates To: Rendering Pipeline, Lighting System
Started: 2026-06-21
Last Updated: 2026-06-25
Overview
Complete directional light shadow mapping system for deferred rendering pipeline.
Camera frustum β light space AABB β orthographic shadow map β PCF + slope-based bias β per-light shading.
Current Status:
Implementation Details
Phase 1: Core Shadow Mapping β
Features:
Files:
Engine/Private/Renderer/Proxy/LightProxy.cppβ DirectionalLightProxy::UpdateView()Engine/Public/Renderer/RenderPass/ShadowPass.cppβ Shadow renderingasset/shader/shadow_directional.vs.glslβ Vertex transformationResult: Basic shadow rendering functional.
Phase 2: Slope-Based Bias + PCF β
Features:
Dynamic Bias:
shadowBias = texelRatio Γ tangentRatiotexelRatio: Shadow map texel size relative to projectiontangentRatio: Surface normal angle vs light directionPCF (Percentage Closer Filtering): 9-sample filter (3Γ3 neighborhood)
Boundary Fade: Smooth transition at shadow map edges
smoothstep()blending at clip boundariesFiles:
asset/shader/lighting_directional.ps.glslβ Fragment shader bias + PCFResult: High-quality shadows with soft edges, no acne on angled surfaces.
Phase 3: Texel Snapping for Temporal Stability β
Features:
texelSize = frustumRadius * 2.f / shadowMapSizefloor(pos / texelSize + 0.5) * texelSizeSolves: Shadow flicker during camera movement (shadow grid no longer drifts relative to world).
Files:
Engine/Private/Renderer/Proxy/LightProxy.cppβ Lines 375-390Result: Stable shadow placement across frames. β
Screenshot:
asset/screenshot/fixed_shadow_shimmering.gifSub-Issues & Resolutions
Sub-Issue 1: Depth Bias Origin (CPU vs GPU Calculation) β
Status: Requires Investigation
Current Observation:
texelSize = frustumRadius * 2.f / shadowMapSizeβ 0.021481.0 / max(shadowSize.x, shadowSize.y)β 0.000977Question: Is the bias calculation actually the cause, or is this a symptom of deeper GPU/CPU synchronization issue?
Next Steps:
Candidate Fixes (if confirmed as root cause):
Sub-Issue 2: Tangent Ratio Magnitude β
Status: Requires Investigation
Current Observation:
tangentRatio = length(light.direction - worldNor * dot(light.direction, worldNor))Ranges from 0 to ~2, producing bias variance of
[0, 0.043]when multiplied by texelRatio.Question: Is this variance causing depth comparison instability, or is it functioning as designed for slope-based bias?
Next Steps:
saturate(tangentRatio)improves stabilityCandidate Fix (if confirmed as root cause):
Sub-Issue 3: Frustum Radius & Snap Grid Stability β
Status: Resolved (as of Phase 3)
Confirmed: Frustum radius is stable and correctly applies to texel size calculation.
Result: Snap grid is consistent and correctly scaled. β
Future Phases
Phase 5: Cascade Shadow Maps (Planned) π§
Goal: Multiple shadow maps at varying resolutions for different camera distances.
Implementation:
Expected Benefit: High shadow resolution near camera, lower at distance (less memory/bandwidth).
Phase 6: Perspective Shadow Mapping (Planned) π§
Goal: Shadow shimmering mitigation for perspective camera (not orthographic).
Note: PSM unsuitable for directional lights (infinite distance); consider alternative approaches:
Testing Checklist
Performance Considerations
Optimization Opportunities:
Decision Log
Notes