Unity Shader案例之——模糊效果

狂拽屌炸天的游戏效果实现,都离不开模糊效果,而我们最常用的方法是后处理,今天介绍几种模糊效果的实现。首先上一张大原图:

以下为了对比各种方法的效果差异,所有参数都保持一致:
Unity Shader案例之——模糊效果_第1张图片

1. 均值模糊

第一种介绍的是均值模糊,三种中最为简单的模糊效果,计算简单,效果也差强人意:

Shader:

Shader "Custom/SimpleBlur"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            ZTest Always
            Cull Off
            ZWrite Off 

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

            struct v2f
            {
                float4 pos : SV_POSITION;
                half2 uv : TEXCOORD0;
                half2 uv1 : TEXCOORD1;
                half2 uv2 : TEXCOORD2;
                half2 uv3 : TEXCOORD3;
                half2 uv4 : TEXCOORD4;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _MainTex_TexelSize;
            float _BlurRadius;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = v.uv.xy;
                o.uv1 = v.uv + _BlurRadius * _MainTex_TexelSize * half2(0,1);
                o.uv2 = v.uv + _BlurRadius * _MainTex_TexelSize * half2(0,-1);
                o.uv3 = v.uv + _BlurRadius * _MainTex_TexelSize * half2(1,0);
                o.uv4 = v.uv + _BlurRadius * _MainTex_TexelSize * half2(-1,0);

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                col += tex2D(_MainTex, i.uv1);
                col += tex2D(_MainTex, i.uv2);
                col += tex2D(_MainTex, i.uv3);
                col += tex2D(_MainTex, i.uv4);
                col *= 0.2;

                return col;
            }
            ENDCG
        }
    }
}

C#:

using UnityEngine;

public class SimpleBlur : PostEffectBase
{
    [Range(0.1f, 10)]
    public float _BlurRadius = 0.1f;

    [Range(0, 10)]
    public int downSample = 1;

    [Range(0, 10)]
    public int iteration = 1;

    public void OnRenderImage(RenderTexture src, RenderTexture dst)
    {
        if (_Material)
        {
            RenderTexture rt1 = RenderTexture.GetTemporary(src.width >> downSample, src.height >> downSample, 0, src.format);
            RenderTexture rt2 = RenderTexture.GetTemporary(src.width >> downSample, src.height >> downSample, 0, src.format);

            _Material.SetFloat("_BlurRadius", _BlurRadius);

            Graphics.Blit(src, rt1, _Material);

            for (int i = 0; i < iteration; i++)
            {
                Graphics.Blit(rt1, rt2, _Material);
                Graphics.Blit(rt2, rt1, _Material);
            }

            Graphics.Blit(rt1, dst, _Material);

            RenderTexture.ReleaseTemporary(rt1);
            RenderTexture.ReleaseTemporary(rt2);
        }
    }
}

2. 高斯模糊

高斯,提到这个词,一般人都“敬而远之”,但其实很多效果都是在“高斯”的基础上实现的,这里实现了两版高斯模糊。

  • 版本一
    Unity Shader案例之——模糊效果_第2张图片

Shader:

Shader "Custom/GaussBlur"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }

    SubShader
    {
        Pass
        {
            ZTest Always
            Cull Off
            ZWrite Off

            CGPROGRAM
            #pragma target 3.0
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

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

            sampler2D _MainTex;
            float4 _MainTex_TexelSize;
            half _BlurRadius;
            float totalWeight;

            half GetWeight(float x, float y, float rho)
            {
                return exp(-(x*x + y*y)/(2.0f*rho*rho));
            }

            fixed4 GaussBlur(v2f i)
            {
                // half totalWeight = 0;
                fixed4 finalColor = fixed4(0, 0, 0, 0);
                half rho = _BlurRadius / 3.0f;

                // for(int w = -_BlurRadius; w<= _BlurRadius; w++)
                // {
                //  for(int h = -_BlurRadius; h<= _BlurRadius; h++)
                //  {
                //      half weight = GetWeight(w, h, rho);
                //      totalWeight += weight;
                //  }
                // }

                for(int x = -_BlurRadius; x<= _BlurRadius; x++)
                {
                    for(int y = -_BlurRadius; y<= _BlurRadius; y++)
                    {
                        half wt = GetWeight(x, y, rho)/totalWeight;

                        fixed4 col = tex2D(_MainTex, i.uv + float2(x, y) * _MainTex_TexelSize.xy);
                        finalColor += col * wt;
                    }
                }

                return finalColor;
            }

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv.xy = v.uv;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = GaussBlur(i);
                return col;
            }

            ENDCG
        }
    }
}

