计算机图形学 23:金属高光与玻璃折射怎样建模
同一盏灯照在金属球上,改变粗糙度会让高光展宽;透过玻璃观察时,光线还会改变方向。继续使用 Lambert 的常量 BRDF 无法表达这两种现象。把高光指数调大也不能自动得到正确的反射份额、折射方向和抽样权重。
本篇选择两种明确的模型:各向同性 GGX 单次散射导体反射,以及理想光滑、无吸收的介质界面。前者检查粗糙度和反射能量,后者检查 Fresnel、全反射与离散事件采样。暂不实现粗糙玻璃、体吸收或完整玻璃球的多界面路径追踪。
先固定方向与介质两侧
沿用第 19 篇的约定,ωᵢ 和 ωₒ 都从表面向外。ωᵢ 指向入射光的来源,实际入射传播方向是 −ωᵢ。法线 n 朝向入射介质,因此 cᵢ=n·ωᵢ≥0。程序显式传入入射侧折射率 ηᵢ 和另一侧折射率 ηₜ,不依靠一个含糊的“玻璃折射率”猜测当前在物体内还是外。
令 q=ηᵢ/ηₜ。反射方向 r=2cᵢn−ωᵢ;折射方向为:
切向分量变成原方向切向分量的 −q 倍,法向分量落到另一侧。这个负号来自“ωᵢ 指向来源”的约定,不能只检查方向长度为 1。代码同时检查切向符号、Snell 正弦关系以及旋转到 +Y 的法线。
若 q²(1−cᵢ²)>1,根号没有实数值,发生全反射。ηᵢ=1.5、ηₜ=1 时,临界角为 arcsin(2/3)≈41.8103°。反过来从 1 进入 1.5,不会在这个单界面模型中发生全反射。
精确临界角对应沿界面传播的极限方向,当前实现将它并入无传播透射的分支。折射率相同则单独处理为直通、F=0,包含精确切线,避免把零除零误判为全反射。
Fresnel 决定反射份额
无吸收介质界面的非偏振 Fresnel 反射率为两个偏振分量反射率的平均:
正常入射时退化为 F=[(ηᵢ−ηₜ)/(ηᵢ+ηₜ)]²。空气近似折射率 1、玻璃示例折射率 1.5,得到 F=0.04;不是所有角度都固定反射 4%。接近掠射时,反射份额通常明显增加。
本实验从公式实际计算得到:
| 入射侧→另一侧 | 入射角 | F | 有传播折射方向 |
|---|---|---|---|
| 1→1.5 | 0° | 0.040000 | 有 |
| 1→1.5 | 60° | 0.089187 | 有 |
| 1→1.5 | 80° | 0.387704 | 有 |
| 1.5→1 | 30° | 0.055190 | 有 |
| 1.5→1 | 40° | 0.245291 | 有 |
| 1.5→1 | 42° | 1.000000 | 无 |
下图由 material-glass.csv 中的计算方向生成,表示单个平面界面的射线方向,不是玻璃物体渲染图。橙色指入射传播,蓝色指反射,绿色指折射;上半区折射率 1.5,下半区为 1。42° 那一格没有绿色射线,是计算结果中的全反射分支。
程序在临界角两侧各取 10⁻⁷ 弧度扰动,分别检查存在和不存在折射方向;另检验两条方向的单位长度、Snell 残差和法向入射的 0.04。边界检查比只画一条看起来弯折的线更能发现介质顺序和符号错误。
金属需要复折射率
导体用复数折射率 η+ik 表达界面响应,k 与介质中的衰减有关。本篇通过 C++17 std::complex 计算复 Fresnel 振幅,再取模平方形成反射率。它没有把金属当成“漫反射颜色加白色高光”。
三个 RGB 通道的合成参数分别为 η=(0.2,0.9,1.5)、k=(3.9,2.5,1.8)。它们只用于固定实验,不对应经测量拟合的铜、金或其他金属,也不是完整光谱模型。
正常入射的独立检查使用 F₀=[(η−1)²+k²]/[(η+1)²+k²]。同时把 k 设成 0、η 设成 1.5,与实数介质 Fresnel 在三个入射余弦处比较。这样既检查复数计算,也检查它退化到已知模型的结果。
一个容易漏掉的边界是 η=1、k=0、余弦=0。介质完全匹配,应为 F=0;不能先套“掠射反射率为 1”的分支。代码为匹配界面单独返回零,其余参数仍要求有限、η 为正、k 非负。
GGX 怎样把微小镜面组成粗糙表面
微表面模型把宏观表面看作具有不同法线的小镜面。若两条反射方向为 ωᵢ、ωₒ,能把它们连接起来的微表面法线为 h=normalize(ωᵢ+ωₒ)。不是所有微表面都能同时看见光与观察方向,因此还需要遮蔽和阴影项。
本篇采用各向同性 GGX 法线分布,以 α 直接作为参数:
α 越大,法线分布越宽。这里没有使用某个引擎将 UI roughness 平方或开方后的映射,正文和代码中的 α 就是公式里的 α。连续模型要求 α>0;理想光滑表面是单独的离散模型,不能把 α=0 直接代进这个表达式。
D 的归一化是 ∫D(h)(n·h)dωₕ=1,带有投影余弦。D 本身不是每球面度的法线采样 PDF。若按完整 NDF 采法线,密度是 D(h)(n·h),再变成反射方向密度还要除以 4|ωₒ·h|;可见法线采样使用另一种密度,不能混搭。
遮蔽项使用 PBRT 第四版的高度相关 Smith 形式:Λ(ω)=[√(1+α²tan²θ)−1]/2,G=1/[1+Λ(ωᵢ)+Λ(ωₒ)]。它不是两个 G₁ 的简单乘积。最终反射函数为:
两条方向都必须在正半球。精确切线先返回零,避免分母为零;这是一条零测边界约定,不是把接近切线的有限响应都删掉。反射函数可能在窄峰处大于 1 sr⁻¹,是否产生多余能量仍需检查积分。
同一光照下的粗糙度图
下面三张球面图均为 128×128,正交观察方向为 +Z,光来自 normalize(−0.5,0.6,1)。这是一束固定方向光,垂直于光束的辐照度设为 4;每个表面点计算 Lₒ=4fᵣ max(0,n·ωᵢ)。它不是有限面积灯的随机采样,也没有环境光或间接反射。
球面法线从可见单位半球解析计算,相机、光照、合成导体参数与曝光完全不变。没有随机数;像素中心一次着色,不做边缘抗锯齿。显示仍先用 L/(1+L),再经 sRGB 编码。
α=0.15:
α=0.35:
α=0.70:
高光随 α 增大而展宽、峰值降低。三图看起来合理仍不足以证明能量正确,尤其显示映射压缩了亮部。程序另保存全部 49,152 个像素的线性 RGB,压缩文件可用于重新显示,不能从 PNG 倒推高精度能量。
单次散射模型为什么会损失能量
固定出射方向,在入射半球积分 fᵣcosθᵢdωᵢ;利用互易性,它也对应固定入射时的反射份额。程序在 μ=cosθ 和 φ 上做中点求积,dω=dμdφ,无需再乘 sinθ。
每档 α=0.15、0.35、0.7,观察角 0°、45°、75°,分别计算三个合成颜色通道和一个 F 恒为 1 的控制。每组先用 256×512 网格,再用 512×1024 网格,共 72 次积分,固定网格结束,不含随机停止。
正常观察方向的细网格结果如下:
| α | R 反射积分 | G 反射积分 | B 反射积分 | F≡1 控制 |
|---|---|---|---|---|
| 0.15 | 0.925026 | 0.616924 | 0.357561 | 0.971765 |
| 0.35 | 0.793537 | 0.529221 | 0.307124 | 0.833726 |
| 0.70 | 0.479379 | 0.319701 | 0.185789 | 0.503717 |
F≡1 时,每次微表面反射本身都不吸收,积分却仍小于 1。原因是该模型只保留微表面的一次散射,未计算被遮挡后再次撞击其他微表面的光。粗糙度较高时,漏掉的多次微表面散射更明显。这不能与场景中“球反射到墙再回来”的多次反射混为一谈。
所有指定参数的反射积分都通过 [0,1.002] 范围检查,加密差小于 0.005;阈值是本组网格的诊断容差,不是全参数证明。α=0.15、法向观察、F≡1 的加密差约为 0.000459,说明窄峰仍有求积误差,不能把表中六位小数都视为准确位。
NDF 另用 65,536 个 μ 中点检查投影归一化,三档结果为 0.999999849、0.999999995、0.9999999998。互易性检查交换两条指定方向,要求结果差小于 10⁻¹²。归一化、互易性与反射积分分别约束不同性质,单独通过其中一个不能代替其余检查。
理想镜面是概率质量,不是很大的普通 PDF
光滑玻璃给定入射方向后,只有一个反射方向和一个折射方向。程序按 F 选择反射,按 1−F 选择折射。这两个数是离散事件的概率质量,不是相对于立体角的连续密度。
若用连续余弦分布在整个半球抽方向,恰好命中唯一理想镜面方向的概率在连续模型中为零。把镜面写成某个有限的巨大 BRDF,再沿用普通方向 PDF,不等同于正确处理 Dirac 分布。第 22 篇的连续密度 MIS 也不能直接套在这两个概率质量上。
sample_dielectric 返回方向、概率质量、是否反射和辐亮度传输权重。按 Fresnel 概率选择后,反射分支的 F 被概率抵消,权重为 1;透射分支在 Radiance 传输约定下还保留 (ηᵢ/ηₜ)²。无吸收界面的功率透射份额是 1−F,但这不意味着辐亮度只需乘 1−F。
程序检查从 1 进入 1.5、再从 1.5 离开的两个 η² 因子乘积为 1。这仅检查折射率因子,不表示整个双界面的最终透射必为 1:各界面的反射概率仍然存在,几何与多次内部反射也尚未追踪。
对前表扩展后的 12 个介质/角度配置,每个使用种子 1–8,各抽 65,536 次实际事件,固定次数结束,共 96 次运行。1→1.5 法向反射频率均值为 0.0400982,跨种子均值标准误为 0.0002911;1.5→1 的 40° 为 0.2461433±0.0005566,理论值为 0.2452912。42°、60°、80° 的全反射配置每次都选反射,频率为 1。
复跑、练习与资料
1 | |
实现位于 material.hpp、material_check.cpp,复用累计向量、随机数、图像和颜色输出。反射积分、界面数据、原始事件计数、事件汇总及线性像素压缩CSV保留实际结果。
练习一:光从折射率 1.5 的介质射向 1,入射角 30°。折射角的正弦是多少?45° 时还能得到传播折射方向吗?
答案:sinθₜ=1.5sin30°=0.75,折射角约 48.59°。45° 时 1.5sin45°>1,发生全反射。交换介质顺序会改变这个判断。
练习二:按 F 抽反射事件后,为什么反射权重不再乘 F?若再次乘 F,会估计什么?
答案:该事件的物理系数与抽中概率相除为 F/F=1。再乘一次会把反射期望改为 F² 倍相应入射辐亮度。透射同理抵消 1−F,但仍需保留指定传输约定的折射率平方因子。
练习三:F≡1 的 GGX 单次散射积分为 0.5,是否足以判断代码漏乘了 2?
答案:不能。先检查归一化、遮蔽模型、积分收敛和微表面多次散射是否包含。单次散射模型本来会遗漏一部分反射路径;直接乘 2 不会普遍修复不同粗糙度和观察角的响应。
上一篇:22 小光源为什么难以采样。下一篇:24 怎样判断渲染器算对了。系列入口。
- PBRT 第四版 §9.3.4–9.3.6:Refract、FrDielectric、FrComplex,方向、Fresnel 和全反射。
- PBRT 第四版 §9.4:导体反射与光滑离散分支。
- PBRT 第四版 §9.5:反射/透射事件与 Radiance 模式的折射率因子。
- PBRT 第四版 §9.6.1–9.6.3:式 9.15、9.16、9.19–9.22,GGX、Smith 及单次散射范围。实际资料核验记录在
research-23.md。






