GPU Gems 3 · Chapter 18. Relaxed Cone Stepping for Relief Mapping

从高度纹理的交点搜索出发,解释如何用放松锥步进扩大安全跳跃,再用二分搜索稳定地恢复凹凸表面。

学习目标

  • 能解释 relief mapping 如何把纹理中的深度与法线带回每个 fragment,并区分搜索阶段与着色阶段
  • 能修改 Relaxed Cone Stepping Lab 的搜索模式、锥比例、步数和采样方式,比较交点可信度与纹理访问预算
  • 能回答:为什么放松锥可以比保守锥跳得更远,却仍然能把二分搜索放在安全区间内

先问:为什么一张平面也能看起来有凹凸

想象一张印着砖缝的纸。真正把每块砖做成几何体会很贵,但如果每个像素都能沿着视线去查一张“表面高度表”,它就能根据看到的凹凸改变颜色、遮挡和自阴影。

本章解决的是:怎样让这条视线快速找到表面,而不漏掉细薄结构,也不因为搜索太保守而停在错误的位置。没有它,凹凸效果要么出现条纹和漏检,要么需要过多纹理读取才能收敛。

1. 先把表面细节留在纹理里

relief mapping 的输入仍然是一个普通多边形,但它额外携带深度纹理和法线纹理。深度表示细节相对于基准平面的高度,法线在最终找到交点后才用于光照。把两者分开存储,搜索阶段只读单通道深度,缓存更友好;法线可以使用适合自己的压缩方式。

对每个 fragment,先把观察方向变换到该点的 tangent space,再在二维纹理坐标中寻找视线与深度表的交点。得到交点位置后,用对应的法线、颜色和更新后的深度缓冲参与遮挡与着色。

per-fragment relief renderingfragment fV → tangent spacedepth mapintersection P in 2D UVshade at Pnormal + colordepth / normal can stay separate: search reads depth first, lighting reads normal last

第 1 / 3 步 · fragment f 先把观察方向变换到 tangent space

播放三步:把表面细节留在纹理里,把交点和法线重新带回片元着色。

2. 交点其实发生在二维高度场上

height field 不是新的几何网格,而是“每个纹理坐标都有一个高度”的函数。设 fragment 的纹理坐标为 (s, t),把观察方向延伸到深度为 1 的位置 (u, v),就可以在这条二维线段上采样并判断射线是否已经进入表面。

找到交点 P 后,要把它的相机空间深度写回 z-buffer,才能让 relief-mapped 表面和场景里的真实物体正确互相遮挡。自阴影也可以沿光线做同样的高度比较:如果光线先撞到别的起伏,当前点就不应被照亮。

3. 先建立可靠区间,再谈快速收敛

binary search 很快,但不能从纹理线段的起点直接使用。它需要一个点在表面外、另一个点在表面内;如果中点恰好落在大结构之前,它会稳定地收敛到错误的 Q。传统 linear search 先用小步长找出穿透区间,再交给二分,但小步长本身可能跳过薄结构。

why the first search matterslinear searchthin structure skippedaliasing artifactbinary from startQno safe bracket yetwrong convergencerelaxed cone + binarywider safe leapthen refine in bracketRCS changes the first phase; binary search remains the precision phase

因此,搜索被拆成两个职责:第一阶段尽量少漏检地找到“已经进入表面”的位置;第二阶段只在已知区间内用固定次数二分精化。步数上限既是画质开关,也是依赖纹理读取和寄存器占用的预算。

4. 用锥图把安全跳跃变宽

普通 cone step mapping 用保守锥保证射线不会穿过高度场,所以它不容易漏掉首个交点,却可能在达到最大迭代次数时停在真实表面之前。锥越窄,安全但越慢;锥越宽,跳跃越远,但必须明确它允许什么样的穿透。

放松锥的约束是:一条射线在锥内最多穿透表面一次,不能穿进去后又出来。这个放宽让每个 texel 的锥半径更大;当射线进入表面后,二分搜索就有了安全的内外端点。它同时保留 cone stepping 的跳跃效率和 binary search 的精度。

the useful relaxation: enter once, never leave twiceconservative cone mapnever pierces → safe but narrowmax steps may stop before Prelaxed cone mapwider leap → reaches inside surfaceat most one crossing → binary safewider cones reduce texture accesses without giving up a valid refinement interval

5. 离线生成每个 texel 的最小比例

放松锥图不在每个 fragment 的主循环里生成。离线阶段把一个 source texel 与所有 destination texel 配对:从源点出发穿过目标点,继续寻找下一次与高度场的交点,用横向距离除以剩余高度得到候选 cone ratio。源 texel 的最终比例取所有候选值中的最小者,确保最坏方向也满足“一次穿透”的约束。

算法是 O(n²) 的预处理,但运行时只需读取一个通道。原书实现把最大比例裁剪到 1.0,以便用整数纹理保存;只有少量 texel 真正能从浮点比例大于 1.0 中获益,先把存储和带宽预算固定下来通常更划算。

