Unity-Shader-KawaseBlur

Unity-Shader-KawaseBlur

KawaseBlur

模糊效果,目前已经越来越受游戏的欢迎,最著名的就是高斯模糊,但是其性能却也让开发者感到很无奈,在移动低端设备上不太友好,本文主要介绍另一种模糊KawaseBlur,该模糊效果跟高斯模糊比较相像,但是性能却比经过优化的高斯模糊的性能约快1.5倍到3倍,还是比较适合移动设备来使用当做游戏里面的模糊效果。

1.效果图
废话不多说,先贴效果图,这里主要分为两种,一种是作用于UI,一种是不包含UI:
(1).包含UI:
在这里插入图片描述
(2).不包含UI:
在这里插入图片描述
2.算法思路

该模糊的算法主要是借鉴于浅墨_毛星云大佬的文章:https://qianmo.blog.csdn.net/article/details/105350519

Kawase Blur的思路是对距离当前像素越来越远的地方对四个角进行采样,且在两个大小相等的纹理之间进行乒乓式的blit,创新点在于,采用了随迭代次数移动的blur kernel,而不是类似高斯模糊,或box blur一样从头到尾固定的blur kernel。

更详细的请见浅墨_毛星云大佬的文章。

3.性能优化
实践数据表明,在相似的模糊表现下,Kawase Blur比经过优化的高斯模糊的性能约快1.5倍到3倍。
(主要是该模糊算法只进行了4次采样,并且其blur kernel会移动,而高斯模糊需要进行8次采样,并且blur kernel不移动)。

4.源码分析

KawaseBlur.cs:

using DG.Tweening;
using UnityEngine;

public class KawaseBlur : MonoBehaviour
{
    #region Variables
    public Shader SCShader;
    [Range(0.0f, 5.0f)] public float BlurRadius = 0f;
    [Range(1, 10)] public int Iteration = 1;
    [Range(1, 8)] public float RTDownScaling = 1;
    private Material SCMaterial;
    private RenderTexture m_rt;
    private RenderTexture m_rt1;
    private bool isGradientBlur = false;
    #endregion

    #region Properties
    Material material
    {
        get
        {
            if (SCMaterial == null)
            {
                SCMaterial = new Material(SCShader);
                SCMaterial.hideFlags = HideFlags.HideAndDontSave;
            }
            return SCMaterial;
        }
    }
    #endregion

    void Start()
    {
        SCShader = Shader.Find("KawaseBlur");
        if (!SystemInfo.supportsImageEffects)
        {
            enabled = false;
            return;
        }
    }

    public void GradientBlur()
    {
        float getter() => BlurRadius;
        void setter(float value) => BlurRadius = value;
        DOTween.To(getter, setter, 0.5f, 0.25f).SetUpdate(true);
        int getter1() => Iteration;
        void setter1(int value) => Iteration = value;
        DOTween.To(getter1, setter1, 5, 0.25f).SetUpdate(true);
        float getter2() => RTDownScaling;
        void setter2(float value) => RTDownScaling = value;
        DOTween.To(getter2, setter2, 2, 0.25f).SetUpdate(true);
    }

    private void Cleanup()
    {
        if (SCMaterial) Object.DestroyImmediate(SCMaterial);
        isGradientBlur = false;
    }

    private void OnEnable()
    {
        Cleanup();
    }

    private void OnDestroy()
    {
        Cleanup();
    }

    void OnRenderImage(RenderTexture sourceTexture, RenderTexture destTexture)
    {
        if (SCShader != null)
        {
            //这里用DOTween插件做了一个模糊渐变效果,如果不想要可以删除该4行代码
            if (!isGradientBlur)
            {
                GradientBlur();
                isGradientBlur = true;
            }
            
            int RTWidth = (int)(Screen.width / RTDownScaling);
            int RTHeight = (int)(Screen.height / RTDownScaling);

            m_rt = RenderTexture.GetTemporary(RTWidth, RTHeight, 0, sourceTexture.format);
            m_rt.filterMode = FilterMode.Bilinear;
            m_rt1 = RenderTexture.GetTemporary(RTWidth, RTHeight, 0, sourceTexture.format);
            m_rt1.filterMode = FilterMode.Bilinear;

            Graphics.Blit(sourceTexture, m_rt);

            for (int i = 0; i < Iteration; i++)
            {
                material.SetFloat("_Offset", i / RTDownScaling + BlurRadius);
                material.SetVector("_ScreenResolution", new Vector2(Screen.width, Screen.height));
                if (i % 2 != 1)
                {
                    Graphics.Blit(m_rt, m_rt1, material, 0);
                }
                else
                {
                    Graphics.Blit(m_rt1, m_rt, material, 0);
                }
            }

            material.SetFloat("_Offset", Iteration / RTDownScaling + BlurRadius);
            Graphics.Blit(m_rt1, destTexture, material, 0);

            RenderTexture.ReleaseTemporary(m_rt);
            RenderTexture.ReleaseTemporary(m_rt1);
        }

    }

