角色实时阴影是游戏开发中比较常见的需求了,但是阴影的实现原理比较难懂,网上有很多关于阴影原理的解释和案例,可以研究一下,这里给出两种在unity中阴影的具体实现。

1.使用Untiy自带的实时阴影

unity自带的实时阴影锯齿比较严重,而且性能不高,一般只给主角使用,由于锯齿严重,可以把影子的质量调高,锯齿感就少了。有时使用unity引擎自带的阴影在game场景看不到,或者与scene场景看到的表现不一致,注意调整这几个地方。

1.光照的Inspector面板:阴影类型选择soft shadows

unity模糊阴影 unity阴影设置_List

2.地面的CastShadows选为On,ReceiveShadows勾选上

unity模糊阴影 unity阴影设置_List_02

3.Edit->PlaySetting->Quality  调整影子的质量 还有影子的距离等

unity模糊阴影 unity阴影设置_Soft_03

 

unity模糊阴影 unity阴影设置_#pragma_04

特别是影子的距离,要根据需要调整,距离越长,需要绘制的东西越多,性能消耗也越大。

 

调整完以上的几处地方,基本能调出一个比较好的阴影方案。但是由于想减少锯齿,把影子的质量调高,也会导致一个问题,就是影子由于太清晰,显得不够真实,这个时候就需要添加模糊效果。在这里推荐一个比较好用的阴影插件: Next-Gen Soft-Shadows 将unitypackage导入,之后简单设置这几个地方,阴影模糊效果就出来了。

 

首先在编辑器扩展Tools下找到Psychose Interactive 在出来的面板下Install Directional libraries

unity模糊阴影 unity阴影设置_#pragma_05

 

然后给场景光照添加NGSS_Directiona 调节NGSS_GLOBAL_SOFTNESS来控制阴影的迷糊程度

l

unity模糊阴影 unity阴影设置_Soft_06

 

这样基本模糊效果就出来了。截张效果图

unity模糊阴影 unity阴影设置_Soft_07

这个方案直接在unity引擎的实时光影的基础上改造的,仍然有unity实时阴影的缺点。

2.使用shader实现平面阴影

 

