PBR Metallic Roughness Lighting in DirectX Engines

Modern DirectX rendering engines evaluate surface appearance by translating metallic and roughness texture inputs into physical parameters within a microfacet Bidirectional Reflectance Distribution Function (BRDF). This article explains how DirectX 11 and 12 shaders process scalar metallic and roughness values to balance diffuse and specular reflection, calculate microfacet distributions, resolve occlusion, and execute real-time direct and image-based indirect lighting calculations via High-Level Shader Language (HLSL).

Parameter Interpretation in the Pixel Shader

DirectX engines sample base color, metallic, and roughness values from texture maps in a pixel shader. Before lighting evaluation begins, these inputs are transformed into physical baseline parameters:

  1. Fresnel Reflectance at Normal Incidence (\(F_0\)): Dielectrics (non-metals) have an achromatic reflectance value typically around 4% (\(0.04\)). Conductors (metals) possess colored reflectance derived entirely from their base color, lacking any internal diffuse scattering. In HLSL, the initial reflectance is computed as:

    float3 F0 = lerp(float3(0.04, 0.04, 0.04), BaseColor.rgb, Metallic);
  2. Perceptual Roughness to Alpha: Artists author roughness on a linear perceptual scale \([0, 1]\). To achieve a more uniform perceptual distribution of highlight size, engines remap roughness to \(\alpha\) via squaring:

    float alpha = Roughness * Roughness;
    float alpha2 = alpha * alpha;
  3. Diffuse Color Attenuation: Pure metals do not exhibit diffuse reflection because free electrons absorb and immediately re-emit light as specular reflection. The diffuse color (\(C_{diff}\)) is nullified as metallicity increases:

    float3 DiffuseColor = BaseColor.rgb * (1.0 - Metallic);

Direct Lighting Evaluation via Cook-Torrance BRDF

Direct lighting calculations calculate the ratio of light reflected toward the camera from an incoming light vector (\(L\)) using the Cook-Torrance model. The surface normal is denoted as \(N\), the view vector as \(V\), and the half-vector as \(H = \text{normalize}(L + V)\).

The total reflected radiance is divided into diffuse (\(f_d\)) and specular (\(f_s\)) components:

\[f(L, V) = (1 - F) \cdot f_d + f_s\]

1. Specular Term

The specular component depends directly on roughness and metallic properties through three functions:

\[f_s = \frac{D(H) \cdot G(L, V) \cdot F(V, H)}{4(N \cdot L)(N \cdot V)}\]

  • Normal Distribution Function \(D(H)\) (Trowbridge-Reitz GGX): Evaluates microfacet orientation relative to \(H\). Roughness (\(\alpha^2\)) determines highlight spread. Lower roughness concentrates microfacets along the macroscopic normal, producing high, sharp peaks.
  • Geometric Shadowing Function \(G(L, V)\) (Smith-GGX): Models self-shadowing and masking where microfacets occlude each other. Modern engines implement height-correlated Smith formulations, parameterized by \(\alpha\), which correctly correlate the microfacet heights viewed from directions \(L\) and \(V\).
  • Fresnel Equation \(F(V, H)\) (Schlick's Approximation): Calculates reflection intensity based on viewing angle. Metallic inputs control the base \(F_0\), while roughness can optionally be used to attenuate grazing-angle Fresnel reflections (\(F_{90}\)).

2. Diffuse Term

Diffuse lighting is typically calculated using the standard Lambertian model:

\[f_d = \frac{DiffuseColor}{\pi}\]

Because \(DiffuseColor\) was scaled by \((1 - Metallic)\), high-metallic surfaces reflect near-zero diffuse light. Energy conservation is strictly maintained by multiplying \(f_d\) by \((1 - F)\), ensuring that light reflected specularly is not re-emitted diffusely.


Indirect Lighting Evaluation (Image-Based Lighting)

To evaluate ambient environment maps without expensive real-time hemisphere integration, DirectX engines utilize the Split-Sum Approximation:

  1. First Sum (Pre-Filtered Environment Map): The environment cubemap is pre-convolved at multiple mip levels, where each mip level corresponds to an increased roughness value. Shaders use HLSL's SampleLevel function to fetch the radiance corresponding directly to the material’s roughness:

    float3 PrefilteredColor = EnvironmentMap.SampleLevel(LinearSampler, ReflectionVector, Roughness * MaxMipLevel).rgb;
  2. Second Sum (Environment BRDF LUT): The integration of the Cook-Torrance specular BRDF across viewing angles and roughness values is precomputed into a 2D Look-Up Table (LUT). The shader indexes this LUT using \(N \cdot V\) and the roughness value to yield a scale and bias for \(F_0\):

    float2 EnvBRDF = BrdfLUT.Sample(ClampSampler, float2(NdotV, Roughness)).rg;
    float3 SpecularIBL = PrefilteredColor * (F0 * EnvBRDF.x + EnvBRDF.y);
  3. Diffuse IBL: Diffuse ambient light is extracted from spherical harmonics or an irradiance cubemap, modulated by \(DiffuseColor\) and ambient occlusion, and zeroed out on fully metallic surfaces.