Unity Shader - 均值模糊和高斯模糊

说到模糊处理我们一般就会想到多种模糊处理方法,如均值模糊,高斯模糊等等方式。
通常用它们来减少图像噪声以及降低细节层次。这种模糊技术生成的图像,其视觉效果就像是经过一个半透明屏幕在观察图像,这与镜头焦外成像效果散景以及普通照明阴影中的效果都明显不同。

均值模糊高斯模糊它们各有各的优势,均值模糊处理速度更快,实现也相对简单一些,高斯模糊处理效果更好,性能差点,实现相对复杂点。

我们先来实现一下相对简单的均值模糊效果!

均值模糊:

最终效果图如下:

左边为原图,右边为均值模糊处理后的效果
在这里插入图片描述
说到模糊处理就不得不说滤波器,也可以叫做卷积核,模糊处理的过程其实就是对原图卷积的一个过程。
滤波器和模糊相关的可以参考这篇文章

说白了均值模糊就是对周围像素点取平均值!

该文章用到的是一个三阶的卷积核
[ 1 1 1 1 1 1 1 1 1 ] \begin{bmatrix}1 & 1 & 1\\\\1 &1& 1\\\\1 &1& 1\end{bmatrix} 111111111

如果处理效果不明显,我们可以通过不断迭代加重模糊效果。

具体实现请看以下代码:

Shader如下:

// ---------------------------【均值模糊】---------------------------
//create by 长生但酒狂
Shader "lcl/screenEffect/BoxBlur"  
{  
    // ---------------------------【属性】---------------------------
    Properties  
    {  
        _MainTex("MainTex", 2D) = "white" {}  
    }  
    // ---------------------------【子着色器】---------------------------
    SubShader  
    {  
        //后处理效果一般都是这几个状态  
        ZTest Always  
        Cull Off  
        ZWrite Off  
        Fog{ Mode Off }  
        // ---------------------------【渲染通道】---------------------------
        Pass  
        {  
            // ---------------------------【CG代码】---------------------------
            CGPROGRAM  
            #pragma vertex vert  
            #pragma fragment frag  
            #include "UnityCG.cginc"  
            //顶点着色器输出结构体 
            struct VertexOutput  
            {  
                float4 pos : SV_POSITION;   //顶点位置  
                float2 uv  : TEXCOORD0; //纹理坐标
                float4 uv1 : TEXCOORD1; //存储两个uv坐标
                float4 uv2 : TEXCOORD2; //存储两个uv坐标
                float4 uv3 : TEXCOORD3; //存储两个uv坐标
                float4 uv4 : TEXCOORD4; //存储两个uv坐标
            };  
            
            // ---------------------------【变量申明】---------------------------
            sampler2D _MainTex;  
            //纹理中的单像素尺寸  
            float4 _MainTex_TexelSize;  
            // 模糊半径
            float _BlurRadius;
            // ---------------------------【顶点着色器】---------------------------
            VertexOutput vert(appdata_img v)  
            {  
                VertexOutput o;  
                o.pos = UnityObjectToClipPos(v.vertex);  
                //uv坐标  
                o.uv = v.texcoord.xy;  
                //计算周围的8个uv坐标
                o.uv1.xy = v.texcoord.xy + _MainTex_TexelSize.xy * float2(1, 0) * _BlurRadius;  
                o.uv1.zw = v.texcoord.xy + _MainTex_TexelSize.xy * float2(-1, 0) * _BlurRadius;

                o.uv2.xy = v.texcoord.xy + _MainTex_TexelSize.xy * float2(0, 1) * _BlurRadius;
                o.uv2.zw = v.texcoord.xy + _MainTex_TexelSize.xy * float2(0, -1) * _BlurRadius;

                o.uv3.xy = v.texcoord.xy + _MainTex_TexelSize.xy * float2(1, 1) * _BlurRadius;
                o.uv3.zw = v.texcoord.xy + _MainTex_TexelSize.xy * float2(-1, 1) * _BlurRadius;

                o.uv4.xy = v.texcoord.xy + _MainTex_TexelSize.xy * float2(1, -1) * _BlurRadius;
                o.uv4.zw = v.texcoord.xy + _MainTex_TexelSize.xy * float2(-1, -1) * _BlurRadius;
                return o;  
            }  
            
            // ---------------------------【片元着色器】---------------------------
            fixed4 frag(VertexOutput i) : SV_Target  
            {  
                fixed4 color = fixed4(0,0,0,0);  
                color += tex2D(_MainTex, i.uv.xy);
                color += tex2D(_MainTex, i.uv1.xy);
                color += tex2D(_MainTex, i.uv1.zw);
                color += tex2D(_MainTex, i.uv2.xy);
                color += tex2D(_MainTex, i.uv2.zw);
                color += tex2D(_MainTex, i.uv3.xy);
                color += tex2D(_MainTex, i.uv3.zw);
                color += tex2D(_MainTex, i.uv4.xy);
                color += tex2D(_MainTex, i.uv4.zw);
                // 取平均值
                return color / 9;
            }
            ENDCG  
        }  
        
    }  
}  

