Unity custom post-processing - blur effect

  Hello everyone, I am Zhao.
  Continue to introduce the practice of post-processing, this issue introduces the practice of blur effect.

1. The principle of blur effect

We still use this character as the background to achieve the blur effect
insert image description here

insert image description here

This is the blurred effect
insert image description here

Depending on the parameters, different degrees of blur can be adjusted.

  Before introducing the method, we must first clarify a basic cognition, the blur effect is a kind of processing that consumes a lot of performance. Normally we display a picture, and each pixel is sampled once according to the UV coordinates to get the color. The blurring process is that each pixel, in addition to sampling itself, also samples multiple pixels around the pixel, and then performs different averaging algorithms on the sampled color values ​​to obtain blur.
  In a nutshell, the method of sampling multiple points around is to define the convolution kernel. The difference between the three different fuzzy algorithms described below is that the calculation methods of the convolution kernel are different.
  In addition to sampling multiple surrounding pixels, we may also need to sample multiple times, because the sampling calculation of a single convolution kernel may not achieve the effect we want.
  Therefore, the blur effect is a very expensive processing method. We often say that the performance consumption of the Bloom glow effect is very high. In fact, it is also because the Bloom effect is also based on the blurring effect and then superimposed. The performance consumption is actually caused by the blurring.

2. Realization of several different blur effects

1. Mean blur (BoxBlur)

  The mean blur is to sample the colors of 9 pixels near the current pixel (including yourself), then add all the colors, and finally divide by 9. Because the color of 9 pixels is directly averaged, it becomes the average value blur.
insert image description here

  Zoom in on the blurred picture, and you can see that the average blur effect of a single shot is actually not very good. There will be some square pixel effects, and the blur effect is not very average. So mean blur also becomes box blur.
  To do the blur effect, you need to do Graphics.Blit multiple times in the C# script, but skip this step here first, first look at how Shader is written, and finally look at the implementation of C#.

Shader "Hidden/AzhaoBoxBlur"
{
	CGINCLUDE
		#include "UnityCG.cginc"


		sampler2D _MainTex;
		float4 _MainTex_TexelSize;
		float _BlurOffset;

		fixed4 fragBoxBlur(v2f_img i) : SV_Target
		{
			half4 col = tex2D(_MainTex, i.uv);
			//均值模糊
			float4 colBlur = 0;
			float4 blurUV = _MainTex_TexelSize.xyxy * float4(1, 1, -1, -1)*_BlurOffset;
			colBlur += tex2D(_MainTex, i.uv + blurUV.xy);//1,1
			colBlur += tex2D(_MainTex, i.uv + blurUV.xw);//1,-1
			colBlur += tex2D(_MainTex, i.uv + blurUV.zy);//-1,1
			colBlur += tex2D(_MainTex, i.uv + blurUV.zw);//-1,-1
			col.rgb = colBlur.rgb / 4;
			return col;
		}
	ENDCG

    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
		_BlurOffset("BlurOffset",Float) = 1
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment fragBoxBlur

            ENDCG
        }
		Pass
		{
			CGPROGRAM
			#pragma vertex vert_img
			#pragma fragment fragBoxBlur

			ENDCG
		}
    }
}

2. Gaussian Blur

  Gaussian blur should be a very commonly used blur method. His convolution kernel is not in the vicinity of 9 grids, but in the range of 5X5, so its sampling times are much more than the average blur. Moreover, Gaussian blur sampling 25 pixels is not directly averaged, but has a weight value that decreases from the middle to the outside. This process can reduce the box-like grid effect of the mean blur.
insert image description here

However, if it is only a single sampling, in fact, the effect is not very good.
Below is Shader

