参考: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.旋转我们可以通过 二维旋转矩阵 来计算 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:Always Comp 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,-2 CGPROGRAM #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 } } }
标签:开销,方式,阴影,worldPos,GroundY,float4,Shadow,v2f From: https://www.cnblogs.com/jeason1997/p/18304894