ScreenSpaceShadowMask Blur推荐

ScreenSpaceShadowMask Blur推荐

在移动游戏中渲染阴影时,我们通常使用低分辨率的ShadowMap来降低内存,其不足之处就是锯齿明显。对此,我们推荐大家使用Unity ScreenSpaceShadowMask Blur技术来增加阴影质量,消除锯齿感。

这是侑虎科技第106篇原创文章,感谢作者赵忠健(QQ:963922432)供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)

同时,作者也是U Sparkle活动参与者哦,UWA欢迎更多开发朋友加入 USparkle开发者计划,这个舞台有你更精彩!


目的

Unity自带的阴影功能ShadowMap占用内存过大,1024 * 1024占用8MB。而我们项目内存吃紧,所以只能选用512 * 512,如下图,带有明显的锯齿感,效果差了很多。

请输入图片描述
如果您也有类似的需求,这时就可以采用 Unity ScreenSpaceShadowMask Blur。原理就是在屏幕空间里对已经投射完阴影的物体进行高斯模糊,以此来增加阴影质量,减少锯齿感。(PS:Unity提供的软阴影应该是PCF,但是效果并不明显)。

经过 ScreenSpaceShadowMask Blur后,效果如下:
请输入图片描述


具体实现步骤

一、 渲染流程

ShadowCamera的ShadowPass
->ShadowCamera 正常Pass
->BlurPass
->Main Camera Pass
->全屏片

二、详细分析

1.ShadowPass要产生ShadowMap,只需让ShadowCamera能看到投射阴影和接收阴影的物体,同时把那些阴影开关打开,Unity就会自动加入该Pass。(PS:如果物体是自己写的Vertext Fragement Shader,则需要自己加入Shadow Caster Pass,调用Unity内置即可。文件在UnityStandardShadow.cginc里)。

Shadow Camera在渲染前会统一替换成一个简单的Shader,只需包含投射跟接收阴影2个Pass即可。这样既可以减少Shader复杂度,又可以合并Draw Call。

2.正常Pass就是正常渲染阴影,比较ShadowMap与自己的深度值。我们将ShadowCamera清屏色设置成黑色,最终效果为有阴影的地方有颜色,其余地方为黑色。(颜色值会当成最后的Alpha值来处理)。

请输入图片描述
3.Blur Pass可以用CommandBuffer,也可以用OnPostRender。输入参数为ShaderCamera的Rendertarget,通过Blit来进行高斯模糊。(建议尽量少用CommandBuffer。我采用CommandBuffer,在小米1s上 RT->blurRT->RT会出现抖动现象,OnPostRender并没有。但是这两者原理应该是一样的,通过GPA查看,代码都是一样的。暂时不清楚其原因,欢迎有兴趣的朋友和我交流)。

4.Main Camera Pass 正常渲染场景。原则上渲染的物体是不会接收阴影的。

5.全屏可以通过CommandBuffer,也可以通过OnPostRender来处理。输入参数为上次经过模糊的RT值。Alpha、阴影颜色、透明度这些参数值自己写两个进行调节即可。最终渲染流程:
请输入图片描述


消耗统计

1. 内存消耗

(1) ShadowMap:512*512 2MB
(2) ShadowCamera RT 1/4屏 带16位深度
(3) Blur RT 1/4屏 不带深度
请输入图片描述
这是小米4c上的数据。

2. 渲染消耗统计

(1)多一个相机投射阴影体,接收阴影体多渲染一次;
(2)2次Blur操作;
(3)1次全屏blit操作;
主要对于带宽影响较大的物体影响较重。


总结

1.CommandBuffer 更自由,比如很多点都可以插入自定义行为,比如对ShadowMap进行操作等。虽然稳定性不佳,性能还是可以的。

2.如果一个Camera被设成RT,它会在其他相机渲染之前渲染,不受Depth影响。


相应资源

1.ShadowPass Shader

