Unity渲染路径
Unity里,渲染路径决定了光照是如何应用到Unity Shader中的,因此,如果跟光源打交道,需要为每个Pass指定使用的渲染路径,只有这样,Unity才会把光源和处理后的光照信息放到数据里,也就是说只有为Shader正确选择和设置了需要的渲染路径,Shader的光照计算才能正确执行。
Unity支持多种类型的渲染路径,前向渲染,延迟渲染。
大多情况下,一个项目只使用一种渲染路径,因此可以为整个项目设置渲染路径,需要在Project Setting设置,默认选择是前向渲染路径,有时候希望使用多个渲染路径,可以在每个摄像机的渲染路径设置中设置相机使用的渲染路径。如果当前显卡不支持所选的渲染路径,会自动使用更低一级的渲染路径。
完成上面设置之后,就可以在每个Pass中使用标签来指定该Pass使用的渲染路径,这是通过设置Pass的LightMode标签实现的,不同类型的渲染路径可能会包含下面标签设置。
Tags {
"LightMode" = "ForwardBase"}
上面的代码告诉Unity,Pass使用前线渲染中的ForwardBase路径,而前向渲染还有一种路径叫做ForwardAdd。表中给出了Pass的LightMode标签支持的渲染路径设置选项:
如果一个Pass没有指定任何渲染路径,一些光照变量很可能不会被正常赋值,计算出的效果很可能是错误的。
前向渲染路径
传统的渲染方式,最常用的一种渲染方式。
原理:
每进行一次完整的前向渲染,我们需要渲染该对象的渲染图元,并计算两个缓冲区的信息:一个是颜色缓冲,一个是深度缓冲。利用深度缓冲来决定一个片元是否可见,如果可见就更新颜色缓冲区的颜色值。伪代码描述前向渲染的大致过程:
对于每个逐像素光源,都需要进行上面一次完整的渲染流程。如果一个物体在多个逐像素光源的影响区域内,那么物体就需要执行多个Pass,每个Pass计算一个逐像素光源的光照结果,然后在帧缓冲中把这些光照结果混合起来得到最终颜色。假设场景中有N个物体,每个物体受M个光源影响,那么要渲染整个场景一共需要N*M个Pass,可以看出,如果有大量的逐像素关照,那么需要执行的Pass数目会更大,因此,渲染引擎通常会限制每个物体的逐像素光照数目。
Unity中的前向渲染
一个Pass不仅仅可以用来计算逐像素光照,也可以计算逐顶点等其他光照,取决计算光照计算所处的流水线阶段以及计算时候使用的数学模型,当我们渲染一个物体时候,Unity会计算哪些光源照亮了他,以及这些光源照亮该物体的方式。
Unity中,前向渲染路径有三种处理关照的方式,逐顶点,逐像素,球谐函数处理,而决定一个光源使用哪种处理模式取决他的类型和渲染方式。光源类型是指该光源是平行光还是其他类型的光源,光源的渲染模式指的是该光源是否是重要的,如果一个光照模式设置为Important,意味光源很重要,逐像素光源来处理,可以在光源的Light组件上设置这些属性
前向渲染中,渲染一个物体时,Unity会根据场景把各个光源的设置以及这些光源对物体的影响程度,对这些光源进行一个重要度排序。其中,一定数目的光源会按照逐像素处理,然后最多有4个光源按逐顶点的方式处理,剩下的光源可以按照SH方式处理。规则如下:
1.场景中最亮的平行光总是按逐像素处理
2.渲染模式设置成不重要的光源,按照逐顶点或者SH处理
3.渲染模式设置为重要,使用逐像素
4.根据以上规则得到的逐像素光源小于Quality Setting中的逐像素光源数量,会有更多光源以逐像素方式渲染。
前向渲染有两种Pass Base Pass和Additional Pass,这两种Pass进行的标签和渲染设置以及常规光照计算如下
上图可以看到,在渲染设置中,除了设置Pass的标签外,还使用了#pragma multi_compile_fwdbase这样的编译指令,实验表明,只有使用了这些编译指令,Pass中才可以得到一些正确的光照变量,例如光照衰减。
Base Pass旁边的注释给出了支持的一些光照特性,可以访问光照纹理。
BasePass中渲染的平行光默认是支持阴影的,而Additional Pass中渲染的光源默认情况下是没有阴影的,即使我们在Light组件中设置了有阴影的Shadow Type。但我们可以在Additional Pass中使用#pragma multi_compile_fwdadd_fullshadows代替#pragma multi_compile_fwdadd编译指令,为点光源聚光灯开启阴影效果,这需要Unity在内部使用更多的Shader变种。
环境光和自发光也是在Base Pass中计算,因为,对于一个物体来说,环境光和自发光我们只希望计算一次即可,如果在Additional Pass中计算,就会叠加多次环境光和自发光。
在Additional Pass的渲染设置中,还开启和设置了混合模式,因为希望每个Additional Pass可以与上一次的光照结果在帧缓存中叠加,从而得到最终的有多个光照的渲染效果,如果没有开启和设置混合模式,那么Additional Pass的渲染模式会覆盖掉之前的渲染结果,看起来就好像只受该光源的影响。通常选择的混合模式是Blend One One。
对于前向渲染,一个Unity Shader通常会定义一个BasePass(也可以定义多次,双面渲染)以及一个Additional pass。一个 Base Pass只会执行一次,一个Additional Pass会根据影响该物体的其他逐像素光源的数目被多次调用,每个逐像素光源会执行一次Additional Pass。
实际上,渲染路径的设置用于告诉Unity该Pass在前向渲染路径中的位置,然后底层的渲染引擎会进行相关计算来填充一些内置变量,比如_LightColor0,如何使用这些内置变量进行计算完全取决于开发者的选择。可以利用Unity提供的内置变量在Base Pass中只进行逐顶点光照,也完全可以在Additional Pass中按逐顶点的方式进行光照计算,不进行任何像素光照计算。
内置光照变量和函数
根据使用的渲染路径,Pass中的LightMode的值,Unity会把不同的光照变量传递给Shader
Unity5中,对于前向渲染(LightMode为ForwardBase或ForwardAdd)来说,下表给出了可以在Shader中访问的光照变量
下面列出前向渲染中可以使用的内置光照函数
顶点照明渲染路径
对硬件配置要求最少,运算性能最高同时也是效果最差的一种类型,不支持逐像素才能得到的效果,例如阴影,法线,高光反射等。实际上他只是前向渲染的一个子集,顶点照明渲染路径只是使用了逐顶点的方式来计算光照,上面的前向渲染路径中也可以计算一些逐顶点的光源,但如果选择使用顶点照明渲染路径,那么Unity会只填充那些逐顶点相关的光源变量,意味着不可以使用一些逐像素光照变量。
Unity中的顶点照明渲染
顶点照明渲染路径通常在一个Pass中就可以完成对物体的渲染。在这个Pass中,会计算所有光源对该物体的照明,这个计算是按逐顶点处理的,Unity中最快的渲染路径。
可访问的内置变量和函数
Unity中,可以在一个顶点照明的Pass中最多访问到8个逐顶点光源。如果只需要渲染其中两个光源对物体的照明,可以只使用下表中内置光照数据的前两个,如果影响该物体的光源数目小于8,那么数组中剩下的光源颜色会设置成黑色。
延迟渲染路径
前向渲染的问题:场景中包含大量实时光源时,前向渲染的性能急速下降。由于前向渲染可能造成的瓶颈问题,延迟渲染流行起来,除了前向渲染中使用的颜色缓冲和深度缓冲外,延迟渲染还会利用额外的缓冲区,统称为G缓冲,G缓冲区存储了我们关心的表面的其他信息,例如法线,位置,用于光照计算的材质属性等。
延迟渲染的原理
主要包含两个Pass。一个Pass,不进行任何光照计算,仅仅计算哪些片元是可见,通过深度缓冲技术实现,当一个片元是可见的,就把相关信息存储在G缓冲。然后,第二个Pass中,利用G缓冲的各个片元信息,例如表面法线,视角方向,漫反射系数,进行真正的光照计算。过程大致可以用下面伪代码来描述:
可以看出,延迟渲染使用的Pass数目通常是2个,跟场景中包含的光源数目没有关系,也就是说延迟渲染效率不依赖于场景的复杂度,而是和我们使用的屏幕空间大小有关。因为,需要的信息都在缓冲区中,这些缓冲区可以理解成是一张2D图像,计算实际上就是在这些图像空间进行。
Unity中的延迟渲染
两种延迟渲染路径。一种是遗留的延迟渲染路径,Unity5之前使用的延迟渲染路径,另一种是Unity5.x中使用的延迟渲染路径,如果游戏中使用了大量的实时光照,最好选择延迟渲染路径,这种路径需要一定的硬件支持。
缺点:
不支持真正的抗锯齿共嗯
不能处理半透明
对显卡有一定要求,显卡必须支持MRT ShaderModel3.0以及以上 深度渲染纹理以及双面模板缓冲
使用延迟渲染,Unity要求我们提供两个Pass:
第一个Pass用于渲染G缓冲,在这个Pass,会把物体的漫反射颜色,高光反射颜色,平滑度,法线,自发光和深度信息渲染到屏幕空间的G缓冲区,对于每个物体,Pass只会执行一次。
第二个Pass用于真正的光照模型,这个Pass会使用上一个Pass中渲染的数据来计算最终的光照颜色,再存储到帧缓冲。
默认的G缓冲包含了以下几个渲染纹理
RT0: 格式ARGB32 RGB通道用于存储漫反射颜色,A通道没被使用。
RT1:格式ARGB32,RGB用于存储高光反射,A用于存储高光反射的指数部分。
RT2:格式ARGB2101010,RGB通道用于存储法线,A通道没被使用
RT3:格式是ARGB32(非HDR) 或ARGBHalf(HDR) 用于存储自发光+lightmap+反射探针
深度缓冲和模板缓冲
在第二个Pass中计算光照时候,默认情况下仅可以使用Unity内置的Standard光照模型,如果想要使用其他光照模型,需要替换掉原来的Internal-DeferredShading.Shader文件。
可访问的内置变量和函数
下表给出处理延迟渲染路径可以使用的光照变量,都可以在UnityDeferredLibrary.cginc文件找到他们的声明:
总体来说,需要更艰巨游戏发布的目标平台来选择渲染路径,当前显卡不支持所选路径,那么Unity会自动使用低一级的渲染路径。
Unity光源类型
Unity一共支持4种光源,平行光,点光源,聚光灯,面光源。面光源仅在烘焙才可发挥作用,这里不讨论。最常用的光源属性有位置,方向,颜色,强度以及衰减五个属性。
平行光
没有位置,照亮范围没有限制,几何属性只有方向,可以调整平行光的Transform组件中的Rotation属性来改变光源方向,平行光到场景所有点的方向都是一样的,平行光没有具体的位置,所以也不存在衰减的概念。
点光源
照亮空间有限,由空间一个球体定义。点光源可以表示由一个点出发,向所有方向延伸的光。下图给出Unity中点光源在Scene视图中的表示以及Light组件的面板。球体半径可以由面板的Range属性调整,也可以在Scene视图中直接拖拉点光源的线框来修改属性。点光源是有位置属性的,由Position属性定义,方向属性需要用点光源的位置减去某点的位置得到该点的方向。而点光源的颜色和强度可以在Light组件面板中调整。同时,点光源也是会衰减的,随着物体逐渐远离点光源,接收到的光照强度也会逐渐减少。点光源球心处光照强度最强,球体边界处最弱,值为0。
聚光灯
照亮空间有限,空间中的一块锥形区域定义的。聚光灯可以用于表示一个特定位置出发,向特定方向延伸的光。锥形区域的半径由面板中的Range属性决定,锥体的
张开角度Spot Angle属性决定。聚光灯的位置同样是Position属性定义,方向属性用聚光灯的位置减去某点的位置来得到他到该点的方向。聚光灯的衰减也是随着物体逐渐远离点光源而逐渐减少,锥形顶点处光照强度最强,锥形边界强度为0。
前向渲染中处理不同的光源类型
Shader "Unity Shaders Book/Chapter 9/Forward Rendering" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Tags {
"RenderType"="Opaque" }
Pass {
// Pass for ambient light & first pixel light (directional light)
Tags {
"LightMode"="ForwardBase" }
CGPROGRAM
// Apparently need to add this declaration
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
fixed atten = 1.0;
return fixed4(ambient + (diffuse + specular) * atten, 1.0);
}
ENDCG
}
Pass {
// Pass for other pixel lights
Tags {
"LightMode"="ForwardAdd" }
Blend One One
CGPROGRAM