GPU Gems 3 · Chapter 5. Generic Adaptive Mesh Refinement

用 per-vertex depth tag 选择预计算三角形模式,在单次 vertex program 中完成 barycentric 细分与 displacement,把高分辨率网格留在 GPU 现场生成。

学习目标

  • 能解释 coarse mesh、depth tag、ARP pool 和 barycentric interpolation 如何在单次 vertex program 中共同生成细分网格
  • 能修改 GAMeR Adaptive Refinement Lab 的细分深度、输入三角形数、tag 来源和 displacement,观察 CPU 上传与 GPU 顶点工作的变化
  • 能回答:输入网格很粗、细分很深,或输入网格很大、细分很浅时,应该把瓶颈判断放在哪里

先把“放大后的地图”留在 GPU

想象一张只画出主要道路的纸地图:放大某一段时,你希望道路边界可以继续长出细节,而不是提前把整张地图印成最大尺寸。不同区域还需要不同的放大倍数,重要区域更细,平坦或遥远区域更粗。

本章解决的问题是:如何只把一张低分辨率表面交给 GPU,却在每个三角形附近按需要生成高分辨率形状?如果先在 CPU 生成完整目标网格,动画和大场景会反复占用 CPU、总线带宽和显存;如果只做统一细分,又会把预算浪费在不需要细节的区域。

低模输入,GPU 现场长出高模coarse meshpositions + attributesdepth tagsdistance / curvatureARP poolindexed barycentric nodesone vertex passtessellate + displaceCPU 不生成 target mesh,也不往 graphics bus 发送它顶点程序先把节点放回 coarse triangle,再沿 displacement function 移动省下 CPU、总线和 GPU 存储;新的瓶颈变成 vertex-processing horsepower

1. GAMeR 的基本模型:粗三角形加连续位移

一张复杂表面不必从一开始就列出所有小三角形。可以保存一个 coarse mesh,再给它配一个连续的几何函数:细分负责“增加节点和连接关系”,位移负责“把新节点移动到最终表面”。这两件事互相独立,所以同一个细分内核可以接平滑曲面、程序噪声、动画形变或高度图。

GAMeR 把每种可能的三角形细分配置预先算成 adaptive refinement pattern,简称 ARP,并把它们存进 GPU 的 ARP pool。运行时不重新创建拓扑,而是按当前三角形的三个边深度选择一个 pattern,再把 pattern 的节点放回当前三角形。

ARP pool 是“同一个三角形的所有细分答案”3D pattern matrixedge depths i · j · kjik每个 configuration 只存一次选中的模式例如 { i=2, j=3, k=4 } 的 adaptive patch

这个“先存答案、后选答案”的设计绕开了传统 vertex shader 不能直接创建任意几何的问题,也避免 geometry shader 输出上限让深细分只能走多 pass。只要 pattern 用参数空间表示,它就与三角形的世界位置、方向和形状无关。

2. depth tag:每个顶点只携带“附近需要多细”

depth tag 是一个很小的 per-vertex scalar,却能表达很多 LOD 策略:距离远近、表面曲率、角色脸部的重要区域、科学可视化的关注区域,都可以参与计算。它不直接描述最终顶点位置,只负责告诉 ARP 选择器“附近要多细”。

真实引擎通常没有显式的 edge 对象,所以 GAMeR 先给顶点打 tag,再把相邻两个顶点的 tag 合成 edge depth。用邻接顶点的均值可以让共享边得到同一个细分等级,避免一侧插入节点而另一侧没有对应节点。

裂缝来自共享边的答案不一致错误:两侧各算各的共享边节点错位 → crack正确:先统一 edge depthtag(v0, v1) 的均值 → 同一组边节点

tag 可以在 CPU 上算,也可以在特殊场景中通过预备 GPU pass 得出;但距离、语义重要性和动画状态往往在 CPU 更容易获得。重点是不要把 tag 当作“网格已经细分”的标记:它只是 ARP pool 的索引输入。

