unity高斯模糊 & Bloom & 运动模糊


大部分解释说明都在代码注释里面,部分代码很长,本人进行了折叠,帮助更好理清思路,推荐复制放入VS CODE观看

   VS Code 展开/折叠快捷键:ctrl+k 再 ctrl+j/0


高斯模糊


  • 高斯核的构建
                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πσ21e2σ2x2+y2
    很简单,从二维正态分布中均匀的采样(按照矩阵的形状),得到采样点的值,之后归一化即可


  • 模糊的程度与参数的关系:

    核的范围越大,模糊程度越高
    核的分布越重尾,模糊程度越高(存疑,还没有实验)
    模糊运算的次数越多越模糊


  • 高斯核的分解:

    由于卷积核是二维的,其运算量是随边长平方数增长的,负担太大;
    若能分解为两个一维的,其运算量只会线性增长
    在这里插入图片描述
    将上述的两条,当作两个模糊核,分别做一次模糊运算,其结果和一个二维高斯模糊是一致的,这大大减少了运算
    另外由于两条的权值是一样的,单独的也是对称的,所以对于一个高斯核的存储,可以大大压缩: 5 × 5 5\times5 5×5的高斯核实际上只用存3个数字


    在实际计算中,会用到两个Pass,分别对应两条一维核(猜测是由于两个核不同,所以不能共用一个pass?)
    实际上为了节省运算,一般还需要缩放图像来节省运算(先把图像分辨率降低,再模糊,反正都是模糊)


代码

脚本

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

非常简单:
选取渲染纹理中亮度足够高的部分,进行高斯模糊,之后再和原图进行混合

  • 这里有一个很容易误解的点:Bloom是屏幕特效,那它是怎么单独模糊一个物体的呢?场景中其他的部分势必会收到影响啊。
  • 其实很简单,把需要bloom的物体单独提出到一张RenderTex,bloom算完之后和原图混合就好了。



代码

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
}


猜你喜欢

转载自blog.csdn.net/dogman_/article/details/129744579