Fast Subsurface Scattering with DirectX Compute

Subsurface scattering (SSS) is a critical lighting phenomenon that simulates how light penetrates translucent materials, scatters internally, and exits at different surface points. This article breaks down the physical fundamentals of subsurface scattering, explains why brute-force evaluation is prohibitive in real-time rendering, and outlines an efficient implementation strategy using DirectX compute shaders. By leveraging screen-space approximations, separable diffusion kernels, and optimized compute passes, developers can achieve high-fidelity rendering of skin, wax, and marble at real-time frame rates.

Understanding Subsurface Scattering

Standard surface rendering uses a Bidirectional Reflectance Distribution Function (BRDF), which assumes that light enters and exits a surface at the exact same point. While this approximation works well for opaque materials like metals or plastics, it fails completely for translucent materials such as human skin, jade, foliage, and liquids.

Subsurface scattering requires a Bidirectional Scattering-Surface Reflectance Distribution Function (BSSRDF). The BSSRDF models light entering at point \(x_i\), undergoing multiple scattering events within the medium, and exiting at a distinct location \(x_o\). Without SSS, objects like human faces appear hard, dry, and plastic-like because the light does not soften across shadow boundaries or produce the subtle warm glows characteristic of internal diffusion.

The Real-Time Challenge: Screen-Space SSS

Full volumetric simulation using path tracing accurately resolves the BSSRDF by tracing millions of random light paths through a volume. However, this is too computationally expensive for real-time graphics.

To overcome this, modern real-time engines utilize Screen-Space Subsurface Scattering (SSSS). This technique approximates subsurface scattering as a post-processing filter applied to the screen-space diffuse lighting buffer. The core realization is that the diffusion profile—the spatial distribution of scattered light—can be modeled mathematically using a sum of Gaussians or a normalized diffusion profile.

Structuring the DirectX Compute Pipeline

Compute shaders in DirectX (Direct3D 11 and Direct3D 12) provide the ideal framework for SSSS because they grant fine-grained control over thread scheduling, texture sampling, and fast on-chip memory access via groupshared memory.

An efficient compute-based SSS pipeline is structured into three primary stages:

1. Lighting Separation

Before scattering, the scene must be rendered with specular and diffuse lighting separated:

  • Specular Component: Specular reflection occurs strictly at the surface interface (Fresnel reflection) and must not be blurred.
  • Diffuse Component: Diffuse light penetrates the material and is written to a dedicated offscreen render target to be scattered.
  • Linear Depth & Normals: Rendered to a G-Buffer to guide the bilateral filtering and prevent bleeding across geometric edges.

2. Separable Blur via Two Compute Passes

A 2D radial blur kernel is computationally expensive, requiring \(O(N^2)\) texture reads per pixel. Because diffusion profiles can be decomposed into a sum of separable 1D Gaussians, the 2D filter can be split into two 1D passes:

  • Pass A (Horizontal Compute Pass): Reads the diffuse buffer, computes horizontal scattering, and writes to an intermediate texture.
  • Pass B (Vertical Compute Pass): Reads the intermediate texture, computes vertical scattering, and writes the final scattered diffuse result.

This reduces complexity from \(O(N^2)\) to \(O(2N)\) samples per pixel.

3. Depth-Aware (Bilateral) Filtering

To ensure that light does not bleed across disocclusions or between disconnected objects, the compute shader must evaluate depth differences between the center pixel and surrounding sample points:

float depthDifference = abs(centerLinearDepth - sampleLinearDepth);
float depthWeight = saturate(1.0 - (depthDifference * depthSensitivity));

The computed depthWeight scales the diffusion kernel weight for that sample, discarding samples that belong to the background or separate surfaces.

DirectX Compute Optimization Techniques

To maximize throughput across compute passes on modern graphics hardware, several critical optimizations should be integrated:

Utilizing groupshared Memory

Rather than having every thread issue high-latency texture reads to global VRAM, threads within a compute thread group can cooperatively load a tile of pixels—including a border region (apron) to accommodate the filter radius—into groupshared memory.

Once the GroupMemoryBarrierWithGroupSync() instruction is executed, all subsequent filter taps are read directly from fast on-chip shared memory, drastically reducing memory bandwidth pressure.

Wave Intrinsics (DirectX 12 / SM6.0+)

If target hardware supports Shader Model 6.0 or higher, lane-swapping intrinsics like WaveReadLaneAt can be used to share data across threads within a SIMD wave without routing through groupshared memory. This eliminates barrier synchronization stalls and optimizes register allocation.

Half-Resolution Execution with Bilateral Upsampling

Because SSS functions as a low-pass filter, the horizontal and vertical compute passes can be executed at half resolution (\(1/4\) the total pixel count). After the vertical scattering pass completes, a bilateral upsampling compute pass reconstructs the full-resolution image using high-resolution linear depth and normal buffers to preserve crisp geometric silhouettes.

Material Masking and Early Exits

Not all pixels on the screen require subsurface scattering. In Direct3D 12, developers can generate an indirect dispatch buffer containing only the tiles or dispatch counts that contain SSS-flagged materials. Alternatively, the compute shader can sample a material ID buffer at the tile or wave level to immediately early-exit threads covering non-scattering geometry.

Final Recombination

Once the horizontal and vertical compute passes finish scattering the diffuse light, a final recombine pass blends the scattered diffuse color, the untouched specular reflections, ambient occlusion, and emissive elements. The result is a physically plausible, soft material appearance calculated in less than a single millisecond of GPU frame time.