GPU Gems 3 · Chapter 21. True Impostors

从相机朝向的卡片出发,解释如何用纹理高度场、片元射线搜索和二分收敛在 GPU 上重建大量可见体积。

学习目标

  • 能解释朝向相机的卡片如何在不提交大量多边形的情况下承载一个可查询的体积
  • 能修改 True Impostor Lab 的打包、步进、细化、材质路径与排序选项,比较读取成本和遮挡风险
  • 能回答:为什么首次命中后还要二分收敛,以及透明表面为什么必须让射线继续前进

先问:为什么远处的复杂物体不必都变成多边形

想象一片塞满细节的远景:如果每个物体都用完整模型绘制,屏幕上真正看见的只是很多小区域,却要为看不见的细节提交大量形状。我们需要一扇轻薄的“窗口”,让每个可见小方块只为自己负责的那一条观察路径付费。

本章解决的是:怎样把物体的厚度与表面信息放进普通图片的通道里,再在 GPU 上按像素找出真正的表面。没有这条路径,远景实例会被顶点处理拖慢;只存一张静态图片,又会在视角变化时露馅。

1. 把复杂物体变成一扇会转身的窗

传统 impostor 把某个视角拍成一张图片贴到矩形卡片上;相机一转,图片仍然是平的,轮廓和遮挡很快就不对。true impostor 保留卡片的低几何成本,却不把颜色预先烤死:卡片只提供观察窗口,片元阶段沿每个像素的视线去查询物体的体积表示。

关键动作是让卡片绕中心转向相机。这样同一套 UV 和体积数据可以面对任意观察方向,最终颜色由当前视线与体积的交点决定,而不是由某一张固定截图决定。

turn one quad into a window for a 3D objectmany detailed modelsthe object stays behind the windowcamera-facing quadrotated around its centerview raytested per pixelgeometry cost becomes one quad; detail is recovered where the ray meets the stored volume

2. 让一张纹理保存一段厚度

普通纹理只有 uv 两个坐标,但一个像素有四个通道。把 R、G、B、A 看成沿第三个方向排列的四个深度函数,就能让一张 RGBA 纹理表达多个表面层;需要更复杂的形状时,再增加纹理数据即可。

顶点把 UV 平移到卡片中心附近,并把一个额外的深度分量从中心开始建立。卡片旋转时,纹理坐标和几何位置使用同一个中心,因此纹理体积也跟着卡片一起转。它不是把二维图片“变成真的网格”,而是把每个片元的查询空间扩展成一条可采样的深度轴。

four channels become depth layers along Wone RGBA textureRGBAeach channel stores a height fieldtexture-coordinate volumeW layer 1W layer 2W layer 3W layer 4depthUV stays 2D; channels provide volume samples

3. 沿每个像素的观察路径找第一次相交

光栅化会把卡片顶点携带的 UV 和方向插值到每个片元。片元拿到一条 view ray 后,沿它在纹理坐标空间中前进:每一步读取高度场,判断当前点是否已经进入体积。第一次跨过表面时,保存当前点和前一个点,得到一个“命中区间”。

这一步适合 GPU,因为所有像素都可以执行相似的固定次数循环。没有命中的射线也会走完预算,换来的好处是分支发散更少;真正要控制的是每个片元的读取次数,以及卡片在屏幕上覆盖了多少像素。

march, bracket, refine1 · marchfixed steps test occupancy2 · bracketprevious and first-hit samples3exact hitthe loop is fixed-size on the GPU; the bracket makes the final search precise

4. 用二分搜索把命中点收窄

固定步长只告诉我们“表面落在这两个样本之间”,直接使用前一个点会出现边缘抖动和厚度误差。二分搜索把这个区间取中点,重新检查中点在哪一侧,再保留正确半区;重复几次后,命中位置就足够稳定,可以用来采颜色和法线。

GPU 实现通常不会因为提前命中就立刻退出循环:固定迭代次数更容易保持吞吐。因此步进次数负责找到区间,细化次数负责在区间内提高位置精度,两者是不同的预算旋钮。

5. 透明材质让射线继续穿过体积