C#如下:

// ---------------------------【均值模糊】---------------------------
using UnityEngine;

//编辑状态下也运行  
[ExecuteInEditMode]
//继承自PostEffectsbase
public class BoxBlur : PostEffectsBase {
    public Shader myShader;
    private Material _material = null;

    public Material material {
        get {
            _material = CheckShaderAndCreateMaterial (myShader, _material);
            return _material;
        }
    }

    //模糊半径  
    [Header("模糊半径")]
    [Range (0.2f, 10.0f)]
    public float BlurRadius = 1.0f;
    //降采样次数
    [Header("降采样次数")]
    [Range (1, 8)]
    public int downSample = 2;
    //迭代次数  
    [Header("迭代次数")]
    [Range (0, 4)]
    public int iteration = 1;

    //-----------------------------------------【Start()函数】---------------------------------------------    
    void Start () {
        //找到当前的Shader文件  
        myShader = Shader.Find ("lcl/screenEffect/BoxBlur");
    }
    //-------------------------------------【OnRenderImage函数】------------------------------------    
    // 说明:此函数在当完成所有渲染图片后被调用,用来渲染图片后期效果
    //--------------------------------------------------------------------------------------------------------  
    void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture) {
        if (material) {
            //申请RenderTexture,RT的分辨率按照downSample降低
            RenderTexture rt = RenderTexture.GetTemporary (sourceTexture.width >> downSample, sourceTexture.height >> downSample, 0, sourceTexture.format);

            //直接将原图拷贝到降分辨率的RT上
            Graphics.Blit (sourceTexture, rt);

            //进行迭代
            for (int i = 0; i < iteration; i++) {
                material.SetFloat ("_BlurRadius", BlurRadius);
                Graphics.Blit (rt, sourceTexture, material);
                Graphics.Blit (sourceTexture, rt, material);
            }
            //将结果输出  
            Graphics.Blit (rt, destTexture);

            //释放RenderBuffer
            RenderTexture.ReleaseTemporary (rt);
        }
    }
}

高斯模糊:

通过上面的均值模糊的实现,我们可以看到均值模糊的处理效果并不是很好,不够平滑,这时我们就可以用高斯模糊来处理了。

高斯模糊 也叫高斯平滑,是在Adobe Photoshop、GIMP以及Paint.NET等图像处理软件中广泛使用的处理效果,通常用它来减少图像噪声以及降低细节层次。
它相对于均值模糊来说,效果更好,但是性能差点,实现起来也相对复杂一点。
并且它之所以叫高斯模糊,正是因为它运用了高斯的正态分布的密度函数!

公式如下:
在这里插入图片描述
在图形上表示为:
在这里插入图片描述
在图形上,正态分布是一种钟形曲线,越接近中心,取值越大,越远离中心,取值越小。

计算平均值的时候,我们只需要将"中心点"作为原点,其他点按照其在正态曲线上的位置,分配权重,就可以得到一个加权平均值。

二维方程则是:
在这里插入图片描述

需要特别注意的地方,由于对于图像的处理高斯正态分布是二维的,如果按照正常情况下计算的话,该算法的计算时间复杂度非常高,假设屏幕分辨率是MN,我们的高斯核大小是mn,那么进行一次后处理的时间复杂度为O(MNmn)!
此时我们可以把拆分为两个一维高斯公式来处理,即横线和纵向分别做处理,那么我们在横向方向需要M
mn次采样操作,而在竖向方向需要Nm*n次采样操作,总共的时间复杂度就是O((M+N)mn)

