曲面细分与几何着色器
Tessellation & Geometry Shader
曲面细分、几何着色器和流输出是顶点着色器之后的三个可选阶段。曲面细分把曲面描述按需生成合适数量的三角形;几何着色器能把图元变成别的图元,但效率不好、实际用得少;流输出把处理后的顶点写回内存以便复用。
曲面细分 (Tessellation)
DirectX 11 引入,OpenGL 4.0 和 OpenGL ES 3.2 也支持。
好处
- 曲面描述比对应的三角形更紧凑,省内存;
- 每帧都在变形的角色或物体,不会让 CPU 到 GPU 的总线成为瓶颈;
- 按视角控制细节:球离得远只要几个三角形,离得近可以有几千个;也能在弱 GPU 上用更低质量来保帧率。
三个组成部分
下图是曲面细分的数据流:外壳着色器决定怎么细分并处理控制点,固定功能的细分器生成新顶点,域着色器算出每个顶点的最终数据。
flowchart LR
P["输入面片<br/>控制点"] --> H["外壳着色器<br/>Hull Shader"]
H -->|"细分因子和类型"| T["细分器<br/>Tessellator,固定功能"]
H -->|"变换后的控制点、细分因子、常量"| D["域着色器<br/>Domain Shader"]
T -->|"生成的顶点和重心坐标"| D
D --> O["输出三角网格"]| DirectX 叫法 | OpenGL 叫法 | 作用 |
|---|---|---|
| 外壳着色器 (Hull Shader) | 细分控制着色器 | 告诉细分器生成多少三角形、怎么排布;处理每个控制点,也可以增删控制点 |
| 细分器 (Tessellator) | 图元生成器 | 固定功能,生成新顶点并给出重心坐标;支持三角形、四边形、等值线(等值线有时用于头发) |
| 域着色器 (Domain Shader) | 细分求值着色器 | 用重心坐标代入面片的求值方程,算出位置、法线、纹理坐标等 |
细分因子分内部和外部两种:内部因子决定三角形或四边形内部细分多少,外部因子决定每条外边分成几段。分开控制可以让相邻面片的边细分一致,避免接缝处出现裂缝。外部因子为 0 或 NaN 时,这个面片会被丢弃。
外壳着色器常根据面片的距离或屏幕大小实时计算细分因子,比如地形渲染。数据放大这一步放在固定功能的细分器里,而不是着色器里,是为了计算效率。
几何着色器 (Geometry Shader)
DirectX 10 引入,位于曲面细分之后,可选。
- 输入:一个图元及其顶点,可以是点、线段、三角形,也可以带相邻顶点。
- 输出:零个或多个顶点,作为点、折线或三角形带。可以修改、添加或删除图元。
- 曲面细分做不到、它能做的:把图元变成另一种图元,比如把三角形变成线框,或把线变成面向相机的四边形来画粗线框。
- 常见用途:一次生成六份变换后的数据来渲染立方体贴图的六个面、生成级联阴影贴图、从点生成大小不一的粒子、沿轮廓挤出毛发鳍片、为阴影算法查找物体边缘。
- DirectX 11 起支持几何着色器实例化,最多可以输出四个流。
为什么实际用得少
- 必须按输入顺序输出结果,多个核心并行时要保存并排序,影响性能;
- 完全可编程,资源和内存需求最难预测;
- 不符合 GPU 的长处,在一些移动设备上甚至是软件实现,RTR4 明确说那里不建议使用。
draw call 之后,GPU 上只有三个地方能"创造"工作量:光栅化、曲面细分、几何着色器。
流输出 (Stream Output)
Shader Model 4.0 引入,OpenGL 里叫 transform feedback。
- 顶点经过顶点着色器(以及可选的曲面细分和几何着色器)后,除了继续送去光栅化,还可以输出成一个有序数组;光栅化甚至可以完全关掉,把管线当作纯粹的流处理器。
- 数据可以再送回管线,实现迭代处理,适合模拟水流和粒子,也可以把蒙皮后的顶点存下来复用。
- 代价:只能输出浮点数,内存开销明显;按图元输出,网格里的顶点共享会丢失,所以通常只把顶点当作点集送进去。
取舍
- 需要按距离控制细节时,曲面细分比在 CPU 上做 LOD 更灵活,但也有开销;RTR4 提到也有研究认为计算着色器可以比外壳着色器更高效(见 计算着色器)。
- 几何着色器能做的事,在性能敏感的场景下通常有更好的替代方案。
GEA 的视角:细分器、几何着色器与流输出(卷 II 11.4.6.1)
外壳/域着色器与细分器
- 可选的细分阶段由两个可编程子阶段(外壳着色器、域着色器)和一个固定功能细分器组成,用低细节输入生成高细节三角网格,称为几何放大 (geometry amplification)。
- 外壳着色器的输入是面片 (patch/hull),例如贝塞尔样条的控制点,输出一个或多个新面片(订正:D3D11 中外壳着色器对每个输入面片输出一个面片——1 到 32 个输出控制点,外加由补丁常量函数算出的细分因子;见 MS Learn "Tessellation Stages");细分器把它们拆成四边形或三角形网格(称为 domain),坐标归一化到 0 到 1;域着色器算出最终顶点位置。
- 细分阶段在顶点着色器之后,所以启用它时顶点着色器处理的其实是面片控制点而不是三角形顶点。
- 主要好处:从很小的顶点缓冲生成大量顶点,可节省大量内存带宽;用途从连续或视角相关的动态 LOD 到细分曲面渲染(见 细分曲线与细分曲面、高效曲面细分)。
几何着色器为什么慢
GEA 把几何着色器称为"较老的技术",列出用途:阴影体挤出、一次渲染立方体贴图六个面、沿轮廓边挤出毛发鳍片、从点生成粒子四边形、闪电的分形细分、布料模拟等。但它天生难以高效实现:
- 图元装配要做两次:一次为几何着色器准备输入,一次处理它(可能放大后)的输出。
- 必须按输入顺序输出:逻辑管线要求保持应用的排序(合成和 alpha 混合需要),GPU 不能重排输出,丢掉了潜在的性能。
- 严格的顺序通常迫使输出先写内存再读回,容易因缓存未命中而停顿。
这与本页「取舍」一节的结论一致。
流输出
- 位于几何着色器和光栅化器之间,把收到的顶点持续写进一个或多个输出缓冲,相当于生成新的顶点缓冲;结果可在当前 draw call 中回送输入装配器,也可留给应用或下一个渲染 pass。
原书问题(11.4.6.1):流输出的数据不能在同一个 draw call 中回送输入装配器;只能在之后的 draw call 中把输出缓冲作为顶点缓冲再次输入,D3D 为此提供了无需 CPU 知道顶点数的
DrawAuto(MS Learn "Stream-Output Stage":"stream-output data can be... recirculated back into the pipeline in a subsequent rendering pass";ID3D11DeviceContext::DrawAuto)。GEA 的毛发例子也只能这样分两次 draw 完成。 - 限制:不能输出带索引的图元,三角形按每个三顶点写出,共享顶点必然重复;邻接信息不会写出。
- GEA 的例子是毛发:以前 CPU 做发丝样条的物理模拟和细分、GPU 只画线段;有了流输出,顶点着色器在样条控制点上做物理,几何着色器细分样条,再经流输出送回管线顶端渲染,全程不需要 CPU(见 毛发渲染)。
- 在新硬件上,这三个阶段的工作都可以由网格着色器和放大着色器承担,见 网格着色器管线。
GPU Gems 的视角:通用自适应网格细化 GAMeR(GPU Gems 3 第 5 章)
来源:Boubekeur & Schlick《Generic Adaptive Mesh Refinement》;摘录 Chapter 5. Generic Adaptive Mesh Refinement — Boubekeur & Schlick。
单遍顶点程序:按需局部细化级别,从 GPU 内存中预细分的重心坐标图案里挑一块,替换每个粗三角,再按位移函数(用户给定或数据驱动)摆顶点;任意拓扑。应用:高阶 Bézier 光滑、程序位移、自由变形、保证轮廓一致的位移贴图、多级地形等。可视为硬件 Tessellation / PN 三角形与 Phong 细分 普及前的「可编程细化」思路。
出处
- realtime_rendering_4ed.pdf 第 3 章 The Graphics Processing Unit
- Game_Engine_Architecture_4th_Edtion_Volume_2_-_Jason_Gregory.pdf
- Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 II 第 11 章 11.4.6.1 节
- Chapter 5. Generic Adaptive Mesh Refinement — Boubekeur & Schlick
- developer.nvidia.com/gpugems/gpugems3/part-i-geometry/chapter-5-generic-adaptive-mesh-refinement