Height-Correlated Smith GGX in GLSL
This article provides a direct guide to implementing the height-correlated Smith masking-shadowing function for the GGX microfacet distribution in GLSL. Microfacet BRDF models require a geometric attenuation factor to account for mutual shadowing and masking between surface irregularities. By reading this guide, you will learn the mathematical formulation that couples view and light visibility for physically accurate microfacet shading, followed by an optimized, production-ready GLSL implementation commonly used in modern physically based rendering (PBR) engines.
Mathematical Formulation
In standard microfacet theory, the specular BRDF is defined as:
\[f_r(\mathbf{v}, \mathbf{l}) = \frac{D(\mathbf{h}) \cdot F(\mathbf{v}, \mathbf{h}) \cdot G(\mathbf{v}, \mathbf{l}, \mathbf{h})}{4 (\mathbf{n} \cdot \mathbf{v}) (\mathbf{n} \cdot \mathbf{l})}\]
To optimize shader evaluation, real-time engines combine the geometric shadowing-masking term \(G(\mathbf{v}, \mathbf{l})\) and the microfacet normalization factor into a single visibility term \(V(\mathbf{v}, \mathbf{l})\):
\[V(\mathbf{v}, \mathbf{l}) = \frac{G(\mathbf{v}, \mathbf{l})}{4 (\mathbf{n} \cdot \mathbf{v}) (\mathbf{n} \cdot \mathbf{l})}\]
Earlier models assumed masking and shadowing were independent events, leading to an overestimation of energy loss at grazing angles. The height-correlated formulation introduced by Eric Heitz models the correlation between the elevation of the microfacets seen by both the observer and the light source.
For a GGX distribution with roughness parameter \(\alpha\) (where \(\alpha = \text{roughness}^2\)), the height-correlated visibility function is formulated as:
\[V = \frac{0.5}{(\mathbf{n} \cdot \mathbf{l}) \sqrt{(\mathbf{n} \cdot \mathbf{v})^2 (1 - \alpha^2) + \alpha^2} + (\mathbf{n} \cdot \mathbf{v}) \sqrt{(\mathbf{n} \cdot \mathbf{l})^2 (1 - \alpha^2) + \alpha^2}}\]
GLSL Implementation
Below is the optimized GLSL function for the height-correlated visibility term. It expects clamped dot products and uses linear perceptual roughness squared to define the distribution parameter \(\alpha\).
/**
* Height-correlated Smith visibility function for GGX microfacets:
* V = G / (4 * NdotL * NdotV)
*
* @param NdotV Cosine of angle between normal and view vector (clamped to [0, 1])
* @param NdotL Cosine of angle between normal and light vector (clamped to [0, 1])
* @param roughness Perceptual linear roughness value [0, 1]
* @return Visibility factor to multiply by D and F
*/
float Visibility_SmithGGXCorrelated(float NdotV, float NdotL, float roughness)
{
// Remap linear roughness to GGX alpha parameter
float a = roughness * roughness;
float a2 = a * a;
// Evaluate lambda terms for light and view
float gv = NdotL * sqrt(NdotV * (NdotV - NdotV * a2) + a2);
float gl = NdotV * sqrt(NdotL * (NdotL - NdotL * a2) + a2);
// Add a small epsilon to the denominator to prevent division by zero
return 0.5 / max(gv + gl, 1e-5);
}Fast Approximation Alternative
For performance-constrained hardware (such as mobile platforms), square root operations can be eliminated by approximating the height-correlated term using a rational polynomial:
float Visibility_SmithGGXCorrelatedFast(float NdotV, float NdotL, float roughness)
{
float a = roughness * roughness;
float gv = NdotL * (NdotV * (1.0 - a) + a);
float gl = NdotV * (NdotL * (1.0 - a) + a);
return 0.5 / max(gv + gl, 1e-5);
}Integration into the BRDF
To calculate the final specular component, multiply the output of the visibility function directly by the GGX normal distribution function (\(D\)) and the Fresnel factor (\(F\)):
vec3 SpecularBRDF = D * V * F;Because the division by \(4(\mathbf{n} \cdot \mathbf{v})(\mathbf{n} \cdot \mathbf{l})\) is already embedded inside the visibility function, this formulation saves arithmetic operations and prevents numerical instability when viewing surfaces at grazing angles.