PBR 理论、微表面 BRDF 与能量分配

PBR 理论、微表面 BRDF 与能量分配:保留 LearnOpenGL 3.3 Core 正文机制,以 context—资源—结果合同、GPU 轨迹和章专属单故障完成可重放验收。

学习目标

  • 能说出微表面模型的核心假设和能量守恒约束:表面由无数微小镜面组成,出去的光不超过进来的光(ks+kd=1k_s + k_d = 1
  • 能推导并手写 Cook-Torrance BRDF 的反射率方程:说清 DD(法线分布)、GG(几何遮蔽)、FF(菲涅尔)三项各自在算什么物理量,以及分母 4(ωon)(ωin)4(\omega_o \cdot n)(\omega_i \cdot n) 的归一化意义
  • 能回答:一块粗糙度 0.8 的红色塑料球和一块粗糙度 0.2 的金球,谁有漫反射、谁的 F0 是 albedo 颜色?两者的 NDF 峰形有什么区别?

为什么同一个球换种材料,看起来就完全不同

用手机拍一张塑料杯和一张不锈钢杯。塑料杯身上有一大片柔和的白色亮斑,杯面颜色是杯子本身的颜色;不锈钢杯上高光又小又亮,杯面反射的是周围环境的颜色而不是金属自己的颜色。两种杯子被同一束光照着,高光形状和颜色来源却完全不同。

上一篇的冯氏/布林-冯氏光照只有一个「反光度」参数,金属和塑料的区别全靠手调,经验多了才大概像。工业界早就发现:真实材质的反光行为是可以用几条物理规律写成公式的——不用猜参数,只要告诉公式「它多粗糙、是不是金属」,高光形状和颜色就能自动算对。

这一章要做的就是:把那几条物理规律拆开、每一项弄清楚它在算什么。读完后你就有了一把「物理尺子」,能度量任何材质的反光行为,而不再靠调反光度蒙。

微表面模型:每块表面都是无数小镜子

所有 PBR 的起点是一条假设:任何表面,无论看起来多平整,放大到微观层面都是由无数朝向各异的微小镜面组成的。每一面微镜只做一件事——把打到它身上的光按镜面反射定律弹出去。这就是

那宏观上为什么有的表面高光清晰、有的模糊?关键是这些微镜朝向的一致性。光滑表面的微面几乎都朝同一个方向——入射光被统一弹到同一侧,你看到一个小而亮的高光斑;粗糙表面的微面朝向杂乱——光被散射到四面八方,高光变得又大又淡。(roughness)就是度量这种「混乱程度」的参数:

光滑(roughness 低)入射光反射集中微面法线对齐高光锐利清晰微面朝向一致 → 反射方向集中粗糙(roughness 高)入射光反射分散微面法线随机高光弥散模糊微面朝向随机 → 反射方向分散粗糙度 = 微表面朝向的「混乱程度」,越高反射越分散
微表面理论:表面由无数微小镜面组成。光滑表面微面朝向一致、反射集中(左);粗糙表面微面随机、反射分散(右)。

能量守恒:出去的光不能比进来的多

微表面模型还隐含一条硬约束:——一束光打到表面,反射出去的 + 折射进去的(被吸收或散射成漫反射)= 打进来的,绝不能凭空多出光来。

把这条约束翻译成系数:镜面反射占比 ksk_s、漫反射占比 kdk_d,则 ks+kd=1k_s + k_d = 1。高光越强,漫反射就越弱——这就是你看到的「金属表面几乎全是反射、看不到自身颜色」的原因(ksk_s 很大→ kdk_d 很小→漫反射几乎没有)。

能量守恒:反射 + 折射 = 入射材质表面入射光 100%反射 k_s镜面反射折射 k_d漫反射/吸收k_s + k_d = 1 (反射越强,折射越弱)
入射光打到表面分成反射(k_s)和折射/吸收(k_d)两部分,总和不超过入射量。反射越强漫反射越弱,反之亦然。

反射率方程:把半球上所有来光加起来

现在要量化「一个点 pp 朝你的眼睛方向 ωo\omega_o 发出多少光」。直觉上:从表面上方半球 Ω\Omega 的每个方向 ωi\omega_i 都可能有光射过来,每一份入射光经过表面的 frf_r 反射后,有一部分沿 ωo\omega_o 方向出去。把所有方向的贡献积分起来,就是

Lo(p,ωo)=Ωfr(p,ωi,ωo)  Li(p,ωi)  (nωi)  dωiL_o(p,\omega_o) = \int_\Omega f_r(p,\omega_i,\omega_o) \; L_i(p,\omega_i) \; (n \cdot \omega_i) \; d\omega_i

  • LoL_o:点 ppωo\omega_o 出射的(就是「有多亮」)
  • frf_r:BRDF——衡量 ωi\omega_i 方向的光有多少沿 ωo\omega_o 方向弹出去
  • LiL_i:从 ωi\omega_i 方向射来的入射辐射度
  • nωin \cdot \omega_i:余弦项——光线越正对表面(夹角小),有效照射面积越大
反射率方程:对半球所有入射方向积分Pomeganomega_i (L_i)omega_o (L_o)n . omega_iL_o(p, omega_o) = integral_omega fr(...) . L_i(...) . (n . omega_i) d omega_iBRDF fr 衡量每条入射光沿 omega_o 出去多少,对半球所有方向求和
反射率方程:对表面点 P 的上半球所有入射方向 omega_i 积分,每条入射光经 BRDF fr 加权后累加得出射辐射度 L_o。n . omega_i 是余弦项(投影面积权重)。

Cook-Torrance BRDF:漫反射 + 镜面反射

现在给 frf_r 一个具体的实现。Cook-Torrance 把 BRDF 拆成两部分相加——一份漫反射、一份镜面反射:

fr=kdcπ+ksDFG4(ωon)(ωin)f_r = k_d \frac{c}{\pi} + k_s \frac{DFG}{4(\omega_o \cdot n)(\omega_i \cdot n)}

  • 左半 kdc/πk_d \cdot c/\picc 是反照率颜色(albedo),除以 π\pi 是归一化(保证半球积分 = cc
  • 右半 ksDFG/4(ωon)(ωin)k_s \cdot DFG / 4(\omega_o \cdot n)(\omega_i \cdot n):Cook-Torrance 镜面项。DDFFGG 三个函数分别描述微表面的三种物理效应。分母 4(ωon)(ωin)4(\omega_o \cdot n)(\omega_i \cdot n) 是归一化校正

下面三节逐一拆开 DDGGFF

Cook-Torrance BRDF = 漫反射 + 镜面反射漫反射 (Lambertian)k_d . c / pic = 反照率颜色(albedo)除以 pi 使半球积分归一化+镜面反射 (Cook-Torrance)k_s . DFG / 4(omega_o.n)(omega_i.n)D = 法线分布(微面对齐度)F = 菲涅尔(反射 vs 折射比例)G = 几何遮蔽(微面自遮挡)fr = k_d . (c / pi) + k_s . (DFG / 4(omega_o . n)(omega_i . n))k_s + k_d = 1(能量守恒),下面三节分别拆 D、G、F
BRDF 分成两项:Lambertian 漫反射(均匀散射,只含颜色 c)和 Cook-Torrance 镜面项(D/F/G 三函数组合),两部分用 k_d + k_s = 1 守恒。

单步确认:BRDF 每一项各管什么

公式很长时,先按因果顺序检查它,而不是把 D、G、F 当成三个可以随意调的高光旋钮。

分步1 / 3

先让镜面与漫反射不抢同一份能量

对不透明 PBR 工作流,先由 Fresnel 得到 kS,再把剩余能量给非金属的漫反射。金属的 metallic=1 会使 kD 归零。

法线分布函数 D:有多少微面对齐了半程向量

DD)回答一个问题:在所有微面中,法线恰好朝向半程向量 hh 的有多大比例? 只有那些法线对齐 hh 的微面才能把入射光精确反射到你的眼睛方向——DD 越大,这种微面越多,高光越亮。

猜一猜:下面三张图分别对应粗糙度 0.1、0.5、1.0。你觉得哪一张的 NDF 曲线最「尖」(集中在正中间)、哪一张最「平」(分散到两侧)?尖的对应什么样的高光?

法线分布函数 D (GGX):粗糙度越高,分布越宽越平alpha = 0.1(光滑)n . h 夹角D高光小而亮alpha = 0.5(中等)n . h 夹角D高光中等alpha = 1.0(粗糙)n . h 夹角D高光大而暗D 越高 = 越多微面对齐 H → alpha 低则集中(锐利)、alpha 高则分散(弥漫)
GGX 法线分布:粗糙度 alpha 越低曲线越窄越高(高光小而亮),alpha 越高曲线越宽越矮(高光大而暗)。

粗糙度低 → 微面朝向集中 → NDF 是窄而高的尖峰 → 高光小而亮。粗糙度高 → 微面朝向分散 → NDF 是宽而矮的丘 → 高光大而暗。

几何函数 G:有多少微面被邻居挡住了

光打到微面上反射出去,但这份反射光未必能到你眼里——因为邻近的微面可能挡在中间。 GG 就是估算这种自遮蔽有多严重。遮蔽来自两个方向:

  • 遮蔽(Masking):反射光朝你飞来,但被旁边凸起的微面挡住——你看不到
  • 阴影(Shadowing):入射光朝这块微面飞来,但被旁边凸起的微面挡住——光照不到
几何函数 G:微面自遮蔽的两种来源遮蔽 (Masking)观察方向 V 被邻面挡住V(被挡)X反射光被发出但看不见阴影 (Shadowing)光线方向 L 被邻面挡住L(被挡)X光照不到这块微面Smith: G(n,v,l,k) = G_sub(n,v,k) . G_sub(n,l,k) —— 观察遮蔽 x 光线遮蔽
微面自遮蔽有两种来源:遮蔽(观察方向被挡、看不到反射光)和阴影(光线被挡、照不到微面)。Smith 方法对两者各算一遍再相乘。

Smith 的做法很直觉:两个方向各算一遍遮蔽比例,再乘起来——G(n,v,l,k)=Gsub(n,v,k)Gsub(n,l,k)G(n,v,l,k) = G_{sub}(n,v,k) \cdot G_{sub}(n,l,k)。表面越粗糙,微面凸起越多,遮蔽越严重,GG 越小。

菲涅尔方程 F:角度越斜反射越强

最后一项 FF 描述的是一个日常也能观察到的现象:你从越斜的角度看一个表面,它反射越强。站在湖边正下方看水,水是透明的(反射弱);远眺湖面,水面像镜子一样反射天空(反射强)。这就是

F0F_0 是材质在正看(0°)时的基础反射率。电介质(非金属)的 F0F_0 普遍很低(约 0.04),金属的 F0F_0 很高且有颜色(等于 albedo)。但不管 F0F_0 多低,掠射角时都会飙升到接近 1:

Fresnel-Schlick:掠射角 → 反射率飙升00.250.50.751反射率 F45°90°观察角度 theta(正看 → 掠射)F0 ~ 0.04F0 ~ 0.7电介质(非金属)金属掠射角 → 全反射F(h,v,F0) = F0 + (1-F0)(1-(h.v))^5
Fresnel-Schlick 近似:正看时反射率等于 F0(电介质约 0.04、金属更高),掠射角附近急剧升至接近 1。所有材质在掠射角都趋近全反射。

金属与非金属:一个 metallic 参数的两条路

理论上 PBR 已经完整了,但实际使用时有个关键简化:绝大多数材质要么是金属、要么是非金属,很少有「半金属」。于是引入一个 参数在两条路之间切换:

金属/非金属工作流:metallic 参数的两端非金属(电介质)metallic = 0F0 ~ 0.04(低,几乎一样)漫反射 = albedo 颜色(有色)镜面反射 ~ 白色(无色彩)k_d 大、k_s 小金属 metallic = 1F0 = albedo 颜色(高,有色)漫反射 = 0(全被吸收)镜面反射 = albedo 颜色(有色)k_d = 0、k_s 大metallic 一个参数决定 F0 来源 + 漫反射有无 → 两套全不同的着色路径
非金属有着色漫反射 + 低 F0 白色镜面;金属无漫反射(全吸收)+ 高 F0 有色镜面。metallic 参数在 0/1 之间切换这两种路径。
  • metallic = 0(非金属)F0F_0 固定 ≈ 0.04,漫反射 = albedo 颜色(你看到球本身的红色/蓝色),镜面几乎白色
  • metallic = 1(金属)F0F_0 = albedo 颜色,漫反射 = 0(折射光全被金属内部的自由电子吸收),镜面反射带颜色(金色金属反射金色光)

数学推导:从方程到每一项

符号表

符号含义
pp表面上被着色的点
nn表面法线(单位向量)
ωi\omega_i入射方向(单位向量,指向光源)
ωo\omega_o出射方向(单位向量,指向观察者)
hh半程向量 =normalize(ωi+ωo)= \text{normalize}(\omega_i + \omega_o)
LiL_iωi\omega_i 方向射来的入射辐射度
LoL_oωo\omega_o 方向出射的辐射度
frf_rBRDF(双向反射分布函数)
cc反照率颜色 (albedo)
α\alpha粗糙度 (roughness)
kk几何项的粗糙度重映射系数
F0F_00° 入射时的基础反射率

反射率方程

出发点:点 ppωo\omega_o 的出射辐射度等于上半球所有入射方向的贡献之和:

Lo(p,ωo)=Ωfr(p,ωi,ωo)  Li(p,ωi)  (nωi)  dωiL_o(p,\omega_o) = \int_\Omega f_r(p,\omega_i,\omega_o) \; L_i(p,\omega_i) \; (n \cdot \omega_i) \; d\omega_i

人话翻译:把半球每个方向飞来的光 LiL_i 乘以「BRDF 说这个方向的光有多少会沿 ωo\omega_o 出去」再乘以「光线越正对表面有效面积越大」的余弦权重,全部加起来。

Cook-Torrance BRDF

frf_r 拆成漫反射 + 镜面反射两部分:

fr=kdcπ+ksDFG4(ωon)(ωin)f_r = k_d \frac{c}{\pi} + k_s \frac{DFG}{4(\omega_o \cdot n)(\omega_i \cdot n)}

人话翻译:左半是 Lambertian 漫反射——光均匀散射到所有方向,亮度只取决于颜色 cc;右半是 Cook-Torrance 镜面——DD 说有多少微面对齐了 hhFF 说当前角度反射占多大比例,GG 说有多少微面没被挡住,分母做归一化。

GGX 法线分布函数 D

D(n,h,α)=α2π((nh)2(α21)+1)2D(n,h,\alpha) = \frac{\alpha^2}{\pi\big((n \cdot h)^2(\alpha^2 - 1) + 1\big)^2}

人话翻译nhn \cdot h 是法线和半程向量的贴合度(1=完美对齐)。α\alpha 小时分母的平方项极大地压低了偏离对齐处的值→尖峰;α\alpha 大时分母变得平缓→宽丘。分子的 α2\alpha^2 保证积分归一化。

Schlick-GGX 几何函数 G

先定义单方向的 GsubG_{sub}

GSchlickGGX(n,v,k)=nv(nv)(1k)+kG_{\text{SchlickGGX}}(n,v,k) = \frac{n \cdot v}{(n \cdot v)(1-k)+k}

人话翻译nvn \cdot v 是表面法线与方向 vv 的余弦。kk 越大(粗糙度越高),分母比分子增长得更快→GG 越小→遮蔽越严重。当 k=0k=0(完美光滑),G=1G=1(无遮蔽)。

Smith 方法把观察方向和光线方向各算一遍再乘:

G(n,v,l,k)=Gsub(n,v,k)Gsub(n,l,k)G(n,v,l,k) = G_{\text{sub}}(n,v,k) \cdot G_{\text{sub}}(n,l,k)

其中 kk 的取法随场景不同:直接光照用 k=(α+1)2/8k = (\alpha + 1)^2 / 8,IBL 用 k=α2/2k = \alpha^2 / 2

Fresnel-Schlick 近似

F(h,v,F0)=F0+(1F0)(1(hv))5F(h,v,F_0) = F_0 + (1 - F_0)(1 - (h \cdot v))^5

人话翻译hvh \cdot v 是半程向量与观察方向的余弦——正看时 hv1h \cdot v \approx 1(1hv)50(1-h \cdot v)^5 \approx 0,反射率 ≈ F0F_0(基础值);掠射时 hv0h \cdot v \to 0(1hv)51(1-h \cdot v)^5 \to 1,反射率 → F0+(1F0)=1F_0 + (1-F_0) = 1(全反射)。五次方决定了「从 F0F_0 到 1 的爬升集中在最后那段掠射角」。

合起来:代入积分

把上面各项代回反射率方程的积分里:

Lo(p,ωo)=Ω(kdcπ+ksDFG4(ωon)(ωin))Li(p,ωi)  (nωi)  dωiL_o(p,\omega_o) = \int_\Omega \bigg(k_d\frac{c}{\pi} + k_s\frac{DFG}{4(\omega_o \cdot n)(\omega_i \cdot n)}\bigg) L_i(p,\omega_i) \; (n \cdot \omega_i) \; d\omega_i

人话翻译:这就是最终要在片段着色器里算的东西。对于直接光源(点光/方向光),积分退化成有限次求和——每盏灯一份贡献,循环累加即可。下一章(PBR 光照)就是把这个公式写成 GLSL。

亲手拖一拖:D / G / F 三条曲线

猜一猜:把粗糙度从 0.1 拉到 0.8,哪条曲线变化最剧烈?切换到金属模式,Fresnel 曲线的起点会跳到多高?

可交互

BRDF 曲线加载中…

上面三条曲线正是 Cook-Torrance BRDF 里的三个核心函数。拖动粗糙度滑块,观察 D 曲线(蓝色)从窄尖峰变成宽矮丘——这就是高光从「小而亮」到「大而暗」的数学原因。G 曲线(绿色)在粗糙度升高时整体下降,说明遮蔽更严重。F 曲线(橙色)切换到金属后起点从 0.04 跳到 0.7,解释了金属为什么正看也有强反射。

代码对照:D / G / F 的 GLSL 实现

下面是 Cook-Torrance 三个子函数的 GLSL 片段。下一章「PBR 光照」会把它们组合起来在真实场景中跑,这里先看每个函数怎么把公式翻译成代码:

// ======== D:GGX / Trowbridge-Reitz 法线分布函数 ========
// 输入:法线 N、半程向量 H、粗糙度 roughness
// 输出:D 值(微面对齐 H 的统计密度)
float DistributionGGX(vec3 N, vec3 H, float roughness) {
    float a  = roughness * roughness;       // alpha = roughness^2(业界约定先平方一次,手感更线性)
    float a2 = a * a;                       // alpha^2
    float NdotH  = max(dot(N, H), 0.0);    // N . H,截到 0 防负
    float NdotH2 = NdotH * NdotH;
    // 公式分母:pi * ((NdotH^2)*(a2-1)+1)^2
    float denom = NdotH2 * (a2 - 1.0) + 1.0;
    denom = 3.14159265 * denom * denom;
    return a2 / denom;                      // alpha^2 / denom
}
// ======== G:Schlick-GGX 几何遮蔽(单方向) ========
// 输入:N . 方向 的余弦 NdotV、重映射系数 k
// 输出:G_sub 值(该方向未被遮挡的比例)
float GeometrySchlickGGX(float NdotV, float k) {
    return NdotV / (NdotV * (1.0 - k) + k);
}
// Smith 方法:观察方向 + 光线方向各算一遍再相乘
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) {
    float r = roughness + 1.0;
    float k = (r * r) / 8.0;               // 直接光照的 k 重映射
    float NdotV = max(dot(N, V), 0.0);
    float NdotL = max(dot(N, L), 0.0);
    return GeometrySchlickGGX(NdotV, k) * GeometrySchlickGGX(NdotL, k);
}
// ======== F:Fresnel-Schlick 近似 ========
// 输入:半程向量与观察方向余弦 cosTheta、基础反射率 F0
// 输出:当前角度的反射率(vec3,金属有色所以是三通道)
vec3 fresnelSchlick(float cosTheta, vec3 F0) {
    return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}

