阴影实现 - 平面阴影Planar Shadow
最终效果
1) 两个阴影重叠的时候,基本正常
2) 在斜坡上,阴影穿插了进去
3) 在平地上遇到障碍物,阴影也会穿插进障碍物
4) 在平台边缘,镂空的地方也显示了阴影
5) 没有做阴影衰减
UpdatePlanarShadowMatrix函数的原理
详解平面阴影 Planar Shadow(方向光篇) - 知乎 (zhihu.com)
using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; public class PlanarShadow : MonoBehaviour { public Transform m_DirLight; //需要根据平行光方向来计算阴影 public Transform m_Player; public Camera m_PlanarShadowCamera; //阴影绘制的相机, 一般和Player的相机相同 public float m_PlanerHeightOffset = 0.02f; private List<Renderer> m_Renderers = new List<Renderer>(); private CommandBuffer m_PlanarShadowCmd; private Material m_PlanarShadowMat; private Vector4 m_PlanarShadowProjectX = Vector4.zero; private Vector4 m_PlanarShadowProjectY = Vector4.zero; private Vector4 m_PlanarShadowProjectZ = Vector4.zero; private Plane m_PlanarShadowProjectPlane = new Plane(Vector3.up, 0); private List<Material> m_TempMatList = new List<Material>(); void Awake() { if (null == m_Player) m_Player = this.transform; if (null == m_PlanarShadowCamera) m_PlanarShadowCamera = Camera.main; } void Start() { GetComponentsInChildren<Renderer>(true, m_Renderers); m_PlanarShadowCmd = new CommandBuffer(); m_PlanarShadowCmd.name = "Planer Shadow Render Cmd"; m_PlanarShadowCamera.AddCommandBuffer(CameraEvent.BeforeForwardAlpha, m_PlanarShadowCmd); m_PlanarShadowMat = new Material(Shader.Find("My/Shadow/PlanarShadow")); } void Update() { if (null == m_DirLight) return; UpdatePlanarShadowMatrix(); m_PlanarShadowCmd.Clear(); for (var i = 0; i < m_Renderers.Count; ++i) { var renderer = m_Renderers[i]; if (null == renderer || !renderer.enabled) continue; m_TempMatList.Clear(); renderer.GetSharedMaterials(m_TempMatList); for (int submeshIndex = 0; submeshIndex < m_TempMatList.Count; submeshIndex++) { m_PlanarShadowCmd.DrawRenderer(renderer, m_PlanarShadowMat, submeshIndex); //用指定的材质绘制renderer } } } void UpdatePlanarShadowMatrix() { Vector3 center = m_Player.position; m_PlanarShadowProjectPlane.distance = -center.y - m_PlanerHeightOffset; var lightDir = m_DirLight.forward; float invNdotL = 1.0f / Vector3.Dot(m_PlanarShadowProjectPlane.normal, lightDir); m_PlanarShadowProjectX.x = 1 - m_PlanarShadowProjectPlane.normal.x * lightDir.x * invNdotL; m_PlanarShadowProjectX.y = -m_PlanarShadowProjectPlane.normal.y * lightDir.x * invNdotL; m_PlanarShadowProjectX.z = -m_PlanarShadowProjectPlane.normal.z * lightDir.x * invNdotL; m_PlanarShadowProjectX.w = -m_PlanarShadowProjectPlane.distance * lightDir.x * invNdotL; m_PlanarShadowProjectY.x = -m_PlanarShadowProjectPlane.normal.x * lightDir.y * invNdotL; m_PlanarShadowProjectY.y = 1 - m_PlanarShadowProjectPlane.normal.y * lightDir.y * invNdotL; m_PlanarShadowProjectY.z = -m_PlanarShadowProjectPlane.normal.z * lightDir.y * invNdotL; m_PlanarShadowProjectY.w = -m_PlanarShadowProjectPlane.distance * lightDir.y * invNdotL; m_PlanarShadowProjectZ.x = -m_PlanarShadowProjectPlane.normal.x * lightDir.z * invNdotL; m_PlanarShadowProjectZ.y = -m_PlanarShadowProjectPlane.normal.y * lightDir.z * invNdotL; m_PlanarShadowProjectZ.z = 1 - m_PlanarShadowProjectPlane.normal.z * lightDir.z * invNdotL; m_PlanarShadowProjectZ.w = -m_PlanarShadowProjectPlane.distance * lightDir.z * invNdotL; m_PlanarShadowMat.SetVector("_PlanarShadowMatrixX", m_PlanarShadowProjectX); m_PlanarShadowMat.SetVector("_PlanarShadowMatrixY", m_PlanarShadowProjectY); m_PlanarShadowMat.SetVector("_PlanarShadowMatrixZ", m_PlanarShadowProjectZ); } }
Shader "My/Shadow/PlanarShadow" { Properties { _ShadowColor("ShadowColor", Color) = (0, 0, 0, 0.2) } SubShader { Tags { "RenderType" = "Transparent" "Queue" = "Transparent-1"} LOD 100 Pass { //处理阴影重叠 Stencil { Ref 1 Comp notequal Pass replace } Blend SrcAlpha OneMinusSrcAlpha, Zero One Cull Back ZWrite Off CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; float4 _PlanarShadowMatrixX; float4 _PlanarShadowMatrixY; float4 _PlanarShadowMatrixZ; half4 _ShadowColor; v2f vert(appdata v) { v2f o; float4 worldPos = mul(unity_ObjectToWorld, v.vertex); //矩阵变换就是矩阵行和列向量点乘 float worldPosX = dot(_PlanarShadowMatrixX, worldPos); float worldPosY = dot(_PlanarShadowMatrixY, worldPos); float worldPosZ = dot(_PlanarShadowMatrixZ, worldPos); o.vertex = mul(UNITY_MATRIX_VP, float4(worldPosX, worldPosY, worldPosZ, 1)); //世界空间转裁剪空间 return o; } fixed4 frag(v2f i) : SV_Target { return _ShadowColor; } ENDCG } } }