合并阶段与帧缓冲
Merging & Framebuffer
合并阶段把像素着色算出的片元颜色和缓冲区里已有的颜色结合起来,同时负责深度测试、模板测试和混合。它通常不可编程但高度可配置,也叫 ROP(光栅操作或渲染输出单元)。帧缓冲是系统中所有缓冲区的总称。
合并阶段负责什么
- 可见性:用 深度缓冲 决定哪个图元可见。
- 混合 (Blending):把新颜色和已有颜色混合,用来实现透明、颜色累积等效果。混合通常通过 API 配置,而不是编程;有些 API 支持光栅顺序视图 (raster order views),也叫像素着色器排序,可以实现可编程混合。
Early-Z(提前深度测试)
如果一个片元跑完像素着色器后才发现被挡住,之前的计算就全浪费了。所以很多 GPU 会在像素着色器之前先做深度测试(以及模板、裁剪矩形测试),被挡住的片元直接剔除,这叫 early-z。
- 如果像素着色器会修改深度或者discard,early-z 通常就无法使用而被关闭,管线效率会变差。
- DirectX 11 和 OpenGL 4.2 允许强制开启 early-z,但有不少限制。
- 用好 early-z 对性能影响很大(RTR4 第 18.4.5 节)。
混合的能力和限制
- 最常见的是颜色和 alpha 值的乘、加、减组合,也支持最小值、最大值和位运算。
- DirectX 10 的双源颜色混合 (dual source-color blending):像素着色器输出两个颜色和帧缓冲混合,不能和多渲染目标一起用。
- 多渲染目标也支持混合,DirectX 10.1 起每个缓冲区可以用不同的混合方式。
- 可编程混合可以用 ROV 实现,但有性能代价(见 像素着色器)。
- 输出不变性 (output invariance):不管像素着色器结果的生成顺序如何,API 都要求按输入顺序(逐物体、逐三角形)送到合并阶段。
DirectX 把这个阶段叫 output merger,OpenGL 叫 per-sample operations。
各种缓冲区
| 缓冲区 | 存什么 | 用途 |
|---|---|---|
| 颜色缓冲 (Color Buffer) | 每个像素的 RGB 颜色 | 最终显示的内容 |
| Alpha 通道 | 每个像素的不透明度 | 透明效果(RTR4 第 5.5 节) |
| 深度缓冲 (Z-Buffer) | 每个像素的深度 | 可见性判断 |
| 模板缓冲 (Stencil Buffer) | 通常每像素 8 位 | 记录图元绘制的位置,用来控制之后哪些地方可以写入颜色和深度 |
模板缓冲例子:先在模板缓冲里画一个实心圆,之后只允许在圆内渲染。游戏里常用来做传送门、镜子、描边、遮罩等特效。
双缓冲 (Double Buffering)
为了不让玩家看到画面一点点被画出来,渲染在屏幕外的后缓冲里进行,画完后与正在显示的前缓冲交换。交换通常在垂直回扫 (vertical retrace) 期间进行,这时交换是安全的,也就是常说的垂直同步,可以避免画面撕裂。更多缓冲方式见 RTR4 第 5.4.2、23.6、23.7 节。
GEA 的视角:帧缓冲的图像平面与输出合并器(卷 II 11.3.4.3、11.3.11、11.4.6.1)
帧缓冲由多个图像平面组成
GEA 把帧缓冲整体看作多个与颜色缓冲同尺寸的图像平面 (image planes):一个存颜色,其余存各种逐像素数据(卷 II 11.3.4.3)。
- 深度缓冲:存每个像素的深度。因为存的是投影后齐次点的 z 或 w 分量,也叫 z-buffer 或 w-buffer。通常与模板缓冲合成一个深度/模板平面:最早的 GPU 用每像素 32 位,24 位定点深度加 8 位模板;如今也支持 16 位和 32 位浮点深度。
原书问题(11.3.4.3):主流 API 中没有 16 位浮点深度格式。16 位深度是归一化定点
DXGI_FORMAT_D16_UNORM/VK_FORMAT_D16_UNORM,浮点深度只有 32 位的D32_FLOAT(及带模板的D32_FLOAT_S8X24_UINT)(MS Learn "DXGI_FORMAT enumeration";Vulkan 规范 "Depth/stencil formats")。另外"最早的 GPU 用 D24S8"也偏简化:早期 3D 加速卡(如 3dfx Voodoo)只有 16 位深度缓冲,24 位深度加 8 位模板是后来(RIVA TNT 一代)才普及的。 - 模板缓冲:每像素通常 8 位整数,可以统计像素被绘制的次数,或给像素编码 ID。例如把敌方 NPC 标为 1、其他几何标为 0,渲染后查模板值就知道某像素是不是敌人。
- 传送门例子:Valve 的《Portal》中,主场景模板值为 0,橙色门为 1,蓝色门为 2;先画主场景,再限制只在模板为 1 的像素上从橙色门视角重画场景,然后对蓝色门做同样的事,共三遍。这与本页「模板缓冲例子」的思路相同,GEA 给了完整的多 pass 流程。
- G-buffer:除颜色、深度、模板外再加平面(如屏幕空间速度、法线)时,整个帧缓冲常被称为 G-buffer,用于屏幕空间光照(见 延迟着色)。
深度测试与深度写入可以分开开关
- 深度测试失败的片元被丢弃;通过测试的片元可以再做深度写入。两者可以独立开关:关闭测试,所有片元都会写颜色;关闭写入,片元不影响之后的深度测试(卷 II 11.3.11)。
- 用法和实例见 深度缓冲 中的 GEA 部分。
- 裁剪矩形测试 (scissor test):只允许落在一个或多个屏幕矩形内的片元写入,例如把三维场景画进二维 UI 中的一个矩形窗口(卷 II 11.3.11.1)。
输出合并器的别名与职责
- 也叫光栅输出单元/光栅操作管线 (ROP),AMD 称为渲染后端 (RB)。主要工作是 alpha 混合,同时负责深度和模板测试、深度写入,以及把 MSAA 的多个采样合并成一个输出像素。
- Direct3D 9 及更早的特性级别中它还做 alpha 测试;Direct3D 10 起不再支持 alpha 测试,需要在像素着色器中实现类似功能(卷 II 11.4.6.1)。这与 深度缓冲 中"早期 API 用 alpha 测试、现在用 discard"的说法一致。
出处
- realtime_rendering_4ed.pdf 第 2 章 The Graphics Rendering Pipeline
- 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.3.4.3、11.3.11、11.4.6.1 节