二维高斯公式 推导为 X轴和Y轴的一维公式 如下:
在这里插入图片描述
上面的一维公式转换为代码:0.39894*exp(-0.5*x*x/(0.20*sigma))/sigma*sigma

有了该函数就可以分别计算出 垂直水平 方向的 每个点的权重了。

例如:
当 sigma = 5 时,带入公式即可计算出水平方向的每个点的权值了。

假定中心点的坐标是(0,0),那么在水平方向上距离它最近的4个点的坐标如下
在这里插入图片描述
为了计算权重,需要设定 sigma 的值。假定 sigma = 5,模糊半径为2,带入以上公式即可算出每个点的权重:
在这里插入图片描述
为了计算这5个点的加权平均值,需要对它们做 “归一化” 处理,即每个点的值除以它们的总和,结果如下:
在这里插入图片描述
把该权值分别乘以对应点的像素值,再加起来,就可以得到最终的值了,当然这只是水平方向的计算,垂直方向上同理。

具体实现代码如下:

shader:

// ---------------------------【高斯模糊】---------------------------
Shader "lcl/screenEffect/gaussBlur"  
{  
    // ---------------------------【属性】---------------------------
    Properties  
    {  
        _MainTex("MainTex", 2D) = "white" {}  
    }  
    // ---------------------------【子着色器】---------------------------
    SubShader  
    {  
        //后处理效果一般都是这几个状态  
        ZTest Always  
        Cull Off  
        ZWrite Off  
        Fog{ Mode Off }  
        // ---------------------------【渲染通道】---------------------------
        Pass  
        {  
            // ---------------------------【CG代码】---------------------------
            CGPROGRAM  
            #pragma vertex vert_blur  
            #pragma fragment frag_blur  
            #include "UnityCG.cginc"  
            
            //顶点着色器输出结构体 
            struct VertexOutput  
            {  
                float4 pos : SV_POSITION;   //顶点位置  
                float2 uv  : TEXCOORD0;     //纹理坐标  
                float4 uv01 : TEXCOORD1;    //存储两个纹理坐标  
                float4 uv23 : TEXCOORD2;    //存储两个纹理坐标  
            };  
            // ---------------------------【变量申明】---------------------------
            sampler2D _MainTex;  
            //纹理中的单像素尺寸  
            float4 _MainTex_TexelSize;  
            //给一个offset,这个offset可以在外面设置,是我们设置横向和竖向blur的关键参数  
            float4 _offsets;  
            
            // ---------------------------【顶点着色器】---------------------------
            VertexOutput vert_blur(appdata_img v)  
            {  
                VertexOutput o;  
                o.pos = UnityObjectToClipPos(v.vertex);  
                //uv坐标  
                o.uv = v.texcoord.xy;  
                
                //计算一个偏移值
                // offset(1,0,0,0)代表水平方向
                // offset(0,1,0,0)表示垂直方向  
                _offsets *= _MainTex_TexelSize.xyxy;  
                
                //由于uv可以存储4个值,所以一个uv保存两个vector坐标,_offsets.xyxy * float4(1,1,-1,-1)可能表示(0,1,0-1),表示像素上下两个  
                //坐标,也可能是(1,0,-1,0),表示像素左右两个像素点的坐标,下面*2.0,同理
                o.uv01 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1);  
                o.uv23 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1) * 2.0;  
                
                return o;  
            }  

            // 计算高斯权重
            float computerBluGauss(float x,float sigma) {
                return 0.39894*exp(-0.5*x*x/(0.20*sigma))/sigma*sigma;
            }
            
            // ---------------------------【片元着色器】---------------------------
            fixed4 frag_blur(VertexOutput i) : SV_Target  
            {  
                fixed4 color = fixed4(0,0,0,0);  
                //将像素本身以及像素左右(或者上下,取决于vertex shader传进来的uv坐标)像素值的加权平均  
                
                //这里的权值由高斯公式计算而来:
                // y = 0.39894*exp(-0.5*x*x/(sigma*sigma))/sigma;  sigma = 7;
                // 例如:
                // float w0 = computerBluGauss(0,8);
                // float w1 = computerBluGauss(1,8);
                // float w2 = computerBluGauss(2,8);
                // float sum = w0+w1*2+w2*2;
                // color += w0/sum * tex2D(_MainTex, i.uv);  
                // color += w1/sum * tex2D(_MainTex, i.uv01.xy);  
                // color += w1/sum * tex2D(_MainTex, i.uv01.zw);  
                // color += w2/sum * tex2D(_MainTex, i.uv23.xy);  
                // color += w2/sum * tex2D(_MainTex, i.uv23.zw);  

                //为了节约性能, 这里就直接取计算后的权值
                color += 0.4026 * tex2D(_MainTex, i.uv);  
                color += 0.2442 * tex2D(_MainTex, i.uv01.xy);
                color += 0.2442 * tex2D(_MainTex, i.uv01.zw);  
                color += 0.0545 * tex2D(_MainTex, i.uv23.xy);  
                color += 0.0545 * tex2D(_MainTex, i.uv23.zw);  
                return color;  
            }
            
            ENDCG  
            
        }  
        
    }  
    //后处理效果一般不给fallback,如果不支持,不显示后处理即可  
}  

