Photon Splatting in 3D Grids Using GLSL Compute
This article provides an overview of the algorithms used to evaluate photon splatting into 3D irradiance grids using OpenGL Shading Language (GLSL) compute shaders. It explores the foundational Light Injection stage of the Light Propagation Volumes (LPV) and Voxel Global Illumination (VXGI) pipelines, detailing how scattered light data is injected, accumulated, and encoded using compute pipelines and atomic operations.
The Core Algorithm: Photon Injection
The primary algorithm that evaluates photon splatting into 3D irradiance grids is the Photon Injection Algorithm, most prominently utilized in Light Propagation Volumes (LPV) and Voxel-Based Global Illumination (VXGI) frameworks.
In this algorithm, the scene's light sources first generate photons or Virtual Point Lights (VPLs)—typically sourced from a Reflective Shadow Map (RSM) or a direct compute-based ray trace. These photons, containing position, normal, and flux data, are then "splatted" or injected into a regular 3D texture grid (an irradiance volume) representing the scene's bounding volume.
GLSL Compute Shader Implementation
Photon splatting represents a scattering operation where an arbitrary number of photons write to discrete spatial cells. GLSL compute shaders evaluate this process through a structured sequence:
1. Spatial Quantization
Each compute thread processes an individual photon. The world-space position of the photon is transformed into normalized grid coordinates and subsequently into discrete voxel cell indices:
\[\text{CellIndex} = \left\lfloor \frac{\mathbf{P}_{\text{photon}} - \mathbf{Grid}_{\text{min}}}{\mathbf{VoxelSize}} \right\rfloor\]
Boundary checks are performed within the compute shader to discard photons existing outside the active irradiance volume.
2. Directional Representation via Spherical Harmonics
To store directional radiance rather than just ambient irradiance, the photon flux is projected onto low-order Spherical Harmonics (SH), typically the second-order (\(L_1\)) band requiring four coefficients:
\[Y_{00} = \frac{1}{2\sqrt{\pi}}, \quad Y_{1,-1} = \sqrt{\frac{3}{4\pi}}y, \quad Y_{10} = \sqrt{\frac{3}{4\pi}}z, \quad Y_{11} = \sqrt{\frac{3}{4\pi}}x\]
The shader calculates the product of the photon’s incident direction and its color flux, distributing this energy into the four SH basis functions.
3. Splatting via Atomic Operations
Because multiple compute threads frequently attempt to write to the same grid cell concurrently, race conditions must be resolved. GLSL compute shaders achieve this through two main strategies:
- Atomic Floating-Point Operations: Utilizing
extensions such as
GL_EXT_shader_atomic_float, shaders callimageAtomicAdddirectly on floating-point 3D textures for each SH coefficient. - Fixed-Point Integer Packing: In core GLSL without
floating-point atomics, float values are scaled and converted to 32-bit
unsigned integers, accumulated via
imageAtomicAdd, and later unpacked into floating-point formats in a subsequent normalization pass.
4. Kernel Splatting
To prevent high-frequency spatial aliasing and light-leaking artifacts, the photon’s energy is distributed across adjacent neighboring cells using a spatial filter (such as a trilinear or Gaussian splatting kernel). The compute shader calculates weights for the \(2 \times 2 \times 2\) neighborhood enclosing the continuous photon position and injects fractional energy into all eight adjacent voxels.
Propagation and Querying
Once photon splatting is complete, the 3D irradiance grid contains initial direct radiance at surfaces. The algorithm subsequently applies a compute-based propagation step (such as a Jacobi iteration or iterative stencil pass) that simulates the transport of radiant flux between adjacent cells across multiple simulation steps. Finally, forward-rendering passes query the 3D irradiance grid using trilinear texture filtering to calculate indirect global illumination for surfaces across the scene.