GPU Gems 3 · Chapter 17. Robust Multiple Specular Reflections and Refractions

从分层距离 cubemap 出发,解释如何用 ray marching、secant search、Fresnel 和折射率在 GPU 上稳定追踪多阶反射与折射。

学习目标

  • 能解释为什么 fragment shader 需要分层距离地图来代替直接访问场景网格,并区分 ray marching 与 secant search 的职责
  • 能修改 Robust Secondary Ray Lab 的路径类型、最大深度、地图层数、搜索加速和步长,观察命中可信度与实时预算
  • 能回答:为什么 Fresnel、折射率和提前终止必须同时参与多阶反射/折射循环,而不是只把环境 cubemap 采样几次

先问:为什么一个反射 cubemap 不够

想象一个玻璃球放在镜子前:球面会看到镜子,镜子里又会看到球;如果球还是透明的,光还要继续穿过表面。只保存“从相机直接看见的颜色”,这些被遮挡的交点就永远不存在。

本章解决的是:怎样把场景中可搜索的几何信息放进 GPU 可访问的纹理,再沿反射和折射方向找到第二、第三甚至更高阶的命中点。没有这条路径,反射只能停在第一跳,透明物体会把背景当作一张不随几何变化的贴图。

1. 把场景表面存进分层距离地图

fragment shader 能查纹理,却不能直接遍历完整的三角网格。因此先从参考点向六个 cubemap 方向采样场景,把每个 texel 的距离、命中法线和材质信息写入纹理。普通深度 cubemap 只保存第一层;反射物体前后或被遮挡的表面则需要额外层。

分层的意义不是把场景变成精确体素,而是让二次光线拥有一个可查询的“几何近似”。距离使用世界空间欧氏距离时,方向与距离可以一起描述命中点,也能减轻 cubemap 面之间插值时的边界特殊处理。空 texel 可以写入负距离,后续搜索时明确识别为无效。

scene geometry → layered distance maps → searchable texture scenereference pointvisible scene surfacescube map layerslayer 1 · d / N / materiallayer 2 · d / N / materiallayer 3 · d / N / materiallayer 4 · d / N / materialfront hit + occluded hitsray lookuptexture, not meshdistance sign can mark empty texels; extra layers recover surfaces hidden from the reference pointsampled geometry is enough for secondary-ray search
fragment shader 不能直接访问三角网格;分层距离 cubemap 把可搜索的场景几何压缩进纹理。

简单的反射物体可以把参考点放在自身中心,分别渲染前后表面得到两层;复杂场景则需要 depth peeling 控制层数,把更远的表面合并到有限的层里。层数越多,遮挡证据越完整,但每帧更新纹理和内存成本也越高。

2. 先不漏交点,再把交点找准

从当前表面点 x 沿反射或折射方向 R 走,先用 ray marching 逐步查询距离地图。步长不能大于相邻 texel 所能表达的距离,否则细薄物体可能完整地落在两个采样点之间而被跳过。它的职责是建立安全的命中区间,而不是在大步进阶段追求最终精度。

robust hit search: march first, refine second12345678hit intervalray marching: no missed crossingsecant search: accurate crossingstep size smaller than neighboring texel distance reduces the chance of skipping a thin reflected object
先用较便宜的线性步进保证不漏过表面,再在命中的区间里用 secant search 精确定位。

找到一对距离符号相反或跨越表面的样本后,再切换到 。它比继续用很小的固定步长更快,也能把命中位置收敛到足以支撑法线和颜色查找的精度。

两阶段的边界要清楚:ray marching 负责“不要漏”,secant search 负责“不要糊”。当距离值变化非常剧烈时,要把线性步长调细;当表面平滑且 texel 代表的距离变化缓慢时,可以用更大的步长换取速度。

3. 在命中点分配反射与折射能量

命中点有法线 N、视线方向 V 和材质数据。理想反射使用 reflect 生成新的方向;透明表面使用 refract,并需要 决定弯折程度。Fresnel term 用角度把能量分成反射部分 F 与透射部分 1 − F

金属可以用基准反射率、粗略的 extinction 相关参数和 Schlick 风格近似;玻璃或水则需要正的折射率。工程上还可以用折射率的符号区分路径类别:负值表示 reflector,零表示 diffuse 终点,正值表示 refractor。这样动态循环不用额外的材质枚举也能选择下一条路径。

hit point + normal + material → reflected / refracted raysNVR = reflectT = refractFresnel Freflected energy1 − F → transmitted energy
同一命中点可以产生反射或折射路径;Fresnel 让掠射角更偏向反射,折射路径则由折射率决定。

不要把 Fresnel 当作最后的颜色滤镜。它会影响下一次路径的 radiance;如果命中点又是镜面或透明表面,就要携带新的方向、法线、折射率和能量继续追踪。

4. 用有限循环追踪多阶路径

每次命中后,循环都要更新四类状态:当前点与方向、路径辐射量、当前材质的 Fresnel 参数、已经走过的深度。命中 diffuse 表面时把它的 radiance 加入结果并停止;命中 reflector 或 refractor 时重新计算方向并继续。

