第八章 更复杂的光照(1)

@数组

Unity的渲染路径

在Unity里,渲染路径(Rendering Path)决定了光照是如何应用到Unity Shader中的。所以,若是要和光源打交道,咱们须要为每一个Pass指定它使用的渲染路径,只有这样才能让Unity知道“哦,原来这个程序员想要用这种渲染路径,那么好的,我把光源和处理后的光照信息都放在这些数据里,你能够访问啦”也就是说,咱们只有为Shader正确的选择和设置了须要的渲染路径,该Shader的光照计算才能被正确执行。
Unity支持多种类型的渲染路径,在Unity5.0版本以前,主要有三种:前向渲染路径(Forward Rendering Path)、延迟渲染路径(Deferred Rendering Path)和顶点照明渲染路径(Vertex Lit Rendering Path)。但在Unity5.0版本之后,Unity作了不少修改,主要有两个变化:首先,顶点照明渲染路径已经被Unity抛弃(但目前仍然能够对以前使用了顶点照明渲染路径的UnityShader兼容);其次,新的延迟渲染路径代替了原来的延迟渲染路径(一样,目前也提供了对较旧版本的兼容)。
大多数状况下,一个项目只能使用一种渲染路径,所以咱们能够为整个项目渲染时的渲染路径。咱们能够经过在Unity的Edit->Project Settings->Player->Other Settings->Rendering Path中选择项目所需的渲染路径。默认状况下,该设置选择的是前向渲染路径,以下图所示:
在这里插入图片描述
但有时,咱们但愿可使用多个渲染路径,例如摄像机A渲染的物体使用前向路径,而摄像机B渲染的物体使用延迟渲染路径。这时,咱们能够在每一个摄像机的渲染路径设置中设置该摄像机使用的渲染路径,以覆盖Project Settings中的设置,如图所示:
在这里插入图片描述
在上面的设置中,若是选择了Use Player Settings,那么这个摄像机就会使用Project Settings中的设置;不然就会覆盖掉Project Settings中的设置。须要注意的是,若是当前的显卡并不支持所选择的渲染路径,Unity会自动使用更低一级的渲染路径。例如,若是一个GPU不支持延迟渲染,那么Unity就会使用前向渲染。
完成了上面的设置后,咱们就能够在每一个Pass中使用标签来指定该Pass使用的渲染路径。这是经过设置Pass的LightMode标签来实现的。不一样类型的渲染路径可能会包含多种标签设置。例如,咱们以前在代码中写的:缓存

Pass{
Tags{"LightMode"=''ForwardBase''}
}

上面的代码告诉Unity,该Pass使用前向渲染路径中的ForwardBase路径。而前向渲染路径还有一种路径叫作ForwardAdd。下表给出了Pass的LightMode标签支持的渲染路径设置选项。
函数

那么指定渲染路径到底有什么用呢?若是一个Pass没有指定任何渲染路径会有什么问题吗?通俗来说,指定渲染路径是咱们和Unity的底层渲染引擎的一次重要的沟通。例如,若是咱们为一个Pass设置了前向渲染路径的标签,至关于会告诉Unity:“嘿,我准备使用前向渲染了,你把那些光照属性都按前向渲染的流程给我准备好,我一下子要用”。随后,咱们能够经过Unity提供的内置光照变量来访问这些属性。若是咱们没有指定任何的渲染路径(实际上,在Unity5.x版本中若是使用了前向渲染又没有为Pass指定任何前向渲染适合的标签,就会被当成一个和顶点照明渲染路径等同的Pass),那么一些光照变量极可能不会被正确赋值,咱们计算出的效果颇有多是错误的。
那么,Unity渲染引擎是如何处理这些渲染路径的呢?下面,咱们对这些渲染路径,进行更加详细的解释。性能

1. 前向渲染路径

前向渲染路径是传统的渲染方式,也是咱们最经常使用的一种渲染路径。在本节,咱们首先会归纳前向渲染路径的原理,而后再给出Unity对于前向渲染路径的实现细节和要求,最后给出UnityShader中哪些变量是用于前向渲染路径的。学习

1.1 前向渲染路径的原理

每进行一次完整的前向渲染,咱们须要渲染该对象的渲染图元,并计算两个缓冲区中的信息:一个是颜色缓冲区,一个深度缓冲区。咱们利用深度缓冲区来决定一个片元是否可见,若是可见就更新颜色缓冲区中的颜色值。咱们可使用下面的伪代码来描述前向渲染路径的大体过程:测试

Pass{
for(each primitive in this model){
for(each fragment covered by this primitive){
if(failed in depth test){
//若是没有经过深度测试,说明该片元是不可见的
discard;}
else{
//若是该片元可见
//就进行光照计算
float4 color = Shading(materialInfo,pos,normal,lightDir,viewDir);
//更新帧缓冲
writeFrameBuffer(fragment,color);
}
}
}
}