逐函数对照

  1. DistributionGGX 直接翻译 D=α2/π((nh)2(α21)+1)2D = \alpha^2 / \pi((n \cdot h)^2(\alpha^2-1)+1)^2。注意 roughness 先平方一次变成 α\alpha(业界约定),再平方得 α2\alpha^2
  2. GeometrySchlickGGX 翻译 Gsub=(nv)/((nv)(1k)+k)G_{sub} = (n \cdot v) / ((n \cdot v)(1-k)+k)。直接光照的 k=(roughness+1)2/8k = (\text{roughness}+1)^2/8
  3. fresnelSchlick 翻译 F=F0+(1F0)(1cosθ)5F = F_0 + (1-F_0)(1-\cos\theta)^5。输入是 cosTheta = h . v;返回 vec3 因为金属的 F0F_0 有颜色

常见误区

小结与练习

这一章从微表面模型出发,经过能量守恒约束,推导出了完整的 Cook-Torrance BRDF 反射率方程。三个核心函数 D/G/F 分别量化了「多少微面对齐半程向量」「多少微面没被遮挡」「当前角度反射占多大比例」。加上 metallic 工作流区分金属/非金属,就构成了 PBR 的完整理论基础。下一章将把这些公式写进 GLSL,在真实场景里点灯看效果。

