Unity的渲染路径
在Unity里,渲染路径(Rendering Path)决定了光照是如何应用到Unity Shader中的,只有正确的设置每个Pass的渲染路径,Unity才会给Shader提供正确的光源信息(位置,方向,颜色,强度,衰减),我们在计算光照时才会有正确的结果。
在Unity中提供了两种渲染路径:
一、前向渲染(Forward Rendering Path)
前向渲染也有两种路径:
Base Pass
Tags{"LightModel"="ForwardBase"}
Addtional Pass
Tags{"LightModel"="ForwardAdd"}
前向渲染路径是传统的渲染方式,也是常用的一种渲染路径
伪代码
Pass
{
for(each primitive in 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);
}
}
}
}
1、原理
每进行一次完整的前向渲染,都需要渲染该对象的渲染图元,并计算两个缓冲区的信息:颜色缓冲区,深度缓冲区。利用深度缓冲区来决定一个片元是否可见,如果可见就更新颜色缓冲区的颜色值。
每个逐像素光源,都需要进行一次完整的渲染流程。如果一个物体在多个逐像素光源的影响区域内,则这个物体需要执行多个Pass,每个Pass计算一个逐像素的的光照结果,然后在帧缓冲中把这些光照结果混合起来得到最终的颜色值。也就是如果有N个物体受M个光源影响,则需要执行N*M次的Pass。所以逐像素的光源如果多了就会有更多的Pass。因此引擎一般都会限制逐像素光照的数目。
2、Unity中的前向渲染
而实际上一个Pass不仅仅是可以执行逐像素的光照,还可以进行逐顶点等光照,这取决于计算光照的流水线以及使用的数学模型。当渲染一个物体时,Unity会计算哪些光源会照亮这个物体,以及这个光源照亮物体的方式(就是用哪种方式来计算)。
在Unity中,前向渲染路径有3种处理光照(即照亮物体的方式)的方式:逐顶点处理,逐像素处理,球谐函数(Spherical Harmonics Function)处理。而决定一个光源使用哪种处理模式取决于光源的类型和渲染的模式。光源类型指的是该光源是平行光还是其他类型光,而光源的渲染模式指的是该光源是否是重要的(Important)。如果设置成Important,就意味着使用逐像素的方式。
在前向渲染中,Unity会根据场景中各个光源的设置以及这些光源对物体的影响程度(距离物体的远近,光强度等)对场景中的光源进行一个重要度的排序。其中一定数目的光源按照逐像素的方式处理,最多有4个光源会逐顶点的方式处理,剩下的就会按照SH处理,具体规则如下:
(1)场景中最亮的平行光总是按照逐像素处理。
(2)渲染模式被设置成Not Important的会按照逐顶点或SH处理
(3)渲染模式被设置成Important的会按照逐像素处理
image.png
(4)如果场景中逐像素的光源数目小于Quality Setting中的逐像素光源数量,那么剩余的逐像素光源也会按照逐像素处理
image.png
两种Pass的对光照的计算:
(1)Base Pass
支持访问光照纹理(lightmap)、平行光默认支持阴影(平行光源开启阴影功能)、可以在Base Pass中计算环境光,自发光(因为这两种光只需要计算一次就可以)。既可以逐像素光照,也可以逐顶点。一般只执行一次。
(2)Addtional Pass
Addtional Pass中渲染的光源默认情况下没有阴影,即便光源本身设置了Shadow Type,但是可以使用编译指令:
#pragma multi_compile_fwadd_fullshadows
为点光源,聚光灯开启阴影效果。还需要开启混合模式,因为我们需要把Addtional Pass中的得到光照颜色和上一次的光照叠加起来,从而得到多个光照渲染的效果,如果没有混合,之前的颜色会覆盖掉,看起来像只受最后一个光源的影响,一般都是Blend One One,也是可以是其他。
Shadow Type
通常对于前向渲染UnityShader只需要定义一个Base Pass(也可以定义多个,如双面渲染,需要先画后面,再画前面),一个Addtional Pass。一般Base Pass只会执行一次(定义多个就执行多次),而Addtional Pass会根据影响该物体的其他逐像素光源的数目被多次调用(逐顶点也可以)。
渲染路径的设置用于告诉Unity该Pass在前向渲染路径中的位置,引擎就会在相关计算中填充一些内置变量。至于如何使用这些变量,完全取决于开发者,如在BasePass和Addtional Pass中逐顶点光照而不是逐像素光照。
两种Pass的特性
二、延迟渲染(Deferred Rendering Path)
前向渲染的问题是:当场景中存在大量实时光源时,渲染性能会急剧下降。当有很多光源重叠在一个区域内,为了得到最终的光照颜色,需要为每个光源执行一次Pass,如果这个区域内很多物体那么执行Pass的次数会成倍增加,而每执行一次Pass就会重新渲染一次物体,这样性能会下降。
延迟渲染(Deferred Rendering)目的是为了解决前向渲染产生的瓶颈问题(延迟渲染已经是一种古老的渲染方法,只是近几年又流行起来)。除了前向渲染使用的颜色缓冲区和深度缓冲区,延迟渲染还会使用额外的缓冲区,被统称为G缓冲区(Geometry,G-Buffer)。G缓冲区存储了我们所关心的表面信息(通常都是离摄像机最近的表面),如表面的法线,位置,材质属性。
1、原理
伪代码
Pass1
{
//第一个Pass不进行光照,只根据深度把能写入G-Buffer的片元挑选出来
for(each primitive in model)
{
//所有被这个三角形包围的片元
for(each fragment covered by this primitive)
{
if(failed in depth test)
{
//如果深度测试失败,说明该片元不可见,不应该被写入G-Buffer
discard;
}
else{
//如果可见就就要把相关信息存储到G-Buffer中
WriteGBuffer(materialInfo,pos,normal,lightDir,viewDir);
}
}
}
}
Pass2
{
//第二个Pass会根据G-Buffer中的片元信息进行光照
for(each pixel in screen)
{
if(the pixel is valid)
{
//如果该像素是有效的
//读取它对应的G缓冲中的信息
readGBuffer(pixel,materialInfo,pos,normal,lightDir,viewDir);
//根据读取的信息进行光照
float4 color = Shading(materialInfo,pos,normal,lightDir,viewDir);
WriteFrameBuffer(pixel,color);
}
}
}
延迟渲染主要包含两个Pass,第一个Pass中不进行任何光照,只根据深度信息把符合条件的片元信息存储到G-Buffer中。第二个Pass,会根据G-Buffer中的各个片元的信息,如法线,视角方向,漫反射系数等进行光照计算。
可以看出延迟渲染和场景中的光源数量是没有关系的,因为经过第一个Pass之后,G-Buffer里面存储的就已经是可以直接进行光照计算的片元信息了,只需要画一次就可以了(对与前向渲染可能得需要画N*M次)。所以对于延迟渲染G-Buffer的大小才是性能的瓶颈所在,而G-Buffer又和屏幕空间大小有关,所以延迟渲染的效率在于我们使用的屏幕空间大小。所以说延迟渲染所有光源都可以使用逐像素处理。
2、延迟渲染的缺点
(1)不支持真正的抗锯齿(anti-aliasing)
(2)无法处理半透明物体(半透明关闭了深度写入)
(3)需要显卡支持
3、Unity中的延迟渲染
在Unity中使用延迟渲染。需要提供两个Pass。
第一个Pass用于渲染G缓冲,在这个Pass中,需要把物体的漫反射颜色,高光反射颜色,平滑度,法线,和深度信息渲染到屏幕空间的G-Buffer中,对于每个物体,这个Pass只会执行一次。
第二个Pass,用于真正的关照计算,会使用上一个Pass中的渲染数据来计算最终的颜色,在存储到帧缓冲区中。第二个Pass可以默认使用Unity内置的Standard光照模型。
网友评论