对于每一个逐像素光源,咱们都须要进行上面一次完整的渲染流程。若是一个物体在多个逐像素光源的影响区域内,那么该物体就须要执行多个Pass,每一个Pass计算一个逐像素光源的光照结果,而后在帧缓冲中把这些光照结果混合起来获得最终的颜色值。假设场景中有N个物体,每一个物体受M个光源的影响,那么渲染整个场景须要N*M个Pass。能够看出,若是有大量的逐像素光照,那么须要执行的Pass数目也会很大。所以渲染引擎一般会限制每一个物体的逐像素光照数目。this

1.2 Unity中的前向渲染

事实上,一个Pass不只仅能够用来计算逐像素光照,它也能够用来计算逐顶点等其它光照。这取决于光照计算所处流水线阶段以及计算时使用的数学模型。当咱们渲染一个物体时,Unity会计算哪些光源照亮了它,以及这些光源照亮该物体的方式。
在Unity中,前向渲染路径有三种处理光照(即照亮物体)的方式:逐顶点处理、逐像素处理,球谐函数(Spherical Harmonics,SH)处理。而决定一个光源使用哪一种处理模式取决于它的类型和渲染模式。光源类型指的是该光源是平行光仍是其余类型光源,而光源的渲染模式指的是该光源是不是重要的(Important)。若是咱们把一个光照的模式设置为Important,意味着咱们告诉Unity,“嘿老兄,这个光源很重要,我但愿你能够认真对待它,把它当成一个逐像素光源来处理”咱们能够在光源的Light组件中设置这些属性,以下图所示:
在这里插入图片描述
在前向渲染中,当咱们渲染一个物体时,Unity会根据场景中各个光源的设置以及这些光源对物体的影响程度(例如,距离该物体的远近、光源强度等)对这些光源进行一个重要度排序。其中,必定数目的光源会按逐像素的方式处理,而后最多有4个光源按逐顶点的方式处理,剩下的光源能够按SH方式处理。Unity使用的判断规则以下。
(1)场景中最亮的平行光老是按逐像素处理的
(2)渲染模式被设置成Not Important的光源,会按逐顶点或者SH处理
(3)渲染模式被设置成Important的光源,会按逐像素处理。
(4)若是根据以上规则获得的逐像素光源数量小于Quality Setting中的逐像素光源数量(Pixel Light Count),会有更多的光源以逐像素的方式进行渲染。
那么,在哪里进行光照计算呢?固然是在Pass里。前面提到过,前向渲染有两种Pass:BasePass和Additional Pass。一般来讲,这两种Pass进行的标签渲染设置以及常规光照计算如图所示:
在这里插入图片描述
上图有几点须要说明的地方
(1)首先,能够发如今渲染设置中,咱们除了设置Pass的标签外,还使用了#pragma multi_compile_fwdbase这样的编译指令。虽然#pragma multi_compile_fwdbase和#pragma multi_compile_fwdadd在官方文档中尚未给出相关说明,但实验代表,只有分别为BasePass和Additional Pass使用这两个编译指令,咱们才能在相关的Pass中获得一些正确的光照变量,例如光照衰减值等。
(2)Base Pass旁边的注释给出了Base Pass中支持的一些光照特性。例如在Base Pass中咱们能够访问光照纹理(lightmap)
(3)Base Pass中渲染的平行光默认是支持阴影的(若是开启了光源的阴影功能),而Additional Pass中渲染的光源在默认状况下是没有阴影效果的,即使咱们在它的Light组件中设置了有阴影的Shadow Type。但咱们能够在Additional Pass中使用#pragma multi_compile_fwdadd_fullshadows代替#pragma multi_compile_fwdadd编译指令,为点光源和聚光灯开启阴影效果,但这须要在Unity内部使用更多的Shader变种。
(4)环境光和自发光也是在Base pass中计算的。这是由于对于一个物体来讲,环境光和自发光咱们只但愿计算一次便可,而若是咱们在Additional Pass中计算这两种光照,就会形成叠加屡次环境光和自发光,这不是咱们想要的。
(5)在Additional Pass的渲染设置中,咱们还开启和设置了混合模式。这是由于咱们但愿每一个Additional Pass能够与上一次的光照结果在帧缓存中进行叠加,从而获得最终有多个光照的渲染效果。若是咱们没有开启和设置混合模式,那么Additional Pass的渲染结果会覆盖掉以前的渲染结果,看起来好像该物体只受该光源的影响。一般状况下,咱们选择的混合模式是Blend One One
(6)对于前向渲染来讲,一个UnityShader一般会定义一个Base Pass(Base Pass也能够被定义屡次,例如须要双面渲染的状况)以及一个Additional Pass。一个Base Pass仅会执行一次(定义了多个Base Pass的状况除外),而一个Additional Pass会根据影响该物体的其余逐像素光源数目被屡次调用,即每一个逐像素光源会执行一次Additional Pass。
上图给出的光照计算是一般状况下咱们在每种Pass中进行的计算。实际上,渲染路径的设置用于告诉Unity该Pass在前向渲染路径中的位置,而后底层的渲染引擎会进行相关计算并填充一些内置变量(如_LightColor0等),如何使用这些内置变量进行计算彻底取决于开发者的选择。例如咱们彻底能够利用Unity提供的内置变量在Base Pass中只进行逐顶点光照;一样,咱们也能够彻底在Additional Pass中按逐顶点的方式进行光照计算,不进行任何逐像素计算。code