练习

推导题

  1. 从 Fresnel-Schlick 公式 F=F0+(1F0)(1cosθ)5F = F_0 + (1-F_0)(1-\cos\theta)^5 出发,验证:当 cosθ=1\cos\theta = 1(正看)时 F=F0F = F_0;当 cosθ=0\cos\theta = 0(掠射)时 F=1F = 1。中间角度呢?cosθ=0.5\cos\theta = 0.5FF 约等于多少(取 F0=0.04F_0 = 0.04)?
  1. GGX NDF 公式 D=α2/π((nh)2(α21)+1)2D = \alpha^2 / \pi((n \cdot h)^2(\alpha^2-1)+1)^2,当 nh=1n \cdot h = 1(微面法线完美对齐 hh)时 DD 等于多少?写出化简过程,说明它只取决于 α\alpha

概念题

  1. 一块粗糙度 0.8 的红色塑料球(metallic=0)和一块粗糙度 0.2 的金球(metallic=1),分别回答:(a) 谁有漫反射?(b) 谁的 F0 是 albedo 颜色?(c) 谁的 NDF 更「尖」?
  1. 为什么 Cook-Torrance 分母里有 4(ωon)(ωin)4(\omega_o \cdot n)(\omega_i \cdot n)?如果去掉会怎样?

