1. Unity Render Pipelines
1.1 Unity 渲染管线的流程
渲染管线是引擎的渲染步骤,也就是游戏引擎把游戏对象转化为图像显示到屏幕上的步骤。
一个渲染管线大致可以分为下列几个步骤:
- 剔除:包括视锥剔除和遮挡剔除(这里的遮挡剔除是指如果物体被另一个物体遮挡就直接剔除,但是通常是固定场景的遮挡进行这种剔除,比如进入房间之后遮挡其他房间,可参考OcclusionCulling。
- 渲染:绘制物体和它们的光照到相应的像素缓冲(pixel buffer)上。
- 后处理:修改像素缓冲为最后的输出结果。这一步可以做出一些效果,比如 bloom、景深等。
有时候会把它和图形管线混淆,实际上 Graphics Pipeline 是指下面这个图(来自Vulkan):
它是 GPU 中绘制图像的流程,首先变换顶点,然后光栅化,再进行片元着色。它实际上仅仅对应渲染管线中用到的一个功能,也就是 Rendering 这个步骤中的着色。如果你学习图形学知识,主要深入的是图形管线的编写。而 Unity 中我们不仅仅需要关注图形管线,还需要了解整个渲染管线。
1.2 URP / HDRP / Built-in
官方有提供三种渲染管线:
- URP:属于可编程渲染管线(SRP,Scriptable Render Pipelines),适配不同平台的可扩展的图形。
- HDRP:SRP,前沿的高保真的显示效果,通常用于高端设备。
- Built-in:通用渲染管线,定制化程度相比 SRP 比较低。
SRP 允许你通过编写 C# 代码来定制上面提到的剔除、渲染和后处理三个步骤,(Built-In 也可以定制修改不过你需要先获得源代码)。除了 URP 和 HDRP,你还可以编写自己的 SRP。
Built-in 是旧版的渲染管线,现在完全不需要去考虑这个,只有一些过去的项目开发者会选择这个。如果现在去学习一些教程,一些2022年以前写的书籍通常会使用这个,而新版的教程视频通常都是采用 URP。
1.3 Render Target 和 MRT
渲染目标就是渲染结果输出的目标,可以理解为是一块画布,能够往上面写入任何东西。
渲染目标可以是多个,例如在前向渲染中,最基本的有3个,颜色(RGBA 4通道)、深度、模板(Stencil),当然深度和模板通常都不是4通道的,所以有可能会合并在一张画布上。
在正向渲染中通常只需要 2 个RT,颜色加 Depth,其中深度是可以配置精度的,比如24位或者32位,以及是否混合 Stencil。
GPU是可以同时输出多张 Render Target 的,这个特性在图形 API 里叫做 MRT(Multiple Render Targets,多渲染目标)。
在延迟渲染中,GBuffer同时输出了多个 RT。
在一次几何遍历中输出了不同的信息用于后续的渲染,例如:
- RT0: 漫反射颜色 (Diffuse Color)
- RT1: 法线 (Normal) 等
- RT2: 镜面反射参数 (Specular, Roughness)
- RT4: 自定义数据,比如材质ID等
这就是延迟渲染 (Deferred Rendering) 使用 MRT 的原因,如果没有这个特性,需要写几个 Pass,每次 Pass 遍历只是为了获取一张 RT,那样效率就低了。
2. Render Path
中文可以叫做渲染路径。
渲染路径是指绘制和照亮相机能看到的游戏对象的系列操作。不同的渲染路径具有不同的功能和性能特点。如果说渲染管线是做饭的一套流程,那么渲染路径可以理解为是放调味品的顺序和时机。比如计算光照的时机到底是在全局深度测试之前,还是全局深度测试之后等。
Unity支持两种方式:前向渲染(Forward)和延迟渲染(Deferred)以及它们的改进形式。
这里推荐阅读:毛星云关于延迟渲染的总结1。
因为 URP 可以同时运行在各种平台上,包括手机,所以通常都是采用 URP 这个管线来开发 Unity 游戏。所以这里不对 HDRP 进行介绍,HDRP 有更多高级功能,支持光线追踪,如果想了解可以去查阅文档。
2.1 Forward Path
正向渲染是一种传统的图形渲染管线方式。在这种模式下,每个物体都会依次经过顶点着色、光栅化、片元着色等阶段,并且在片元着色器(Fragment Shader)中会直接计算光照和材质效果。
正向渲染在处理物体的时候,会进行深度测试与着色,先渲染的物体有可能被后渲染的物体覆盖,因为后渲染的物体距离屏幕更近。
这样在最坏的情况下,所有物体都会经过着色,此时如果有 n 个物体,m 个光源,那么复杂度就是 $O(n * m)$。
2.2 PrePass / Early-Z
注意,上面提到的是经典的正向渲染,没有考虑 prePass 来先遍历深度等方式。实际上现在的 URP 中的 Forward Path 是会先用 prePass 获取深度,这种情况下复杂度就不能说是 $O(n * m)$,而是 $O(n + m)$。这里的光源计算次数应该是分辨率乘以光照数,但是分辨率通常会认为是个常数。
前向渲染通过 prePass 方式来处理还是和延迟渲染有区别的,区别在于带宽和遍历次数,按照 prePass 方式有 2 次遍历,按照延迟渲染的方式没有 2 次遍历,不过需要多输出几张 RT。
还有一种叫做 Early-Z,是在对片元着色(光照计算)之前先进行深度测试,而不是计算之后再判断。现在的 GPU 普遍提供这个技术了。但是这个技术没有 prePass 彻底,它只是避免了近处物体计算后又计算远处物体,但是没有避免先计算远处物体导致的多余计算。
2.3 Deferred Path
延迟渲染,对比经典的前向渲染,对于多光源的性能效果非常好。
延迟渲染首先遍历整个场景,进行一次绘制,但是它的输出不是光照后的结果,而是一些基本的信息,比如反照率、法线、高光度等等,输出几张 RT。在全部遍历完毕以后,再对所有像素进行一次遍历,根据 RT 对每个像素进行光照计算。
我们统称这些 RT 为 GBuffer,实际上就是一个个像素的信息。
这么来看,延迟渲染就是单纯把光照计算这个部分拿出来了,法线采样、贴图采用次数什么的都没有减少,只是都存起来。
而前向渲染如果单独把深度测试提取出来,那么整个其实最后计算的光照数量应该和延迟是一样的。但是这样就少用一些图去存储GBuffer,但同时也会多一些遍历次数。
因为延迟渲染GBuffer生成之后只要遍历pixel,但是如果是深度测试单独提取出来,依然还是需要遍历一次所有物体。
从下图可以看到,延迟渲染首先渲染了 GBuffer,之后在 Deferred Pass 中进行光照计算,光照计算也是通过多次着色来实现的,每多一个光照就需要多进行一些绘制。
可以看到,图中处理的方式是,对于每个光源,遍历一次受影响的像素,来进行针对该光源的颜色绘制,这样一个光源一个光源地添加颜色。
延迟渲染还是要考虑多材质这个问题,例如,上图的地面和方块就不是一个材质。
解决方法有 2 种,URP 中默认的方式是在 GBuffer 阶段通过 Stencil 标记材质。
然后在 Deferred Pass 阶段根据 Stencil 来走不同的 Pass 进行绘制。当然你会看到 Stencil Ref 有点差异是因为还有一个上色的过程,这里有一个 Bit 用来标记是否需要上色(是否被某个光照到)。
另一个方式就是不需要这么多的 Pass,通过在 GBuffer 中使用某些比特位来标记材质,这样在同一个 Pass 中走不同的分支就可以代表不同材质了。
2.4 Shadow Map
这么多光源,会不会影响性能?除了前面的地 Deferred Pass,阴影部分也是影响性能的地方。
前面讨论的延迟渲染中,光源是没有阴影的,所以我们只感觉添加一个光就是添加了 一点 Deferred Pass。
但是如果是下图这种聚光灯效果:
可以直接穿透物体照射后面的物体。
原因是我们没有开启光源的阴影,需要在光源的设置中开启它。
开启之后,光源就会通过 Shadow Map 技术,来生产光源的 Shadow Map,1个点光源通常生成 6 张图,涵盖 6 个方向。
这时就产生了遮挡关系。
2.5 半透明物体
延迟渲染中如何渲染半透明物体?答案是在延迟渲染完不透明物体之后,再用前向渲染的方式渲染半透明物体。
2.6 在延迟渲染中使用前向渲染
如何在延迟渲染中增加一个前向渲染?
可以通过增加 Render Objects 这样一个 Renderer Features,在某个事件后插入。比如这里选择在渲染透明物体之前渲染。
当延迟渲染完毕后,还没有渲染物体。
插入了一个渲染物体的 Pass。
不用担心光照遮挡问题,因为光照可以设置影响的 Layer,设置正确的话遮挡关系也是会正确的。
关于插入的位置,基本上 Opaques 之后都是可以的,因为参考Render event,Deferred Pass 发生在 BeforeRenderingOpaques 之前。
2.7 Forward+ Path
前向+渲染路径与标准的前向渲染路径类似,但没有限制每个游戏对象可以受多少个光源影响。不过,每台摄像机可见光源的数量仍然有限制。
使用前向+渲染路径减少了Unity为每个游戏对象计算的光源数量。Unity将屏幕划分为多个瓦片,然后确定哪些光源会影响这些瓦片。当Unity计算某个游戏对象的光照时,它只会使用影响该游戏对象所在瓦片的光源。
每个网格方块是一个瓦片,每个值表示影响该瓦片的灯光数量。
Unity 在选择 Forward+ 渲染路径时会忽略以下设置:
- URP 资源中的附加灯光。
- URP 资源中的主灯光。
- URP 资源中附加灯光 > 每个对象限制。
毛星云, 【《Real-Time Rendering 3rd》 提炼总结】(七) 第七章续 · 延迟渲染(Deferred Rendering)的前生今世, https://zhuanlan.zhihu.com/p/28489928 ↩︎