Shader "PlanarShadow/Player"
{
        Properties
        {
               _MainTex ("Texture", 2D) = "white" {}
               _ShadowInvLen ("ShadowInvLen", float) = 1.0 //0.4449261
               //_ShadowFalloff ("ShadowFalloff", float) = 0.1
               
        }
        
        SubShader
        {
               Tags{ "RenderType" = "Opaque" "Queue" = "Geometry+10" }
               LOD 100
               
               Pass
               {
                       CGPROGRAM
                       #pragma vertex vert
                       #pragma fragment frag
                       // make fog work
                       #pragma multi_compile_fog
                       
                       #include "UnityCG.cginc"
                       
                       struct appdata
                       {
                              float4 vertex : POSITION;
                              float2 uv : TEXCOORD0;
                       };
                       struct v2f
                       {
                              float2 uv : TEXCOORD0;
                              UNITY_FOG_COORDS(1)
                              float4 vertex : SV_POSITION;
                       };
                       sampler2D _MainTex;
                       float4 _MainTex_ST;
                       v2f vert (appdata v)
                       {
                              v2f o;
                              o.vertex = UnityObjectToClipPos(v.vertex);
                              o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                              UNITY_TRANSFER_FOG(o,o.vertex);
                              return o;
                       }
                       
                       fixed4 frag (v2f i) : SV_Target
                       {
                              // sample the texture
                              fixed4 col = tex2D(_MainTex, i.uv);
                              // apply fog
                              UNITY_APPLY_FOG(i.fogCoord, col);
                              return col;
                       }
                       
                       ENDCG
               }
               Pass
               {              
                       Blend SrcAlpha  OneMinusSrcAlpha
                       ZWrite Off
                       Cull Back
                       ColorMask RGB
                       
                       Stencil
                       {
                              Ref 0                  
                              Comp Equal                    
                              WriteMask 255          
                              ReadMask 255
                              //Pass IncrSat
                              Pass Invert
                              Fail Keep
                              ZFail Keep
                       }
                       
                       CGPROGRAM
                       
                       #pragma vertex vert
                       #pragma fragment frag
                       float4 _ShadowPlane;
                       float4 _ShadowProjDir;
                       float4 _WorldPos;
                       float _ShadowInvLen;
                       float4 _ShadowFadeParams;
                       float _ShadowFalloff;
                       
                       struct appdata
                       {
                              float4 vertex : POSITION;
                       };
                       struct v2f
                       {
                              float4 vertex : SV_POSITION;
                              float3 xlv_TEXCOORD0 : TEXCOORD0;
                              float3 xlv_TEXCOORD1 : TEXCOORD1;
                              
                       };
                       v2f vert(appdata v)
                       {
                              v2f o;
                              float3 lightdir = normalize(_ShadowProjDir);
                              float3 worldpos = mul(unity_ObjectToWorld, v.vertex).xyz;
                              // _ShadowPlane.w = p0 * n  // 平面的w分量就是p0 * n
                              float distance = (_ShadowPlane.w - dot(_ShadowPlane.xyz,  worldpos)) / dot(_ShadowPlane.xyz, lightdir.xyz);
                              worldpos = worldpos + distance * lightdir.xyz;
                              o.vertex = mul(unity_MatrixVP, float4(worldpos, 1.0));
                              o.xlv_TEXCOORD0 = _WorldPos.xyz;
                              o.xlv_TEXCOORD1 = worldpos;
                              return o;
                       }
                       
                       float4 frag(v2f i) : SV_Target
                       {
                              float3 posToPlane_2 = (i.xlv_TEXCOORD0 - i.xlv_TEXCOORD1);
                              float4 color;
                              color.xyz = float3(0.0, 0.0, 0.0);
                         
                              // 下面两种阴影衰减公式都可以使用(当然也可以自己写衰减公式)
                              // 1. 王者荣耀的衰减公式
                              color.w = (pow((1.0 - clamp(((sqrt(dot(posToPlane_2,  posToPlane_2)) * _ShadowInvLen) - _ShadowFadeParams.x), 0.0, 1.0)), _ShadowFadeParams.y) *  _ShadowFadeParams.z);
                              // 2. https://zhuanlan.zhihu.com/p/31504088 这篇文章介绍的另外的阴影衰减公式
                              //color.w = 1.0 - saturate(distance(i.xlv_TEXCOORD0,  i.xlv_TEXCOORD1) * _ShadowFalloff);
                              //color.w = 0.5;
                              return color;
                       }
                       
                       ENDCG
               }
        }
}

把shader赋给主角的材质,然后写C#脚本,在update中更新阴影的位置,赋给要添加阴影的物体。

private List<Material> mMatList = new List<Material>();
private void Awake()
    {
        SkinnedMeshRenderer[] renderlist = GetComponentsInChildren<SkinnedMeshRenderer>();
        foreach (var render in renderlist)
        {
         
            if (render == null)
                continue;
            mMatList.Add(render.material);
        }
    }
void Update()
    {
       
        UpdateShader();
    }
  private void UpdateShader()
    {
        //Vector4 worldpos = transform.position;
        Vector4 worldpos = new Vector4(transform.position.x, 0, transform.position.z);
        //Vector4 projdir = new Vector4(-0.06323785f, -0.9545552f, -0.2912483f, 1.0f);
        //mLight.transform.rotation = Quaternion.LookRotation(projdir);
        Vector4 projdir = mLight.transform.forward;
        foreach (var mat in mMatList)
        {
            if (mat == null)
                continue;
            mat.SetVector("_WorldPos", worldpos);
            mat.SetVector("_ShadowProjDir", projdir);
            mat.SetVector("_ShadowPlane", new Vector4(0.0f, 1.0f, 0.0f, 0.1f));
            mat.SetVector("_ShadowFadeParams", new Vector4(0.0f, 1.5f, 0.7f, 0.0f));
            mat.SetFloat("_ShadowFalloff", 1.35f);
        }
    }

这样也能实现一个不错的阴影效果。