名词解释

本章出现的专业名词,用大白话再讲一遍。

微表面模型

PBR 的核心几何假设:任何表面在微观层面由无数朝向各异的微小完美镜面(microfacet)组成。宏观看到的漫反射/模糊高光是无数微面反射的统计叠加。

粗糙度

roughness(0~1),描述微表面朝向有多混乱。0 = 完美光滑(微面对齐,反射集中),1 = 极粗糙(微面随机,反射分散)。

能量守恒

一个表面反射出去的光总量不超过照进来的光。PBR 中体现为 k_s + k_d = 1,反射越强漫反射越弱。

BRDF

双向反射分布函数(Bidirectional Reflective Distribution Function)。给定入射方向和出射方向,返回入射光沿出射方向反射出去的比例。Cook-Torrance 是一种具体实现。

反射率方程

Lo(p, omega_o) = 对半球所有入射方向积分 fr . Li . cos(theta)。描述一个表面点最终朝观察方向发出多少光。

辐射度

Radiance:沿某方向单位立体角、单位投影面积上的辐射功率。通俗说就是「这个方向上光有多亮」。

NDF(法线分布函数)

Normal Distribution Function:给定半程向量 h 和粗糙度 alpha,返回微面中法线对齐 h 方向的统计密度。常用实现是 GGX/Trowbridge-Reitz。

