原理就是将模型压扁之后绘制在需要接受阴影的物体上,这种方式十分高效,消耗很低。具体实现过程参考Unity Shader - Planar Shadow - 平面阴影。具按照自己的理解,其实就是根据光照方向计算片元在接受阴影的平面上的投影位置,然后绘制即可,这种方式还是只适合在平面上绘制阴影。
PlanarShadow.shader
Shader "Custom/PlanarShadow"
{
Properties
{
_Tint("_Tint", Color) = (1,1,1,1)
_MainTex("_MainTex (albedo)", 2D) = "white" {}
[Header(Alpha)]
[Toggle(_CLIPPING)] _Clipping ("Alpha Clipping", Float) = 1
_Cutoff("_Cutoff (Alpha Cutoff)", Range(0.0, 1.0)) = 0.5 // alpha clip threshold
[Header(Shadow)]
// _GroundHeight("_GroundHeight", Range(-100, 100)) = 0
_GroundHeight("_GroundHeight", Float) = 0
_ShadowColor("_ShadowColor", Color) = (0,0,0,1)
_ShadowFalloff("_ShadowFalloff", Range(0,1)) = 0.05
// Blending state
[HideInInspector] _SrcBlend("__src", Float) = 1.0
[HideInInspector] _DstBlend("__dst", Float) = 0.0
[HideInInspector] _ZWrite("__zw", Float) = 1.0
[HideInInspector] _Cull("__cull", Float) = 2.0
}
SubShader
{
Pass {
// 其他Pass请自行实现
}
// Planar Shadows平面阴影
Pass
{
Name "PlanarShadow"
//用使用模板测试以保证alpha显示正确
Stencil
{
Ref 0
Comp equal
Pass incrWrap
Fail keep
ZFail keep
}
Cull Off
//透明混合模式
Blend SrcAlpha OneMinusSrcAlpha
//关闭深度写入
ZWrite off
//深度稍微偏移防止阴影与地面穿插
Offset -1 , 0
CGPROGRAM
#pragma shader_feature _CLIPPING
#pragma shader_feature _ALPHATEST_ON
#pragma shader_feature _ALPHAPREMULTIPLY_ON
#include "UnityCG.cginc"
#pragma vertex vert
#pragma fragment frag
float _GroundHeight;
float4 _ShadowColor;
float _ShadowFalloff;
half4 _Tint;
sampler2D _MainTex;
float4 _MainTex_ST;
float _Clipping;
half _Cutoff;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float4 color : COLOR;
float2 uv : TEXCOORD0;
};
float3 ShadowProjectPos(float4 vertPos)
{
float3 shadowPos;
//得到顶点的世界空间坐标
float3 worldPos = mul(unity_ObjectToWorld , vertPos).xyz;
//灯光方向
// float3 lightDir = normalize(_LightDir.xyz);
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//阴影的世界空间坐标(低于地面的部分不做改变)
shadowPos.y = min(worldPos .y , _GroundHeight);
shadowPos.xz = worldPos .xz - lightDir.xz * max(0 , worldPos .y - _GroundHeight) / lightDir.y;
return shadowPos;
}
float GetAlpha (v2f i) {
float alpha = _Tint.a * tex2D(_MainTex, i.uv.xy).a;
return alpha;
}
v2f vert (appdata v)
{
v2f o;
//得到阴影的世界空间坐标
float3 shadowPos = ShadowProjectPos(v.vertex);
//转换到裁切空间
o.vertex = UnityWorldToClipPos(shadowPos);
//得到中心点世界坐标
float3 center = float3(unity_ObjectToWorld[0].w , _GroundHeight , unity_ObjectToWorld[2].w);
//计算阴影衰减
float falloff = 1-saturate(distance(shadowPos , center) * _ShadowFalloff);
//阴影颜色
o.color = _ShadowColor;
o.color.a *= falloff;
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
if (_Clipping)
{
float alpha = GetAlpha(i);
i.color.a *= step(_Cutoff, alpha);
}
return i.color;
}
ENDCG
}
}
// FallBack "Diffuse"
}
Projector Shadow是常用的实时阴影实现方式,其基本原理是通过摄像机将需要显示阴影的物体,渲染到一张RenderTexture(RT)上,记录下物体的颜色值(可设置为自定义颜色),并将RT关联到Projector组件的材质上;然后通过Projector组件将需要接收阴影的物体以Projector组件的材质再渲染一遍来实现阴影的显示。
可下载这个插件DynamicShadowProjector
结构是这样的