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;
}