1、高斯模糊的基本原理

高斯模糊效果是一种用于平滑图像并减少图像噪声和细节的图像处理技术,高斯模糊的主要目的是使图像的边缘和细节变得模糊和平滑,高斯模糊相当于利用 Shader 代码自动给屏幕图像进行模糊处理

高斯模糊是利用 高斯函数 计算出 高斯滤波核 中每个元素并进行归一化处理后,再和目标像素通过 卷积 计算后得到最终的效果

高斯滤波核也称为高斯核,它其实就是一个 N x N 的卷积核,它的大小可以自定义,但是一般会是一个 奇数 x 奇数 的大小,通常会是一个 3x3 、5x5、7x7、9x9 的大小
滤波核越大,模糊效果越明显,从效果和效率综合考虑,我们通常会使用 5x5 的大小,高斯滤波核中各元素的具体值,我们通过高斯函数来确定

高斯函数是由德国数学家和物理学家卡尔·弗里德里希·高斯(Carl Friedrich Gauss)提出的。我们将使用二维高斯函数来计算高斯模糊效果中的卷积核

σ 是标准方差,一般取值为1即可
x 和 y 是相对于高斯核中心的整数距离
e 是自然对数的底 ≈ 2.71828
π 是圆周率 ≈ 3.14159

归一化处理

刚才利用高斯函数得到的高斯滤波核,但是并没有对它进行归一化处理,这样是为了避免在卷积计算过程中引入额外的亮度变化或偏差
举例:假设一个3x3的卷积核,如果不进行归一化,卷积会将图像的亮度放大9倍,归一化后卷积核为1,图像亮度将保持不变

总体流程:

注意:高斯滤波核中的数值是定死的规则,可以直接写死参与计算,不用在代码中用高斯函数计算,会浪费性能

也就是说,我们利用计算出来的这个高斯滤波核(高斯核 或 卷积核 或 滤波核)和对应的像素点进行卷积计算,便可以得到最终该像素高斯模糊后的结果

2、高斯模糊效果的计算公式优化

如果直接基于它的基本原理进行计算,计算效率是较低的,因为对于一张 长W,宽H 的图像,会进行 5 * 5 * W * H 次纹理采样的颜色计算

为了降低计算次数,我们可以利用二维高斯函数的数学特性 —— 可分离性,即 二维高斯函数可以表示为两个一维高斯函数的乘积,从而大幅减少计算量

Gx 和 Gy 可以分别代表沿x轴和y轴的一维高斯函数,我们只需要让每个像素分别,与 Gx 进行水平卷积计算 ,与Gy进性垂直卷积计算,再将最终的计算结果相乘即可得出和之前一样的结果

因此我们可以利用这两个一维高斯函数,得到相同的卷积核结果,一个是水平方向的,一个是竖直方向的,但是内容一致

也就是说,我们可以利用计算出来的这两个一维高斯滤波核(高斯核 或 卷积核 或 滤波核)和对应的像素点进行卷积计算,再将两个结果相乘,便可以得到最终该像素高斯模糊后的结果

通过对计算公式的优化,我们将原本需要 N * N * W * H 的计算次数(N为高斯核大小)优化为了 2 * N * W * H,相当于将时间复杂度从 O(n²) 降低到了 O(n)

3、高斯模糊效果的计算方式优化

如果想要图片越模糊,那么需要扩大高斯滤波核的大小,越大越模糊,但是同样通过刚才的基础原理和计算公式优化能够感受到,如果通过扩大高斯滤波核的大小来达到更模糊的目的,付出的代价就是会更加消耗性能。
因此在Shader中我们不会提供控制高斯滤波核大小的参数,我们的滤波核始终会使用5x5大小的。
因此,我们就只能使用其他方式来控制模糊程度了,我们一般会使用以下三种方式:

  • 控制缩放纹理大小

高斯模糊的目的是让源纹理看起来模糊,那么我们完全可以缩放源纹理,Shader从更小的主纹理中进行采样,尺寸小了,自然计算的也少,也能更模糊

  • 控制模糊代码执行次数

高斯模糊的目的是让源纹理看起来模糊,那么我们完全可以缩放源纹理,Shader从更小的主纹理中进行采样,尺寸小了,自然计算的也少,也能更模糊

  • 控制纹理采样间隔距离

在Shader中进行uv采样时,可以自己控制采样像素的间隔位置,而不是只以一个单位来计算间隔,具体间隔几个单位,我们可以自定义通过这种方式也能增加模糊程度

4、基本实现

在Shader中写两个Pass,一个Pass用来计算 水平方向卷积,一个Pass用来计算 竖直方向卷积
两个Pass的区别:
顶点着色器中计算的uv偏移位置不同,一个水平偏移,一个竖直偏移。为什么不写在一个 Pass,因为写在一个 Pass 里的话,假如只迭代一遍,处理的像素都是同一个像素,而用两个 Pass,第二个 Pass 处理的像素是第一个 Pass 处理过后的像素

