前言:
陰影的實(shí)現(xiàn)方式之一是平面陰影壕鹉,也就是假陰影,這種陰影適合應(yīng)用到平面上你踩,不適合不規(guī)則物體上(會(huì)穿幫)歉闰。但是這種陰影效率更高,因?yàn)楦静恍枰?jì)算真實(shí)的光照與陰影信息,也是目前多數(shù)手游公司采用的一種陰影方案之一(王者榮耀等一大批)。
原理:
- 因?yàn)橐谄矫嫔夏M物體陰影,所以如果將投影光源與目標(biāo)物體變換到平面坐標(biāo)系中萌朱,然后根據(jù)相似三角形定理得到目標(biāo)物體投影到平面的模型坐標(biāo)空間中,并把目標(biāo)的y軸壓扁在平面坐標(biāo)系中呆贿,然后給要投影的物體一個(gè)陰影顏色嚷兔,就可以得到類似陰影的效果。
- 這種計(jì)算方法同時(shí)適合點(diǎn)光源與平行光做入,因?yàn)橐谄矫婵臻g中應(yīng)用三角形相似原理進(jìn)行計(jì)算物體的投影點(diǎn)冒晰,所以需要兩個(gè)uniform矩陣float4x4 WorldToGround與float4x4 GroundToWorld矩陣。這兩個(gè)矩陣顧名思義分別將目標(biāo)物體從世界坐標(biāo)系轉(zhuǎn)換到平面(地面)坐標(biāo)系與從平面(地面)坐標(biāo)系轉(zhuǎn)換到世界坐標(biāo)系竟块。從外部應(yīng)用GetCompent<Renderer>()獲取相應(yīng)矩陣傳入shader即可壶运。
- 在頂點(diǎn)著色器中將光線與物體頂點(diǎn)坐標(biāo)變換到平面坐標(biāo)空間,然后根據(jù)三角形相似定理得到平面頂點(diǎn)坐標(biāo)值浪秘,然后將坐標(biāo)值的y值指定為0蒋情,以點(diǎn)光源為例,相關(guān)計(jì)算代碼如下:
//點(diǎn)光源
float4 pointLightGroundPos = mul(World2Ground,pointLightWorldPos);
float3 lightDir;
float4 vt;
vt = mul(unity_ObjectToWorld,v.vertex);
vt = mul(World2Ground,vt);
lightDir = normalize((vt-pointLightGroundPos).xyz);
vt.xz = vt.xz - (lightDir.xz * vt.y)/lightDir.y;
vt.y = 0;
- 將目標(biāo)物體從平面坐標(biāo)系中變換到世界坐標(biāo)系耸携,然后再用unity_WorldToObject變換到自身的模型坐標(biāo)系中棵癣,最后用UnityObjectToClipPos(v.vertex)得到在裁剪標(biāo)準(zhǔn)坐標(biāo)系中。
- 這樣得到的結(jié)果有點(diǎn)問題就是精度造成的Z-fighting夺衍,用Offset -1狈谊,0給一個(gè)小的偏移即可;或者另外一種方式為最后渲染陰影并關(guān)閉深度測試ztest off沟沙。
- 另外河劝,為了保證陰影一直在平面上,而不會(huì)出現(xiàn)在平面外部矛紫,可以使用stencil狀態(tài)指令赎瞎,在地面渲染像素的區(qū)域內(nèi)才通過模板測試,當(dāng)然需要在渲染地面的shader中給地面渲染過的像素對應(yīng)的模板緩存值更改為1颊咬,如下:
地面:
Stencil {
Ref 1
Comp always
Pass replace
}
目標(biāo)物體:
Stencil{
Ref 1
Comp equal
}
- 陰影一般有透明效果务甥,可以使用blend狀態(tài)實(shí)現(xiàn)牡辽。
- 最終代碼:
Shader "FFD/MyPlanerShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" "RenderQueue" = "Transparent"}
LOD 100
Pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "unitycg.cginc"
#include "lighting.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
v2f vert(appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
o.normal = mul(v.normal,(float3x3)unity_WorldToObject);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex,i.uv);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
float3 normal = normalize(i.normal);
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.xyz * saturate(dot(normal,lightDir));
return fixed4(col.xyz*(ambient+diffuse),1);
}
ENDCG
}
Pass
{
Tags{"LightMode" = "ForwardBase" }
Blend SrcAlpha OneMinusSrcAlpha
Offset -1,0
Stencil{
Ref 1
Comp equal
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
uniform float4x4 World2Ground;
uniform float4x4 Ground2World;
uniform float4 MainColor;
uniform float4 pointLightWorldPos;
v2f vert (appdata v)
{
v2f o;
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
//平行光
// float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
// lightDir = mul((float3x3)World2Ground,lightDir);
// lightDir = normalize(lightDir);
//點(diǎn)光源
float4 pointLightGroundPos = mul(World2Ground,pointLightWorldPos);
float3 lightDir;
float4 vt;
vt = mul(unity_ObjectToWorld,v.vertex);
vt = mul(World2Ground,vt);
lightDir = normalize((vt-pointLightGroundPos).xyz);
vt.xz = vt.xz - (lightDir.xz * vt.y)/lightDir.y;
vt.y = 0;
o.vertex = mul(Ground2World,vt);
o.vertex = mul(unity_WorldToObject,o.vertex);
o.vertex = UnityObjectToClipPos(o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
col = fixed4(0.3,0.3,0.3,0.3);
return col;
}
ENDCG
}
}
}