Implementing DoF with Circle of Confusion Raymarching

This article examines how modern game engines achieve cinematic depth-of-field effects by raymarching the circle of confusion directly across screen-space buffers. It covers the mathematical formulation of the circle-of-confusion field, the core raymarching algorithm that traces through color and depth layers, techniques to resolve near-field and far-field occlusion artifacts, and performance optimizations required to maintain high framerates in real-time rendering.

The Physics-Based Circle of Confusion (CoC)

Implementing realistic depth of field begins with camera optics. Modern engines use the thin-lens equation to calculate the diameter of the circle of confusion (\(CoC\)) for every pixel on the screen:

\[CoC = \left| A \cdot \frac{f}{z_{\text{focus}} - f} \cdot \left(1 - \frac{z_{\text{focus}}}{z}\right) \right|\]

Where:

  • \(A\) is the lens aperture diameter (\(f / N\), with \(N\) as the f-stop).
  • \(f\) is the focal length.
  • \(z_{\text{focus}}\) is the distance to the focus plane.
  • \(z\) is the linear scene depth of the current pixel.

Developers compute this value in a dedicated pre-pass, storing a signed CoC scalar in a render target or packed alongside depth. A positive CoC represents the background (far field), while a negative CoC represents the foreground (near field).

The Limits of Traditional DoF vs. CoC Raymarching

Standard post-process DoF implementations rely on separable blurs, Poisson disc sampling, or compute-based scatter algorithms. These approaches struggle with edge bleeding: sharp background geometry often bleeds onto out-of-focus foreground silhouettes, and out-of-focus foreground elements fail to naturally occlude sharp background details.

CoC raymarching treats the depth of field as an optical visibility and light accumulation problem. Instead of simply averaging neighboring pixels according to a kernel, the shader traces screen-space rays outward across the lens pupil's projection, sampling the depth buffer iteratively to determine visibility and volumetric light contribution.

Implementing the Raymarching Loop

CoC raymarching is typically dispatched as a compute shader pass operating on half-resolution or quarter-resolution color and CoC buffers:

  1. Kernel Generation and Ray Direction: For each pixel, the shader determines the ray direction in 2D screen space. Using blue noise or Halton sequences combined with temporal jitter, the shader generates sample offsets distributed in a disc or polygonal aperture shape (matching the virtual camera's aperture blades).
  2. Stepping Through the CoC Field: The shader marches outward from the source pixel along the disc radius. At each step, it samples the target pixel's linear depth and CoC.
  3. Intersection and Coverage Test: At step distance \(r\), the sample at position \(P\) contributes to the center pixel \(C\) if the sample’s CoC is large enough to encompass the distance \(r\), or if the center pixel's CoC is large enough to spread onto \(P\). The coverage weight \(W\) is evaluated:
    • Far Field: The sample is occluded if a closer surface intervenes. The weight depends on whether the sample's depth falls behind the focal plane and its CoC spans back to the ray's origin.
    • Near Field: The sample naturally expands over the center pixel regardless of background depth, requiring front-to-back alpha compositing: \[C_{\text{accum}} = C_{\text{accum}} + (1 - \alpha_{\text{accum}}) \cdot \text{Color}_{\text{sample}} \cdot W\] \[\alpha_{\text{accum}} = \alpha_{\text{accum}} + (1 - \alpha_{\text{accum}}) \cdot W\]
  4. Energy Normalization: The accumulated color is normalized by the total sample weights to maintain photometric consistency and prevent unnatural brightening or dimming.

Mitigating Undersampling and Artifacts

Raymarching introduces structural noise when sample counts are kept low enough for 60 FPS budgets. Developers apply several strategies to hide undersampling:

  • Separation of Fields: Rendering the near field and far field into separate buffers eliminates cross-depth sorting conflicts. The near-field buffer accumulates color and opacity, which is then composited over the combined far-field and in-focus layers.
  • Temporal Accumulation: Ray directions and jitter offsets are varied per frame. The game’s Temporal Anti-Aliasing (TAA) or dedicated spatio-temporal denoiser resolves the low-frequency noise across successive frames.
  • Hierarchical Min/Max Tile Depth: Before raymarching, downsampled min/max depth and CoC tiles (e.g., \(16 \times 16\) pixels) are generated. The compute shader queries these coarse tiles to skip raymarching entirely in flat, fully in-focus regions, executing rays only where significant CoC gradients exist.
  • Bilateral Upsampling: The low-resolution raymarched results are upsampled to native resolution using a bilateral depth-aware filter, preventing blurred colors from leaking across crisp, in-focus geometric edges.