Cesium中实现G-Buffer渲染管线
延迟渲染
要理解什么是 G-Buffer (Geometry Buffer) 我们首先得了解什么是延迟渲染。
了解渲染流程的同学应该知道,在传统的前向渲染中,每个三角面片在顶点着色器经过 MVP 变换转换到屏幕空间,然后经过片元着色器的计算获得三角面片占用的像素应该是什么颜色,最后通过深度测试得到最后保留哪些三角片面的像素。
对于前向渲染而言,无论三角面片最终是否会被其他面片遮挡,他都要进行一次完整的着色计算。而在我们的渲染流程当中,最昂贵的计算通常就是复杂的着色计算。
特别是对于渲染海量的光源而言,每个三角面片无论最终是否会在屏幕中显示,都要进行所有光源的着色计算,这将导致渲染效率的下降。

延迟渲染就是为了解决这一问题而产生的一种渲染技术,其核心机制是将场景几何信息存储在几何缓冲区(G-Buffer)后进行集中光照计算。

该技术采用两阶段处理流程:
- 几何处理阶段:走一遍前向渲染的流程,只不过在着色阶段,不是进行颜色的计算,而是将位置、法线、材质等属性渲染至几何缓冲区(G-Buffer)。
- 光照处理阶段:基于屏幕空间统一处理所有光源的光照效果。

相比于前向渲染而言,延迟渲染能够有效规避传统前向渲染中多光源场景的重复计算问题。实现成百上千动态光源的实时渲染。
G-Buffer
G-Buffer 是延迟渲染中的核心数据结构,它是一个存储场景几何和材质信息的中间缓冲区集合。在延迟渲染的第一阶段(几何处理阶段),所有可见表面的属性被写入G-Buffer,然后在第二阶段(光照处理阶段)用于光照计算。

G-Buffer的基本构成
一个典型的G-Buffer包含多个渲染目标(MRT),每个存储不同类型的几何/材质信息:
深度缓冲区(Depth Buffer)
存储每个像素的深度值(Z值)
法线缓冲区(Normal Buffer)
存储表面法线向量
通常编码为RGB颜色(xyz分量映射到RGB)
可以是视图空间或世界空间法线
漫反射颜色缓冲区(Albedo/Diffuse Buffer)
存储表面的基础颜色/反射率
不包含光照信息(无阴影、无高光)
通常是sRGB颜色空间
镜面反射缓冲区(Specular Buffer)
存储镜面反射系数和光泽度
通常包含:
R:镜面反射强度
G:光泽度/粗糙度
B:金属度(PBR中)
位置缓冲区(Position Buffer)(可选)
直接存储视图空间位置
替代从深度重建位置的计算
其他可选缓冲区
材质ID:用于不同材质类型
发射光(Emissive):自发光表面
环境光遮蔽(AO)
运动向量:用于运动模糊/TAA
Cesium中的G-Buffer
由于Cesium中的3DTiles、地形等模型通常缺乏光泽度、金属度等信息,因此该实现仅实现深度缓冲区、法线缓冲区、漫反射颜色缓冲区、位置缓冲区以及反射率缓冲区。
Cesium中的实现细节
想要在 Cesium 中实现 G-Buffer 我们得手动执行一次 Cesium 的渲染流程,Cesium 的渲染流程相当复杂,这里不展开讲,具体方法参考 Scene 源码当中的 render 方法。
原始的 render 方法我们当然不能原封不动的执行,为了获取 G-Buffer,我们需要对 render 方法抽出来进行爆改。改动的地方主要有以下几个方面:
- 去除所有与 G-Buffer 所需内容无关的渲染(如后处理、oit、classification相关的计算等等)
- 创建 G-Buffer 缓冲区
- 对每个需要使用 G-Buffer 的 DrawCommand 创建对应的G-Buffer派生命令(参考对数深度的实现方式),这个派生命令的作用就是将深度、法线、位置等信息存储到对应缓冲区。
- 最终渲染所有经过 Cesium 计算可见的 G-Buffer 派生命令。
一些额外的细节
经常搞 Cesium 渲染的同学可能会遇到一个问题,就是在渲染的过程中 Cesium 的透明材质我们是无法获取深度的,这就导致一些需要基于深度值的计算无法在透明材质上正常的显示。
在我这个实现思路中,这个问题可以顺带解决,解决思路就是将 Pass.TRANSLUCENT 的 DrawCommand 当做正常的 DrawCommand 同样生成 G-Buffer 派生命令并执行,来获取深度、法线等信息。
当然这里也只是给大家提供一种思路,可能会有更好的方法?