渲染路径&复杂光照
渲染路径(Rendering Path)决定光照如何应用到UnityShader中。为了获取场景中的光源数据,需要为每个Pass指定其渲染路径。5.0以后的版本中,Unity支持前向渲染(Forward Renddering Path)和 延迟渲染(Deferred Rendering Path)。系统默认情况下选择的是前向渲染路径,可以更改,同时若想使用多个渲染路径,可以在不同的摄像机中调节 Rendering Path选项。
通过在每个Pass中使用标签来指定Pass使用的渲染路径,即使用 LightMode标签实现。不同类型的渲染路径可能包含多种标签设置。
Pass{
Tags{"LightMode"="ForwardBase"}
}
前向渲染路径除了有ForwardBase,还有 ForwardAdd。Pass中的 LightMode支持的渲染路径设置选项:
当我们为一个Pass设置了渲染路径的标签,就可以通过Unity提供的内置光照变量来访问这些属性。
前向渲染路径
每进行一次完整的前向渲染,都需要渲染该对象的渲染图元,并计算颜色和深度缓冲区的值。**对于每一个逐像素光源,都需要进行一次该过程。**也就是说,如果一个物体在多个逐像素光源的影响区域内,那么就需要执行对应数量的Pass,每个Pass对应一个逐像素的光源,然后在帧缓冲内将这些 光照结果混合起来得到最终的颜色值。
Unity中的前向渲染
Unity中,前向渲染路径有3种处理光照的方式:逐顶点处理,逐像素处理,球谐函数 光源类型和渲染重要度决定了使用哪一种处理方式。
光源类型指该光源是平行光还是其他类型的光源。
渲染重要度是指该光源是否为重要的(IMPORTANT),如果将一个光源设置为重要的,那么Unity将其作为逐像素光源来处理。
在前向渲染中,Unity根据场景中的光源对物体的影响程度对光源进行重要度排序。
一定数目光源按照 逐像素处理
最多 4个光源按照 逐顶点方式处理
剩下的光源按照 球谐函数方式处理
Unity使用的判定规则:
光照计算在Pass中,前向渲染有两种:ForwardBase和 ForwardAdd,使用如下:
内置的光照变量&函数
前向渲染中可以使用的内置光照变量
前向渲染中可以使用的内置光照函数
延迟渲染路径
Pass中的渲染路径的设置是根据场景中的光源数据来进行选择。当场景中包含大量实时光源时,前向渲染的性能会下降,因为需要多次Pass来计算不同光源对该物体的光照结果,在颜色缓冲区将结果混合起来得到最终光照效果。实际上每执行一个Pass都需要重新再渲染一遍物体,很多计算实际上是重复的。延迟渲染除了使用颜色和深度缓冲区,还会使用额外的缓冲区,称为G-缓冲,存储表面(通常离摄像机最近的表面)的法线,位置,用于光照计算的材质属性等。
延迟渲染原理
延迟渲染主要包含两个Pass。第一个Pass中不做任何光照计算,只计算片元的可见性,通过深度缓冲来实现,当片元可见,将该片元相关信息存储到G缓冲中。第二个Pass中,利用G缓冲的各个片元信息,进行光照计算。延迟渲染使用的数目通常就2个,和场景中的光源数量并没有关系。
Unity中的延迟渲染
延迟渲染路径适合场景中光源数量较多、使用前向渲染路径会造成性能瓶颈的情况下使用,延迟渲染中的每个光源都可以按逐像素的方式进行处理。不过,延迟渲染也存在缺点:
Unity中使用延迟渲染路径,需要提供两个Pass。
第一个Pass用于渲染G缓冲,在该Pass中将物体漫反射、高光反射、颜色、平滑度、法线、自发光和深度等信息渲染到屏幕空间的G缓冲区,因此,延迟渲染路径的效率不依赖与场景是否复杂,而是屏幕的分辨率高低。对于每个物体来说,这个Pass仅会执行一次。
第二个Pass用于计算真正的光照模型。这个Pass使用上一个Pass中渲染的数据来计算最终的光照颜色,再存储到帧缓冲中。默认的G缓冲区包括以下渲染纹理(Render Texture):
Unity光源类型
Unity支持4种光源类型:平行光、点光源、聚光灯和面光源。面光源只有在烘焙后才能起作用,不属于实时光源。Shader中常用的光源属性包括:光源的位置、方向、颜色、强度和衰减。
前向渲染中处理不同的光源类型
使用一个平行光和一个点光源共同照亮物体
实例代码:
Shader "Custom/Chapter9_ForwardRendering" {
Properties{
_Color("Color",Color)=(1,1,1,1)
_SpecularColor("SpecularColor",Color)=(1,1,1,1)
_Gloss("Gloss",Range(8.0,256))=20
}
SubShader{
Pass{
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma multi_compile_fwdbase
//#pragma multi_compile_fwdbase指令保证Shader中使用光照衰减变量可以被正确赋值
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
fixed4 _SpecularColor;
float _Gloss;
struct a2v{
float4 vertex:POSITION;
float3 normal:NORMAL;
};
struct v2f{
float4 pos:SV_POSITION;
float3 worldPos:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
};
v2f vert(a2v v){
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
o.worldNormal=UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag(v2f i):SV_Target{
fixed3 worldLightDir=normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 worldNormal=normalize(i.worldNormal);
fixed3 worldViewDir=normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse=_LightColor0.rgb*_Color.rgb*max(0,dot(worldNormal,worldLightDir));
fixed3 halfDir=normalize(worldViewDir+worldLightDir);
fixed3 specularColor=_LightColor0.rgb*_SpecularColor.rgb*pow(saturate(dot(worldNormal,halfDir)),_Gloss);
fixed atten=1.0;
//Unity选择最亮的平行光在BasePass中处理,平行光的衰减值设为1,认为没有衰减
return fixed4(ambient+(diffuse+specularColor)*atten,1.0);
}
ENDCG
}
Pass{
Tags{"LightMode"="ForwardAdd"}
Blend One One
CGPROGRAM
#pragma multi_compile_fwdadd
//#pragma multi_compile_fwdadd指令保证Shader中访问正确的光照变量
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
//需要引入改文件,才能正确的访问到_LightMatrix0光照矩阵,对坐标进行转换
//以便对光照衰减纹理进行采样
#include "AutoLight.cginc"
fixed4 _Color;
fixed4 _SpecularColor;
float _Gloss;
struct a2v{
float4 vertex:POSITION;
float3 normal:NORMAL;
};
struct v2f{
float4 pos:SV_POSITION;
float3 worldPos:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
};
v2f vert(a2v v){
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
o.worldNormal=UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag(v2f i):SV_Target{
//Additional中去掉BasePass中的环境光、自发光、逐顶点光照和SH光照部分
//AddPass中处理的光源可能是非最亮平行光、点光源、聚光灯,因此计算光源的
//位置、方向、颜色、强度和衰减时,需要根据光源类型分别进行计算
#ifdef USING_DIRECTIONAL_LIGHT
fixed3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz);
#else
fixed3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz-i.worldPos.xyz);
#endif
//上述过程先判断处理的光源是否为平行光,如果是平行光,渲染引擎会定义USING_DIRECTIONAL_LIGHT
//若没有定义则说明不是平行光,光源位置通过运算得到
fixed3 worldNormal=normalize(i.worldNormal);
fixed3 worldViewDir=normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 diffuse=_LightColor0.rgb*_Color.rgb*max(0,dot(worldNormal,worldLightDir));
fixed3 halfDir=normalize(worldViewDir+worldLightDir);
fixed3 specularColor=_LightColor0.rgb*_SpecularColor.rgb*pow(saturate(dot(worldNormal,halfDir)),_Gloss);
//处理衰减过程
//针对其他光源类型,Unity使用一张纹理作为查找表,来得到片元着色器中的光照衰减值
//先得到光源空间下的坐标,使用该坐标进行采样得到衰减值
#ifdef USING_DIRECTIONAL_LIGHT
fixed atten=1.0;
#else
float3 lightCoord=mul(_LightMatrix0,float4(i.worldPos,1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#endif
return fixed4((diffuse+specularColor)*atten,1.0);
}
ENDCG
}
}
FallBack "Specular"
}
实例效果:
左侧没有AddPass的BlinnPong光照效果,右侧为增加上述AddPass的BlinnPhong光照效果,可以看出右侧可以接收场景中的点光源的影响。