3. barycentric coordinate:把 pattern 放回任意三角形

ARP 中每个节点不存固定的 world position,而存三个 barycentric coordinate。设 coarse triangle 顶点为 p0p1p2,节点位置就是三个顶点按权重的线性组合;法线、颜色和其他 per-vertex attribute 也可以使用相应的权重插值。

顶点程序因此有两个连续阶段:第一步用 pattern 的坐标生成 tessellated position;第二步在这个位置上采样 ,沿法线或任意向量移动节点。高阶 Bézier、程序噪声、height map 和动画自由形变都可以替换第二步,而不必重写 pattern 数据。

同一组 barycentric 坐标可以复用到任何三角形coarse trianglep0p1p2w0 + w1 + w2 = 1ARP stores coordinates, not world positionsvertex programtessellate → sample displacement → shade位置、法线和其他属性可分别插值
vec3 p = p0 * w + p1 * u + p2 * v;
vec3 n = n0 * w + n1 * u + n2 * v;
float d = displacementFunction(p);
 
vec3 refinedPosition = p + d * normalize(n);
output.position = project(refinedPosition);
output.normal = n;

这段代码把关键顺序写清楚:先定位细分节点,再应用位移;如果直接对 coarse 顶点做位移,就没有生成新的采样点,也无法捕捉高频 displacement 的几何细节。

4. crack-free adaptive refinement:邻接边必须共享答案

裂缝不是由 displacement function 单独造成的,而是相邻三角形对公共边选了不同的细分拓扑。GAMeR 先统一 edge depth,再从 ARP pool 选择对应 pattern;这样公共边上的节点序列一致,两个 patch 可以无缝拼接。

对于三个边深度不一样的配置,可以先达到最小深度,再在剩余边附近继续拆分;更大的 support 还能让三角形更接近等边。实现时要记住“同一配置可以有多种 pattern”,ARP pool 不是数学上唯一的答案,而是质量、节点数量和索引布局之间的工程选择。

裂缝来自共享边的答案不一致错误:两侧各算各的共享边节点错位 → crack正确:先统一 edge depthtag(v0, v1) 的均值 → 同一组边节点

5. 单次 vertex program:细分和位移一起在 GPU 完成

把 pipeline 串起来:CPU 维护 coarse triangle 的属性和 depth tags;渲染时按三个 edge depth 绑定 ARP 的 index buffer;vertex program 读取 pattern 的 barycentric node,用它插值位置、法线和其他属性,再采样 displacement function。目标网格不会回到 CPU,也不必作为完整 vertex buffer 存在 GPU。

一次 vertex pass 里的四个决定1tagper-vertex LOD2patternARP index3interpolatebarycentric4displacesample + shade先确定“需要多细”,再确定“如何长出来”单步观察:depth tag 只选模式,barycentric 坐标才放置节点播放、暂停、单步或拖动进度,比较细分与位移的两个阶段

第 1 / 4 步 · 为 coarse mesh 的顶点计算 depth tag

GAMeR 把 topology 选择和 displacement 采样放进同一个 vertex program。

先预测:把 depth 调高只会提高 GPU vertex work 还是也会增加 ARP bind 次数?把粗网格三角形数提高十倍、但把 depth 降低,会不会把瓶颈推回 CPU attribute upload?

coarse-to-refined 比率决定谁在等谁refined triangles / coarse triangles →relative workCPU upload / bindcoarse mesh 大,depth 小GPU vertex workcoarse mesh 小,depth 大sweet spot少上传,深细分在 GPU

官方样例的结果呈现了这个取舍:在粗输入、深细分时,GPU 的顶点处理能力是主要限制;在输入很大、细分很浅时,上传 coarse polygon attributes 和 bind 操作更容易成为瓶颈。相同目标规模下,输入约 4,000 个三角形、平均深度 4 的场景明显比输入约 65,000 个三角形、平均深度 2 更适合把工作留在 GPU;这是架构信号,不是今天硬件的固定帧率承诺。

