阴影实现 - 平面阴影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
        }
    }
}

 

posted @ 2023-05-10 23:58  yanghui01  阅读(273)  评论(0)    收藏  举报