Unity Gaussian Blur & Bloom & Motion Blur


Most of the explanations are in the code comments. Some of the codes are very long. I have folded them to help clarify ideas better. It is recommended to copy and put them in VS CODE to watch

   VS Code expand/collapse shortcut keys:ctrl+k 再 ctrl+j/0


Gaussian blur


  • Free equation
               G ( x , y ) = 1 2 π σ 2 e − x 2 + y 2 2 σ 2 G(x,y)=\frac{1}{2\pi\sigma^2}e^{ -\frac{x^2+y^2}{2\sigma^2}}G(x,y)=2 p.s _21e2 p2x2+y2
    Very simple, evenly sample from the two-dimensional normal distribution (according to the shape of the matrix), get the value of the sampling point, and then normalize


  • The relationship between the degree of fuzziness and parameters:

    the larger the range of the kernel, the higher the degree of fuzziness The
    heavier the distribution of the kernel, the higher the degree of fuzziness (doubtful, no experiment yet) The
    more times of fuzzy operations, the more fuzzy


  • Decomposition of the Gaussian kernel:

    Since the convolution kernel is two-dimensional, its calculation volume increases with the square number of the side length, and the burden is too large;
    if it can be decomposed into two one-dimensional ones, its calculation volume will only increase linearly
    insert image description here
    . Two, as two fuzzy kernels, do a fuzzy operation separately, and the result is consistent with a two-dimensional Gaussian blur, which greatly reduces the operation. In addition,
    because the weights of the two are the same, they are also symmetrical. So for the storage of a Gaussian kernel, it can be greatly compressed: 5 × 5 5\times55×The Gaussian kernel of 5 actually only needs to store3numbers


    . In the actual calculation, two passes will be used, corresponding to two one-dimensional kernels(guessing that the two kernels are different, so they cannot share a pass?)
    In fact, for To save calculations, it is generally necessary to zoom the image to save calculations (first reduce the resolution of the image, and then blur it, anyway it is blurred)


the code

screenplay

using UnityEngine;

public class G_Blur : PostEffectsBase
{
    
    
    public Shader G_Blur_Shader;
    private Material G_Blur_Mat = null;

    public Material material{
    
    
        get{
    
    
            G_Blur_Mat = CheckShader_CreateMat(G_Blur_Shader,G_Blur_Mat);
            return G_Blur_Mat;
        }
    }


    //迭代次数
    [Range(0,4)]
    public int iterations = 3;
    //控制相邻两个采样点的距离
    [Range(0.2f, 10.0f)]
    public float blurSpread = 0.6f;
    //下采样次数 这个参数是控制图像分辨率的,越多次图像越像素
    [Range(1,8)]
    public int downSample = 2;    
    


    //1.0这里参数中就直接创建了两个纹理变量
 //   void OnRenderImage(RenderTexture src, RenderTexture dest) 
 //   {
    
    
//       if (material != null)
 //      {
    
    
//        //这里我们建立了一块缓冲区,大小与屏幕一致
//        //原因因为两次模糊需要两个Pass,所以在第一次模糊后不能直接输出到目标纹理,而是先输出到缓冲中暂存
//        int rtW = src.width;    //src因为是渲染纹理,所以其长宽和屏幕一致
 //       int rtH = src.height;
 //       RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH,0); //这个函数能分配一块缓存用来作buffer

        //列,调用第1个pass进行第一次模糊,并存储到刚才创建的缓冲中
 //       Graphics.Blit(src, buffer, material, 0);
        //行,调用第2个pass,输出到目标纹理
 //       Graphics.Blit(buffer, dest, material ,1);

  //      RenderTexture.ReleaseTemporary(buffer);//释放缓存
   //    } 

 //      else //没材质直接原样输出(实际上上面有创建材质的函数,没材质说明出错了)
  //     {
    
    
  //      Graphics.Blit(src, dest);
  //     }


void OnRenderImage (RenderTexture src, RenderTexture dest) {
    
    
		if (material != null) {
    
    
			int rtW = src.width/downSample;
			int rtH = src.height/downSample;

			RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);

			buffer0.filterMode = FilterMode.Bilinear;

			Graphics.Blit(src, buffer0); //因为没有材质参数,会直接传递


			//请注意这里的渲染纹理,循环内部创建的渲染纹理在循环外部是访问不到的
			//所以这里用buffer0和buffer1来来回回换数据
			for (int i = 0; i < iterations; i++) 
			{
    
    
				material.SetFloat("_BlurSize", 1.0f + i * blurSpread);

				RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

				// Render the vertical pass
				Graphics.Blit(buffer0, buffer1, material, 0);

				RenderTexture.ReleaseTemporary(buffer0);
				buffer0 = buffer1;
				buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

				// Render the horizontal pass
				Graphics.Blit(buffer0, buffer1, material, 1);

				RenderTexture.ReleaseTemporary(buffer0);
				buffer0 = buffer1;
			}

			Graphics.Blit(buffer0, dest);
			RenderTexture.ReleaseTemporary(buffer0);
		} else {
    
    
			Graphics.Blit(src, dest);
		}


    }
    
    
    
}