C#:

using UnityEngine;

public class GaussBlur : PostEffectBase
{
    [Range(0.1f, 10)]
    public float _BlurRadius = 0.1f;

    [Range(0, 10)]
    public int downSample = 1;

    [Range(0, 10)]
    public int iteration = 1;

    public void OnRenderImage(RenderTexture src, RenderTexture dst)
    {
        if (_Material)
        {
            RenderTexture rt1 = RenderTexture.GetTemporary(src.width >> downSample, src.height >> downSample, 0, src.format);
            RenderTexture rt2 = RenderTexture.GetTemporary(src.width >> downSample, src.height >> downSample, 0, src.format);

            _Material.SetFloat("_BlurRadius", _BlurRadius);
            _Material.SetFloat("totalWeight", GetTotalWeight());

            Graphics.Blit(src, rt1, _Material);

            for (int i = 0; i < iteration; i++) 
            {
                Graphics.Blit(rt1, rt2, _Material);
                Graphics.Blit(rt2, rt1, _Material);
            }

            Graphics.Blit(rt1, dst, _Material);

            RenderTexture.ReleaseTemporary(rt1);
            RenderTexture.ReleaseTemporary(rt2);
        }
    }

    private float GetWeight(float x, float y, float rho) {
        return Mathf.Exp(-(x * x + y * y) / (2.0f * rho * rho));
    }

    private float GetTotalWeight()
    {
        float totalWeight = 0;
        float rho = _BlurRadius / 3.0f;

        for (int w = -(int)_BlurRadius; w <= _BlurRadius; w++) {
            for (int h = -(int)_BlurRadius; h <= _BlurRadius; h++) {
                float weight = GetWeight(w, h, rho);
                totalWeight += weight;
            }
        }

        return totalWeight;
    }
}
  • 版本二

Shader:

Shader "Custom/GaussBlur2"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }

    SubShader
    {
        CGINCLUDE
        #include "UnityCG.cginc"

        sampler2D _MainTex;
        float4 _MainTex_TexelSize;
        half _BlurRadius;

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

        struct v2f
        {
            float4 vertex : SV_POSITION;
            float2 uvs[5] : TEXCOORD1;
        };

        v2f vert_VerticalBlur(appdata v){
            v2f o;
            o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
            o.uvs[0] = v.uv;
            o.uvs[1] = v.uv + float2(0, _MainTex_TexelSize.y * 1) * _BlurRadius;
            o.uvs[2] = v.uv + float2(0, _MainTex_TexelSize.y * -1) * _BlurRadius;
            o.uvs[3] = v.uv + float2(0, _MainTex_TexelSize.y * 2) * _BlurRadius;
            o.uvs[4] = v.uv + float2(0, _MainTex_TexelSize.y * -2) * _BlurRadius;
            return o;
        }

        v2f vert_HorizontalBlur(appdata v){
            v2f o;
            o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
            o.uvs[0] = v.uv;
            o.uvs[1] = v.uv + float2(_MainTex_TexelSize.x * 1, 0) * _BlurRadius;
            o.uvs[2] = v.uv + float2(_MainTex_TexelSize.x * -1, 0) * _BlurRadius;
            o.uvs[3] = v.uv + float2(_MainTex_TexelSize.x * 2, 0) * _BlurRadius;
            o.uvs[4] = v.uv + float2(_MainTex_TexelSize.x * -2, 0) * _BlurRadius;
            return o;
        }

        fixed4 fragBlur (v2f i) : SV_Target
        {
            half weight[3] = {0.4026, 0.2442, 0.0545};

            fixed4 col = tex2D(_MainTex, i.uvs[0]) * weight[0];

            for(int j = 1; j < 3; j++)
            {
                col += tex2D(_MainTex, i.uvs[2*j - 1]) * weight[j];
                col += tex2D(_MainTex, i.uvs[2*j]) * weight[j];
            }

            return col;
        }
        ENDCG

        ZTest Always
        Cull Off
        ZWrite Off

        //Pass1
        Pass
        {
            NAME "GAUSSIAN_BLUR_VERTICAL"

            CGPROGRAM
            #pragma vertex vert_VerticalBlur
            #pragma fragment fragBlur
            ENDCG
        }

        //Pass2
        Pass
        {
            NAME "GAUSSIAN_BLUR_HORIZONTAL"

            CGPROGRAM
            #pragma vertex vert_HorizontalBlur
            #pragma fragment fragBlur
            ENDCG
        }
    }

    FallBack Off
}

