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);
}
}