• The Core Idea

    • Real surfaces are not perfectly smooth — they have microscopic bumps (microfacets)
    • Each microfacet is a perfect mirror (specular reflector)
    • The macroscopic BRDF is the statistical aggregate of all microfacet reflections
    • Roughness controls the spread of microfacet normals

  • The Microfacet BRDF Formula

    • f_r(ω_i, ω_o) = D(h) * G(ω_i, ω_o) * F(ω_o, h) / (4 * NdotL * NdotV)
    • h = normalize(ω_i + ω_o) — half-vector (microfacet normal that reflects ω_i to ω_o)
    • Three components: NDF, Geometry, Fresnel

  • D — Normal Distribution Function (NDF)

    • Describes the statistical distribution of microfacet normals
    • D(h) = density of microfacets with normal h (per steradian)
    • Must satisfy: ∫_Ω D(h) cos(θ_h) dω_h = 1
    • GGX (Trowbridge-Reitz) NDF
      • D(h) = α² / (π * (NdotH² * (α² - 1) + 1)²)
      • α = roughness² — perceptual roughness remapping
      • α = 0 → perfect mirror (delta distribution), α = 1 → fully rough
      • Why roughness²? Perceptual linearity — equal steps in roughness look equal
      • GGX has heavier tails than Beckmann — better matches real surfaces
    • Beckmann NDF (older, used in Cook-Torrance)
      • D(h) = exp(-tan²(θ_h) / α²) / (π * α² * cos⁴(θ_h))
      • Lighter tails than GGX — less realistic for rough surfaces
    • Anisotropic GGX
      • Different roughness along tangent and bitangent directions
      • D(h) = 1 / (π * α_x * α_y * (NdotH/α_x)² + (TdotH/α_x)² + (BdotH/α_y)²)²
      • Used for brushed metal, hair, fabric

  • G — Geometric Attenuation (Shadowing-Masking)

    • Accounts for microfacets blocking each other
    • Shadowing: incoming light blocked by other microfacets
    • Masking: outgoing light blocked by other microfacets
    • Smith G term (separable approximation)
      • G(ω_i, ω_o) = G1(ω_i) * G1(ω_o)
      • G1(ω) = NdotV / (NdotV * (1 - k) + k)
      • For direct lighting: k = (roughness + 1)² / 8
      • For IBL: k = roughness² / 2
    • Height-correlated Smith (more accurate)
      • G(ω_i, ω_o) = 1 / (1 + Λ(ω_i) + Λ(ω_o))
      • Λ(ω) = (-1 + sqrt(1 + α² * tan²(θ))) / 2
      • Accounts for correlation between shadowing and masking
    • Why the 4 * NdotL * NdotV denominator?
      • Jacobian of the half-vector transform: dω_h / dω_i = 1 / (4 * dot(ω_o, h))
      • Combined with the NdotL and NdotV from the rendering equation


  • Energy Conservation

    • The microfacet BRDF is not perfectly energy-conserving
    • Multiple scattering between microfacets is ignored
    • At high roughness: energy is lost (surface appears too dark)
    • Fix: multi-scattering BRDF (Heitz et al. 2016)
      • Add a compensation term for energy lost to multiple bounces
      • f_ms = (1 - E(ω_o)) * (1 - E(ω_i)) / (π * (1 - E_avg))
      • E(ω) = directional albedo (precomputed LUT)

  • Roughness Remapping

    • Artists work with “perceptual roughness” r ∈ [0,1]
    • α = r² — squaring gives more intuitive control
    • At r = 0.5: α = 0.25 — medium roughness
    • Without remapping: most of the interesting range is compressed near 0
    • Some engines use α = r directly — check which convention your engine uses

  • Sampling the GGX NDF

    • To importance sample GGX, sample the half-vector h from D(h) * cos(θ_h)
    • Spherical coordinates:
      • θ_h = arctan(α * sqrt(ξ₁ / (1 - ξ₁)))
      • φ_h = 2π * ξ₂
    • Convert to Cartesian (in tangent space):
      • h = (sin(θ_h)*cos(φ_h), sin(θ_h)*sin(φ_h), cos(θ_h))
    • Transform to world space using TBN matrix
    • Reflect view direction: ω_i = reflect(-ω_o, h)
    • PDF: p(ω_i) = D(h) * NdotH / (4 * VdotH)
    • Visible NDF sampling (VNDF) — Heitz 2018
      • Sample proportional to D(h) * G1(ω_o) * max(0, dot(ω_o, h))
      • Better importance sampling — fewer wasted samples for grazing angles
      • Significantly reduces variance for rough surfaces viewed at grazing angles