延迟纹理与可见性缓冲
Deferred Texturing & Visibility Buffer
延迟着色避免了着色的过度绘制,但生成 G-buffer 时仍有过度绘制:先画的物体读了大量纹理,被后画的物体挡住后这些带宽全部浪费。延迟纹理只用一遍几何 pass、存极少的数据(纹理坐标与切线基,或仅三角形与实例 ID),直到着色时才读纹理。由于 GPU 算力增长快于带宽,让瓶颈落在计算而非缓冲访问被视为面向未来的做法。
动机(20.5 节)
- 部分或完整的 z-prepass 能避免被覆盖表面的纹理访问,但许多系统想避免额外几何 pass:顶点数据也耗带宽,几何细密时额外一遍可能比它省下的纹理访问还贵。
- G-buffer 越多越占内存和带宽。瓶颈总在变化,各种效率方案都是针对特定平台和场景开发的;实现难度、内容制作便利性等人为因素也决定了最终方案。
虚拟延迟纹理(《刺客信条:大革命》)
Haar 与 Aaltonen 维护一张 8192×8192 的本地纹理图集,存放从更大集合中选出的可见纹理,每张 128×128。
| 存储内容 | 位数 | 说明 |
|---|---|---|
| (u, v) 图集坐标 | 每分量 16 位 | 13 位寻址 8192 个位置,剩余 3 位(8 级)用于亚纹素精度 |
| 切线基 | 32 位 | 编码为四元数 |
| 合计 | 64 位 | 只需一张 G-buffer |
几何 pass 不读纹理,过度绘制极其便宜;着色时再访问虚拟纹理。mipmap 所需梯度不存储,而是检查邻居像素,用 (u, v) 最接近的邻居现场计算;材质 ID 由所访问的图集瓦片推出(纹理坐标除以 128)。
他们还用四分之一分辨率 + 自定义 MSAA 采样模式:在支持自定义采样位置的平台上把 4×MSAA 设为网格模式,使每个样本正好对应全分辨率像素的中心。(u, v) 和切线基可在表面上无损插值;8×MSAA(相当于每像素 2×)也可行。对植被、树木这类过度绘制严重的场景,显著减少着色器调用和 G-buffer 带宽。
可见性缓冲
Burns 与 Hunt 的可见性缓冲 (visibility buffer) 每像素只存三角形 ID 和实例 ID。几何 pass 极快:不读纹理,只写两个 ID。所有三角形和顶点数据(位置、法线、颜色、材质等)放在全局缓冲中。
flowchart LR A["几何 pass: 写三角形 ID + 实例 ID"] --> B["着色 pass: 按 ID 取三角形顶点数据"] B --> C["像素视线与三角形求交得重心坐标"] C --> D["插值顶点属性, 逐像素执行顶点着色计算, 计算纹理梯度"] D --> E["按任意光源分类方案着色"]
如上图,平时频率更低的计算(如顶点着色器计算)也要逐像素执行,纹理梯度也从头计算而非插值。听起来昂贵,但符合"算力增长快于带宽"的趋势,以计算换取过度绘制造成的带宽损失。场景少于 64k 个网格、每个网格少于 64k 个三角形时,每个 ID 16 位,G-buffer 小到每像素 32 位;更大场景为 48 或 64 位。
变体与扩展:
- Stachowiak 利用 GCN 架构在第一遍中低成本计算并存储重心坐标,代价是额外存储;好处是动画网格不必把变形后的顶点流出到缓冲供着色阶段再取(原方案的缺点,额外耗带宽)。但若需要到相机的距离,也必须在第一遍存下,之后无法重建。
- Aaltonen 指出 MSAA 网格采样法可用于上述各方案,进一步降低平均内存;Schied 与 Dachsbacher 在可见性缓冲上利用 MSAA 降低高质量抗锯齿的内存和着色量。
- Pettineo 指出无绑定纹理 (bindless) 让延迟纹理更容易实现;他的系统 G-buffer 较大(深度、材质 ID、深度梯度)。在 Sponza 上与分簇前向(含或不含 z-prepass)比较:关闭 MSAA 时延迟纹理始终更快,开 MSAA 后变慢;而多数游戏已因分辨率提高转向时间抗锯齿,MSAA 支持在实践中不那么重要。
- Engel 指出 DirectX 12 / Vulkan 让可见性缓冲更有吸引力:计算着色器剔除三角形集合,ExecuteIndirect 生成只含未剔除三角形的等效索引缓冲。配合先进剔除系统,在 San Miguel 场景中可见性缓冲在所有分辨率和抗锯齿设置下都胜过延迟着色,且分辨率越高差距越大。Lauritzen 讨论了 GPU 需要演进以改善延迟环境下材质着色器的访问与处理。
deferred+
Doghramachi 与 Bucci 的延迟纹理系统在前期集成激进剔除:把上一帧深度降采样并重投影,得到当前帧每像素的保守剔除深度,用来测试视锥内所有网格包围体的遮挡(见 遮挡剔除)。注意若有 alpha 裁剪纹理,任何初始 pass(包括 z-prepass)都必须读取它,否则裁剪区域后面的物体会被错误遮挡。产出的 G-buffer 含深度、纹理坐标、切线空间、梯度和材质 ID,数量多于其他延迟纹理方案,但避免了不必要的纹理访问。在《杀出重围:人类分裂》的两个简化场景中比分簇前向更快,且 warp 利用率明显更好,小三角形问题更少,GPU 曲面细分表现更好;还能更高效地处理更多种材质。主要缺点与多数延迟方案相同:透明与抗锯齿。
取舍
| 方案 | 每像素存储 | 优点 | 代价 |
|---|---|---|---|
| 虚拟延迟纹理 | 64 位 | 过度绘制极便宜,配合虚拟纹理 | 需管理纹理图集;梯度靠邻居推算 |
| 可见性缓冲 | 32~64 位 | 几何 pass 最快,带宽最低 | 着色阶段逐像素重做顶点工作与求交;动画网格需额外处理 |
| deferred+ | 较多 | 剔除激进、材质灵活 | G-buffer 较大 |
GEA 的视角:可见性缓冲是"扩展的深度缓冲"(卷 II 11.5.2.1、11.6.8.6)
- GEA 在讨论过度绘制时把可见性缓冲称为一种特殊的"扩展深度缓冲",作为 z 预通道之外的另一种解决办法(卷 II 11.5.2.1)。
- 在 Nanite 一节中,它存每个像素的深度、物体 ID 和三角形 ID,把可见性问题与光照问题解耦:像深度预通道,但同时又是"廉价的 G-buffer",只保存足以在后续光照 pass 中查到材质的信息(卷 II 11.6.8.6)。这与本页「可见性缓冲」的描述一致,具体见 Nanite 虚拟化几何体 中的 GEA 部分。GEA 第 12 章还会进一步讨论 G-buffer 与可见性缓冲。
GEA 的视角:可见性缓冲(卷 II 12.5.12.2)
GEA 把三角形可见性缓冲看作 G-buffer 的一种变体:帧缓冲里的一个图像平面存"原始场景中哪个三角形贡献了这个像素",屏幕空间光照时据此查出每个三角形的完整材质描述。它通常更省内存,在高分辨率显示模式下尤其明显。GEA 推荐 Wolfgang Engel 关于可见性缓冲的论文(卷 II 12.5.12.2)。这与本页的描述一致,本页还讨论了 RTR4 中的延迟纹理、带宽与计算的权衡。
出处
- realtime_rendering_4ed.pdf 第 20 章 Efficient Shading
- Game_Engine_Architecture_4th_Edtion_Volume_2_-_Jason_Gregory.pdf
- Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 II 第 11 章 11.5.2.1、11.6.8.6 节
- Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 II 第 12 章 12.5.12.2 节