前言:
阴影的实现方式之一是平面阴影,也就是假阴影,这种阴影适合应用到平面上,不适合不规则物体上(会穿帮)。但是这种阴影效率更高,因为根本不需要计算真实的光照与阴影信息,也是目前多数手游公司采用的一种阴影方案之一(王者荣耀等一大批)。
原理:
- 因为要在平面上模拟物体阴影,所以如果将投影光源与目标物体变换到平面坐标系中,然后根据相似三角形定理得到目标物体投影到平面的模型坐标空间中,并把目标的y轴压扁在平面坐标系中,然后给要投影的物体一个阴影颜色,就可以得到类似阴影的效果。
- 这种计算方法同时适合点光源与平行光,因为要在平面空间中应用三角形相似原理进行计算物体的投影点,所以需要两个uniform矩阵float4x4 WorldToGround与float4x4 GroundToWorld矩阵。这两个矩阵顾名思义分别将目标物体从世界坐标系转换到平面(地面)坐标系与从平面(地面)坐标系转换到世界坐标系。从外部应用GetCompent<Renderer>()获取相应矩阵传入shader即可。
- 在顶点着色器中将光线与物体顶点坐标变换到平面坐标空间,然后根据三角形相似定理得到平面顶点坐标值,然后将坐标值的y值指定为0,以点光源为例,相关计算代码如下:
//点光源
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;
- 将目标物体从平面坐标系中变换到世界坐标系,然后再用unity_WorldToObject变换到自身的模型坐标系中,最后用UnityObjectToClipPos(v.vertex)得到在裁剪标准坐标系中。
- 这样得到的结果有点问题就是精度造成的Z-fighting,用Offset -1,0给一个小的偏移即可;或者另外一种方式为最后渲染阴影并关闭深度测试ztest off。
- 另外,为了保证阴影一直在平面上,而不会出现在平面外部,可以使用stencil状态指令,在地面渲染像素的区域内才通过模板测试,当然需要在渲染地面的shader中给地面渲染过的像素对应的模板缓存值更改为1,如下:
地面:
Stencil {
Ref 1
Comp always
Pass replace
}
目标物体:
Stencil{
Ref 1
Comp equal
}
- 阴影一般有透明效果,可以使用blend状态实现。
- 最终代码:
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);
//点光源
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
}
}
}