Shader "Shadow/ShadowMask" {
    Properties 
    {
    }
    SubShader 
    {
        Tags { "RenderType"="Geometry" }
        Pass 
        {
            Tags {"LightMode"="ForwardBase"} 
            CGPROGRAM
            #pragma multi_compile_fwdbase
            #pragma vertex   vertForwardBase
            #pragma fragment fragForwardBase
            struct appdata_input 
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 pos          : SV_POSITION;
                LIGHTING_COORDS(1,2)
            };

            v2f vertForwardBase (appdata_input v)
            {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                TRANSFER_VERTEX_TO_FRAGMENT(o);
                return o;
            }

            fixed4 fragForwardBase (v2f i) : SV_Target
            {
                fixed4 col = fixed4(1,0,0,1);
                fixed attenuation = LIGHT_ATTENUATION(i);
                col = col * (1-attenuation);
                return col;
            }
            ENDCG
        }
        Pass 
        {
            Name "ShadowCaster"
            Tags { "LightMode" = "ShadowCaster" }
            
            Fog {Mode Off}
            ZWrite On ZTest LEqual Cull Off
            Offset 1, 1
    
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_shadowcaster
            #pragma fragmentoption ARB_precision_hint_fastest
            #include "UnityCG.cginc"
    
            struct v2f { 
                V2F_SHADOW_CASTER;
            };
    
            v2f vert( appdata_base v )
            {
                v2f o;
                TRANSFER_SHADOW_CASTER(o)
                return o;
            }
    
            float4 frag( v2f i ) : COLOR
            {
                SHADOW_CASTER_FRAGMENT(i)
            }
            ENDCG
        }
     }
}

2. 高斯模糊Shader

3. 全屏blit shader

Shader "BlitQuad1"
{
    Properties
    {
        _MainTex("MainTexture",2D) = "white"{}
        _Color("ShadowColor",Color) = (0,0,0,1)
        _Intension("Intension",Float) = 0.6
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" }

        Blend SrcAlpha OneMinusSrcAlpha 
        zwrite off

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            half4 _MainTex_TexelSize;
            float _Intension;
            fixed _Color;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = v.vertex;
                o.uv = v.uv;
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                half4 color = half4(1,1,1,1) * _Color;
                half4 alpha = tex2D(_MainTex,i.uv);
                color.a = alpha.r * _Intension;
                return color;
            }
            ENDCG
        }
    }
}

C#脚本

1. ShadowCamera挂载实现 注释代码为CommandBuffer

using UnityEngine;
using UnityEngine.Rendering;
using System.Collections;

public class ShadowMask : MonoBehaviour
{
    //public int maxBlurCount = 2;
    public int sampleOffset = 2;

    private const float UIWIDTH = 1136f;
    private const int MAX_VALUE = 2048;
    private int screenWidth;
    private int screenHeight;

    public RenderTexture  renderTexture;
    public RenderTexture blurRT;

    //private RenderTargetIdentifier rtl;
    //private RenderTargetIdentifier blurRtl;

    //private CommandBuffer blurCommand;
    //private CommandBuffer screenQuadCommand;

    private Material material1;
    private Material material2;

    public Camera shadowCamera;
    //public Camera mainCamera;

    private int SCALE = 2;

    //private int pass = 0;
    //private bool hasAdd = false;

    void Awake()
    {
        RenderTextureFormateSupportTool.GetInstance().Init();

        screenWidth = Mathf.Min(Screen.width, MAX_VALUE);
        screenHeight = Mathf.Min(Screen.height, MAX_VALUE);

        renderTexture = RenderTextureFormateSupportTool.GetInstance().GetRenderTexture(screenWidth / SCALE, screenHeight / SCALE, 16, RenderTextureFormateSupportTool.H3DRenderTextureFormate.R8);

        renderTexture.name = "RenderTexture";

        blurRT = RenderTextureFormateSupportTool.GetInstance().GetRenderTexture(screenWidth / SCALE, screenHeight / SCALE,0, RenderTextureFormateSupportTool.H3DRenderTextureFormate.R8);

        blurRT.name = "BlurRT";

        float aspectValue = shadowCamera.aspect;
        shadowCamera.targetTexture = renderTexture;
        shadowCamera.aspect = aspectValue;


        Shader shadowShader = Shader.Find("Shadow/ShadowMask");
        shadowCamera.SetReplacementShader(shadowShader, "");

        //blurCommand = new CommandBuffer();
        //blurCommand.name = "BlurShadowMask";

        //screenQuadCommand = new CommandBuffer();
        //screenQuadCommand.name = "ScreenQuad";

        //rtl = new RenderTargetIdentifier(renderTexture);
        //blurRtl = new RenderTargetIdentifier(blurRT);
 
        Shader blurShader = Shader.Find("Shadow/ShadowMaskBlur");
        material1 = new Material(blurShader);
        material2 = new Material(blurShader);
    }