float3 radiance = float3(1, 1, 1);
float3 direction = normalize(viewDirection);
for (int depth = 0; depth < MAX_DEPTH; ++depth) {
  float3 hitNormal, hitRadiance;
  float hitDistance;
  if (!Hit(distanceMaps, origin, direction, hitDistance, hitRadiance, hitNormal)) break;
  float3 F = Fresnel(hitNormal, direction, materialFp0);
  if (materialIor == 0) { radiance *= hitRadiance; break; }
  bool mirror = materialIor < 0;
  direction = mirror ? reflect(direction, hitNormal) : refract(direction, hitNormal, materialIor);
  radiance *= mirror ? F : (1.0 - F);
  origin += direction * hitDistance;
}
return radiance;

实际 shader 还需要处理路径离开 cubemap、命中无效 texel、全黑 radiance 和动态分支支持。MAX_DEPTH 是正确性护栏,也是性能旋钮;增加它不一定改善画面,因为更高阶路径可能已经低于可见阈值。

dynamic path loop: reflect, refract, accumulate, terminatecurrent rayI · V · N · depthhit material?negative IOR → mirrorpositive IOR → glasszero → diffuse endreflect + Fcontinue pathrefract + 1−Fcontinue pathstop when depth reaches MAXDEPTH, radiance becomes negligible, ray leaves the map, or diffuse is hitearly termination is both a correctness guard and a performance feature
多阶反射/折射不是无限递归:动态循环带最大深度,命中 diffuse、路径过暗或搜索失败时都可提前结束。

5. 用 min-max 和层数做工程预算

复杂场景每帧更新多层 cubemap 会很贵。min-max pair 可以为每层距离数据保存上下界,光线先跳过不可能相交的区间,再把剩下的区间交给精确搜索。它不是放宽命中标准,而是减少明显不可能命中的 texture lookup。

per-layer bounds → skip empty intervals → refine real hitdistance layermin / maxmin / maxmin / maxmin / maxray intervalskip impossible rangessearch fewer texelshitrefine
min-max pair 不是改变交点定义,而是给线性搜索一个可跳过的安全区间,减少无效纹理查找。

实现时要同时看三个预算:层数控制被遮挡表面的完整性,线性步长控制薄物体漏检概率,secant 迭代次数控制命中精度。简单物体可用两层前后表面;镜子、玻璃和复杂遮挡使用更多层;远处或小屏幕占比物体则可以降低层数和最大深度。

6. 三步把二次光线接入实时管线

分步1 / 3

先生成可搜索的分层场景表示

选择参考点,写入方向 cubemap 中的距离、法线和材质;简单对象使用前后两层,复杂遮挡再用 depth peeling 增加层数,并标记无效 texel。

scene geometry → layered distance maps → searchable texture scenereference pointvisible scene surfacescube map layerslayer 1 · d / N / materiallayer 2 · d / N / materiallayer 3 · d / N / materiallayer 4 · d / N / materialfront hit + occluded hitsray lookuptexture, not meshdistance sign can mark empty texels; extra layers recover surfaces hidden from the reference pointsampled geometry is enough for secondary-ray search
fragment shader 不能直接访问三角网格;分层距离 cubemap 把可搜索的场景几何压缩进纹理。
per-layer bounds → skip empty intervals → refine real hitdistance layermin / maxmin / maxmin / maxmin / maxray intervalskip impossible rangessearch fewer texelshitrefine
min-max pair 不是改变交点定义,而是给线性搜索一个可跳过的安全区间,减少无效纹理查找。

Robust Secondary Ray Lab

让二次光线既不漏,又能实时跑

调整路径类型、最大光线深度、距离地图层数、搜索加速和步长,比较命中可信度、路径亮度、纹理工作量与薄物体漏检风险。

mixed secondary pathhit normalF · reflect1−F · refractconfidence 69% · radiance 61% · work 48 unitsthin-object miss risk 24%layered maps: three · max depth: 3

hit confidence

69%

path radiance

61%

texture work

48 units

thin-object risk

24%

ray marching 负责不漏交点,secant search 负责把命中点收准。这些读数是解释算法取舍的稳定示意,不是特定硬件的 benchmark。

本章小结

  • 分层距离地图把被遮挡表面变成 fragment shader 可查询的几何近似
  • ray marching 负责不漏交点,secant search 负责提高交点精度
  • Fresnel 和折射率共同决定反射/透射方向与能量
  • 多阶路径必须有最大深度、能量和无效命中终止条件
  • 层数、步长、min-max 与屏幕占比共同决定实时预算

练习

问题 1|修改 Demo 代码。 当前命中搜索只使用固定大步长。请增加“找到命中区间后再细化”的阶段,并说明如何减少薄物体漏检。

for (int i = 0; i < MAX_STEPS; ++i) {
  float distance = SampleDistance(rayOrigin + rayDir * t);
  if (distance < 0) return t;
  t += STEP_SIZE;
}

问题 2|诊断反射/折射能量错误。 玻璃球的反射和透射都很亮,且掠射角没有明显变亮。你会检查哪些输入和更新?

问题 3|场景选型。 近距离玻璃球、远处金属装饰和包含细电线的镜面场景分别怎样选择层数、步长和最大深度?

名词解释

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

layered distance map
depth peeling
ray marching
Fresnel term
index of refraction

讨论

评论区加载中…