曲面细分 Tessellation
曲面细分 Tessellation
曲面细分(tessellation)在 GPU 上把一个粗网格的三角形拆成更多小三角形,并把新顶点偏移到合适位置,从而在不增加原始模型顶点数的前提下丰富细节。这篇讲三种主流细分算法的取舍、硬件细分管线的三个着色器阶段,以及我在一个自研引擎里踩到的一个坑——骨骼动画和曲面细分同时开时,动画矩阵变换到底该放在哪个着色器阶段。
三种细分算法怎么选
| 算法 | 优点 | 缺点 |
|---|---|---|
| PN Triangles | UE4 材质用的就是它;可控细分级别;性能开销最小 | 细节有限,平滑度不如下面两种 |
| Catmull-Clark | UE5 通过插件支持;对规则网格效果好;细节质量高 | 计算成本较高;对不规则形状适应性差 |
| Loop Subdivision | UE5 通过插件支持;细节保持能力更强 | 只能处理纯三角形网格 |
选择的经验法则:要平滑曲面和边界保持,选 Loop 或 Catmull-Clark;要可控细分级别和 LOD 无缝切换,选 PN Triangles。我个人偏好 PN Triangles,因为它只需要三角形的三个顶点位置加法线就能算,落地成本最低。
静态还是动态细分是另一条正交的取舍:
| 优点 | 缺点 | |
|---|---|---|
| 静态(离线)细分 | 细分质量高;运行时开销低 | 预处理时间长;内存占用高 |
| 动态(运行时)细分 | 支持动态 LOD;内存需求低 | 每帧都要算,运行时开销高;低端硬件上质量会掉 |
需要按视距动态调 LOD、且对实时性能敏感,就用动态细分;要极致质量又不需要动态调整,离线细分更合适。我更推荐动态细分——低模跑物理和动画、再细分出高模显示,这套组合的计算效率优势很实在。
硬件细分管线:三个必经阶段
开启曲面细分后,顶点着色器和像素着色器之间会插入三个阶段:外壳着色器(HS)、镶嵌器(Tessellator)、域着色器(DS)。
- 外壳着色器(HS) 分两半。常量外壳着色器每个面片调用一次,输出细分因子——也就是这个面片要镶嵌成几份、按什么方式细分(面片类型、绕序)。控制点外壳着色器每输出一个控制点调用一次,可以改变曲面表示(比如把 4 控制点面片升成 16 控制点曲面),也可以什么都不改,当直通着色器用。
- 镶嵌器(Tessellator) 根据常量外壳着色器给的细分因子,把面片镶嵌成一堆小面片。这一步全程硬件处理,不可编程。
- 域着色器(DS) 逐面片调用,拿着细分因子和位置参数(三角形面片用重心坐标,四边形面片用 UV 坐标),通过插值(重心坐标插值、双线性插值等)算出每个新顶点的实际坐标。
PN Triangles 原理
UE4 材质系统的细分用的就是 PN Triangles。它用顶点位置和法线来构建曲面:只需要一个三角形的三个顶点位置和顶点法线,据此生成 10 个控制点,再由这些控制点求出贝塞尔三角面上的顶点;法线做同样的操作。最终用一张贝塞尔曲面替换掉原始网格里的每个三角形,从而改善三角网格的阴影和轮廓。
它的好处是适用面广(不规则形状、复杂曲面都能用)、细分级别可控、能在不同级别间无缝切换。代价是插值带来的细节上限和平滑度不如 Catmull-Clark / Loop。
坑:骨骼动画 × 曲面细分,动画矩阵该在哪个阶段变换
这是整块内容里最值得记的一条。当一个骨骼网格同时开了动画和 PN Triangles 细分时,动画的骨骼矩阵变换放错阶段,结果就会错——而且错得五花八门。我把各种组合都试了一遍,规律是这样的:
先明确一个事实:细分发生在 HS/DS,位置数据在 DS 阶段才被镶嵌出新顶点。如果你在 VS 里就做了 MVP 变换,到 DS 阶段再插值出来的顶点已经在裁剪空间里,PN Triangles 的曲面构造是在这个空间里做的,几何就不对了。
几组关键实验结论:
- position 在 VS 做动画矩阵变换 + MVP 变换,再进 HS/DS:错。position 提前进了裁剪空间,细分基于错误的空间做插值。
- position 在 VS 只做动画矩阵变换(不做 MVP),HS 做 PN-Triangles,DS 里做完 PN-Triangles 后再做 MVP 变换:对。这是正解——动画在细分之前应用(让曲面基于动画后的世界空间几何构造),MVP 留到 DS 最后一步。
- 把动画矩阵变换挪到 DS 里做:会出问题。DS 阶段拿到的是镶嵌出来的 3 个控制点,「只取第一个点的骨骼矩阵做变换」和「对 3 个点的骨骼矩阵求平均」两种做法结果都不对——细分产生的新顶点没有正确的骨骼权重,事后再补动画矩阵是补不回来的。
- normal 的动画变换要和 position 保持一致:normal 不做动画矩阵变换、position 做,会导致光照法线和几何对不上;正确做法是 normal 和 position 在 VS 阶段一起做动画矩阵变换。
一句话总结这条坑:动画骨骼矩阵变换必须在细分之前(VS 阶段)完成,投影变换(MVP)必须推迟到细分之后(DS 阶段)完成。顺序反了,要么几何炸开,要么法线错乱。
DX12 集成的三个关键点
initConstants()创建根参数时,常量缓冲区的ParameterType要按使用范围设D3D12_SHADER_VISIBILITY_ALL——因为 VS 和 DS 都要用这个常量缓冲区,所以给 ALL。- PSO 阶段
PrimitiveTopologyType赋值D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH。 IASetPrimitiveTopology赋值D3D_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST(三控制点面片列表)。
参考文献
- PN Triangles: Curved PN Triangles (Vlachos et al.)、John McDonald, GDC2011
- Catmull-Clark / Loop Subdivision: GAMES101 相关章节
- OpenSubdiv 文档