unity自带的着色器源码剖析之——前向渲染和延迟渲染

一、前向渲染概述

传统的渲染方式下所做的光照计算流程称为前向渲染。这是一种十分直接的方式,在顶点着色器中对所有待渲染对象的顶点进行一系列的变换,这些变换通常是将顶点的法线和位置变换到裁剪空间。如果采用逐片元光照的方式,在渲染每一帧时,每个片元都要执行一次片元着色器的代码,这时需要将所有的光照信息都传递到片元着色器中。虽然在大部分情况下的光源都趋向于小型化,而且其照亮的区域也不大,但即便是光源离这个像素所对应的世界空间中的位置很远,但当片元着色器中计算光照时,还是会把所有的光源都考虑进去。例如场景中有n个光源,那么在每一个片元执行着色器代码时,都必须把这n个光源都传递进着色器去执行光照计算。

前向渲染的方式比较容易理解,但却有一些性能上的缺陷:

1. 首先不能很好地支持场景中存在大量的光源,试考虑场景中有几十上百个光源时,片元着色器要做的运算量非常大。

2. 其次,如果待渲染的场景十分复杂,里面包含了大量的待渲染对象,将不可避免地产生“在屏幕中同一个像素中,其实会被多个物体所对应覆盖”的情况,即有很大的深度复杂度,我们会浪费很多GPU资源。例如,如果某像素的深度复杂度值为n,表示在每一帧中,这个像素上将有n个待渲染对象,进行了n次光照计算,而后绘制(即写入该像素的颜色值)。但其实有n-1次的光照计算及绘制是无用的,因为只有最靠近摄像机的那个待渲染对象的计算及绘制才会被最终显示到屏幕上。

二、延迟渲染概述

延迟渲染的出现就是为了解决上述问题,关键思想是把大部分光照计算等计算量很大的操作,延迟或者推迟到尽可能后的阶段中进行。与在前向渲染中总是将所有的待渲染对象从顶点缓冲区一路线性地渲染到最后的颜色缓冲区的流程不同,延迟渲染将这个过程拆分成了两个处理通路(pass)。

1. 第一个处理通路称为几何处理通路。在此处理通路中,首先将场景渲染一次,获取到待渲染对象的各种几何信息,如位置向量、颜色向量、法线向量、深度值等,并且会把这些几何信息存储到名为几何缓冲区。即G-buffer缓冲区(geometry buffer)中,这些缓冲区将会在之后用做更复杂的光照计算。由于有深度测试,所以最终写入G-buffer中的各个数据都是离摄像机最近的片元的几何属性。这意味着所有不会出现最终屏幕上的片元都在深度测试中被丢弃,最后会被留在G-buffer中的片元都是必定要进行光照计算的。

下图是G-buffer在某一帧的画面:

unity自带的着色器源码剖析之——前向渲染和延迟渲染

G-buffer在本质上和一个普通纹理类似,因此可以使用对纹理进行采样的着色器内置函数来获得G-buffer的每一个纹素。

第二个处理通路称为光照处理通路(lighting pass)。在此处理通路中,将会遍历所有G-buffer中的每一纹素。这些纹素的内容要传给执行光照计算的片元着色器所用到的位置、颜色、法线等参数,这些参数在前向渲染模式下,要么通过片元着色器的uniform变量从CPU传递进来,要么通过顶点着色器经过光栅化处理阶段插值生成之后传递进来。最关键的是,每一个可渲染物体都要执行一次片元着色器中的光照计算,而在延迟着色器(deferred shader)中只需要执行一次光照计算即可,因为G-buffer中的每一个纹素中对应的光照信息都是最终可视的。而延迟渲染器可以在一个和屏幕大小相等的矩形图元上执行之,并最终显示在屏幕上。

延迟渲染的流程图如下图所示:

unity自带的着色器源码剖析之——前向渲染和延迟渲染

每一个待绘制的对象都不在他们自身的片元着色器中执行光照计算。光照计算集中在一个片元着色器中,该片元着色器由一个大小覆盖住屏幕的矩形图元执行,这个矩形图元的渲染效果就是最终要渲染的画面。

延迟渲染可以不需要消耗大量的性能去执行光照计算,但它本身并不能支持非常大量的光源,因为即使把原来分散到每一个待渲染物体的片元着色器中执行的光照计算集中到最后的片元着色器中去计算,依然是场景中有n个光源,就要在最后的片元着色器内用n个光源去对每一个片元进行光照计算。因此,要真正能支持大量光源去进行延迟渲染,需要对此技术进行优化。