1.3内置的光照变量和函数

前面说过,根据咱们使用的渲染路径(即Pass标签中LightMode的值),Unity会把不一样的光照变量传递给Shader。
在Unity5中,对于前向渲染(即LightMode为ForwardBase或ForwardAdd)来讲,下表给出了咱们能够在Shader中访问到的光照变量。
orm

咱们在之前已经给出了一些能够用于前向渲染路径的函数,例如WorldSpaceLightDir、UnityWorldSpaceLightDir和ObjSpaceLightDir。为了完整性,咱们在下面再次列出前向渲染中可使用的内置光照函数。

须要说明的是,上面给出的变量和函数并非完整的,一些前向渲染可使用的内置变量和函数官方文档并无给出说明。在后面的学习中,咱们会使用到一些不在这些表中的变量和函数,那时咱们会特别说明。

2.顶点照明渲染路径

顶点照明渲染路径是对硬件配置要求最少、运算性能最高,但同时也是获得的效果最差的一种类型,它不支持那些逐像素才能获得的效果,例如阴影、法线映射、高精度的高光反射等。实际上,它仅仅是前向渲染的一个子集,也就是说,全部能够在顶点照明渲染路径中实现的功能均可以在前向渲染路径中完成。就如它的名字同样,顶点照明渲染路径只是使用了逐顶点的方式来计算光照,并无什么神奇的地方。实际上,咱们在上面的前向渲染路径中也能够计算一些逐顶点的光源。但若是选择使用顶点照明渲染路径,那么Unity会只填充那些逐顶点相关的光源变量,意味着咱们不可使用一些逐像素光照变量。

2.1 Unity中的顶点照明渲染

顶点照明渲染路径一般在一个Pass中就能够完成对物体的渲染。在这个Pass中,咱们会计算咱们关心的全部光源对该物体的照明,而且这个计算是按逐顶点处理的。这是Unity中最快速的渲染路径,而且具备最普遍的硬件支持(但游戏机上并不支持这种路径)

2.2 可访问的内置变量和函数

在Unity中,咱们能够在一个顶点照明的Pass中最多访问到8个逐顶点光源。若是咱们只须要渲染其中两个光源对物体的照明,能够仅使用下表中内置光照数据的前两个。若是影响该物体的光源数目小于8,那么数组中剩下的光源颜色会设置成黑色。

能够看出,一些变量咱们一样能够在前向渲染路径中使用,例如unity_LightColor。但这些变量数组的维度和数值在不一样渲染路径中的值是不一样的。
下表给出了顶点照明渲染路径中可使用的内置函数。

3. 延迟渲染路径

前向渲染的问题是:当场景中包含大量实时光源时,前向渲染的性能会急速降低。例如,若是咱们在场景的某一块区域放置了多个光源,这些光源影响的区域互相重叠,那么为了获得最终的光照效果,咱们就须要为该区域内的每一个物体执行多个Pass来计算不一样光源对该物体的光照结果,而后在颜色缓存中把这些结果混合起来获得最终的光照。然而,每执行一个Paa咱们都须要从新渲染一遍物体,但不少计算其实是重复的。
延迟渲染是一种更古老的渲染方法,但因为上述前向渲染可能会形成的瓶颈问题,近几年又流行起来。除了前向渲染中使用的颜色缓冲和深度缓冲外,延迟渲染还会利用额外的缓冲区,这些缓冲也被称为G缓冲(G-buffer),其中G是英文Geometry的缩写。G缓冲区存储了咱们所关心的表面(一般指的是离计算机最近的表面)的其余信息,例如该表面的法线、位置、用于光照计算的材质属性等。

3.1延迟渲染的原理

延迟渲染主要包含了两个Pass。在第一个Pass中,咱们不进行任何光照计算,而是仅仅计算哪些片元是可见的,这主要是经过深度缓冲技术来实现,当发现一个片元是可见的,咱们就把它的相关信息存储到G缓冲区中。而后,在第二个Pass中,咱们利用G缓冲区的各个片元信息,例如表面法线、视角方向、漫反射系数等,进行真正的光照计算。
延迟渲染的大体过程能够用下面的伪代码来描述:

Pass1{
//第一个Pass不进行真正的光照计算
//仅仅把光照计算须要的信息存储到G缓存中
for(each primitive in this moadel){
for(each fragment covered by this primitive){
if(failed in depth test){
//若是没有经过深度测试,说明该片元是不可见的
discard;
}else{
//若是该片元可见
//就须要把信息存储到G缓冲区中
writeGBuffer(materialInfo,pos,normal,lightDir,viewDir)
}
}
}
}
Pass2{
//利用G缓冲区中的信息进行真正的光照计算
for(each pixel in the screen){
if(the pixel is valid){
//若是该像素是有效的
//读取它对应的G缓冲中的信息
readBuffer(pixel,materialInfo,pos,normal,lightDir,viewDir);
//根据读取到的信息尽行光照计算
float4 color=shading(materialInfo,pos,normal,lightDir,viewDir);
//更新帧缓冲
writeFrameBuffer(pixel,color);
}
}
}

能够看出,延迟渲染使用的Pass数目一般就是两个,这跟场景中包含的光源数目是没有关系的。换句话说,延迟渲染的效率不依赖于场景的复杂度,而是和咱们使用的屏幕空间大小有关。这是由于,咱们须要的信息都存储在缓冲区中,而这些缓冲区能够理解成是一张张2D图像,咱们的计算实际上就是在这些图像空间中进行的。

3.2 Unity中的延迟渲染

Unity有两种延迟渲染路径,一种是遗留的延迟渲染路径,即Unity5以前使用的延迟渲染路径,而另外一种是Unity5.x中使用的延迟渲染路径。若是游戏中使用了大量的实时光照,那么咱们可能但愿选择延迟渲染路径,但这种路径须要必定的硬件支持。
新旧延迟渲染路径之间的差异很小,只是用了不一样的技术来权衡不一样的需求。例如,较旧版本的延迟渲染路径不支持Unity5的基于物理的Standard Shader。如下咱们只讨论Unity5后使用的延迟渲染路径。
对于延迟渲染路径来讲,它最适合在场景中光源数目不少、若是使用前向渲染会形成性能瓶颈的状况下使用。并且,延迟渲染路径中的每一个光源均可以按照逐像素的方式处理。可是,延迟渲染也有一些缺点。
(1)不支持真正的抗锯齿(anti-aliasing)功能。
(2)不能处理半透明物体
(3)对显卡有必定要求。若是要使用延迟渲染的话,显卡必须支持MRT(Multiple Render Targets)、Shader Mode3.0及以上、深度渲染纹理以及双面的模板缓冲。
当使用延迟渲染时,Unity要求咱们提供两个Pass。
(1)第一个Pass用于渲染G缓冲。在这个Pass中,咱们会把物体的漫反射颜色、高光反射颜色、平滑度、法线、自发光和深度等信息渲染到屏幕空间的G缓冲区中。对于每一个物体来讲,这个Pass仅会执行一次。
(2)第二个Pass用于计算真正的光照模型。这个Pass会使用上一个Pass中渲染的数据来计算最终的光照颜色,再存储到帧缓冲中。
默认的G缓冲区(注意,不一样Unity版本的渲染纹理存储内容会有所不一样)包含了如下几个渲染纹理(Render Texture,RT)。
●RT0:格式是AGRB32,RGB通道用于存储漫反射颜色,A通道没有被使用
●RT1:格式是AGRB32,RGB通道用于存储高光反射颜色,A通道用于存储高光反射的指数部分。
●RT2:格式是ARGB2101010,RGB通道用于存储法线,A通道没有使用
●RT3:格式是ARGB32(非HDR)或ARGBHalf(HDR),用于存储自发光+lightmap+反射探针(reflection probes)
当在第二个Pass计算光照时,默认状况下仅可使用Unity内置的Standard光照模型。若是咱们想要使用其它的光照模型,就须要替换掉原有的Internal-DefferedShading.shader文件。

3.3可访问的内置变量和函数

下表给出了处理延迟渲染路径可使用的光照变量。这些变量均可以在UnityDefferedLibrary.cginc文件中找到它们的声明。

3.4 选择哪一种渲染路径

Unity的官方文档给出了4种渲染路径(前向渲染路径、延迟渲染路径、遗留的延迟渲染路径和顶点照明渲染路径)的详细比较,包括它们的性能比较(是否支持逐像素光照、半透明物体、实时阴影等)、性能比较以及平台支持。 整体来讲,咱们须要根据游戏发布的目标平台来选择渲染路径。若是当前显卡不支持所选渲染路径,那么Unity会自动使用比其低一级的渲染路径。 咱们主要讲Unity的前向渲染路径。

相关文章
相关标签/搜索