Physically Based Rendering

f = k_d · albedo/π + D·F·G / (4 (N·V)(N·L))

28.1 Principles

Energy conservation: reflected light ≤ incoming light.

Microfacet theory: surfaces are made of tiny mirrors; roughness controls their distribution.

Fresnel: reflectance increases at grazing angles.

Metallic/roughness workflow (glTF standard): albedo, metallic, roughness, normal, ao, emissive.

28.2 Cook–Torrance BRDF

f = k_d · albedo/π + D·F·G / (4 (N·V)(N·L)) D — normal distribution function (GGX/Trowbridge–Reitz).

F — Fresnel (Schlick approximation), F0 = 0.04 for dielectrics, albedo for metals.

G — geometry/shadowing-masking (Smith with Schlick-GGX).

const float PI = 3.14159265359;

float D_GGX(float NdotH, float roughness) {
    float a = roughness * roughness;
    float a2 = a * a;
    float d = NdotH * NdotH * (a2 - 1.0) + 1.0;
    return a2 / (PI * d * d);
}

float G_SchlickGGX(float NdotX, float roughness) {
    float r = roughness + 1.0;
    float k = (r * r) / 8.0;                     // direct lighting remapping
    return NdotX / (NdotX * (1.0 - k) + k);
}

float G_Smith(float NdotV, float NdotL, float roughness) {
    return G_SchlickGGX(NdotV, roughness) * G_SchlickGGX(NdotL, roughness);
}

vec3 F_Schlick(float cosTheta, vec3 F0) {
    return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}

vec3 pbrDirect(vec3 N, vec3 V, vec3 L, vec3 radiance, vec3 albedo, float metallic, float roughness) {
    vec3 H = normalize(V + L);
    float NdotL = max(dot(N, L), 0.0), NdotV = max(dot(N, V), 1e-4), NdotH = max(dot(N, H), 0.0);
    vec3 F0 = mix(vec3(0.04), albedo, metallic);
    vec3 F = F_Schlick(max(dot(H, V), 0.0), F0);
    float D = D_GGX(NdotH, roughness);
    float G = G_Smith(NdotV, NdotL, roughness);
    vec3 specular = (D * G * F) / (4.0 * NdotV * NdotL + 1e-4);
    vec3 kD = (1.0 - F) * (1.0 - metallic
…

28.3 Image-based lighting (IBL)

Precompute from an HDR environment map:

Irradiance cube map — cosine-convolved environment for diffuse lighting.

Prefiltered specular cube map — mip levels for increasing roughness (GGX importance sampling).

BRDF integration LUT — 2-D texture of (NdotV, roughness) → scale & bias for F0 (split-sum approximation).

Extensions to the model: clearcoat, sheen (cloth), anisotropy, transmission/volume, subsurface scattering, iridescence (glTF PBR extensions; Disney/OpenPBR principled BSDF).

vec3 computeIBL(vec3 N, vec3 V, vec3 albedo, float metallic, float roughness) {
    vec3 F0 = mix(vec3(0.04), albedo, metallic);
    float NdotV = max(dot(N, V), 0.0);
    vec3 F = F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - NdotV, 5.0);   // roughness-aware Fresnel
    vec3 kD = (1.0 - F) * (1.0 - metallic);
    vec3 diffuse = texture(uIrradianceMap, N).rgb * albedo;
    vec3 R = reflect(-V, N);
    vec3 prefiltered = textureLod(uPrefilterMap, R, roughness * uMaxMipLevel).rgb;
    vec2 brdf = texture(uBRDFLUT, vec2(NdotV, roughness)).rg;
    vec3 specular = prefiltered * (F * brdf.x + brdf.y);
    return kD * diffuse + specular;
}