几何函数

Geometry Function:估算微面中有多少比例既没被遮住观察方向也没被遮住光线方向。Smith 方法把两个方向分开算再相乘。

菲涅尔效应

Fresnel Effect:光线以越大的入射角打到表面,反射率越高。正看时反射率为 F0,掠射时趋近 1。Schlick 近似:F = F0 + (1-F0)(1-(h.v))^5。

Lambertian 漫反射

最简单的漫反射模型——假设光打进表面后被均匀散射到所有方向,f_lambert = c / pi。

metallic

金属度参数(0~1)。0 = 电介质(低 F0,有色漫反射,白色镜面);1 = 金属(高有色 F0,无漫反射,有色镜面)。

版本、来源与运行边界

本章以 Joey de Vries 的 LearnOpenGL 原章 为授权改编依据,教学运行基线是 OpenGL 3.3 Core Profile。Khronos 当前发布的规范参照是 OpenGL 4.6 Core Profile;这里用 4.6 规范核查术语和状态合同,但不把 4.6 API 偷偷倒填为原教程内容。GLFW、GLAD、Assimp 与驱动版本都属于运行环境,不能拿“编译通过”替代对 context、资源和 framebuffer 结果的验证。

正式概念与状态责任

  • microfacet:在“PBR 理论、微表面 BRDF 与能量分配”中由BRDF 求值合同与材质参数负责解释其输入、受控状态和可观察结果;运行时以材质输入、Ndot*、D/G/F、kD/kS、BRDF 分量与极端参数检查定位它的第一处变化。
  • energy conservation:在“PBR 理论、微表面 BRDF 与能量分配”中由BRDF 求值合同与材质参数负责解释其输入、受控状态和可观察结果;运行时以材质输入、Ndot*、D/G/F、kD/kS、BRDF 分量与极端参数检查定位它的第一处变化。
  • brdf:在“PBR 理论、微表面 BRDF 与能量分配”中由BRDF 求值合同与材质参数负责解释其输入、受控状态和可观察结果;运行时以材质输入、Ndot*、D/G/F、kD/kS、BRDF 分量与极端参数检查定位它的第一处变化。
  • cook-torrance:在“PBR 理论、微表面 BRDF 与能量分配”中由BRDF 求值合同与材质参数负责解释其输入、受控状态和可观察结果;运行时以材质输入、Ndot*、D/G/F、kD/kS、BRDF 分量与极端参数检查定位它的第一处变化。

