【UnityShader】平面阴影

平面阴影作为一种最简单的实时阴影实现,尽管其仅能局限于在完全平坦的地面的情况下使用,但由于其性能良好,在许多移动端手游中仍然可以发挥较强的使用价值。

平面阴影的实现原理比较简单,一般由如下几个过程:
1.向shader传入世间到平面的矩阵和平面到世界的矩阵
2.向shader传入灯光方向
3.在平面空间下根据灯光方向计算顶点在平面上的投影坐标,最终得到阴影

但这种方式存在一个问题:我们需要向shader传入至少两个矩阵,而实际上计算顶点沿某方向在平面上的交点要简单的多。

首先考虑以下情况,3D空间中存在某点o,以及平面P,其中P的法线为n,并且存在某点p为平面P上的一点,现在假设从o点沿d方向发射射线,求射线在平面P上的交点h,这时时间上就可以看成计算射线与平面的交点。

根据射线的参数方程:ray = ray.origin+t*ray.direction,即可以知道我们实际上只需求出t,即可确定该射线在平面上的交点:
h = o+t*d

【UnityShader】平面阴影_第1张图片
通过计算,得到最终的t:
t = (p – o)·n/(d·n) = (p·n–o·n)/(d·n)

该t表示顶点到平面上的投影位置的距离,且存在如下3中情况:
1.t>0,表示顶点在平面上存在投影点
2.t==0,表示顶点位于平面上
3.t<0,表示顶点在平面上没有投影点, 因此根据t值,我们可以方便的判断是否产生阴影。 而对于已知平面P,我们可以将其以向量方式表达:P=,其中d=n·p

通过以上推导,我们只需向shader传入一个平面的向量表达,即可方便求出平面阴影,而不需要向shader传入两个矩阵:

float4 _ShadowColor;
half4 _Plane;
half4 _LightDir;
v2f vert(appdata v)
{
     v2f o;
    float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
    float t = (_Plane.w - dot(worldPos.xyz, _Plane.xyz)) / dot(_LightDir.xyz, _Plane.xyz);
    worldPos.xyz = worldPos.xyz + t*_LightDir.xyz;
    o.vertex = mul(unity_MatrixVP, worldPos);
    o.col = _ShadowColor *step(0, t);
    return o;
}

【UnityShader】平面阴影_第2张图片
【UnityShader】平面阴影_第3张图片

完整实现如下:

Shader "Hidden/Shadow/FakeShadow/PlaneShadow"
{
    Properties
    {
        _ShadowCol("Color", color) = (1,1,1,1)
        _StencilID("StencilID", float) = 2
        _Plane("Plane", vector) = (1,1,1,1)
        _LightDir("LightDir", vector) = (1,1,1,1)
    }
    SubShader
    {
        Tags{ "RenderType" = "Transparent" "Queue" = "Transparent" }
 
                //Pass {  渲染非阴影部分 ...  }
 
        Pass
        {
            Stencil{
                Ref [_StencilID]
                Comp NotEqual
                Pass replace
            }
            zwrite off
            blend srcalpha oneminussrcalpha
            offset -1,-1
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fog
             
            #include "UnityCG.cginc"
 
            struct appdata
            {
                float4 vertex : POSITION;
            };
 
            struct v2f
            {
                UNITY_FOG_COORDS(0)
                float4 vertex : SV_POSITION;
                float4 col : COLOR;
            };
 
            float4 _ShadowCol;
            half4 _Plane;
            half4 _LightDir;
             
            v2f vert (appdata v)
            {
                v2f o;
                float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
                float t = (_Plane.w - dot(worldPos.xyz, _Plane.xyz)) / dot(_LightDir.xyz, _Plane.xyz);
                worldPos.xyz = worldPos.xyz + t*_LightDir.xyz;
                o.vertex = mul(unity_MatrixVP, worldPos);
 
                o.col = _ShadowCol *step(0, t);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }
             
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = i.col;
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}


更多内容: http://www.lsngo.net

你可能感兴趣的:(游戏开发,Unity,Shader)