Shader "Hidden/AzhaoGaussianBlur"
{
	CGINCLUDE
	#include "UnityCG.cginc"
	sampler2D _MainTex;
	float4 _MainTex_TexelSize;
	float _BlurOffset;
		//高斯模糊横向
	half4 frag_HorizontalBlur(v2f_img i) : SV_Target
	{
		half2 uv1 = i.uv + _MainTex_TexelSize.xy* half2(1, 0)*_BlurOffset * -2.0;
		half2 uv2 = i.uv + _MainTex_TexelSize.xy* half2(1, 0)*_BlurOffset * -1.0;
		half2 uv3 = i.uv;
		half2 uv4 = i.uv + _MainTex_TexelSize.xy* half2(1, 0)*_BlurOffset * 1.0;
		half2 uv5 = i.uv + _MainTex_TexelSize.xy* half2(1, 0)*_BlurOffset * 2.0;

		half4 s = 0;
		s += tex2D(_MainTex, uv1) * 0.05;
		s += tex2D(_MainTex, uv2) * 0.25;
		s += tex2D(_MainTex, uv3) * 0.40;
		s += tex2D(_MainTex, uv4) * 0.25;
		s += tex2D(_MainTex, uv5) * 0.05;
		return s;
	}
		//高斯模糊纵向
	half4 frag_VerticalBlur(v2f_img i) : SV_Target
	{
		half2 uv1 = i.uv + _MainTex_TexelSize.xy* half2(0, 1)*_BlurOffset * -2.0;
		half2 uv2 = i.uv + _MainTex_TexelSize.xy* half2(0, 1)*_BlurOffset * -1.0;
		half2 uv3 = i.uv;
		half2 uv4 = i.uv + _MainTex_TexelSize.xy* half2(0, 1)*_BlurOffset * 1.0;
		half2 uv5 = i.uv + _MainTex_TexelSize.xy* half2(0, 1)*_BlurOffset * 2.0;

		half4 s = 0;
		s += tex2D(_MainTex, uv1) * 0.05;
		s += tex2D(_MainTex, uv2) * 0.25;
		s += tex2D(_MainTex, uv3) * 0.40;
		s += tex2D(_MainTex, uv4) * 0.25;
		s += tex2D(_MainTex, uv5) * 0.05;
		return s;
	}
	ENDCG
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
		_BlurOffset("BlurOffset", Float) = 1
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag_HorizontalBlur

            ENDCG
        }
		Pass
		{
			CGPROGRAM
			#pragma vertex vert_img
			#pragma fragment frag_VerticalBlur

			ENDCG
		}
    }
}

3. Kawas Blur

  I learned this algorithm from the Internet. According to the introduction, the idea of ​​Kawase blur is to sample the four corners farther and farther away from the current pixel, and perform a ping-pong blit between two equal-sized textures. The innovation is that it uses a blur kernel that moves with the number of iterations, rather than a convolution kernel that is fixed from beginning to end like Gaussian blur or mean blur.
insert image description here

  It can be seen that this blurring method is much better than the above two effects, and those mosaic stripes are basically invisible.