章专属 OpenGL 状态实验

先预测“由材质参数求 D/G/F,分配 diffuse/specular 能量并计算 BRDF”发生后,BRDF 求值合同与材质参数应怎样改变normal/view/light/half vectors、roughness、metallic、F0、D/G/F、kD/kS 和分母;再操作三个实验。实验不生成变化率或正确率等虚构总分,只显示真实 GL 状态、资源、命令和可观察结果。

实验一:Context—资源—结果合同

选择任一正式概念与基线/单故障场景,核对它是否进入本章状态合同。正式概念只有同时出现在解释、可视状态和交付证据中才算覆盖。

Context · resource · observable result

PBR 理论、微表面 BRDF 与能量分配:状态合同

用微表面 D/G/F 与能量守恒解释 Cook-Torrance BRDF 中每一项的物理责任

验证场景

官方教程正式概念

logl-39 · 基线帧

microfacet固定 context、资源内容与输入事件,执行“由材质参数求 D/G/F,分配 diffuse/specular 能量并计算 BRDF”

状态所有者BRDF 求值合同与材质参数
受控状态/资源normal/view/light/half vectors、roughness、metallic、F0、D/G/F、kD/kS 和分母
触发命令由材质参数求 D/G/F,分配 diffuse/specular 能量并计算 BRDF