    void OnPostRender()
    {
        material1.SetTexture("_MainTexture", renderTexture);
        material1.SetInt("_Horizontal", 0);
        material1.SetInt("_SampleOffset", sampleOffset);
        Graphics.Blit(renderTexture, blurRT, material1);


        material2.SetTexture("_MainTexture", blurRT);
        material2.SetInt("_Horizontal", 1);
        material2.SetInt("_SampleOffset", sampleOffset);
        Graphics.Blit(blurRT, renderTexture, material2);
    }

    //void Update()
    //{
    //    if (hasAdd)
    //    {
    //        blurCommand.Clear();
    //    }
    //    else
    //    {
    //        shadowCamera.AddCommandBuffer(CameraEvent.AfterEverything, blurCommand);
    //        hasAdd = true;
    //    }

    //    for (int i = 0; i < maxBlurCount; i++)
    //    {
    //        if (i % 2 == 0)
    //        {
    //            material1.SetTexture("_MainTexture", renderTexture);
    //            material1.SetInt("_Horizontal", pass % 2);
    //            material1.SetInt("_SampleOffset", sampleOffset);
    //            blurCommand.Blit(rtl, blurRtl, material1);
    //        }
    //        else
    //        {
    //            material2.SetTexture("_MainTexture", blurRT);
    //            material2.SetInt("_Horizontal", pass % 2);
    //            material2.SetInt("_SampleOffset", sampleOffset);
    //            blurCommand.Blit(blurRtl, blurRt2, material2);
    //        }
    //        pass++;
    //    }
    //}
}

2.mainCamera挂载实现 全屏blit操作

using UnityEngine;
using System.Collections;

public class MainCameraTest : MonoBehaviour 
{

    public ShadowMask shadowMask;
    public float shadowIntensity = 1f;
    public Color shadowColor = Color.black;


    private Material screenQuadMaterial;
    private Mesh screenQuadMesh;
    private Matrix4x4 matrix;
    public void Start()
    {
        Shader screenQuadShader = Shader.Find("BlitQuad");
        screenQuadMaterial = new Material(screenQuadShader);
        screenQuadMaterial.SetTexture("_MainTex", shadowMask.renderTexture);

        screenQuadMesh = new Mesh();
        Vector3[] verts = new Vector3[4];
        verts[0] = new Vector3(-1, -1, 0);
        verts[1] = new Vector3(-1, 1, 0);
        verts[2] = new Vector3(1, 1, 0);
        verts[3] = new Vector3(1, -1, 0);
        screenQuadMesh.vertices = verts;
        Vector2[] uvs = new Vector2[4];
        uvs[0] = new Vector2(0, 0);
        uvs[1] = new Vector2(0, 1);
        uvs[2] = new Vector2(1, 1);
        uvs[3] = new Vector2(1, 0);
        screenQuadMesh.uv = uvs;

        int[] indices = new int[6];
        indices[0] = 0;
        indices[1] = 1;
        indices[2] = 2;

        indices[3] = 0;
        indices[4] = 2;
        indices[5] = 3;
        screenQuadMesh.triangles = indices;

        screenQuadMesh.Optimize();

        matrix = Matrix4x4.identity;

    }

    void OnPostRender()
    {
        screenQuadMaterial.SetFloat("_Intension", shadowIntensity);
        screenQuadMaterial.SetColor("_Color", shadowColor);
        screenQuadMaterial.SetPass(0);
        Graphics.DrawMeshNow(screenQuadMesh, matrix);
    }
        
}

文末,再次感谢赵忠健的分享,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)
也欢迎大家来积极参与U Sparkle开发者计划,简称"US",代表你和我,代表UWA和开发者在一起!