Shader "fake_shadow" { Properties { //材质属性面板 _MainTex ("主贴图",2D) = "white"{} _GroundY ("地面Y高度 (外部传入)",float) = 0 _Shadow_Color("影子颜色",Color) = (1,1,1,1) _Shadow_Length("影子长度",float) = 0 _Shadow_Rotated("影子旋转角度",float) = 0 } SubShader { Tags { "Queue" = "Transparent+5" /*"RenderType"="Transparence" "RenderPipeline" = "UniversalPipeline" *///注意这里很重要,因为影子是要绘制在地面上,所以地面必须应该先绘制,否则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 NotEqual //或者改成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; float worldPosY : TEXCOORD2; }; // CBUFFER_START CBUFFER_START(UnityPerMaterial) half _GroundY; half4 _Shadow_Color; half _Shadow_Length; half _Shadow_Rotated; CBUFFER_END // CBUFFER_END v2f vert(appdata v) { v2f o = (v2f)0; //获取世界空间的位置 o.worldPos = mul(unity_ObjectToWorld,v.vertex); //缓存世界空间下的y分量,后续两点作用 //第一点 : 做插值用做计算xz的偏移量的多少 //第二点 : 防止在地面以下 o.cacheWorldY = o.worldPos.y; o.worldPosY = UnityObjectToWorldNormal(v.vertex).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 } } }
using UnityEngine; public class SetShadowRotationAndHeight : MonoBehaviour { public int materialIndex = 2; // 设置材质索引 [SerializeField] private Transform rootTransform = null; //创建模型时设置 private Renderer rend = null; private bool isFindRend = false; private float extend = 30f; private Transform lightTrans=null; void Awake() { if (GetComponent<MeshFilter>() != null) { Mesh mesh = GetComponent<MeshFilter>().mesh; Bounds bd = mesh.bounds; bd.Expand(extend); mesh.bounds = bd; } else if(GetComponent<SkinnedMeshRenderer>() != null) { //SkinnedMeshRenderer skinnedMesh = GetComponent<SkinnedMeshRenderer>(); //Bounds bd = skinnedMesh.bounds; //bd.Expand(extend); //skinnedMesh.bounds = bd; } } public void SetRootTransFrom(Transform root) { rootTransform = root; //GameObject mainLightObj = GameObject.Find("MainLight_Day"); //if(mainLightObj==null) //{ // mainLightObj = GameObject.Find("FillLight_Day"); //} //if (mainLightObj != null) // lightTrans = mainLightObj.transform; } public Renderer GetTransformRenderer() { if (rend != null) return rend; if (isFindRend) return null; rend= GetComponent<Renderer>(); isFindRend = true; return rend; } bool isDebug = false; void Update() { //if (lightTrans == null) //{ // if(!isDebug) // { // Debug.Log("找不到光源"); // isDebug = true; // } // return; //} if (rootTransform == null) { rootTransform = transform; } Renderer tempRend = GetTransformRenderer(); if (tempRend == null) return; // 确保指定的材质索引有效 if (materialIndex < 0 || materialIndex >= tempRend.materials.Length) { Debug.LogError(transform.name + "Invalid material index!"); return; } Material mat = tempRend.materials[materialIndex]; // 获取指定索引的材质 // 获取父节点的 Y 坐标 float parentY = rootTransform.position.y + 0.2f; // 将父节点的 Y 坐标传递给指定索引的材质的 _GroundY 属性 mat.SetFloat("_GroundY", parentY); // 获取主光源的方向 if (RenderSettings.sun == null) return; Vector3 lightDirection = -RenderSettings.sun.transform.forward; //Vector3 lightDirection = -lightTrans.forward; // 计算主光源的旋转角度的 Y 值(使用 Atan2 函数) float shadowRotationY = Mathf.Atan2(lightDirection.x, lightDirection.z) * Mathf.Rad2Deg; // 将主光源的旋转角度的 Y 值传递给指定索引的材质的 _Shadow_Rotated 属性 mat.SetFloat("_Shadow_Rotated", shadowRotationY); } }