glTF Sample Viewer Split-Sum Environment Lighting
The glTF Sample Viewer implements image-based lighting (IBL) using the split-sum approximation technique to evaluate the physically based rendering (PBR) specular reflectance equation in real time. By decoupling the radiance integral into two separate, precomputable components—a pre-filtered radiance environment map and an integrated Bidirectional Reflectance Distribution Function (BRDF) lookup texture (LUT)—the viewer resolves complex environmental reflections efficiently within the fragment shader while adhering strictly to the Khronos PBR glTF 2.0 specifications.
The Split-Sum Formulation
The specular reflection integral under environment lighting evaluates the incoming radiance along with the microfacet specular BRDF over the hemisphere:
\[L_o(v) = \int_{\Omega} L_i(l) f(l, v) (n \cdot l) dl\]
The glTF Sample Viewer approximates this continuous integral by splitting it into the product of two independent sums:
- The Pre-Filtered Environment Map: The incoming radiance convolved with a GGX distribution at varying roughness levels.
- The Environment BRDF LUT: The integrated microfacet BRDF under varying roughness and viewing angles with unit radiance.
First Sum: Pre-Filtered Radiance Texture
The Sample Viewer relies on a cubemap that stores the environment convolved across multiple mipmap levels. Each mip level corresponds to an increased perceptual roughness value.
Filtering Approach: The environment is pre-convolved using importance sampling with the microfacet GGX distribution.
Sampling Direction: The fragment shader calculates the reflection vector \(R = \text{reflect}(-V, N)\) from the view direction \(V\) and the shading normal \(N\).
Mip Level Selection: The shader derives the mipmap level directly from the material's perceptual roughness:
float lod = perceptualRoughness * (float(u_MipCount - 1)); vec3 prefilteredColor = textureLod(u_PrefilteredEnvironmentMap, R, lod).rgb;
Second Sum: The 2D BRDF Lookup Table (LUT)
The second part of the approximation pre-computes the directional albedo scale and bias terms for the Fresnel-Schlick equation. Because this integration depends only on the roughness and the cosine of the angle between the surface normal and the view direction (\(N \cdot V\)), it is stored in a static 2D texture.
Inputs: The texture is indexed by the clamped dot product \(N \cdot V\) along the horizontal axis and perceptual roughness along the vertical axis.
Stored Channels: The red and green channels of the LUT store the scale and bias factors respectively:
vec2 brdfSamplePoint = clamp(vec2(NdotV, perceptualRoughness), vec2(0.0), vec2(1.0)); vec2 brdf = texture(u_BRDF_LUT, brdfSamplePoint).rg;
Shader Integration and Material Extensions
In the fragment shader, the glTF Sample Viewer combines these pre-computed elements to produce the final specular environment light. The viewer computes the Fresnel reflectance (\(F\)) using the material's specular reflectance at normal incidence (\(F_0\)) and reflectance at grazing angles (\(F_{90}\)):
vec3 specularLight = prefilteredColor * (specularColor * brdf.x + brdf.y);For advanced glTF extensions, the Sample Viewer adapts the split-sum method to support additional surface behaviors:
- Sheen (
KHR_materials_sheen): The engine uses a separate Charlie distribution BRDF LUT to evaluate microfiber cloth reflections alongside a corresponding pre-filtered radiance query. - Clearcoat (
KHR_materials_clearcoat): A second specular evaluation layer is applied on top of the base layer with its own roughness, normal, and Fresnel term, attenuating the underlying lighting based on clearcoat absorption. - Transmission
(
KHR_materials_transmission): The viewer reads refracted background scene pixels, blurring them using roughness approximations to simulate rough transmissive media.