Shader

Shader "Unlit/G_Blur"
{
    
    
    Properties
    {
    
    
        _MainTex ("Base", 2D) = "white" {
    
    }
        _BlurSize ("Blur Size", float) = 1.0
        //控制的是卷积核的每个元素之间的距离,1则为一个像素,1.5则为一个半像素
        //你要清楚这个卷积采样不是以像素为单位的,而是浮点距离,像素大小只不过是参考而已
        //过大的blursize会产生虚影,所以不能单纯的扩大blursize
    }
    SubShader
    {
    
    
    	//CGINCLUDE中的代码相当于在所有的Pass中写一遍
        CGINCLUDE

        #include "UnityCG.cginc"

        sampler2D _MainTex;
        half4 _MainTex_TexelSize; //内置变量,可以获取纹素大小
        float _BlurSize;

        //后处理不需要顶点数据结构

        struct v2f
        {
    
    
            float2 uv[5] : TEXCOORD0; //5维数组,用来存储卷积核
            float4 vertex : SV_POSITION;
        };

        v2f vertBlurCol(appdata_img v)//注意这里的appdata_img
        {
    
    
            v2f o;
            o.vertex = UnityObjectToClipPos(v.vertex);

            half2 uv = v.texcoord;

            o.uv[0] = uv;
            o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
            o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
            o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
            o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;

            return o;
        }

        v2f vertBlurRow(appdata_img v)//注意这里的appdata_img
        {
    
    
            v2f o;
            o.vertex = UnityObjectToClipPos(v.vertex);

            half2 uv = v.texcoord;

            o.uv[0] = uv;
            o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
			o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
			o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
			o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;

            return o;
        }

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

            fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];
            sum += tex2D(_MainTex, i.uv[1]) * weight[1];
            sum += tex2D(_MainTex, i.uv[2]) * weight[1];
            sum += tex2D(_MainTex, i.uv[3]) * weight[2];
            sum += tex2D(_MainTex, i.uv[4]) * weight[2];

            return fixed4(sum,1.0);

        }

        ENDCG


        ZTest Always Cull Off ZWrite Off

        Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vertBlurCol
            #pragma fragment fragBlur
            ENDCG
        }

        Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vertBlurRow
            #pragma fragment fragBlur
            ENDCG
        }

    }
    Fallback Off
}






Bloom

It's very simple:
select the part with high enough brightness in the rendered texture, perform Gaussian blur, and then mix it with the original image

  • Here is a point that is easily misunderstood: Bloom is a screen effect, so how does it blur an object alone? Other parts of the scene are bound to be affected.
  • In fact, it is very simple. Put the objects that need bloom into a RenderTex, and mix them with the original image after the bloom is calculated.



the code

Script

using UnityEngine;

public class bloom : PostEffectsBase
{
    
    
    public Shader bloomShader;
    private Material bloom_Mat = null;

    public Material material{
    
    
        get{
    
    
            bloom_Mat = CheckShader_CreateMat(bloomShader, bloom_Mat);
            return bloom_Mat;
        }
    }