C#:

using UnityEngine;

public class GaussBlur2 : PostEffectBase
{
    [Range(0.1f, 10)]
    public float _BlurRadius = 0.1f;

    [Range(0, 10)]
    public int downSample = 1;

    [Range(0, 10)]
    public int iteration = 1;

    public void OnRenderImage(RenderTexture src, RenderTexture dst)
    {
        if (_Material)
        {
            RenderTexture rt1 = RenderTexture.GetTemporary(src.width >> downSample, src.height >> downSample, 0, src.format);
            RenderTexture rt2 = RenderTexture.GetTemporary(src.width >> downSample, src.height >> downSample, 0, src.format);

            _Material.SetFloat("_BlurRadius", _BlurRadius);

            Graphics.Blit(src, rt1, _Material, 1);

            for (int i = 0; i < iteration; i++) 
            {
                Graphics.Blit(rt1, rt2, _Material, 0);
                Graphics.Blit(rt2, rt1, _Material, 1);
            }

            Graphics.Blit(rt1, dst, _Material, 0);

            RenderTexture.ReleaseTemporary(rt1);
            RenderTexture.ReleaseTemporary(rt2);
        }
    }
}

这两版原理上是一样的,只是后者是借鉴前人的,而前者是自己对高斯计算做了封装,增加了模糊半径的动态调整,并合为一个Pass。

3. 中心模糊

这个是应需求而生的效果,让模糊度当前像素与屏幕中心的距离相关联。仔细观察,中间清晰,周围模糊,类似于吃鸡游戏里的开镜效果,嗯……暂且就叫中心模糊吧。
Unity Shader案例之——模糊效果_第3张图片

Shader:

Shader "Custom/CenterBlur"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }

    CGINCLUDE

    #include "UnityCG.cginc"

    sampler2D _MainTex;
    float4 _MainTex_ST;
    float4 _MainTex_TexelSize;

    half _BlurRadius;
    half _CenterRadius;
    float totalWeight;

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

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

    v2f vert (appdata v)
    {
        v2f o;
        o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);

        o.uv.xy = v.uv;
        float2 uv1 = o.uv.xy;
        o.uv.zw = o.uv.xy;

#if UNITY_UV_STARTS_AT_TOP  
        if (_MainTex_TexelSize.y < 0)  
            o.uv.w = 1 - o.uv.w;  