冻结输入:microfacet

BRDF 求值合同与材质参数记录normal/view/light/half vectors、roughness、metallic、F0、D/G/F、kD/kS 和分母

结果:得到可重复的初始 GL 状态与资源身份

观测:材质输入、Ndot*、D/G/F、kD/kS、BRDF 分量与极端参数检查中的初始快照

预期:BRDF 求值合同与材质参数得到可复查结果,并持续满足“kD+kS 不凭空增能;金属的 diffuse 贡献归零;分母避免零除”

实验二:CPU 命令到 GPU 结果的五段轨迹

逐段执行“由材质参数求 D/G/F,分配 diffuse/specular 能量并计算 BRDF”,在每一步记录资源身份、状态变化与第一个可观察结果,并持续核对“kD+kS 不凭空增能;金属的 diffuse 贡献归零;分母避免零除”。

CPU command · GL state · GPU result

PBR 理论、微表面 BRDF 与能量分配:五段轨迹

选择一段命令—资源—结果1 / 5

当前观测:材质输入、Ndot*、D/G/F、kD/kS、BRDF 分量与极端参数检查中的初始快照

不变量:kD+kS 不凭空增能;金属的 diffuse 贡献归零;分母避免零除

实验三:单故障与同输入恢复

注入“金属材质仍保留 Lambert diffuse,同时又计算有色镜面,反射能量重复”,保存首个分岔;撤销后沿用完全相同的 context、资源内容、uniform 和 draw 输入重放。只有材质输入、Ndot*、D/G/F、kD/kS、BRDF 分量与极端参数检查一起恢复才算修复。

