03 / 08 · RELIGHTING
Build a moving highlight做出会移动的高光
Read every factor in the actual GGX shader.读懂实际 GGX shader 的每一个因子。
Imagine tiny mirrors想象表面有很多小镜子
A rough surface has tiny facets pointing in different directions. Facets facing the halfway vector h between light l and viewer v reflect that light toward the viewer. Normalize l+v to get h. D describes their orientation density; G discounts tiny facets hidden by others; F describes angle-dependent reflectance.粗糙表面可想象成朝向各异的小镜面。朝向光线 l 和观察方向 v 之间半程向量 h 的小镜面,能把光反到观察者。把 l+v 归一化得到 h。D 描述朝向密度,G 扣除被其他小镜面挡住的部分,F 描述随角度变化的反射率。
These are model choices, not consequences of Gaussian density. We use single-scattering GGX, separable Smith masking and Schlick Fresnel. PBRT explains the full statistical derivation; here we unpack the exact shader model.这些是着色模型选择,不是从高斯密度推出来的。这里使用单次散射 GGX、可分离 Smith 遮蔽和 Schlick Fresnel。完整统计推导见 PBRT;这里拆解实际 shader 模型。
Roughness controls the spread粗糙度控制高光分布
The slider is perceptual roughness r. Square it to get a=r², then use a²=r⁴ in D. Let nh=max(n·h,0). Compute nh², multiply by (a²−1), add 1, square, multiply by π, then divide a² by that result. Lower roughness makes a narrower, taller highlight.滑块是感知粗糙度 r。先平方得到 a=r²,D 中使用的 a² 就是 r⁴。令 nh=max(n·h,0):计算 nh²,乘以 (a²−1),加 1,再平方、乘 π,最后用 a² 除以结果。更低的粗糙度使高光更窄、更高。
At r=0.5 and nh=1, a=0.25, a²=0.0625, D=1/(π×0.0625)≈5.093. D is a density, not a color, so values above 1 are valid. We clamp r to at least 0.12 because this coarse surfel scene cannot resolve near-perfect mirror highlights.r=0.5、nh=1 时,a=0.25、a²=0.0625,D=1/(π×0.0625)≈5.093。D 是密度,不是颜色,大于 1 合法。这个粗粒度 surfel 场景无法稳定表达近乎完美镜面的高光,所以 r 最小取 0.12。
Split diffuse and specular energy分开漫反射与镜面反射
For a typical dielectric choose normal-incidence reflectance F₀=0.04. The metallic workflow interpolates it toward base color b as metallic m approaches 1. Schlick adds a grazing-angle increase using (1−v·h)⁵. This is an approximation; 0.04 is not universal.典型非金属取垂直入射反射率 F₀=0.04。金属度 m 接近 1 时,将它插值到 base color b。Schlick 用 (1−v·h)⁵ 增加掠射角反射。这是近似,0.04 并非普适常数。
The diffuse factor (1−F)(1−m) removes diffuse reflection at m=1. The lab isolates Diffuse and Specular before display mapping. It includes no subsurface scattering, transmission or microfacet multiple-scattering compensation.漫反射因子 (1−F)(1−m) 使 m=1 时漫反射为零。实验在显示映射前分离 Diffuse 和 Specular,没有次表面散射、透射或微表面多次散射补偿。
Work through one numerical example完整代入一次
Take n=l=v=(0,0,1), r=0.5, m=0, red base color bᵣ=0.55, E⊥=3. Then h=n, G=1, F=0.04. Specular BRDF =5.093×0.04/4≈0.05093; diffuse BRDF =0.96×0.55/π≈0.16807. Red outgoing radiance =(0.05093+0.16807)×1×3≈0.6570.取 n=l=v=(0,0,1)、r=0.5、m=0、红通道 bᵣ=0.55、E⊥=3。此时 h=n、G=1、F=0.04。镜面 BRDF=5.093×0.04/4≈0.05093;漫反射 BRDF=0.96×0.55/π≈0.16807。出射红通道辐亮度=(0.05093+0.16807)×1×3≈0.6570。
Predict it, then run it先预测,再运行
Open this experiment →打开对应实验 →The formula meets actual code公式连接真实代码
This excerpt is extracted from the repository during the site build. SH environment lighting and inverse training are future extensions; their chapters point to the current direct-light/data boundary, not an implemented trainer.此片段在网站构建时从仓库提取。SH 环境光照与逆向训练是未来扩展;对应章节指向当前直射光/数据接口,不代表已实现训练器。
src/relight.wgsl · L24–54
fn masking(cosine: f32, a2: f32) -> f32 {
return 2.0 * cosine / max(cosine + sqrt(a2 + (1.0-a2)*cosine*cosine), 1e-6);
}
fn shade(s: Splat) -> vec3<f32> {
let n = normalize(s.normal.xyz);
let v = normalize(g.eye.xyz - s.center.xyz);
let l = g.light.xyz;
let mode = u32(g.material.z);
if mode == 3u { return n * 0.5 + 0.5; }
if mode == 4u { return s.base.xyz; }
let nl = max(dot(n,l), 0.0);
let nv = max(dot(n,v), 0.0);
if nl <= 0.0 || nv <= 0.0 { return vec3(0.0); }
let h = normalize(l+v);
let nh = max(dot(n,h), 0.0);
let vh = max(dot(v,h), 0.0);
let a = g.material.x * g.material.x;
let a2 = a*a;
let denominator = nh*nh*(a2-1.0)+1.0;
let d = a2 / (3.14159265359 * denominator * denominator);
let f0 = mix(vec3(0.04), s.base.xyz, g.material.y);
let fresnel = f0 + (1.0-f0)*pow(1.0-vh, 5.0);
let geometry = masking(nl,a2)*masking(nv,a2);
let specular = d*fresnel*geometry / max(4.0*nl*nv, 1e-6);
let diffuse = (1.0-fresnel)*(1.0-g.material.y)*s.base.xyz/3.14159265359;
var brdf = diffuse+specular;
if mode == 1u { brdf = diffuse; }
if mode == 2u { brdf = specular; }
// One white distant light, no occlusion, no indirect illumination.
return brdf * nl * g.light.w;
}Check your understanding检查是否真正理解
Why can metal have colored highlights and zero diffuse response here?为什么这里的金属会有彩色高光而漫反射为零?
Reveal the reasoning展开推理
Base color becomes F₀, while (1−metallic) removes the diffuse lobe. A colored dielectric instead has colored diffuse and mostly neutral specular reflection under white light.Base color 进入 F₀,而 (1−metallic) 去掉漫反射瓣。有色非金属在白光下则是有色漫反射和大体中性的镜面反射。