三、Unity3D中的各种渲染路径

Unity支持不同的渲染路径,渲染路径就是指在着色器代码中应用光照计算的方式。不同的渲染途径具有不同的性能特征,对光照和阴影计算也有不同的处理方式。

要对整个项目设置某一种渲染途径,可选择Editor|Project Settings|Graphics命令,在弹出的GraphicsSettings面板中,单击Tier Settings面板中的Rendering Path属性项就是项目整体的渲染途径设置项,如下图:

unity自带的着色器源码剖析之——前向渲染和延迟渲染

Unity3D允许在一个工程中使用多个渲染路径,如摄像机A使用前向渲染途径进行渲染,摄像机B使用延迟渲染途径进行渲染。这时,可在每个摄像机对象的Camera组件中的Rendering Path属性选项中设置摄像机专门使用的渲染途径,如下图:

unity自带的着色器源码剖析之——前向渲染和延迟渲染

如果选择使用Use Graphics Settings类型,则此摄像机将会使用整个工程的设置,否则就使用专门为它指定的设置。但如果当前显卡不支持所指定的渲染路径,Unity引擎会自动往下降级渲染路径的类型,直至显卡支持为止。

Unity有4种渲染路径,分别为延迟渲染(deferred rendering)、前向渲染(forward rendering)、旧式延迟渲染(legacy deferred rendering)、旧式顶点照明(legacy vertex lit)。

延迟渲染途径具有光照和阴影效果的保真度最高特点,如果场景中有大量的实时光源,且硬件性能足以支持,则使用这种路径最为合适。

前向渲染路径是引擎默认使用的渲染路径,此渲染路径支持所有典型的图形功能。如法线贴图、逐像素光照、阴影等。但是在默认设置下,即使指定使用逐片元光照模式进行光照计算,前向渲染路径中也仅仅是少数光源会使用逐片元光照模式,其余光源则是逐顶点计算的。

旧式延迟渲染路径类似于延迟渲染路径,只是使用不同的技术实现,旧式延迟渲染路径不支持基于物理渲染(physically based rendering)的标准着色器(standard shader)。

旧式顶点照明途径是一种光照效果保真度最低的渲染路径,此路径不支持实时阴影,可以认为它是前向渲染路径的一个子集。

3.1 Unity的前向渲染路径细节

根据光源对待绘制对象的作用方式,前向渲染路径技术在一个或者多个渲染通路中渲染每一个待绘制对象,光源本身也会依据它们自身的设置和强度在前向渲染路径中得到不同的处理。

1. 实现细节

在前向渲染路径中,若干个最亮的且能照亮每个物体的光源执行逐片元光照计算。剩下的光源中,最多4个光源执行逐顶点光照计算,剩下的光源则以球面调谐(spherical harmonics,简称球谐函数)的方式计算光照。球谐函数的方式要快得多,但是是一种近似的模拟。一个光源是否以逐片元光照的模式去计算光照效果,主要取决于以下几个条件:

(1)光源游戏对象的Light组件中,如果Render Mode选项设置为Not Important,就必定不使用逐片元光照模式计算光照效果。

(2)亮度值最高的,且不满足条件1的有向平行光源使用逐片元光照模式计算光照效果。

(3)如果光源游戏对象的Light组件的Render Mode选项设置为Important,就使用逐片元光照模式计算光照效果。

经过上述条件筛选之后,如果执行逐片元光照模式的光源个数不大于Quality Setting面板中的指明逐片元光源个数上限的Pixel Light Count选项值,则可以有更多的光源以逐片元光照模式计算光照效果。

着色器的光照计算在其渲染通路中进行。前向渲染的渲染通路有两种,分别是基本通路和附加通路。基本通路中,对默认的有向平行光源进行逐片元的光照计算。并且当默认的有向平行光源的Light组件中的Shadow Type属性为Soft Shadows或者Hard Shadows时,将会启动阴影效果。所有其他的基于顶点或者基于球谐光照的光源也在基本通路中进行光照计算,物体的自发光和环境光计算也在基本通路中进行。

其他逐片元光照计算的光源则在附加通路中进行光照计算,且每个这样的光源执行一次。

假设光源A——H的颜色和亮度都一样,其Light组件的Render Mode属性值也都为Auto。即他们采用何种模式进行光照计算将由引擎决定。如下图,从圆球的角度来看,离圆球越近的光源越亮,所以最亮的4个光源A-D将使用逐片元光照,光源D—G使用逐顶点光照,光源G—H使用球谐光照:

unity自带的着色器源码剖析之——前向渲染和延迟渲染

然后从下图我们可以看到,光源D参与了逐片元光照和逐顶点光照两种模式的计算,光源G参与了逐顶点光照和球谐光照两种模式的计算,这样处理是为了光照效果能平滑地变化而不至于变得很突兀。

unity自带的着色器源码剖析之——前向渲染和延迟渲染

2. 声明一个渲染通路为前向渲染路径中基本通路的设置 

我们使用LightMode标签的值用来指定着色器的渲染通路类型,引擎会根据该标签的值决定光照计算方式。在基本通路中,如果启用了OnlyDirectional标识符,则只对默认的有向平行光源、环境光、光照探针和光照贴图进行计算,原本在基本通路中计算的顶点光照和球谐光照不再计算。另外,除了设置LightMode为ForwardBase之外,还需要使用编译指示符multi_compile_fwdbase指令,这样才会激活基本通路中进行光照计算所需要的一些宏,如光照衰减值等。

3. 声明一个渲染通路为前向渲染路径中附加通路的设置

把LightMode的类型指定为ForwardAdd已声明本渲染通路是前向渲染路径中的基本通路,同时也要启用multi_compile_fwdadd编译指示符相关的宏、变量和函数。默认地其他通路是没有阴影效果的,即使光源的Light组件中shadow Type属性设置为Soft Shadows或者Hard Shadows也是没有效果的。要使用阴影效果,要改为multi_compile_fwdadd_fullshadows,才能令点光源和聚光灯光源开启阴影效果。另外,还需要设置混合模式,一般采用Blend One One。这是因为其他通路是每一个光源执行一次,而大多数情况下是希望每个光源产生的照明效果是叠加再一次的,如果不采用Blend One One,则执行本次additional pass的光照结果会覆盖上一次的执行结果,这样看起来好像只受到最后一次执行additional pass的光源影响。

3.2 Unity3D的延迟渲染路径细节

Unity支持两种延迟渲染路径,即5.0版本之前的旧式延迟渲染路径和之后版本的延迟渲染路径。两者差异不大,主要区别是旧式延迟渲染路径不支持基于物理渲染的标准着色器。

使用延迟渲染时,能够对待渲染物体进行照明的光源格式在理论上是无限的。所有的光源都执行逐片元光照,因此所有光源都能使用cookie纹理和产生阴影,在光照计算中可以使用诸如法线贴图等逐片元光照技术,当然延迟渲染缺点也比较明显:

(1)不支持真正的反走样(anti-aliasing)功能。

(2)无法处理半透明物体的渲染。

(3)对显卡的性能有较高的要求。显卡必须支持多渲染目标功能,必须使用shader model 3.0及以上版本,支持深度纹理和双面模板缓冲。

(4)如果执行延迟渲染的摄像机是正交的,即摄像机Camera组件的Projection属性设置为Orthographic,则摄像机执行的渲染路径将会回退(fall back)到使用前向渲染路径。

延迟渲染中实时光源的渲染开销与该光源所能照射到的像素数成正比,而与场景本身的复杂度无关,所以照射范围很小的光源的光照计算是很高效的。如果它们完全或被场景中的物体遮挡住,性能代价更为低廉。当然要产生阴影效果的光源比不产生的性能消耗要大得多。在延迟渲染中,投射阴影到场景中的物体仍然需要对产生阴影的光源执行一次或多次渲染计算。

当使用延迟渲染时,Unity3D引擎要求提供两个渲染通路:第一个渲染通路用于渲染G-buffer,会把待渲染物体的漫反射颜色、镜面反射颜色、平滑度(smoothness)、法线、自发光颜色及深度值信息渲染到基于屏幕空间的G-buffer中。每一个待渲染物体仅执行一次此通路。

默认的G-buffer包含以下几个可渲染纹理(RT)和一些缓冲区,如下:

unity自带的着色器源码剖析之——前向渲染和延迟渲染

在引擎 提供的Internal-DeferredShading.shader文件中预定了_CameraGBufferTexture0~2这3个着色器变量,分别对应RT1~3这3个可渲染纹理。

第二个渲染通路用于执行真正的光照计算,这个通路会使用第一个渲染通路得到的技术来计算最终的光照颜色,然后存储到一个帧缓冲区中。

版权声明:本文为CSDN博主「小橙子0」的原创文章,
遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/cgy56191948/article/details/104523439

推荐阅读