Efficient Radiance Cascades in DirectX 12

Radiance cascades are a hierarchical lighting technique that resolves the radiance equation by decoupling angular and spatial resolution across multiple distance scales. Instead of using traditional ray marching, screen-space approximations, or dense voxel grids, this method splits ray lengths into exponentially expanding intervals called cascades. This article explains the foundational mathematics and mechanics behind radiance cascades and details how to implement and optimize them efficiently in DirectX 12 using modern compute shaders, DXR 1.1 inline ray tracing, and low-level memory management.

Understanding Radiance Cascades

Traditional global illumination techniques struggle with a fundamental trade-off: nearby geometry requires dense spatial sampling with fewer angular directions, while distant geometry demands high angular precision but can tolerate sparse spatial sampling.

Radiance cascades solve this by dividing the integration space into a sequence of cascades (\(C_0, C_1, \dots, C_n\)). Each cascade handles a distinct range of ray distances:

  • Cascade 0 (Near Field): High spatial density (e.g., one probe per pixel or texel), low angular resolution (e.g., 4 to 8 ray directions), covering a short distance range \([0, d_1]\).
  • Cascade \(i\) (Far Field): Reduced spatial density (probes spaced further apart by factors of \(2\times\) or \(4\times\)), increased angular resolution (scaled proportionally by \(4\times\)), covering an interval \([d_i, d_{i+1}]\).

At runtime, rays are traced strictly within their assigned interval. Once all cascades are evaluated, an upstream merging process begins. The outermost cascade (\(C_n\)) interpolates its radiance into the spatial and angular domain of \(C_{n-1}\). This combination proceeds inward until Cascade 0 contains fully integrated, unoccluded indirect lighting at full screen resolution, virtually eliminating light leaks and specular undersampling.

Efficient DirectX 12 Architecture

Achieving real-time performance with radiance cascades in DirectX 12 requires maximizing GPU occupancy, minimizing VRAM bandwidth, and leveraging modern hardware features.

1. Ray Evaluation with DXR 1.1 Inline Ray Tracing

Instead of creating a heavyweight Ray Tracing Pipeline State Object (RTPSO) with standard hit shaders, implement the ray tracing phase inside a standard DirectX 12 Compute Shader using RayQuery (DXR 1.1 / Shader Model 6.5+).

Inline ray tracing allows you to:

  • Avoid the overhead of dynamic shader dispatching, state object management, and call-stack allocations.
  • Restrict ray traversal strictly to the cascade interval using the RayDesc.TMin and RayDesc.TMax parameters, instantly terminating rays that exit the cascade boundary.
  • Interleave scene traversal with fast analytical or signed-distance-field (SDF) marching for rough geometry, falling back to the acceleration structure (RaytracingAccelerationStructure) only when needed.
RayQuery<RAY_FLAG_CULL_NON_OPAQUE | RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES> q;
RayDesc ray;
ray.Origin = probeWorldPos;
ray.Direction = directionFromIndex(rayIndex, cascadeIndex);
ray.TMin = cascadeIntervals[cascadeIndex].x;
ray.TMax = cascadeIntervals[cascadeIndex].y;

q.TraceRayInline(SceneBVH, RAY_FLAG_NONE, 0xFF, ray);
q.Proceed();

if (q.Status() == RAY_STATUS_COMPLETE) {
    // Process hit or evaluate sky/distant radiance
}

2. Texture Layout and Data Packing

Radiance cascade data should be packed into a 2D Texture Array or a 3D Texture to ensure cache coherency:

  • Storage Format: Use DXGI_FORMAT_R11G11B10_FLOAT for radiance values to reduce bandwidth by 50% compared to standard R16G16B16A16_FLOAT.
  • Directional Mapping: Map angular directions to a 2D octahedron or equirectangular grid for each probe. Aligning the ray indices to a Morton curve (Z-order) improves texture cache hits during the spatial-angular merge pass.

3. Compute-Based Merging via Groupshared Memory

Merging cascade \(C_{i+1}\) into cascade \(C_i\) involves spatial interpolation followed by angular averaging:

  • Dispatch compute thread groups sized to match the probe dimensions of \(C_i\) (e.g., \(8 \times 8\) threads).
  • Preload the coarser samples of cascade \(C_{i+1}\) into groupshared memory. Because \(C_{i+1}\) has fewer probes covering the same area, multiple threads in the wave share the same base probe samples.
  • Perform bilinear interpolation on the loaded samples before accumulating them with the local ray results of \(C_i\), avoiding redundant texture lookups across adjacent lanes.

4. Barrier Synchronization and Transient Heaps

DirectX 12 gives precise control over synchronization. Radiance cascades execute sequentially from the outer cascade inward:

  • Enhanced Barriers: Use DirectX 12 Enhanced Barriers (D3D12_BARRIER_LAYOUT_DIRECT_QUEUE_UNORDERED_ACCESS) between the compute passes to overlap the dispatch of independent cascades when tracing intervals do not depend on one another.
  • Transient Heaps and Aliasing: Since intermediate cascade buffers are only needed during the merge chain, allocate them on a single placed heap (ID3D12Heap) using resource aliasing. Once Cascade \(i+2\) has been merged, its memory can be immediately reused by Cascade \(i-1\), keeping the VRAM footprint flat regardless of cascade depth.

5. Wave-Level Optimization

Take advantage of Wave Intrinsics (Shader Model 6.0+) inside the compute shaders:

  • If an entire wave's ray rays hit fully opaque geometry within the cascade's interval, branch out early using WaveActiveAllTrue().
  • Combine angular samples within a wave using WaveActiveSum() to speed up spherical harmonic projections or pre-filtered diffuse irradiance calculations directly in GPU registers before writing to the UAV.