不透明表面在第一次命中后就可以着色并结束查询。透明对象不能这样做:命中点的法线需要参与折射,折射后的新方向还要继续寻找后续表面。把射线在各层之间走过的距离累加起来,就能用一个局部近似表达体积的透光程度。

这一扩展仍然复用同一组高度场,只是把“第一次命中”改成“命中后更新方向并继续”。因此它的纹理读取会增加,但不需要把每个透明物体重新膨胀成大量多边形。

translucent surfaces reuse the remaining rayfirst surfacenext surfaceaccumulate distance inside the volume to approximate translucency

6. 规模化时,瓶颈从顶点转到片元

这套方法的细节等级天然由屏幕占用决定:远处卡片只覆盖少量片元,就只支付少量搜索;近处卡片覆盖更多像素,片元阶段的步进和纹理读取成为主要成本。原书的核心取舍不是“永远比多边形快”,而是用可预测的填充率预算换取一次绘制大量简单实例的能力。

卡片还可能遮挡邻居。多个实例重叠时,CPU 端深度排序可以减少不必要的 overdraw;不排序时,即使最后的颜色被丢弃,前面的片元搜索仍然已经付出了成本。透明路径更需要谨慎,因为它不能简单用第一次深度命中结束。

7. 把卡片、搜索和着色串成一个片元循环

float4 ShadeTrueImpostor(float2 uv, float3 ray, Texture2D volume) {
  float depth = 0.0;
  float previous = 0.0;
  bool bracketed = false;
  for (int i = 0; i < MARCH_STEPS; ++i) {
    float4 heights = volume.SampleLevel(linearSampler, uv, 0.0);
    float surface = selectHeight(heights, depth);
    if (rayInsideVolume(ray, depth, surface)) {
      bracketed = true;
      break;
    }
    previous = depth;
    depth += STEP_SIZE;
  }
  if (!bracketed) return discardPixel();
  float hit = refineBinary(previous, depth, uv, volume);
  return shadeAtHit(uv, hit, ray);
}

代码中的 MARCH_STEPS 负责找到命中区间,refineBinary 负责提高命中位置精度。若材质需要透明或折射,shadeAtHit 之后不能直接结束,而是要更新 ray、累计穿透距离,再开始下一段搜索。

分步1 / 3

先生成朝向相机的查询窗口

把卡片绕中心旋转,使它的法线朝向相机;同时把每个顶点的 UV 和 view ray 送进光栅化,让每个片元都获得自己的查询起点和方向。

turn one quad into a window for a 3D objectmany detailed modelsthe object stays behind the windowcamera-facing quadrotated around its centerview raytested per pixelgeometry cost becomes one quad; detail is recovered where the ray meets the stored volume
four channels become depth layers along Wone RGBA textureRGBAeach channel stores a height fieldtexture-coordinate volumeW layer 1W layer 2W layer 3W layer 4depthUV stays 2D; channels provide volume samples
GPU Gems 3 · Chapter 21

True Impostor Lab

可交互

调整体数据打包、射线步数、交点细化、材质路径和深度排序,观察每个可见片元的搜索成本与遮挡风险。

first-hit ray castinghit首次命中后停止着色,适合不透明表面。RGBA packs four height fields · coverage 90%
交点覆盖90%
最终区间宽度0.6%
纹理读取估算24 reads
卡片遮挡风险可控

本章小结

  • true impostor 用低成本卡片承载可查询的体积
  • RGBA 通道可以保存多个高度场层
  • ray marching 先找区间,binary search 再收窄交点
  • 透明材质要折射并继续穿过后续表面
  • 规模化实例的成本主要落在片元、读取和 overdraw

练习

问题 1|修改 Demo 代码。 把 Lab 的 march steps 调到 8,再把 binary refinement 调到 8;你预计覆盖率、区间宽度和读取次数分别如何变化?

问题 2|诊断纸片轮廓。 一个实例正面正确、斜视时仍像平面图片。请按“数据、射线、着色”列出三个检查点。

问题 3|场景选型。 大量不透明远景、少量近处透明物体、许多互相重叠的卡片,分别如何分配预算?

名词解释

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

true impostor
billboard
height field
view ray
ray marching

资料与写作方式声明

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

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

讨论

评论区加载中…