分簇着色
Clustered Shading
分簇着色把整个视锥(而不只是屏幕平面)划分为三维单元——簇 (cluster),按簇建立光源列表。与依赖场景几何深度的分块着色不同,簇的划分与几何无关,可只由视图和光源计算,因此对相机位置的性能波动更小、对深度不连续更稳健,并为不透明、透明、体积物体提供统一的光照方案。它同样可配合前向或延迟着色,并衍生出 z-binning、光源树等更省内存的变体。本页还汇总了各类多光源方案的对比。
簇的划分(20.4 节)
透视下瓦片截面随距离增大,均匀划分深度会得到扁平或细长的体素。Olsson 等人在视空间按指数划分深度(不依赖几何的 /),使簇更接近立方体。实例:《正当防卫 3》用 64×64 像素瓦片 16 个深度切片;Unreal Engine 用同样大小的瓦片,通常 32 个深度切片。
flowchart LR A["视锥按屏幕瓦片 x 深度切片划分为簇"] --> B["光源与簇求交, 生成每簇光源列表"] B --> C["任意表面按自身位置查所在簇"] C --> D["不透明: 前向或延迟"] C --> E["透明与体积: 前向"]
如上图,任何表面——不透明或透明——都用自身位置找到簇并取光源列表。
前向还是延迟
- 《极限竞速:地平线 2》在 GPU 上算簇,再用前向着色,因为这样无需额外工作即可支持 MSAA。单遍前向会有过度绘制,可用粗略前到后排序或只对部分物体做预通道缓解;但 Pettineo 发现即使如此,单独 z-prepass 仍更快。
- 《正当防卫 3》不透明用延迟、透明用同一光源列表做前向,光源列表在 CPU 上生成;Dufresne 也在 CPU 上并行生成,因为这一过程不依赖场景几何。
相对分块着色的优势
每个列表光源更少、视角依赖更小。瓦片的细长视锥会因相机微小移动而内容剧变——一排直线路灯可能恰好对齐填满一个瓦片;即使用深度细分,单个像素变化也可能使瓦片的近远距离剧烈跳动。分簇方式对此不敏感。
优化
- 为光源建 BVH 快速判断与簇的重叠(任一光源移动就需重建);大量光源时排序很贵。
- 延迟着色下按簇内表面的量化法线方向剔除:Olsson 等把法线归入立方体每面 3×3、共 54 个方向组成法线锥,剔除位于簇内所有表面背后的光源。
- 有可见几何深度时(延迟或 z-prepass)可剔除空簇得到稀疏网格,但需先找出被占用的簇,只能在 GPU 上进行;还可用簇内样本构造紧 AABB 剔除更多光源。优化后的系统能处理上百万个光源,随光源数扩展良好,光源很少时也高效。
- 深度不必按指数划分:指数分布使远处簇体积大、光源列表过长。对策是限制分簇的"远平面",更远的光源淡出、用粒子或眩光表示或烘焙,或用更简单的着色器、lightcuts 等 LOD 技术;反之最近处簇细分很密但往往很空,可把分类视锥的"近平面"推到合理距离,更近的光源都归入第一切片。
光源插入方式
- 《DOOM》(2016):结合 Olsson 与 Persson 的方法实现分簇前向。z-prepass 约 0.5 ms;建表可视为裁剪空间体素化:对每个单元的 AABB 测试插入光源、环境光照探针和贴花。加入贴花使分簇前向也获得延迟着色在贴花上的优势,且混合方式任意、可用于透明表面。
- CPU 解析测试:球与簇盒的解析测试便宜,不需要几何;但聚光灯若用包围球会被加入许多无关簇,精确解析测试又贵。Persson 给出快速把球体素化到簇的方法。
- GPU 光栅化插入(Örtegren 与 Persson):两遍——壳 pass 用包住光源的低分辨率网格做保守光栅化,记录每个光源覆盖的最小和最大簇;填充 pass 用计算着色器把光源加入这两者之间每个簇的链表。网格比包围球对聚光灯更紧,几何还能直接遮挡光源可见性。无保守光栅化时,Pettineo 用表面深度梯度选择像素最远(或最近)角计算深度,并截断到光源整体深度范围,防止接近侧视的三角形估计失真。Wronski 给每个网格单元套包围球与锥做相交测试,单元接近立方体时快且好用,细长时较差。
- 《使命召唤:无限战争》(Drobot):把静态聚光灯基于静态几何的阴影贴图烘焙成保守的低分辨率网格(取每块区域的最大深度),作为光源的有效体积,覆盖的簇远少于原始锥体。
z-binning
同样来自 Drobot,存储远少于分簇着色:光源按屏幕深度排序并按深度分配 ID;用等厚(而非指数)的 z 切片分类,每个切片只存与之重叠光源的最小和最大 ID;另外生成二维瓦片光源列表(几何剔除可选)。像素着色时取瓦片列表,再用自身深度取所在切片的 ID 范围,两者交集即为该簇的有效光源。
| 方案 | 存储 | 每像素工作 |
|---|---|---|
| 分簇 | 每个三维簇一个列表 | 直接读取 |
| z-binning | 每个二维瓦片一个列表 + 一个小的定长切片数组 | 实时求交,略多 |
可能误分类部分光源,但 Drobot 发现人造环境中光源在 xy 与 z 上很少同时重叠,在深度不连续的瓦片中也能得到近乎完美的剔除。
其他结构
三维访问结构可分为体积型(网格、八叉树)、物体型(BVH)与混合型。Bezrati 让瓦片光源列表中每个光源附带最小和最大深度,片元可快速拒绝不重叠的光源。O'Donnell 与 Chajdas 的分块光源树在 CPU 上对每个瓦片内光源的 z 范围建一维包围区间层次,比三维层次简单,适合 GPU,并能处理大量光源落入同一瓦片的情形;他们还给出在簇划分与光源树之间选择的混合算法,光源树在单元与光源平均重叠较低时效果最好。
移动端
- 移动 GPU 把 G-buffer 保留在片上本地内存,所以传统逐光源延迟反而可能最高效。
- OpenGL ES 2.0 即可实现 tiled forward;OpenGL ES 3.0 加像素本地存储 (pixel local storage) 扩展可利用 ARM GPU 的分块渲染高效生成和应用光源列表。
- Frostbite 移植到移动端时,因为移动端计算着色器支持较弱,需要在光源分类方案上取舍。framebuffer fetch 与像素本地存储可把整体带宽降低一半以上。
方案对比(典型桌面 GPU,表 20.1,据 Olsson)
| 项目 | 传统前向 | 传统延迟 | 分块/分簇延迟 | 分块/分簇前向 |
|---|---|---|---|---|
| 几何 pass | 1 | 1 | 1 | 1~2 |
| 光照 pass | 0 | 每光源 1 | 1 | 1 |
| 光源剔除粒度 | 每网格 | 每像素 | 每体积 | 每体积 |
| 透明 | 容易 | 不支持 | 需配合前向 | 容易 |
| MSAA | 内建 | 难 | 难 | 内建 |
| 带宽 | 低 | 高 | 中 | 低 |
| 多种着色模型 | 简单 | 难 | 较复杂 | 简单 |
| 小三角形 | 慢 | 快 | 快 | 慢 |
| 寄存器压力 | 可能高 | 低 | 可能低 | 可能高 |
| 阴影贴图复用 | 否 | 是 | 否 | 否 |
| 贴花 | 昂贵 | 便宜 | 便宜 | 昂贵 |
说明:小三角形一栏反映前向着色中 quad 着色四个样本都完整计算的低效;分块/分簇延迟若采用着色器精简(见 分块着色)寄存器压力可以很低;阴影贴图复用在显存充裕的今天已不那么关键。
多光源阴影与世界空间分簇
- 光源很多时阴影是挑战:常见做法是只为最近、最亮的光源和太阳计算阴影,代价是次要光源漏光;Harada 等在 tiled forward 中为每个可见像素向附近光源发射光线;Olsson 等用被占用的网格单元作为几何代理按需生成阴影贴图,并与光线投射混合。
- 也可在世界空间而非屏幕空间生成光源列表,但大场景受内存限制,且远处簇只有像素大小,影响性能;Persson 提供了把静态光源存入三维世界空间网格的基础分簇前向代码。
总结
分块、分簇等光源列表剔除可用于延迟或前向,也可用于贴花;光源体积剔除旨在减少每个片元评估的光源数,而几何与着色解耦用于平衡计算与带宽。和视锥剔除一样,在某些条件下这些技术收益很小:只有太阳时不需要光源剔除;有大量有限范围光源时,建立局部光源列表无论前向还是延迟都值得。"哪个更好"的答案是"看情况":平台、场景、光照模型、设计目标都有影响。
出处
- realtime_rendering_4ed.pdf 第 20 章 Efficient Shading