Shader "ShaderProj/10/GaussianBlur"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        CGINCLUDE
            
        #include "UnityCg.cginc"

        sampler2D _MainTex;
        half4 _MainTex_TexelSize;

        struct v2f
        {
            float2 uv[5] : TEXCOORD0;  //5个像素的uv坐标偏移
            float4 vertex : SV_POSITION;
        };

        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];

            for (int it = 1; it < 3; it++)
            {
                sum += tex2D(_MainTex, i.uv[it * 2 - 1]).rgb * weight[it];
                sum += tex2D(_MainTex, i.uv[it * 2]).rgb * weight[it];
            }

            return fixed4(sum, 1);
        }

        ENDCG
        
        Tags { "RenderType"="Opaque" }

        ZTest Always
        Cull Off
        ZWrite Off

        Pass
        {
            CGPROGRAM
            #pragma vertex vertBlurHorizontal
            #pragma fragment fragBlur

            v2f vertBlurHorizontal(appdata_base v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);

                half2 uv = v.texcoord;
                o.uv[0] = uv;
                o.uv[1] = uv + half2(_MainTex_TexelSize.x*1, 0);
                o.uv[2] = uv - half2(_MainTex_TexelSize.x*1, 0);
                o.uv[3] = uv + half2(_MainTex_TexelSize.x*2, 0);
                o.uv[4] = uv - half2(_MainTex_TexelSize.x*2, 0);

                return o;
            }
            ENDCG
        }

        Pass
        {
            CGPROGRAM
            #pragma vertex vertBlurVertical
            #pragma fragment fragBlur

            v2f vertBlurVertical(appdata_base v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);

                //5个像素的uv偏移
                half2 uv = v.texcoord;

                //去进行5个像素 水平位置的偏移获取
                o.uv[0] = uv;
                o.uv[1] = uv + half2(0, _MainTex_TexelSize.x*1);
                o.uv[2] = uv - half2(0, _MainTex_TexelSize.x*1);
                o.uv[3] = uv + half2(0, _MainTex_TexelSize.x*2);
                o.uv[4] = uv - half2(0, _MainTex_TexelSize.x*2);

                return o;
            }
            ENDCG
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GaussianBlur : PostEffectBase
{
    protected override void OnRenderImage(RenderTexture source, RenderTexture destination) {
        if (material != null) {
            RenderTexture buffer = RenderTexture.GetTemporary(source.width, source.height, 0);
            Graphics.Blit(source, buffer, material, 0);
            Graphics.Blit(buffer, destination, material, 1);
            RenderTexture.ReleaseTemporary(buffer);
        }
        else {
            Graphics.Blit(source, destination);
        }
    }
}

5、完整实现(计算方式优化)

Shader "ShaderProj/10/GaussianBlur"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _BlurSpread("BlurSpread", Float) = 1
    }
    SubShader
    {
        CGINCLUDE
            
        #include "UnityCg.cginc"

        sampler2D _MainTex;
        half4 _MainTex_TexelSize;
        float _BlurSpread;

        struct v2f
        {
            float2 uv[5] : TEXCOORD0;  //5个像素的uv坐标偏移
            float4 vertex : SV_POSITION;
        };

        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];

            for (int it = 1; it < 3; it++)
            {
                sum += tex2D(_MainTex, i.uv[it * 2 - 1]).rgb * weight[it];
                sum += tex2D(_MainTex, i.uv[it * 2]).rgb * weight[it];
            }

            return fixed4(sum, 1);
        }

        ENDCG
        
        Tags { "RenderType"="Opaque" }

        ZTest Always
        Cull Off
        ZWrite Off

        Pass
        {
            CGPROGRAM
            #pragma vertex vertBlurHorizontal
            #pragma fragment fragBlur

            v2f vertBlurHorizontal(appdata_base v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);

                half2 uv = v.texcoord;
                o.uv[0] = uv;
                o.uv[1] = uv + half2(_MainTex_TexelSize.x*1, 0) *_BlurSpread;
                o.uv[2] = uv - half2(_MainTex_TexelSize.x*1, 0) *_BlurSpread;
                o.uv[3] = uv + half2(_MainTex_TexelSize.x*2, 0) *_BlurSpread;
                o.uv[4] = uv - half2(_MainTex_TexelSize.x*2, 0) *_BlurSpread;

                return o;
            }
            ENDCG
        }

        Pass
        {
            CGPROGRAM
            #pragma vertex vertBlurVertical
            #pragma fragment fragBlur

            v2f vertBlurVertical(appdata_base v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);

                //5个像素的uv偏移
                half2 uv = v.texcoord;

                //去进行5个像素 水平位置的偏移获取
                o.uv[0] = uv;
                o.uv[1] = uv + half2(0, _MainTex_TexelSize.x*1) *_BlurSpread;
                o.uv[2] = uv - half2(0, _MainTex_TexelSize.x*1) *_BlurSpread;
                o.uv[3] = uv + half2(0, _MainTex_TexelSize.x*2) *_BlurSpread;
                o.uv[4] = uv - half2(0, _MainTex_TexelSize.x*2) *_BlurSpread;

                return o;
            }
            ENDCG
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GaussianBlur : PostEffectBase
{
    [Range(1,8)]
    public int downSample = 1;
    [Range(1, 16)]
    public int iterations = 1;
    [Range(0, 8)]
    public float blurSpread = 0.6f;

    protected override void OnRenderImage(RenderTexture source, RenderTexture destination) {
        if (material != null) {
            int rtW = source.width / downSample;
            int rtH = source.height / downSample;

            RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
            buffer.filterMode = FilterMode.Bilinear;
            Graphics.Blit(source, buffer);

            for (int i = 0; i < iterations; i++) {
                //如果想要模糊半径影响模糊想过更强烈 更平滑
                //一般可以在我们的迭代中进行设置 相当于每次迭代处理高斯模糊时 都在增加我们的间隔距离
                material.SetFloat("_BlurSpread", 1 + i * blurSpread);

                RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
                Graphics.Blit(buffer, buffer1, material, 0); //Color1
                RenderTexture.ReleaseTemporary(buffer);

                buffer = buffer1;
                buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
                Graphics.Blit(buffer, buffer1, material, 1);
                RenderTexture.ReleaseTemporary(buffer);
                buffer = buffer1;
            }
            
            Graphics.Blit(buffer, destination);
            RenderTexture.ReleaseTemporary(buffer);
        }
        else {
            Graphics.Blit(source, destination);
        }
    }
}

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