参考:Unity无光照假阴影Shader实现及常见问题总结 - 简书 (jianshu.com)
游戏实现阴影的常见处理方式 (动态人或物,非烘焙)
1.实时光照
实时光照属于真阴影,一般来说效果是最好的,但是开销也是最大的。 Shadow Map(阴影贴图)跟Soft Shadows(软阴影) - JeasonBoy - 博客园 (cnblogs.com)
2.脚底放置阴影面片模拟阴影
一般是无光照小型游戏的常见解决方案,开销较小,表现形式较差,面片是死的,无法根据人物动作变化
3.通过顶点shader变换成面片模拟阴影
如上图所示
优点 : 表现形式上比方案2强,阴影可跟随顶点动画,开销比实时阴影要少
缺点 : 无法在 "非平面" 使用,比如在斜坡上,会穿帮
4.通过 Projector 或者 Decal 来模拟投射阴影
优点 : 表现效果更近一步,也可以在斜面上进行投影了
缺点 : 开销也更近一步
方式3实现思路
1.我们通过2个Pass来渲染,第二个Pass正常渲染角色,第一个Pass模拟渲染阴影
2.我们需要将模型的所有 Y 值压到地面高度,这样就形成了一个头顶俯视图的阴影效果
3.我们再对 XZ 方向进行偏移,偏移量根据模型原先 Y 值高度为参考做插值
4.阴影的方向我们规定在 XZ 平面上 (X=0,Z=1) 为初始默认方向,以这个向量为基准进行旋转
5.旋转我们可以通过 二维旋转矩阵 来计算
1.我们通过2个Pass来渲染,第二个Pass正常渲染角色,第一个Pass模拟渲染阴影
2.我们需要将模型的所有 Y 值压到地面高度,这样就形成了一个头顶俯视图的阴影效果
3.我们再对 XZ 方向进行偏移,偏移量根据模型原先 Y 值高度为参考做插值
4.阴影的方向我们规定在 XZ 平面上 (X=0,Z=1) 为初始默认方向,以这个向量为基准进行旋转
5.旋转我们可以通过 二维旋转矩阵 来计算
Shader代码
Shader "loom/fake_shadow_test_pass_order" {Properties{//材质属性面板_MainTex ("主贴图",2D) = "white"{}_GroundY ("地面Y高度 (外部传入)",float) = 0_Shadow_Color("影子颜色",Color) = (1,1,1,1)_Shadow_Length("影子长度",float) = 0_Shadow_Rotated("影子旋转角度",range(0,360)) = 0}SubShader{Tags{"Queue" = "Geometry+1" //注意这里很重要,因为影子是要绘制在地面上,所以地面必须应该先绘制,否则blend混合的时候就是和背后的skybox进行混合了 }pass{Stencil{Ref 1//Comp取值依次为 0:Disabled 1:Never 2:Less 3:Equal 4:LessEqual 5:Greater 6:NotEqual 7:GreaterEqual 8:AlwaysComp Greater //或者改成NotEqual//Pass取值依次为 0:Keep 1:Zero 2:Replace 3:IncrementSaturate 4:DecrementSaturate 5:Invert 6:IncrementWrap 7:DecrementWrap Pass Replace}Blend SrcAlpha oneMinusSrcAlpha //因为和地面重叠所以做个偏移//也可以不做偏移,将传入的地面高度抬高一点即可Offset -2,-2CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"struct appdata{float4 vertex : POSITION;};struct v2f{float4 pos : SV_POSITION;//这里worldPos一定是float4,因为vert()中实际是手动两次空间变换如果是float3会导致w分量丢失,透视除法会出错//如果不参与变换,只是传到frag()中使用的话,比如进行Blinn-Phong光照计算V向量那么float3就够了 float4 worldPos : TEXCOORD0;//做阴影插值和Clip地面以下阴影用float cacheWorldY : TEXCOORD1;};half _GroundY;half4 _Shadow_Color; half _Shadow_Length; half _Shadow_Rotated;v2f vert(appdata v){v2f o = (v2f)0;//获取世界空间的位置o.worldPos = mul(unity_ObjectToWorld,v.vertex);//缓存世界空间下的y分量,后续两点作用//第一点 : 做插值用做计算xz的偏移量的多少//第二点 : 防止在地面以下o.cacheWorldY = o.worldPos.y;//设置世界空间下y的值全部都设置为传入的地面高度值o.worldPos.y = _GroundY;//根据世界空间下模型y值减去传入的地面高度值_GroundY//以这个值为传入 lerp(0,_Shadow_Length) 进行线性插值//最后获取到模型y值由低到高的插值lerpVal//这个max()函数 假设腿部在地面以下则裁切掉腿部阴影,后续使用clip后无需Max//half lerpVal = lerp(0,_Shadow_Length,max(0,o.cacheWorldY-_GroundY));half lerpVal = lerp(0,_Shadow_Length,o.cacheWorldY-_GroundY);//常量PI//const float PI = 3.14159265;//角度转换成弧度half radian = _Shadow_Rotated / 180.0 * UNITY_PI;//旋转矩阵,对(0,1)向量进行旋转,计算旋转后的向量,该向量就是阴影方向//2D旋转矩阵如下// [x] [ cosθ , -sinθ ]// [ ] 乘以 // [y] [ sinθ , cosθ ]// x' = xcosθ - ysinθ// y' = xsinθ + ycosθhalf2 ratatedAngle = half2((0*cos(radian)-1*sin(radian)),(0*sin(radian)+1*cos(radian)));//用以y轴高度为参考计算的插值 lerpVal 去 乘以一个旋转后的方向向量,作为阴影的方向//最终得到偏移后的阴影位置o.worldPos.xz += lerpVal * ratatedAngle;//变换到裁剪空间o.pos = mul(UNITY_MATRIX_VP,o.worldPos);return o;}fixed4 frag(v2f i) : SV_TARGET{//剔除低于地面部分的片段clip(i.cacheWorldY - _GroundY);//用作阴影的Pass直接输出颜色即可return _Shadow_Color;}ENDCG}pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"sampler2D _MainTex;half4 _MainTex_ST;struct appdata{float4 vertex : POSITION;float2 uv0 : TEXCOORD0;};struct v2f{float4 pos : SV_POSITION;float2 uv : TEXCOORD0;};v2f vert(appdata v){v2f o = (v2f)0;o.pos = UnityObjectToClipPos(v.vertex);o.uv = TRANSFORM_TEX(v.uv0,_MainTex);return o;}fixed4 frag(v2f i) : SV_TARGET{return tex2D(_MainTex,i.uv);}ENDCG}} }