常见渲染管线整理与总结


图形渲染管线是图形渲染引擎物件绘制的流程逻辑,平时在工作过程中经常会碰到各种跟管线相关的专业术语:前向渲染(Forward Rendering),延迟渲染(Deferred Rendering),Forward+, Forward Clustering等等,那么这些管线究竟对应的是怎样的工作流程,他们之间的优劣以及应用场景又是怎么样的呢?为了解答这个问题,因此新开一篇文章,尝试通过聚少成多的方式,一点点的汇总被业界所接受的各种渲染流程,因为时间关系,文章可能不是一蹴而就的,会不定期更新。

为了统一叙述,下面的渲染管线名称统一使用英文词汇。

Forward Rendering

Forward Rendering就是俗称的前向渲染管线,是最为传统最为简单的渲染管线,也是目前移动端上应用最为广泛的渲染管线。

所谓的前向渲染,就是将物件串成一个队列,一个接一个的进行渲染,通常有两种实现路径,这两种实现路径都对应于两个嵌套的循环,区别在于循环的主题顺序有所不同,按照Nvidia的说法,可以分别用Single-Pass Lighting与Multipass-Lighting对之进行区分,实现的伪代码分别给出如下:

1
2
3
4
5
6
7
8
9
//Single-Pass Lighting
for obj in obj_list
  for light in light_list
    lighting(obj, light);

//Multi-Pass Lighting
for light in light_list
  for obj obj_list
    lighting(obj, light);

所谓的Single-Pass Lighting指的是影响物体的所有的光照一次性计算完毕,而Multi-Pass Lighting则是以Light作为循环主体,物件作为循环副体,分多次完成物体上光照的处理。

前向渲染管线实现思路比较简洁,但是其缺陷也非常的明显:

  1. 从上面的伪代码我们知道,整个场景渲染时光照计算时间复杂度为MxN(M为物件数目,N为动态光源数目),因此如果动态光源数目增加,场景渲染的效率就会直线下降,因此通常会对场景中同时存在的动态光源数目进行限制,在当前的硬件条件下,通常限制为4或者6。当然伪代码里面给的算法都比较粗浅,有极大的优化空间,比如Multi-Pass Lighting中,可以在CPU中挑出受某个光照影响的物件列表,副循环中只需要传入这个物件列表而非所有物件列表降低渲染消耗。
  2. 如果不做特殊处理的话,场景渲染的overdraw比较高。针对这个问题,有比较多的解决方案,比如使用大块物件制作的HZB,以及将动态物件使用depth-only模式提前绘制一遍的Early-Z Cull算法等,都是通过增强剔除力度的方法来降低overdraw,不过这些方案本身也不是没有消耗的,因此通常在使用的时候会需要在收支之间做一个平衡。

另外,从物件渲染顺序来看,前向渲染的渲染逻辑通常按照如下逻辑完成:

1
Opaque -> Translucent -> PostProcess

Deferred Rendering

延迟渲染Deferred Rendering管线的引出是为了解决前向渲染管线中光源数目过多时渲染效率低下的问题,简单来说,延迟渲染通过两个pass完成:

  1. Geometry Pass,完成物体的几何数据处理,将光照计算所需要的数据提取出来写入到GBuffer中
  2. Lighting Pass,通过一个后处理对每盏光源所覆盖的像素进行PS计算,输出结果到FrameBuffer中

关于延迟渲染,此前已经写过一篇比较详细的文章,此处不再赘述,有需要的同学请自行移步。

Tiled Deferred Shading

由于延迟渲染的实现需要较高的带宽,而移动端在带宽能力上严重不足,因此移动端常用的渲染管线是前向渲染;而我们前面介绍过,前向渲染的缺点在于动态光源较多的时候,场景负载较高,且由于较高的overdraw,所以如果不做特殊处理,在移动端上的消耗还是非常高,因此有人尝试寻找一种性能更优的解决方案。

延迟渲染的缺陷在于带宽要求高,那么一种非常直观的思路就是,能否将屏幕划分成多个子区域,分块进行渲染,这样就能将每次渲染的带宽需求降低了。乍一听这个想法是一个馊主意,因为如果物件跨越多个区域的话,可能就需要进行多次渲染,但实际证明,只要框架设计的好,收益远比付出高,再加上硬件支持,这个方案很快就称为移动端硬件的标配(这个方案最开始是Imagination Tech公司提出,简称TBDR,应用在苹果的PowerVR芯片上,后来陆续被其他Android芯片厂商跟进发展)。