    void Update()
    {

#if UNITY_EDITOR
        if (Application.isPlaying != true)
        {
            SCShader = Shader.Find("KawaseBlur");
        }
#endif

    }
}

KawaseBlur.shader:

Shader "KawaseBlur" 
{
	Properties
	{
		_MainTex("Base (RGB)", 2D) = "white" {}
		_Offset("_Offset", Range(0.0, 5.0)) = 1.0
		_ScreenResolution("_ScreenResolution", Vector) = (0.,0.,0.,0.)
	}

	SubShader
	{
		Cull Off ZWrite Off ZTest Always

		Pass
		{
		    CGPROGRAM
			#include "UnityCG.cginc"
			#pragma vertex VertDefault
			#pragma fragment Frag
			#pragma target 3.0
            #pragma glsl
			
			uniform sampler2D _MainTex;
			uniform float4 _MainTex_TexelSize;
	        uniform half _Offset;
			uniform float4 _ScreenResolution;

            struct appdata_t
	        {
	        	float4 vertex   : POSITION;
	        	float4 color    : COLOR;
	        	float2 texcoord : TEXCOORD0;
	        };
			struct v2f
	        {
	        	float2 texcoord  : TEXCOORD0;
	        	float4 vertex   : SV_POSITION;
	        	float4 color : COLOR;
	        };
	        v2f VertDefault(appdata_t IN)
	        {
	        	v2f OUT;
	        	OUT.vertex = UnityObjectToClipPos(IN.vertex);
	        	OUT.texcoord = IN.texcoord;
	        	OUT.color = IN.color;
	        	return OUT;
	        }


            //核心算法
	        half4 KawaseBlur(sampler2D tex, float2 uv, float2 texelSize, half pixelOffset)
	        {
	        	half4 o = 0;
	        	o += tex2D(tex, uv + float2(pixelOffset +0.5, pixelOffset +0.5) * texelSize); 
	        	o += tex2D(tex, uv + float2(-pixelOffset -0.5, pixelOffset +0.5) * texelSize); 
	        	o += tex2D(tex, uv + float2(-pixelOffset -0.5, -pixelOffset -0.5) * texelSize); 
	        	o += tex2D(tex, uv + float2(pixelOffset +0.5, -pixelOffset -0.5) * texelSize); 
	        	return o * 0.25;
	        }
	        
	        
	        half4 Frag(v2f i): SV_Target
	        {
	        	return KawaseBlur(_MainTex, i.texcoord.xy, _MainTex_TexelSize.xy, _Offset);
	        }
			ENDCG
		}
	}
}

5.使用
只要将KawaseBlur.cs代码添加到你想渲染的Camera上就行了。

不过如果想对UI起作用的话,这里有几个说明:

(1).将KawaseBlur.cs添加到UICamera上
(2).需要额外给UICamera添加一个脚本,在Awake的时候,调整UICamera的位置和范围大小,如果不调整的摄像机的位置和范围大小的话,会破坏你游戏里面的UI显示,脚本如下:

using UnityEngine;

public class UICameraController : MonoBehaviour
{
    [SerializeField] private Camera uiCamera = null;

    private void Awake()
    {
        CameraAdaptiveScreen();
    }

    private void CameraAdaptiveScreen()
    {
        uiCamera.orthographicSize = Screen.height / 2;
        uiCamera.transform.position = new Vector3(Screen.width / 2, Screen.height / 2, 0);
    }
}

(3).将你想要模糊处理的UI,其Canvas的Render Mode设置为ScreenSpace-Camera,并将其PlaneDistance设置为UICamera的ClippingPlanes的Near值一样,例如:
在这里插入图片描述

这里有个小bug,如果你的Canvas里面实现了ScrollRect和ScrollView功能,将其Canvas的PlaneDistance设置比UICamera的ClippingPlanes的Near值大一点点,比如0.01,要不然会导致ScrollView功能失效,具体原因我现在也没搞懂,如果后来明白了我会补充。
(4).不想模糊处理的UI,将其Canvas的Render Mode设置为ScreenSpace-Overlay就行了,该模式下,Canvas不受Camera的影响。

6.扩展
其实还有一种简单的方式对UI起作用,就是使用Unity内置的特殊通道GrabPass,只需要使用该通道的shader+materail,并将material放到你的Image中,或者Plane中就好了,但是我测试的结果,性能是真的差(尤其是移动设备),不建议使用,如果想自己试试的话,可以百度直接搜GrabPass,有很多关于这方面的文章,这里我就不再叙述。

最后,如果文章中有错误内容的话,随时欢迎来纠正,我会马上改正,谢谢,也建议大家每次学习新知识的时候,不光光要会用,更要弄明白其原理是怎么实现的,这样才算是掌握了该知识点,哈哈,我就走了弯路,现在发现改正还为时不晚,大家一起加油!

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值