Camera, Lighting, and Shadows
This page documents the three interconnected systems that define how the renderer perceives and illuminates the scene: the Camera for viewpoint transformation, Directional Lighting for direct illumination, and Cascaded Shadow Maps (CSM) for high-quality dynamic shadows. Together these systems form the visual foundation of the rendering pipeline, bridging CPU-side scene management with GPU-side shading computations.
Camera System
The camera system provides viewpoint transformation using a yaw-pitch orientation model with reverse-Z perspective projection. The Camera struct in the framework layer stores both input state (position, orientation angles, projection parameters) and derived state (view, projection, and combined matrices), following a clear separation between mutable configuration and cached computations.
Coordinate System and Orientation
The camera uses a right-handed coordinate system with Y-up. At yaw=0 and pitch=0, the camera looks along the negative Z axis. Yaw rotates horizontally around the Y axis, while pitch rotates vertically (positive values look upward). The forward() and right() methods compute direction vectors from these angles, with right() always remaining horizontal regardless of pitch to prevent roll. Sources: camera.h, camera.cpp
Reverse-Z Projection
A critical design decision is the use of reverse-Z depth buffering: the near plane maps to depth 1.0, while the far plane maps to depth 0.0. This distribution provides dramatically improved depth precision for distant objects compared to conventional depth buffering. The projection matrix is constructed manually rather than using GLM's standard functions to achieve this mapping:
z_ndc = (A * z_eye + B) / (-z_eye)
where A = near / (far - near), B = near * far / (far - near)Sources: camera.h, camera.cpp
Camera Controller
The application layer provides CameraController for interactive camera manipulation. It implements WASD movement along the camera's forward/right directions, Space/Ctrl for vertical movement, and right-mouse-drag for rotation. The controller handles cursor capture (hiding the cursor and enabling raw mouse motion when available) and integrates with ImGui's input capture system to avoid conflicts with UI interaction. A sprint modifier (Shift) triples movement speed, and the F key triggers automatic framing of a target AABB. Sources: camera_controller.h, camera_controller.cpp
Directional Lighting
The lighting system currently supports directional lights (sun/moon) with shadow casting capabilities. Each DirectionalLight stores a normalized direction vector (pointing toward the scene), linear-space color, intensity multiplier, and a boolean indicating whether it casts shadows. The design anticipates future extension to point and spot lights while maintaining a compact GPU representation.
GPU Light Representation
Lights are uploaded to the GPU as GPUDirectionalLight structures in a shader storage buffer object (SSBO). The structure packs direction and intensity into one vec4, and color with a shadow-casting flag into another, achieving 32 bytes per light with std430 alignment. The forward pass iterates over active lights, computing Cook-Torrance BRDF contributions for each. Sources: scene_data.h, bindings.glsl, forward.frag
Cascaded Shadow Maps (CSM)
The shadow system implements Cascaded Shadow Maps with Practical Split Scheme (PSSM) distribution, Percentage-Closer Filtering (PCF), and optional Percentage-Closer Soft Shadows (PCSS) for contact-hardening effects. The architecture separates CPU-side cascade computation from GPU-side sampling, connected through the GlobalUBO.
Cascade Computation
The compute_shadow_cascades() function in the framework layer computes per-cascade view-projection matrices each frame. It uses PSSM to distribute cascade boundaries logarithmically (controlled by split_lambda), providing higher shadow resolution near the camera where detail matters most. For each cascade:
- Sub-frustum extraction: The camera frustum is sliced at the cascade's near and far boundaries
- Light-space transformation: A view matrix is constructed with the light direction as the look-at vector, centered on the sub-frustum for numerical precision
- Tight orthographic fit: XY extents tightly bound the sub-frustum corners in light space
- Z extension: The depth range is extended to the scene AABB to capture shadow casters outside the view frustum
- Texel snapping: The projection translation is adjusted to align with shadow map texel boundaries, eliminating edge shimmer during camera translation
Sources: shadow.h, shadow.cpp
Shadow Map Rendering
The ShadowPass renders depth into a 2D array texture (D32Sfloat format, up to 4 layers). Two pipelines handle different geometry types: an opaque pipeline performs depth-only rendering with no fragment shader, while a masked pipeline includes alpha testing for materials with transparent cutouts. The pass uses reverse-Z depth comparison (VK_COMPARE_OP_GREATER) with slope-based depth bias to prevent shadow acne. Sources: shadow_pass.cpp, shadow.vert
Shadow Sampling and Filtering
The shader-side shadow evaluation (shadow.glsl) provides a complete shadow sampling library with multiple filtering modes:
| Mode | Description | Use Case |
|---|---|---|
| Hard Shadows | Single hardware comparison | Performance-critical, distant geometry |
| PCF | Fixed-radius Percentage-Closer Filtering | Basic soft shadows |
| PCSS | Percentage-Closer Soft Shadows with blocker search | Contact-hardening, high quality |
The PCSS implementation follows a three-step algorithm: (1) blocker search in an elliptical region around the projected point, (2) penumbra width estimation based on blocker-receiver depth separation, and (3) variable-width PCF with the computed kernel size. Poisson disk sampling with per-pixel rotation (plus temporal variation) provides stable results without regular grid artifacts. Sources: shadow.glsl
Cascade Selection and Blending
The select_cascade() function chooses the appropriate cascade based on view-space depth, returning both the cascade index and a blend factor for transitions. In blend regions near cascade boundaries, shadow values from adjacent cascades are interpolated to hide discontinuities. A distance fade at the far shadow boundary smoothly transitions to unshadowed lighting. Sources: shadow.glsl, shadow.glsl
Receiver Plane Depth Bias
To eliminate shadow acne on surfaces nearly parallel to the light direction, the system computes receiver plane depth gradients using screen-space derivatives (dFdx/dFdy). These gradients enable per-sample depth adjustment during PCF/PCSS sampling, providing superior results compared to constant bias alone. The implementation carefully handles cascade boundary cases where derivatives would otherwise produce garbage values. Sources: shadow.glsl
Integration with Forward Rendering
The forward pass integrates shadows through the blend_cascade_shadow() function, called once per directional light that casts shadows. The function handles cascade selection, PCF/PCSS sampling based on the configured shadow mode, cascade blending, and distance fading. The resulting shadow attenuation multiplies the light's radiance contribution. For the primary directional light, additional contact shadows (screen-space ray-marched occlusion) are composited for enhanced detail on small features. Sources: forward.frag, forward.frag
Configuration Parameters
The shadow system exposes extensive runtime configuration through ShadowConfig:
| Parameter | Description | Typical Range |
|---|---|---|
cascade_count |
Number of active cascades (1-4) | 3-4 |
split_lambda |
PSSM log/linear blend (0=linear, 1=log) | 0.5-0.8 |
max_distance |
Shadow coverage distance in meters | 50-200 |
pcf_radius |
PCF kernel radius (0=off, 1=3x3, etc.) | 1-3 |
shadow_mode |
0=PCF, 1=PCSS | 0 or 1 |
light_angular_diameter |
Light size for PCSS penumbra (radians) | 0.009 (sun) |
pcss_quality |
Sample count preset (0=Low, 1=Med, 2=High) | 1-2 |
Sources: scene_data.h
Architecture Diagram
Related Documentation
- For shadow pass implementation details, see Shadow Mapping (CSM) and Contact Shadows
- For the complete forward rendering pipeline, see Depth PrePass and Forward Rendering
- For camera integration with the application, see Camera Controller and Debug UI
- For scene data structures and culling, see Scene Data and Culling