Deferred Lighting

Deferred Lighting是与Deferred Shading名字非常相似的一种渲染管线,曾经在CryEngine 3中被使用。Deferred Shading是将所有的着色处理都放到PostProcess阶段完成,而这个步骤需要较多的输入数据,因此对于带宽具有较高要求,而Deferred Lighting则是只将光照部分放到后处理阶段完成(光照计算只需要提供对应的法线数据与高光Power数据就已经足够),相对而言,所需要的输入数据有所减少,因而可以降低带宽的要求,提升渲染性能。

通过对Deferred Shading进行分析,发现G-buffer数据中Diffuse Color跟Specular Color占了很大一部分,而这部分是Lighting所不需要的数据,因此考虑将Lighting单独做延迟处理来降低带宽消耗,其基本实现步骤给出如下:

  1. 跟Deferred Shading一样,经过一个Geometry Pass,这个Pass输出的数据只包含三项:
    1.1 Normal
    1.2 Specular Power
    1.3 Depth
    除了Depth之外,其余两项可以共用一个RenderTarget(如果对发现进行压缩的话,只需要三个通道即可)。
  2. Lighting Pass,对G-Buffer数据进行取用,计算各个输入光源作用后的Lighting Accumulation Buffer
  3. 进行一遍额外的Geometry Pass,使用Lighting Accumulation Buffer作为光照结果与Diffuse Color进行混合得到最终的输出Color,由于已经进行过一遍Geometry Pass,此时有此前的Depth Buffer的作用,这一个Geometry Pass可以节省大量的PS计算(相当于屏幕空间渲染),效率非常高。

跟Deferred Shading一样,上述步骤只针对不透明物件,透明物件的渲染流程在不透明物件渲染完成之后,按照前向渲染完成。

这种方案相对于Deferred Shading而言,其优点在于,具有更小的带宽要求。

Forward+

Forward+渲染管线也叫Tiled Fowrad Rendering,是AMD在EUROGRAPHICS 2012上首次提出的渲染管线,其实现也是通过3个Pass来完成,参考文献[3]中给出了此方案的源码与相应的输出结果,下面介绍中的部分资源就来源于此:
1. Depth Pass
前面介绍过的Tiled Deferred Rendering第一遍Geometry Pass主要用于获取Tiled G-Buffer数据,而这里的Depth Pass只是为了获取屏幕空间的Depth数据,方便下一步进行逐Tile的Light Culling,也是为了避免后面Geometry Shading Pass的Overdraw。

Depth Pass

2. Light Culling Pass
众所周知,前向渲染之所以慢,就是因为每个像素需要考虑每盏灯光的影响,为了减少浪费,可以对每盏灯光影响的像素范围进行计算,使得不受光照影响的像素无需考虑此盏灯光的输入。

这里会将屏幕空间划分成四方的Tile,Tile尺寸过大,这个算法优化幅度就非常有限,而如果Tile尺寸过小就会导致后面Geometry Shading Pass中物件被绘制的次数过多,都会导致效率的下降,一般来说,此Tile可以跟移动端硬件划分的Tile统一起来,最大程度保证渲染效率。

根据Tile Size以及上一步获取到的Depth Buffer,获取每个Tile的Depth Extent,并根据每个Tile的Sub Frustum与Light的作用范围计算出影响到当前Tile的Light List,整个过程可以通过Compute Shader完成,有Scatter跟Gather两种实现方案,Gather方案中,每个Tile可以通过一个Thread Group(每个Thread对应于一个像素)来统计,输出Tile的Depth Extent(即Min Depth与MAX Depth),之后进行Light与Sub Frustum的相交检测时,也可以通过Thread Group来完成,并行进行多盏灯光的Cull处理。

Light Culling

每个Tile Cull之后的Visible Light Indices被写入到Shader Storage Buffer中。