Shader "Hidden/AzhaoKawaseBlur"
{
	CGINCLUDE
#include "UnityCG.cginc"
		uniform sampler2D _MainTex;
	uniform float4 _MainTex_TexelSize;
	uniform half _BlurOffset;

	struct v2f_DownSample
	{
		float4 pos: SV_POSITION;
		float2 uv: TEXCOORD1;
		float4 uv01: TEXCOORD2;
		float4 uv23: TEXCOORD3;
	};


	struct v2f_UpSample
	{
		float4 pos: SV_POSITION;
		float4 uv01: TEXCOORD1;
		float4 uv23: TEXCOORD2;
		float4 uv45: TEXCOORD3;
		float4 uv67: TEXCOORD4;
	};


	v2f_DownSample Vert_DownSample(appdata_img v)
	{
		v2f_DownSample o;
		o.pos = UnityObjectToClipPos(v.vertex);

		_MainTex_TexelSize = 0.5 * _MainTex_TexelSize;
		float2 uv = v.texcoord;
		o.uv = uv;
		o.uv01.xy = uv - _MainTex_TexelSize * float2(1 + _BlurOffset, 1 + _BlurOffset);//top right
		o.uv01.zw = uv + _MainTex_TexelSize * float2(1 + _BlurOffset, 1 + _BlurOffset);//bottom left
		o.uv23.xy = uv - float2(_MainTex_TexelSize.x, -_MainTex_TexelSize.y) * float2(1 + _BlurOffset, 1 + _BlurOffset);//top left
		o.uv23.zw = uv + float2(_MainTex_TexelSize.x, -_MainTex_TexelSize.y) * float2(1 + _BlurOffset, 1 + _BlurOffset);//bottom right

		return o;
	}

	half4 Frag_DownSample(v2f_DownSample i) : SV_Target
	{
		half4 sum = tex2D(_MainTex, i.uv) * 4;
		sum += tex2D(_MainTex, i.uv01.xy);
		sum += tex2D(_MainTex, i.uv01.zw);
		sum += tex2D(_MainTex, i.uv23.xy);
		sum += tex2D(_MainTex, i.uv23.zw);

		return sum * 0.125;
	}


		v2f_UpSample Vert_UpSample(appdata_img v)
	{
		v2f_UpSample o;
		o.pos = UnityObjectToClipPos(v.vertex);

		float2 uv = v.texcoord;

		_MainTex_TexelSize = 0.5 * _MainTex_TexelSize;
		_BlurOffset = float2(1 + _BlurOffset, 1 + _BlurOffset);

		o.uv01.xy = uv + float2(-_MainTex_TexelSize.x * 2, 0) * _BlurOffset;
		o.uv01.zw = uv + float2(-_MainTex_TexelSize.x, _MainTex_TexelSize.y) * _BlurOffset;
		o.uv23.xy = uv + float2(0, _MainTex_TexelSize.y * 2) * _BlurOffset;
		o.uv23.zw = uv + _MainTex_TexelSize * _BlurOffset;
		o.uv45.xy = uv + float2(_MainTex_TexelSize.x * 2, 0) * _BlurOffset;
		o.uv45.zw = uv + float2(_MainTex_TexelSize.x, -_MainTex_TexelSize.y) * _BlurOffset;
		o.uv67.xy = uv + float2(0, -_MainTex_TexelSize.y * 2) * _BlurOffset;
		o.uv67.zw = uv - _MainTex_TexelSize * _BlurOffset;

		return o;
	}

	half4 Frag_UpSample(v2f_UpSample i) : SV_Target
	{
		half4 sum = 0;
		sum += tex2D(_MainTex, i.uv01.xy);
		sum += tex2D(_MainTex, i.uv01.zw) * 2;
		sum += tex2D(_MainTex, i.uv23.xy);
		sum += tex2D(_MainTex, i.uv23.zw) * 2;
		sum += tex2D(_MainTex, i.uv45.xy);
		sum += tex2D(_MainTex, i.uv45.zw) * 2;
		sum += tex2D(_MainTex, i.uv67.xy);
		sum += tex2D(_MainTex, i.uv67.zw) * 2;

		return sum * 0.0833;
	}

		ENDCG
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
		_BlurOffset("BlurOffset", Float) = 1
    }
	SubShader
	{
		Cull Off ZWrite Off ZTest Always

		Pass
		{
			CGPROGRAM
			#pragma vertex Vert_DownSample
			#pragma fragment Frag_DownSample	
			ENDCG
		}

		Pass
		{
			CGPROGRAM
			#pragma vertex Vert_UpSample
			#pragma fragment Frag_UpSample
			ENDCG

		}
	}
}

4. Double Blur

  Here is a comparison of mean blur and Gaussian blur.
  The effect of Gaussian blur is better than average blur, but the mosaic stripes still exist, and the performance consumption is huge.
To solve this problem, we can perform a double blur operation.
The specific method is to open 2 loops when doing Graphics.Blit in C#.
In the first loop, the width and height of the RenderTexture are divided by 2 each time to perform downsampling.
In the second loop, the width and height of the RenderTexture are multiplied by 2 each time for upsampling.
After changing the width and height of RenderTexture, the original mosaic stripe effect can basically be eliminated.
  Under the same parameter settings, the effect of double sampling is better and smoother than Gaussian blur. However, one thing to pay attention to in this method is that because multiple RenderTextures are created, you must remember to perform the corresponding release operation.