    //模糊所需参数
    [Range(0,4)]
    public int iterations = 3;
    [Range(0.2f, 10.0f)]
    public float blurSpread = 0.6f;
    [Range(1,8)]
    public int downSample = 2;    
    //bloom截取强度
    [Range(0.0f, 4f)]
    public float lumi = 0.6f; //不止到1,是为了HDR


    
    void OnRenderImage(RenderTexture src, RenderTexture dest) {
    
    
        if (material != null){
    
    
            material.SetFloat("_Lumi", lumi);

            int rtW = src.width/downSample;
			int rtH = src.height/downSample;
            RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH,0); 
            buffer0.filterMode = FilterMode.Bilinear;

            //先走第一个pass提取需要bloom的部分
            Graphics.Blit(src, buffer0,material,0);

            //注意循环内部创建的RenderTex在循环外是访问不到的,因此需要左手倒右手
			for (int i = 0; i < iterations; i++) {
    
    
				material.SetFloat("_BlurSize", 1.0f + i * blurSpread);

				RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

				Graphics.Blit(buffer0, buffer1, material, 1);
				RenderTexture.ReleaseTemporary(buffer0);
				buffer0 = buffer1;
				buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
				Graphics.Blit(buffer0, buffer1, material, 2);
				RenderTexture.ReleaseTemporary(buffer0);
				buffer0 = buffer1;
			}

            //这一步很重要啊,把bloom好的图像传递给Shader
            material.SetTexture("_Bloom", buffer0);
            Graphics.Blit(src, dest, material, 3);

			RenderTexture.ReleaseTemporary(buffer0);
        }else{
    
    
            Graphics.Blit(src, dest);
        }
    }
}




Shader

Shader "Unlit/bloom"
{
    
    
    Properties
    {
    
    
        _MainTex ("Base", 2D) = "white" {
    
    }
        _Bloom ("Bloom", 2D) =  "black"{
    
    } //用于存储亮度图
        _BlurSize ("Blur Size", float) = 1.0
        _Lumi ("lumi", float) = 0.6   //这个值是用来控制bloom的阈值
    }

    SubShader
    {
    
    

//region CGINCLUDE
        CGINCLUDE
        #include "UnityCG.cginc"

        sampler2D _MainTex;
        sampler2D _Bloom;
        half4 _MainTex_TexelSize; //内置方法,可以获取纹素大小
        float _BlurSize;
        float _Lumi;

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

        v2f vertBloom(appdata_img v){
    
    
            v2f o;
            o.vertex = UnityObjectToClipPos(v.vertex);
            o.uv = v.texcoord;
            return o;
        }

        fixed Luminance(fixed4 color){
    
     //这个是明度的计算,可以去看本人的文章“色彩知识总结”
            return 0.2125*color.r + 0.7154*color.g + 0.0721*color.b;
        }

        fixed4 fragBloom(v2f i): SV_Target {
    
    
            fixed3 c = tex2D(_MainTex, i.uv);
            fixed l = clamp(Luminance(c) - _Lumi, 0, 1); //clamp函数是一个截取函数,后面两个参数是范围
            return  fixed4 (c*l,1);//为什么这里要乘?
            //这里要乘而不是单纯的取0/1,是因为:
            //(1)GPU不擅长分支运算
            //(2)数值越大的fragment在模糊时,影响的范围更大,符合真实逻辑,乘法保留了这种特性
        }
//endregion

//region PASS_2
        struct v2fMerg{
    
    
            float4 vertex : SV_POSITION;
            float4 uv : TEXCOORD0;
        };
        
        v2fMerg vertMerge(appdata_img v)
        {
    
    
            v2fMerg o;
            o.vertex = UnityObjectToClipPos(v.vertex);

            //将两张纹理的坐标分开是为了处理平台差异化
            o.uv.xy = v.texcoord;
            o.uv.zw = v.texcoord;

            //平台差异化处理,详见“Unity的一些机制”和《精要》5.6.1
            #if UNITY_UV_STARTS_AT_TOP			
			if (_MainTex_TexelSize.y < 0.0)
				o.uv.w = 1.0 - o.uv.w;
			#endif

            return o;
        }

        fixed4 fragMerge(v2fMerg i): SV_Target{
    
    
            //直接加?
            //当然直接相加,不然不就是单纯的模糊了,bloom的亮度就是要超额,超额也更适合HDR
            return tex2D(_MainTex, i.uv.xy) + tex2D(_Bloom, i.uv.xy);
        }

//endregion
        ENDCG
//endregion


        ZTest Always Cull Off ZWrite Off

        Pass//提取亮度Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vertBloom
            #pragma fragment fragBloom
            ENDCG
        }

        //模糊Pass
        //注意这里是直接引用了同工程下的另外一个shader中的Pass
        UsePass "Unity Shaders Book/Chapter 12/Gaussian Blur/GAUSSIAN_BLUR_VERTICAL"
		UsePass "Unity Shaders Book/Chapter 12/Gaussian Blur/GAUSSIAN_BLUR_HORIZONTAL"

        Pass//混合Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vertMerge
            #pragma fragment fragMerge
            ENDCG
        }

    }
    Fallback Off
}


Guess you like

Origin blog.csdn.net/dogman_/article/details/129744579