C#:

// ---------------------------【高斯模糊】---------------------------
using UnityEngine;

//编辑状态下也运行  
[ExecuteInEditMode]
//继承自PostEffectsbase
public class GaussBlur : PostEffectsBase {
    public Shader gaussianBlurShader;
    private Material gaussianBlurMaterial = null;

    public Material material {
        get {
            gaussianBlurMaterial = CheckShaderAndCreateMaterial (gaussianBlurShader, gaussianBlurMaterial);
            return gaussianBlurMaterial;
        }
    }

    //模糊半径  
    [Header("模糊半径")]
    [Range (0.2f, 3.0f)]
    public float BlurRadius = 1.0f;
    //降采样次数  
    [Header("降采样次数")]
    [Range (1, 8)]
    public int downSample = 2;
    //迭代次数  
    [Header("迭代次数")]
    [Range (0, 4)]
    public int iteration = 1;
    // [Range (0, 100)]
    // public float sigma = 1;
    //-----------------------------------------【Start()函数】---------------------------------------------    
    void Start () {
        //找到当前的Shader文件  
        gaussianBlurShader = Shader.Find ("lcl/screenEffect/gaussBlur");
    }

    //-------------------------------------【OnRenderImage函数】------------------------------------    
    // 说明:此函数在当完成所有渲染图片后被调用,用来渲染图片后期效果
    //--------------------------------------------------------------------------------------------------------  
    void OnRenderImage (RenderTexture source, RenderTexture destination) {
        if (material) {
            //申请RenderTexture,RT的分辨率按照downSample降低  
            RenderTexture rt1 = RenderTexture.GetTemporary (source.width >> downSample, source.height >> downSample, 0, source.format);
            RenderTexture rt2 = RenderTexture.GetTemporary (source.width >> downSample, source.height >> downSample, 0, source.format);

            //直接将原图拷贝到降分辨率的RT上  
            Graphics.Blit (source, rt1);

            //进行迭代高斯模糊  
            for (int i = 0; i < iteration; i++) {
                //第一次高斯模糊,设置offsets,竖向模糊  
                material.SetVector ("_offsets", new Vector4 (0, BlurRadius, 0, 0));
                // material.SetFloat ("_sigma", sigma);
                Graphics.Blit (rt1, rt2, material);
                //第二次高斯模糊,设置offsets,横向模糊  
                material.SetVector ("_offsets", new Vector4 (BlurRadius, 0, 0, 0));
                // material.SetFloat ("_sigma", sigma);
                Graphics.Blit (rt2, rt1, material);
            }

            //将结果输出  
            Graphics.Blit (rt1, destination);

            //释放申请的RenderBuffer
            RenderTexture.ReleaseTemporary (rt1);
            RenderTexture.ReleaseTemporary (rt2);
        }
    }
}

最后看看两种模糊处理的效果对比:
在这里插入图片描述
🤣然而我好像看不出什么区别…hhhh

参考文献

https://blog.csdn.net/poem_qianmo/article/details/51871531
https://www.zhihu.com/question/54918332/answer/142137732
https://blog.csdn.net/puppet_master/article/details/52783179

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值