3. C# code

  The following code is the result after the C# side is compatible with the three different fuzzy algorithms mentioned above, and then double-sampled.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BlurCtrl : MonoBehaviour
{
    private Material blurMat;
    public bool isBlur = false;
    [Range(0, 4)]
    public float blurSize = 0;
    [Range(-3, 3)]
    public float blurOffset = 1;
    [Range(1, 3)]
    public int blurType = 3;
    void Start()
    {

    }


    void Update()
    {

    }

    private Material GetBlurMat(int bType)
    {
        if (bType == 1)
        {
            return new Material(Shader.Find("Hidden/AzhaoBoxBlur"));
        }
        else if (bType == 2)
        {
            return new Material(Shader.Find("Hidden/AzhaoGaussianBlur"));
        }
        else if (bType == 3)
        {
            return new Material(Shader.Find("Hidden/AzhaoKawaseBlur"));
        }
        else
        {
            return null;
        }
    }

    private void ReleaseRT(RenderTexture rt)
    {
        if (rt != null)
        {
            RenderTexture.ReleaseTemporary(rt);
        }
    }

    private bool CheckNeedCreateBlurMat(Material mat, int bType)
    {
        if (mat == null)
        {
            return true;
        }
        if (mat.shader == null)
        {
            return true;
        }
        if (bType == 1)
        {
            if (mat.shader.name != "Hidden/AzhaoBoxBlur")
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        else if (bType == 2)
        {
            if (mat.shader.name != "Hidden/AzhaoGaussianBlur")
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        else if (bType == 3)
        {
            if (mat.shader.name != "Hidden/AzhaoKawaseBlur")
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        else
        {
            return false;
        }
    }
    private void BlurFun(RenderTexture source, RenderTexture destination, float blurTime, int bType, float offset)
    {
        if (CheckNeedCreateBlurMat(blurMat, bType) == true)
        {
            blurMat = GetBlurMat(bType);
        }
        if (blurMat == null || blurMat.shader == null || blurMat.shader.isSupported == false)
        {
            return;
        }
        blurMat.SetFloat("_BlurOffset", offset);
        float width = source.width;
        float height = source.height;
        int w = Mathf.FloorToInt(width);
        int h = Mathf.FloorToInt(height);
        RenderTexture rt1 = RenderTexture.GetTemporary(w, h);
        RenderTexture rt2 = RenderTexture.GetTemporary(w, h);
        Graphics.Blit(source, rt1);
//降采样
        for (int i = 0; i < blurTime; i++)
        {
            ReleaseRT(rt2);
            width = width / 2;
            height = height / 2;
            w = Mathf.FloorToInt(width);
            h = Mathf.FloorToInt(height);
            rt2 = RenderTexture.GetTemporary(w, h);
            Graphics.Blit(rt1, rt2, blurMat, 0);
            width = width / 2;
            height = height / 2;
            w = Mathf.FloorToInt(width);
            h = Mathf.FloorToInt(height);
            ReleaseRT(rt1);
            rt1 = RenderTexture.GetTemporary(w, h);
            Graphics.Blit(rt2, rt1, blurMat, 1);
        }
//升采样
        for (int i = 0; i < blurTime; i++)
        {
            ReleaseRT(rt2);
            width = width * 2;
            height = height * 2;
            w = Mathf.FloorToInt(width);
            h = Mathf.FloorToInt(height);
            rt2 = RenderTexture.GetTemporary(w, h);
            Graphics.Blit(rt1, rt2, blurMat, 0);
            width = width * 2;
            height = height * 2;
            w = Mathf.FloorToInt(width);
            h = Mathf.FloorToInt(height);
            ReleaseRT(rt1);
            rt1 = RenderTexture.GetTemporary(w, h);
            Graphics.Blit(rt2, rt1, blurMat, 1);
        }
        Graphics.Blit(rt1, destination);
        ReleaseRT(rt1);
        rt1 = null;
        ReleaseRT(rt2);
        rt2 = null;
        return;
    }
    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (isBlur == true)
        {
            if (blurSize > 0)
            {
                BlurFun(source, source, blurSize, blurType, blurOffset);
            }
            Graphics.Blit(source, destination);

        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }
}

Guess you like

Origin blog.csdn.net/liweizhao/article/details/131820804