Single fault · first divergence · replay

PBR 理论、微表面 BRDF 与能量分配:反例与恢复

故障:金属材质仍保留 Lambert diffuse,同时又计算有色镜面,反射能量重复

1. 冻结输入一致

第 1 次沿用同一 context、资源、uniform 与 draw 输入

2. 注入单故障一致

保持其余输入不变,仅注入“金属材质仍保留 Lambert diffuse,同时又计算有色镜面,反射能量重复”

3. 定位首差一致

kD+kS 不凭空增能;金属的 diffuse 贡献归零;分母避免零除

4. 清理并重放一致

材质输入、Ndot*、D/G/F、kD/kS、BRDF 分量与极端参数检查

最小可重放检查

unit: logl-39
owner: BRDF 求值合同与材质参数
state_or_resource: normal/view/light/half vectors、roughness、metallic、F0、D/G/F、kD/kS 和分母
command: 由材质参数求 D/G/F,分配 diffuse/specular 能量并计算 BRDF
pass_invariant: kD+kS 不凭空增能;金属的 diffuse 贡献归零;分母避免零除
single_fault: 金属材质仍保留 Lambert diffuse,同时又计算有色镜面,反射能量重复
required_evidence: 材质输入、Ndot*、D/G/F、kD/kS、BRDF 分量与极端参数检查

复核者先仅依据以上合同写出预期,再运行基线、单故障和清理后重放。若两次基线的资源身份、首个状态变化或 framebuffer 结果不同,必须保留差异,不能用最终截图相似掩盖中间状态错误。

出处声明

本文为改编重写,改编自 Joey de Vries 的 LearnOpenGL。原文:learnopengl.com

原作及译作以 CC BY-NC 4.0 协议授权,本改编版同样遵循该协议(署名—非商业性使用)。

讨论

评论区加载中…