效果图:
代码:
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);
//计算环境光且只计算一次,在additionalPass中不会再计算
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//处理了场景中的最重要的平行光。在这个例子中,场景中只有一个平行光。如果场景中包含了多个平行光,Unity 会选择最亮的平行光传递给Base Pass进行逐像素处理,其他平行光会按照逐顶点或在Additional Pass中按逐像素的方式处理。
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
// Apparently need to add this declaration
#pragma multi_compile_fwdadd
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.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);
#ifdef USING_DIRECTIONAL_LIGHT
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
#endif
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);
#ifdef USING_DIRECTIONAL_LIGHT
fixed atten = 1.0;
#else
#if defined (POINT)
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#elif defined (SPOT)
float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
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
return fixed4((diffuse + specular) * atten, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
BasePass:
Tags { "LightMode"="ForwardBase" } #pragma multi_compile_fwdbase
需要注意的是,我们除了设置渲染路径外,还使用了#pragma编译指令。#pragma multi_compile_ fwdbase 指令可以保证我们在Shader中使用光照衰减等光照变量可以被正确赋值。这是不可缺少的。
对于Base Pass来说,它处理的逐像素光源类型一定是平行光。我们可以使用_WorldSpaceLightPos0 来得到这个平行光的方向(位置对平行光来说没有意义),使用_LightColor0 来得到它的颜色和强度( _LightColor0 已经是颜色和强度相乘后的结果),由于平行光可以认为是没有衰减的,因此这里我们直接令衰减值为1.0。相关代码如下:
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
fixed atten = 1.0;
AdditionalPass:
Tags { "LightMode"="ForwardAdd" } Blend One One #pragma multi_compile_fwdadd #include "AutoLight.cginc"
除了设置渲染路径标签外,我们同样使用了#pragma multi_ compile_ fwdadd 指令,如前面所说,这个指令可以保证我们在Additional Pass中访问到正确的光照变量。与Base Pass不同的是,我们还使用Blend命令开启和设置了混合模式。这是因为,我们希望Additional Pass计算得到的光照结果可以在帧缓存中与之前的光照结果进行叠加。如果没有使用Blend命令的话,AdditionalPass会直接覆盖掉之前的光照结果。
计算不同光源的光照方向:
#ifdef USING_DIRECTIONAL_LIGHT
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
#endif
在上面的代码中,我们首先判断了当前处理的逐像素光源的类型,这是通过使用#ifdef指令判断是否定义了USING_ DIRECTIONAL LIGHT来得到的。如果当前前向渲染Pass处理的光源类型是平行光,那么Unity的底层渲染引擎就会定义USING_ DIRECTIONAL_ LIGHT。 如果判断得知是平行光的话,光源方向可以直接由_ WorldSpaceLightPos0.xyz 得到;如果是点光源或聚光灯,那么_WorldSpaceLightPos0.xyz 表示的是世界空间下的光源位置,而想要得到光源方向的话,我们就需要用这个位置减去世界空间下的顶点位置。
计算不同光源的衰减:
#ifdef USING_DIRECTIONAL_LIGHT
fixed atten = 1.0;
#elif defined (POINT)
float3 lightCoord = mul(unity_WorldToLight,float4(i.worldPos,1)).xyz;
fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL;
#elif defined (SPOT)
float4 lightCoord = mul(unity_WorldToLight,float4(i.worldPos,1)).xyz;
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
我们同样通过判断是否定义了USINGDIRECTIONALLIGHT来决定当前处理的光源类型。如果是平行光的话,衰减值为1.0。如果是其他光源类型,那么处理更复杂一些。尽管我们可以使用数学表达式来计算给定点相对于点光源和聚光灯的衰减,但这些计算往往涉及开根号、除法等计算量相对较大的操作,因此Unity选择了使用一张纹理作为查找表(Lookup Table, LUT),以在片元着色器中得到光源的衰减。我们首先得到光源空间下的坐标,然后使用该坐标对衰减纹理进行采样得到衰减值。