Specular Cone Tracing Using 3D Mipmaps in GLSL
This article details how specular cone tracing is evaluated in GLSL by marching through pre-filtered, mipmapped 3D voxel textures. It covers the mathematical formulation of the reflection cone, the method for computing continuous level-of-detail (LOD) values based on cone spread and distance, and the front-to-back accumulation loop used to approximate glossy reflections in real-time.
Cone Definition and Aperture Calculation
Specular cone tracing approximates indirect specular reflections by casting a cone instead of an infinitely thin ray. The reflection cone origin is set at the world-space surface position, offset slightly along the surface normal to prevent self-intersection. The central direction vector \(\vec{D}\) is calculated as the reflection of the view vector \(\vec{V}\) across the surface normal \(\vec{N}\):
\[\vec{D} = \text{reflect}(-\vec{V}, \vec{N})\]
The cone aperture (half-angle \(\theta\)) is derived from the surface roughness parameter. A perfectly smooth surface (roughness = 0) produces a zero-aperture ray, while increasing roughness expands the cone angle:
\[\tan(\theta) = \text{roughness} \times k\]
where \(k\) is an empirical scaling factor (typically around 0.5 to 1.0) defining maximum dispersion.
Stepping and LOD Derivation
The algorithm steps along \(\vec{D}\) by an increment proportional to the cone's current diameter, ensuring larger steps as the cone expands. At a travel distance \(t\), the diameter of the cone cross-section is:
\[\text{diameter} = 2.0 \times t \times \tan(\theta)\]
To simulate the integration of all voxels intersecting the cone footprint, the diameter is converted to a texture level-of-detail (LOD) for sampling the 3D mipmap chain. Given a base voxel size \(V_{\text{size}}\) at mip level 0, the continuous mip level \(\lambda\) is computed as:
\[\lambda = \log_2\left(\frac{\text{diameter}}{V_{\text{size}}}\right)\]
Higher mip levels contain pre-averaged, downsampled spatial and directional radiance, which acts as a low-cost spatial filter over the widening cone.
GLSL Sampling and Accumulation
Inside the marching loop, the current sample coordinate in normalized
voxel space is computed. GLSL’s textureLod function samples
the 3D texture sampler (sampler3D) with trilinear filtering
enabled, which automatically interpolates across both the 3D spatial
grid and the fractional component of the calculated mip level \(\lambda\).
The fetched sample typically contains pre-multiplied RGB radiance and an alpha channel representing voxel opacity:
vec4 sampleColor = textureLod(u_VoxelGrid, voxelCoord, lod);Colors and opacities are accumulated using standard front-to-back compositing:
\[\vec{C}_{\text{acc}} = \vec{C}_{\text{acc}} + (1.0 - \alpha_{\text{acc}}) \times \vec{C}_{\text{sample}} \times \alpha_{\text{sample}}\] \[\alpha_{\text{acc}} = \alpha_{\text{acc}} + (1.0 - \alpha_{\text{acc}}) \times \alpha_{\text{sample}}\]
Loop Execution and Termination
The stepping distance \(t\) advances dynamically by a fraction of the current diameter (e.g., \(t += \max(V_{\text{size}}, \text{diameter} \times 0.5)\)) to avoid undersampling or excessive oversampling. The marching loop terminates when one of three conditions is met:
- The accumulated opacity \(\alpha_{\text{acc}}\) reaches a threshold close to full saturation (\(\ge 0.95\)).
- The current coordinate steps outside the normalized \([0, 1]^3\) bounds of the voxel volume.
- A predefined maximum tracing distance or step count is exceeded.
The resulting \(\vec{C}_{\text{acc}}\) represents the final filtered specular radiance, which is then modulated by the material's Fresnel reflectance before being combined into the final lighting equation.