3. Geometry Shading Pass
这个Pass分别针对每个Tile提交相应的待绘制的物件列表(需要经过Cull处理,保证提交的物件均与Tile相交,如果与移动端硬件的Tile架构结合起来的话,这一步就不需要手动处理,硬件会自动完成;此外需要注意,这里的物件不仅仅局限于不透明物件,透明物件也可以一起提交,不过如果需要提交透明物件的话,要保证绝对精确,前面Depth Pass可能需要将透明物件的深度写入到Color Buffer中;但如果不需要保证绝对精确,也可以直接使用不透明物件的深度进行光照剔除,之后将光照应用到所有物件上,其结果可能会有部分光源损失),之后逐Tile通过Forward Rendering模式完成Tile Color的输出。

Final Output

这里给出了Forward+与Deferred Rendering方案的性能对比,可以看到,虽然增加了一个Pass,但实际上其渲染性能要优于Deferred Rendering(这个优化应该是来源于Light Culling)

Forward+ VS Deferred

下面列出两者分阶段的耗时对比,中间的‘<’表示优化,‘<<’表示大幅优化:


Time Consumption Comparison

Clustered Forward Rendering

Forward+是在屏幕空间中划分Tile进行光源过滤来降低消耗的,这个划分是在XY 2D平面进行的,而Clustered Forward Rendering则是在这个基础上更进一步,在Depth方向上也同样进行一次划分,进一步缩小光照的影响范围,降低光照计算的浪费。因为划分的结果是3D的Frustum,每个Frustum被称为一个Cluster,这就是Cluster的由来。

为什么要在Depth方向上进行划分?
在部分场景中,渲染的距离比较广,在局部光源比较密集的情况下,单个Tile中可能存在着较多的光源,这些光源在深度范围上比较分散,如果使用Forward+ Rendering管线,会存在较大的浪费,因此考虑在深度方向上增加一维划分,希望通过这种方式降低光照计算的消耗。

在Depth方向上的划分具体是如何实施的?
在Depth方向上的划分并不是线性的(指数划分),各个Cluster在Depth方向上的划分是对齐的(是否可以考虑非对齐划分?可以仿照Forward+,先通过一个Depth Pass获取各个Tile上的深度范围,之后在此范围上进行划分)

Cluster Split

为什么Depth上不做线性划分?
由于透视相机的作用,需要保留更多近处细节,导致近大远小,如果按照线性划分,性价比可能会有所下降。

整个实现过程如果做成与Forward+类似,那么就同样需要分成三个Pass:

  1. Depth Pass,用于实现Cluster划分与Overdraw过滤
  2. Cluster Filling,通过Compute Shader完成(需要三个Pass),输出Lighting List Info
    Subpass1:统计影响各个Cluster的Light数目

Subpass2:根据上一个subpass的结果,计算当前Cluster对应的Light在Light Indices数组中的起始位置

Subpass3:填充Light Indices数组,数组中存储的是影响各个Cluster的Light在全局Light Info数组中的索引

  1. Shading Pass,逐Cluster渲染Geometry,进行Shading处理。

除了上述实现方案之外,也可以用不同于Forward+的实现方案来完成,我们说过,Cluster的划分是规则的,也就是不需要任何的Depth信息,就可以完成,那么就不必像Forward+一样,使用Depth Pass来一遍强制的Early-Z,如果沿着这种思路,那么实现方案需要2个Pass(其实私下里认为,还是在前面加一个Depth Pass效果更好,可以避免大量的Overdraw):

  1. Cluster Filling Pass,跟上面的Pass2一样,填充各个Cluster的光照信息
  2. Shading Pass,不需要逐Cluster进行,而是全场景一遍Geometry Shading,在PS中根据Pixel Depth恢复期世界坐标,并进而找到对应的Cluster,之后使用Cluster对应的Lighting数据进行光照处理。

具体性能表现又是如何的?
‘Detroit: Become Human’ 方案只给出了在主机上的时间消耗,没有给出与其他方案的对比数据:

Performance

Avalanche Studios的Emil Persson在Siggraph 13上分享的Clustered Forward Rendering方案与Deferred Rendering方案的性能对比(如果有与Forward+的性能对比就好了):

参考文献

1. 游戏引擎中的光照算法 - 知乎fengliancanxue
2. Deferred Shading VS Deferred Lighting
3. Forward+ Renderer
4. Forward+ (EUROGRAPHICS 2012) Slides
5. Clustered Forward Rendering and Anti-Aliasing in ‘Detroit: Become Human’ - GDC 2018
6. Practical Clustered Shading - Emi Persson - Siggraph 13