首先,我们来了解渲染路径。之前我们在代码中设置标签:
该标签表示告诉Unity,该Pass使用前向渲染中的ForwardBase路径。而前向渲染路径还有一种路径叫ForwardAdd,分别对应了下文说到的Base Pass已经Addtional Pass。这里我们将讨论最常用的前向渲染路径,即ForwardBase和ForwardAdd。而Pass的LightMode标签还支持如下的渲染路径设置:
在处理多光源的光照时,我们需要在Base Pass中处理场景中最重要的平行光,而在Addtional Pass中处理额外的光源类型的光照结果,每个额外的光源会调用一次Addtional Pass。关于这2者有需要说明的几点如下:
- 在渲染设置中,除了设置Pass的标签外,还使用了#pragma multi_compile_fwdbase和#pragma multi_compile_fwdadd的编译指令,以得到我们在shader中使用光照衰减等正确的光照变量。
- Base Pass中渲染的平行光默认是支持阴影的(如果开启了光源的阴影功能),而Addtional Pass中渲染的光源在默认情况下是没有阴影效果的,即便我们在它的Light组件中设置了有阴影的shadow Type。但我们仍然可以在Addtional Pass中使用#pragma multi_compile_fwdadd_fullshadows代替#pragma multi_compile_fwdadd编译指令,为点光源和聚光灯开启阴影效果。
- 环境光和自发光也是在Base Pass中计算的,这是因为对于一个物体,环境光和自发光我们只需要计算一次即可。在Addtional Pass中再计算一次则会叠加多次的环境光和自发光。
- Addtional Pass中我们需要开启混合模式,防止覆盖Base Pass中的光照结果。一般使用Blend One One,其效果呈现为叠加
- 对于前向渲染路径来说,一个Unity Shader通常会定义一个Base Pass(Base Pass也可以多次定义,例如前文章九说到的双面渲染的半透明效果时)。一个Base Pass仅会执行一次(定义了多个Base Pass的情况除外),而一个Addtional Pass会根据影响该物体的其他逐像素光源的数目而多次调用,即每个逐像素光源会执行一次Addtional Pass。
注意:除平行光外,其他点光源或者聚光灯的RenderMode需要设置为Auto,否则该shader代码只会显示平行光的光照结果。
shader代码如下:
// Upgrade NOTE: replaced '_LightMatrix0' with 'unity_WorldToLight'
Shader "Custom/MultiLights"
{
Properties
{
_Gloss("Gloss", Range(8.0,256)) = 20
_Diffuse("Diffuse", Color) = (1,1,1,1)
_Specular("Specular", Color) = (1,1,1,1)
}
SubShader
{
//Opaque: 用于大多数着色器(法线着色器、自发光着色器、反射着色器以及地形的着色器)。
//Transparent : 用于半透明着色器(透明着色器、粒子着色器、字体着色器、地形额外通道的着色器)。
//TransparentCutout : 蒙皮透明着色器(Transparent Cutout,两个通道的植被着色器)。
//Background : Skybox shaders.天空盒着色器。
//Overlay : GUITexture, Halo, Flare shaders.光晕着色器、闪光着色器
Tags { "RenderType" = "Opaque" }
//Base Pass
Pass
{
//用于向前渲染,该Pass会计算环境光,最重要的平行光,逐顶点/SH光源和LightMaps
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//multi_compile_fwdbase指令可以保证我们在shader中使用光照衰减等光照变量可以被正确赋值
#pragma multi_compile_fwdbase
#include "Lighting.cginc"
float _Gloss;
float4 _Diffuse;
float4 _Specular;
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.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(UnityWorldSpaceLightDir(i.worldPos));
//获取世界空间下的视角方向的单位向量
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
//获取环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//漫反射光的计算公式
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
//环境光的计算公式
fixed3 halfDir = normalize(worldLightDir + worldViewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
//平行光的距离永远为1,不存在光照衰减
float atten = 1.0;
return fixed4(ambient + (diffuse + specular) * atten, 1.0);
}
ENDCG
}
//Addtional Pass
Pass
{
//用于向前渲染,该模式代表除场景中最重要的平行光之外的额外光源的处理,每个光源会调用该pass一次
Tags{"LightMode" = "ForwardAdd"}
//混合模式,表示该Pass计算的光照结果可以在帧缓存中与之前的光照结果进行叠加,否则会覆盖之前的光照结果
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//multi_compile_fwdadd指令可以保证我们在shader中使用光照衰减等光照变量可以被正确赋值
#pragma multi_compile_fwdadd
#include "Lighting.cginc"
#include "AutoLight.cginc"
float _Gloss;
float4 _Diffuse;
float4 _Specular;
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.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 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
#ifdef USING_DIRECTIONAL_LIGHT //平行光下可以直接获取世界空间下的光照方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else //其他光源下_WorldSpaceLightPos0代表光源的世界坐标,与顶点的世界坐标的向量相减可得到世界空间下的光照方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
#endif
//漫反射光的计算公式
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
//环境光的计算公式
fixed3 halfDir = normalize(worldLightDir + worldViewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
/*这段ifelse的代码可以直接使用unity内置宏UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
但是该结果表示光照衰减和该物体接收来自其他物体的阴影纹理的效果,阴影在下一章节有讲。
*/
#ifdef USING_DIRECTIONAL_LIGHT //平行光下不存在光照衰减,恒值为1
fixed atten = 1.0;
#else
#if defined (POINT) //点光源的光照衰减计算
//unity_WorldToLight内置矩阵,世界空间到光源空间变换矩阵。与顶点的世界坐标相乘可得到光源空间下的顶点坐标
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
//利用Unity内置函数tex2D对Unity内置纹理_LightTexture0进行纹理采样计算光源衰减,获取其衰减纹理,
//再通过UNITY_ATTEN_CHANNEL得到衰减纹理中衰减值所在的分量,以得到最终的衰减值
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#elif defined (SPOT) //聚光灯的光照衰减计算
float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
//(lightCoord.z > 0):聚光灯的深度值小于等于0时,则光照衰减为0
//_LightTextureB0:如果该光源使用了cookie,则衰减查找纹理则为_LightTextureB0
fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#else
fixed atten = 1.0;
#endif
#endif
//这里不再计算环境光,在上个Base Pass中已做计算
return fixed4((diffuse + specular) * atten, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}