#endif 
        return o;
    }

    half GetWeight(float x, float y, float rho)
    {
        return exp(-(x*x + y*y)/(2.0f*rho*rho));
    }

    fixed4 GaussBlur(v2f i)
    {
        fixed4 finalColor = fixed4(0, 0, 0, 0);
        half rho = _BlurRadius / 3.0f;

        for(int x = -_BlurRadius; x<= _BlurRadius; x++)
        {
            for(int y = -_BlurRadius; y<= _BlurRadius; y++)
            {
                half wt = GetWeight(x, y, rho)/totalWeight;

                fixed4 col = tex2D(_MainTex, i.uv + float2(x, y) * _MainTex_TexelSize.xy);
                finalColor += col * wt;
            }
        }

        return finalColor;
    }

    ENDCG

    SubShader
    {
        Tags { "RenderType"="Opaque" }

        //Pass1
        Pass{
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 texCol = tex2D(_MainTex, i.uv);
                fixed4 blur = GaussBlur(i);

                half radius = length(i.uv - fixed2(0.5f,0.5f));
                half dis = max(0, min(1, _CenterRadius - radius));

                fixed4 finalCol = lerp(blur, texCol, dis);

                return finalCol;
            }
            ENDCG
        }

        //Pass2
        Pass{
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            int showFactor = 0;

            sampler2D _BlurTex;

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 texCol = tex2D(_MainTex, i.uv.xy);
                fixed4 blur = tex2D(_BlurTex, i.uv.zw);

                half radius = length(i.uv - fixed2(0.5f,0.5f));
                half factor = max(0, min(1, _CenterRadius - radius));

                fixed4 finalCol = lerp(blur, texCol, factor);
                fixed4 factorCol = fixed4(1,1,1,1) * factor;
                finalCol = lerp(finalCol, factorCol, showFactor);

                return finalCol;
            }
            ENDCG
        }
    }
}

C#:

using UnityEngine;

public class CenterBlur : PostEffectBase
{
    public bool ShowFactor = false;

    [Range(0.1f, 10)]
    public float _BlurRadius = 0.1f;

    [Range(0.1f, 1.8f)]
    public float CenterRadius = 0.1f;

    [Range(0, 10)]
    public int downSample = 1;

    [Range(0, 10)]
    public int iteration = 1;

    public void OnRenderImage(RenderTexture src, RenderTexture dst)
    {
        if (_Material)
        {
            RenderTexture rt1 = RenderTexture.GetTemporary(src.width >> downSample, src.height >> downSample, 0, src.format);
            RenderTexture rt2 = RenderTexture.GetTemporary(src.width >> downSample, src.height >> downSample, 0, src.format);

            _Material.SetFloat("_BlurRadius", _BlurRadius);
            _Material.SetFloat("_CenterRadius", CenterRadius);
            _Material.SetFloat("totalWeight", GetTotalWeight());
            _Material.SetFloat("showFactor", ShowFactor ? 1 : 0);


            Graphics.Blit(src, rt1, _Material, 0);
            for (int i = 0; i < iteration; i++)
            {
                Graphics.Blit(rt1, rt2, _Material, 0);
                Graphics.Blit(rt2, rt1, _Material, 0);
            }

            _Material.SetTexture("_BlurTex", rt1);
            Graphics.Blit(src, dst, _Material, 1);

            RenderTexture.ReleaseTemporary(rt1);
        }
        else
        {
            Graphics.Blit(src, dst);
        }
    }

    private float GetWeight(float x, float y, float rho) {
        return Mathf.Exp(-(x * x + y * y) / (2.0f * rho * rho));
    }

    private float GetTotalWeight() {
        float totalWeight = 0;
        float rho = _BlurRadius / 3.0f;

        for (int w = -(int)_BlurRadius; w <= _BlurRadius; w++) {
            for (int h = -(int)_BlurRadius; h <= _BlurRadius; h++) {
                float weight = GetWeight(w, h, rho);
                totalWeight += weight;
            }
        }

        return totalWeight;
    }
}

为了更好的观察当前模糊范围,可以把ShowFactor打开,然后来调整参数。其中,越黑的地方越模糊:
Unity Shader案例之——模糊效果_第4张图片

最后,由于后处理的“失之毫厘差之千里”特性,所以这次用的图都比较大,由于文章排版限制,图可能会比较小,你可以把图片单独拖出来看,大图看着会更清晰,对比差异更明显哦!OK,今天就到这里,如果有更多更好的效果,欢迎交流讨论,共同学习进步!

你可能感兴趣的:(Unity,Shader案例)