offline relaxed-cone-map constructionsource texel tistart at depth 0scan every destinationO(n²), offline onlypairwise ratioti → tj → next hitratio = width / heightcone map texelratio 0.30ratio 0.62ratio 0.95take minimumclamp max to 1.0a single channel stores w/h; the runtime shader only reads the result

6. 运行时:放松锥步进接上二分

运行时先把射线方向按 z 分量缩放,使它与纹理坐标中的高度变化保持同一尺度。每次采样 cone ratio 和当前高度差,计算下一段步长;步进循环结束后,以起点附近的 H 和最后位置 K 建立二分区间。即使步数偏少,二分也会收敛到 K 附近,而不是无界地采样。

float3 rayIntersectRcs(Texture2D relief, float2 uv, float3 rayDir) {
  float3 pos = float3(uv, 0.0);
  rayDir /= rayDir.z;
  float rayRatio = length(rayDir.xy);
  for (int i = 0; i < CONE_STEPS; ++i) {
    float4 tex = relief.SampleLevel(linearSampler, pos.xy, 0.0);
    float coneRatio = tex.b;
    float height = saturate(tex.a - pos.z);
    float distance = coneRatio * height / (rayRatio + coneRatio);
    pos += rayDir * distance;
  }
  float3 range = 0.5 * rayDir * pos.z;
  float3 refined = pos - range;
  for (int i = 0; i < BINARY_STEPS; ++i) {
    float depth = relief.SampleLevel(linearSampler, refined.xy, 0.0).a;
    range *= 0.5;
    refined += (refined.z < depth ? range : -range);
  }
  return refined;
}
runtime: leap to K, refine between H and KHKP ≈ binary resultbinary search halves [H, K] for a fixed number of iterationsfewer dependent texture reads and more active threads: keep the loop compact

循环要保持短小:依赖纹理读取的延迟可以由更多并发线程隐藏,但复杂分支和额外寄存器会减少同时运行的线程。经验上,典型高度场用固定数量的放松锥步数就能达到比线性搜索更少的访问;如果仍有扭曲,优先检查 K 是否足够接近表面,而不是盲目增加所有参数。

7. 过滤也必须尊重锥比例的含义

颜色和法线是可以平滑缩小的信号,常规 mipmap 通常有帮助。cone ratio 却是“安全上限”,把相邻值做普通平均会制造一个原本不存在的比例,进而破坏交点保证。更稳妥的做法是离线用保守最小值生成锥图 mip,或者在需要时使用 nearest 采样。

filter the data according to its meaningcolor / normal mapregular mip OKcone map averageaverage cone wrongcone map safe pathmin reduce / nearestfiltering color is smoothing; filtering cone ratios changes the intersection guarantee
分步1 / 3

先把 relief 交点放回片元管线

在 fragment 的 tangent space 中生成观察射线;搜索阶段只读取 depth map,命中后再读取 normal map 和颜色,并用交点深度更新 z-buffer。

per-fragment relief renderingfragment fV → tangent spacedepth mapintersection P in 2D UVshade at Pnormal + colordepth / normal can stay separate: search reads depth first, lighting reads normal last

第 1 / 3 步 · fragment f 先把观察方向变换到 tangent space

播放三步:把表面细节留在纹理里,把交点和法线重新带回片元着色。

GPU Gems 3 · Chapter 18

Relaxed Cone Stepping Lab

可交互

调整搜索路径与锥图参数,观察“先跨越、后精化”如何在纹理访问、薄结构风险和寄存器预算之间取舍。

tangent-space ray / relief height fieldP放松锥先进入高度场,再把安全区间交给二分搜索。
命中可信度98%
薄结构风险10%
纹理访问估算18 reads
寄存器压力14 regs

本章小结

  • relief mapping 用深度与法线纹理模拟细节几何
  • 先找可靠区间,再用 binary search 精化交点
  • relaxed cone stepping 允许至多一次穿透,换取更宽跳跃
  • 离线 cone map 取最小比例,运行时只读一个通道
  • cone map 不能普通平均过滤,应使用保守最小值或 nearest

练习

问题 1|修改 Demo 代码。 把线性搜索替换为放松锥步进时,你会把哪两个值存进纹理?运行时为什么还需要读取 depth?

问题 2|诊断薄结构漏检。 你把 cone steps 从 12 降到 4 后,细小凸起消失但大块砖面仍然正常。请按“数据、区间、预算”的顺序列出三个检查点。

问题 3|场景选型。 近景石墙、远处大面积砖地和需要稳定自阴影的高度场,分别怎样选择 cone-map 采样与搜索预算?

名词解释

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

relief mapping
height field
cone map
relaxed cone stepping
ray-height-field intersection

资料与写作方式声明

本章以GPU Gems 3 · Chapter 18. Relaxed Cone Stepping for Relief Mapping权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…