GAMeR 自适应细分实验

先预测:把 depth 调高时,CPU 上传记录还是 GPU vertex work 会更快上涨?把 coarse mesh 变大后,答案会不会反过来?

depth 4 · distance tags · animatedrefined preview 16×16ratio 256×
refined triangles204,800
CPU tag records800
GPU vertex work275,342
likely bottleneckGPU vertex processing

per-frame tags can follow motion;depth 只改变细分配置,不会把 target mesh 存回 CPU。

对比“输入大小”和“细分深度”两个方向:同样的输出规模,coarse mesh 越小,越能把工作留给 GPU。

6. 同一个内核可以承载多种表面

变化的是 displacement function,不变的是细分内核barycentric kernelsame ARP + same vertex passposition / normal / attributesBézier smoothingnew displacementprocedural detailnew displacementanimated deformationnew displacementheight-map terrainnew displacement

GAMeR 的“generic”不是说所有 displacement 都长得一样,而是细分内核不依赖 mesh topology、displacement 类型或 refinement level。平滑模型可以接高阶 Bézier;程序几何可以接高频函数;动画角色或软体可以每帧更新 coarse mesh;地形可以按距离 tag 后采样 height map。

真正的边界在于采样和顶点吞吐:displacement 的频率越高,需要的细分越深;输出越密,vertex program 越忙。反过来,若输入 coarse mesh 本身已经很大,继续降低细分深度也未必能解决总线与 bind 成本。先看 coarse-to-refined ratio,再决定把复杂度交给 CPU 还是 GPU。

三步验收:从 tag 到可用的自适应表面

分步1 / 3

第一步:把 refinement 选择和输入数据分开

先列出 coarse mesh、每个顶点的 depth tag 和 ARP pool。tag 只说明附近需要多细,ARP 才包含具体节点和连接关系;不要把 target mesh 当作 CPU 输入的一部分。

低模输入,GPU 现场长出高模coarse meshpositions + attributesdepth tagsdistance / curvatureARP poolindexed barycentric nodesone vertex passtessellate + displaceCPU 不生成 target mesh,也不往 graphics bus 发送它顶点程序先把节点放回 coarse triangle,再沿 displacement function 移动省下 CPU、总线和 GPU 存储;新的瓶颈变成 vertex-processing horsepower
ARP pool 是“同一个三角形的所有细分答案”3D pattern matrixedge depths i · j · kjik每个 configuration 只存一次选中的模式例如 { i=2, j=3, k=4 } 的 adaptive patch

本章小结

  • GAMeR 只上传 coarse mesh 与 per-vertex depth tag。
  • ARP pool 预存细分模式,运行时按 edge depth 选择。
  • barycentric coordinate 让同一 pattern 适配任意三角形。
  • 统一共享边的 depth 才能保持 crack-free refinement。
  • coarse-to-refined ratio 决定 CPU 上传还是 GPU 顶点处理占主导。

练习

问题 1|追踪一次顶点。 一个 coarse triangle 的三个顶点为 p0p1p2,某个 ARP 节点的权重为 w=0.2u=0.3v=0.5。它的 tessellated position 如何写?如果 displacement function 返回沿法线 0.1 的位移,下一步做什么?

问题 2|修改 Demo 代码。 给 GAMeR Adaptive Refinement Lab 增加 edgeDepthBias 控件:在生成 ARP key 前,把共享边两端的均值加上 bias 并 clamp 到最大深度。你需要在哪里保证相邻三角形仍使用同一条边的结果?

问题 3|场景选型。 一个动画角色只有 2,000 个 coarse triangles,却需要高频位移;另一个 CAD 网格有 65,000 个 coarse triangles,只需要浅层细分。哪一个更适合 GAMeR 的深细分路径?另一个瓶颈应先查什么?

名词解释

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

adaptive refinement pattern
depth tag
barycentric coordinate
displacement function

资料与写作方式声明

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

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

讨论

评论区加载中…