一.简介

上一篇文章学习了模糊的原理以及基本的模糊实现,对于清晰和模糊这个定义感觉还是比较说明问题,这里再贴出一下:“清晰的图片,各个像素之间会有明显的过渡,而如果各个像素之间的差距不是很大,那么图像就会模糊了”。鉴于这个定义,我们就可以通过代码来实现模糊的效果。上一篇Unity Shader-后处理:均值模糊中实现了一个基本的均值模糊,也就是将一个像素和其周围的像素取平均值从而进行模糊,并且通过迭代处理的方式,增强了模糊的效果。但是,均值模糊由于采样次数较少,每个像素以及其周围像素的权值是相同的,模糊出来的效果不佳,而多次迭代处理虽然可以增强模糊效果,但是迭代大大地增加了性能的消耗,虽然在学习时可以用迭代来达到效果,但是要实际使用的时候,效率就不得不成为我们考虑的重要因素。所以,这一次,我们来学习一下更加高级的模糊效果-高斯模糊。
高斯模糊(Gaussian Blur),又叫做高斯平滑。高斯模糊主要的功能是对图片进行加权平均的过程,与均值模糊中周围像素取平均值不同,高斯模糊进行的是一个加权平均操作,每个像素的颜色值都是由其本身和相邻像素的颜色值进行加权平均得到的,越靠近像素本身,权值越高,越偏离像素的,权值越低。而这种权值符合我们比较熟悉的一种数学分布-正态分布,又叫高斯分布,所以这种模糊就是高斯模糊啦。

二.概念介绍

1.正态分布

先来复习一下正太分布,上一次听说这个词儿应该还是大二时的《概率与数理统计》课上。正太分布,又名高斯分布,这个分布函数具有很多非常漂亮的性质,使得其在诸多领域都有非常重要的影响力。而且,正态分布是一个比较自然的分布,在特定条件下,大量统计独立的随机变量的平局值的分布趋近于正态分布,这也就是传说中的中心极限定理。说了这么多,归结起来就是一句话,高斯分布比较好看,所以我们就用高斯分布作为我们进行加权计算时的权值参考。
高斯分布的定义如下:
其中μ是正态分布随机变量的均值,也就是期望值,也就是下图中x轴上最中间的位置,随机变量在μ两侧对称分布,而第二个参数σ^2是这个随机变量的方差,因而正态分布记为N(μ,σ2 ),符合正态分布的随机变量越靠近μ则概率越大,而越远离μ概率越小,σ越小,分布越集中在μ附近,σ越大,分布越分散。而当μ=0,σ^2=1时,称为标准正态分布,记为N(0,1)。正态分布的图像如下图所示:
那么,这个分布和我们的高斯模糊有什么关系呢?简单来说,我们需要让我们的采样符合高斯分布。那么,我们处理每个像素的时候,像素本身的点就对应着μ对应的权值,而我们要在像素周围采样,这个采样的范围就可以用σ表示,比如我们的方差为1,那么我们直接向外采一个像素的值,基本就可以达到模糊的效果了;而如果方差为0,那么μ点的权值最大,加权平均后仍然为原像素值,等于没模糊;而方差很大,那么采样的范围就很广,就会更加模糊。
关于高斯模糊,还有一点,就是采样的个数,也就是传说中的高斯模板(高斯核)的大小。也就是我们要取几个采样点的问题,μ对应像素点本身,μ±1σ,μ±2σ分别代表向外1个采样点,两个采样点,当然由于越向外,对应的权值越小,所以后面的我们基本可以不予考虑,这里我们就采用一个μ,μ±1σ,μ±2σ,μ±3σ一共7个采样点作为高斯高斯核。
关于高斯核的权值设定,本人看了好几篇文章以及书籍,然而每个高斯模糊用的高斯核都不同,这里也没有什么标准。正如某图形学大牛说的:“图形学这东西,看起来是对的,那就是对的”,不管怎么样,毕竟最后都是给人看的,效果最好计算简单比什么都强!下面代码里面我自己设置了一套高斯权重,虽然看起来山寨了一点,不过满足相加等于1就行啦。

2.卷积

卷积是一个神奇的概念,最近看图像处理倒是经常看到这个词儿,想到上学的时候也没有搞懂这个东西,于是强迫症发作,决定查一查卷积到底怎么解释。对于卷积,百度百科上是这么说的:卷积是通过两个函数f和g生成第三个函数的一种数学算子。不过,高手在民间,知乎上对卷积的解释更加通俗易懂,这里摘抄一小段个人认为最为精辟的:
比如说你的老板命令你干活,你却到楼下打台球去了,后来被老板发现,他非常气愤,扇了你一巴掌(注意,这就是输入信号,脉冲),于是你的脸上会渐渐地(贱贱地)鼓起来一个包,你的脸就是一个系统,而鼓起来的包就是你的脸对巴掌的响应,好,这样就和信号系统建立起来意义对应的联系。下面还需要一些假设来保证论证的严谨:假定你的脸是线性时不变系统,也就是说,无论什么时候老板打你一巴掌,打在你脸的同一位置(这似乎要求你的脸足够光滑,如果你说你长了很多青春痘,甚至整个脸皮处处连续处处不可导,那难度太大了,我就无话可说了哈哈),你的脸上总是会在相同的时间间隔内鼓起来一个相同高度的包来,并且假定以鼓起来的包的大小作为系统输出。好了,那么,下面可以进入核心内容——卷积了!

如果你每天都到地下去打台球,那么老板每天都要扇你一巴掌,不过当老板打你一巴掌后,你5分钟就消肿了,所以时间长了,你甚至就适应这种生活了……如果有一天,老板忍无可忍,以0.5秒的间隔开始不间断的扇你的过程,这样问题就来了,第一次扇你鼓起来的包还没消肿,第二个巴掌就来了,你脸上的包就可能鼓起来两倍高,老板不断扇你,脉冲不断作用在你脸上,效果不断叠加了,这样这些效果就可以求和了,结果就是你脸上的包的高度随时间变化的一个函数了(注意理解);如果老板再狠一点,频率越来越高,以至于你都辨别不清时间间隔了,那么,求和就变成积分了。可以这样理解,在这个过程中的某一固定的时刻,你的脸上的包的鼓起程度和什么有关呢?和之前每次打你都有关!但是各次的贡献是不一样的,越早打的巴掌,贡献越小,所以这就是说,某一时刻的输出是之前很多次输入乘以各自的衰减系数之后的叠加而形成某一点的输出,然后再把不同时刻的输出点放在一起,形成一个函数,这就是卷积,卷积之后的函数就是你脸上的包的大小随时间变化的函数。本来你的包几分钟就可以消肿,可是如果连续打,几个小时也消不了肿了,这难道不是一种平滑过程么?反映到剑桥大学的公式上,f(a)就是第a个巴掌,g(x-a)就是第a个巴掌在x时刻的作用程度,乘起来再叠加就ok了


简单来说,卷积就是一个进行数学处理的一个算子。在图像处理中,设图像为f(x),模板g(x),然后图像处理就是将模板g(x)在图像f中移动,每移动到一个像素位置,就把f(x)与g(x)定义域相交的元素进行乘积并求和,得出新的图像中的该像素点,当全部像素点操作完成后,就得到了卷积后的图像,模板就是卷积核,上文中我们定义的高斯核就是一个卷积核。
上面我们说过高斯核,正常来看,我们应该是取像素为μ点,然后像素上下左右分别取一些像素点作为采样点,然后根据距离μ点的距离分别乘以相应的权值,作为处理后的这一点的像素值。但是这样做有一个弊端,就是我们在处理每个像素点的时候,需要进行大量的采样计算,需要像素点以及像素点周围几圈的采样点才能将中间像素周围所有的像素进行加权平均。而这样的操作是逐像素计算的,更可怕的是,这种效果是全屏幕后处理效果!!假设屏幕分辨率是M*N,我们的高斯核大小是m*n,那么进行一次后处理的时间复杂度为O(M*N*m*n)
有什么好办法进行优化吗?
高斯模糊就是一个卷积操作,而这个操作是一个线性操作,换句话说这个系统是一个线性系统。所谓线性系统是一个系统的输入和输出是线性关系,就是说整个系统可以拆分成多个无关的独立变化,而整个系统就是这些变化的累加。比如一个系统,输入x1(t)产生输出y1(t),表示为x1(t)->y1(t),而另一个输入x2(t)产生输出y2(t)即x2(t)->y2(t)。这个系统是线性的,当且仅当x1(t)+x2(t)->y1(t)+y2(t)。
我画了一个简单的算平均数的图示,希望可以解释清楚这样的问题:
从上面的图中我们看出,直接计算整个的平均数,和先计算横向的平均,再计算竖向的平均,得到的结果是相同的,也就是说两者是等价的(注意,本图只是一个类比,并不代表高斯模糊的原理,证明线性系统可以拆分成多个独立的操作,或者哪位高人有更好的证明方式也可以指点小弟一下)。
我们的高斯模糊操作,如果整个图像进行采样,那么会进行M*N*m*n次采样操作,而如果是先横向,再竖向,那么我们在横向方向需要M*m*n次采样操作,而在竖向方向需要N*m*n次采样操作,总共的时间复杂度就是O((M+N)*m*n)。从M*N降到M+N,一般地,M和N为屏幕分辨率,比如1024*768,那么,这样一个操作就大大降低了时间复杂度!!!不过需要一点点空间作为中间结果的缓存,不过这点缓存对于性能的优化还是很值得的。

三.高斯模糊的实现

扯了这么久的理论,终于要开始写代码了,这里不多说,直接上带有注释的代码了。
shader部分:
Shader "Custom/GaussianBlur"
{Properties{_MainTex("Base (RGB)", 2D) = "white" {}}//通过CGINCLUDE我们可以预定义一些下面在Pass中用到的struct以及函数,//这样在pass中只需要设置渲染状态以及调用函数,shader更加简洁明了CGINCLUDE#include "UnityCG.cginc"//blur结构体,从blur的vert函数传递到frag函数的参数struct v2f_blur{float4 pos : SV_POSITION; //顶点位置float2 uv  : TEXCOORD0;       //纹理坐标float4 uv01 : TEXCOORD1;  //一个vector4存储两个纹理坐标float4 uv23 : TEXCOORD2; //一个vector4存储两个纹理坐标float4 uv45 : TEXCOORD3; //一个vector4存储两个纹理坐标};//shader中用到的参数sampler2D _MainTex;//XX_TexelSize,XX纹理的像素相关大小width,height对应纹理的分辨率,x = 1/width, y = 1/height, z = width, w = heightfloat4 _MainTex_TexelSize;//给一个offset,这个offset可以在外面设置,是我们设置横向和竖向blur的关键参数float4 _offsets;//vertex shaderv2f_blur vert_blur(appdata_img v){v2f_blur o;o.pos = mul(UNITY_MATRIX_MVP, v.vertex);//uv坐标o.uv = v.texcoord.xy;//计算一个偏移值,offset可能是(0,1,0,0)也可能是(1,0,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,*3.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;o.uv45 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1) * 3.0;return o;}//fragment shaderfixed4 frag_blur(v2f_blur i) : SV_Target{fixed4 color = fixed4(0,0,0,0);//将像素本身以及像素左右(或者上下,取决于vertex shader传进来的uv坐标)像素值的加权平均color += 0.4 * tex2D(_MainTex, i.uv);color += 0.15 * tex2D(_MainTex, i.uv01.xy);color += 0.15 * tex2D(_MainTex, i.uv01.zw);color += 0.10 * tex2D(_MainTex, i.uv23.xy);color += 0.10 * tex2D(_MainTex, i.uv23.zw);color += 0.05 * tex2D(_MainTex, i.uv45.xy);color += 0.05 * tex2D(_MainTex, i.uv45.zw);return color;}ENDCG//开始SubShaderSubShader{//开始一个PassPass{//后处理效果一般都是这几个状态ZTest AlwaysCull OffZWrite OffFog{ Mode Off }//使用上面定义的vertex和fragment shaderCGPROGRAM#pragma vertex vert_blur#pragma fragment frag_blurENDCG}}
//后处理效果一般不给fallback,如果不支持,不显示后处理即可
}

c#部分:
using UnityEngine;
using System.Collections;//编辑状态下也运行
[ExecuteInEditMode]
//继承自PostEffectBase
public class GaussianBlur : PostEffectBase
{//模糊半径public float BlurRadius = 1.0f;//降分辨率public int downSample = 2;//迭代次数public int iteration = 1;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));Graphics.Blit(rt1, rt2, _Material);//第二次高斯模糊,设置offsets,横向模糊_Material.SetVector("_offsets", new Vector4(BlurRadius, 0, 0, 0));Graphics.Blit(rt2, rt1, _Material);}//将结果输出Graphics.Blit(rt1, destination);//释放申请的两块RenderBuffer内容RenderTexture.ReleaseTemporary(rt1);RenderTexture.ReleaseTemporary(rt2);}}
}
注意,这里的GaussianBlur继承了PostEffectBase类。该类在之前的文章《UnityShader-后处理:简单亮度对比度饱和度调整》中有完整的实现,这里就不贴出代码了。
其实GaussianBlur和上一篇文章中的SimpleBlurEffect基本一致,改变的地方就在于我们需要两遍高斯模糊,这两遍模糊分别是针对竖向和横向进行模糊的,区别和设置就在于offset的设置。其实也可以分别使用两个不同的pass分别作为horizontal和vertical方向的模糊,不过这样代码有冗余,所以还是用这种比较“优雅”的方式实现了。

四.效果展示

我们在MainCamera上挂在GaussianBlur脚本,然后把GaussianBlur.shader赋给shader槽,就可以看到模糊的效果了。
首先看一下原图效果,如果不需要后处理,直接关掉后处理控件最好,因为Graphic.Blit等操作也是很费的操作。不过这里为了演示,直接模糊半径为0,不降分辨率,迭代1次,即可显示清晰的原图效果:
当模糊半径设为1,分辨率降低为1/2,迭代1次,轻微的模糊效果:
模糊半径为1,分辨率将为1/4,迭代一次,更加模糊的效果:
模糊半径为1,分辨率将为1/4,迭代两次,可以达到下图这种毛玻璃效果(PS,这种效果在最近新出的《天下》手游里面,点击界面后,屏幕背景就会变成类似的效果):
通过上面的图片我们看到,其实降低分辨率对我们的高斯模糊效果有很大的提升。其实降分辨率的那个操作本身就是一个类似模糊的效果,这种效果在我们想要清晰的图片时看起来会特别蛋疼,如下图所示:
但是,如果不想要清晰的图片,降分辨率反倒让我们的模糊效果更好。而且,最最重要的是,降分辨率之后,pixel shader采样会大大降低,这对我们的效率会有很大的提升。
高斯模糊的效果更加平滑,没有简单的均值模糊那种"近视眼"或者像素块的感觉,下面看一下高斯模糊和上一篇文章中的简单均值模糊的对比:

五.参考文献

unity shader部分:
http://blog.csdn.net/poem_qianmo/article/details/51871531
http://blog.csdn.net/costfine/article/details/46975441
图像处理部分:
http://blog.csdn.net/markl22222/article/details/10313565
http://www.cnblogs.com/hoodlum1980/p/4528486.html
http://www.cnblogs.com/hoodlum1980/archive/2008/03/03/1088567.html

Unity Shader-后处理:高斯模糊相关推荐

  1. unity shader 后处理实现水墨风格渲染「Low Poly 」变「水墨画 」

    #水墨风格渲染 这次学校的比赛打算做一个中国古代背景的游戏,所以尝试做了水墨风格的渲染. 主要按以下四步来实现的效果: 根据色调和饱和度调整饱和度. 对图像进行模糊 水墨风格的物体边缘 物体内画笔笔触 ...

  2. Unity Shader - 后处理:油画效果

    效果图:                                                                                     效果对比图(一)

  3. 【Unity Shader】屏幕后处理3.0:均值模糊和高斯模糊

    发现之前学习记录的太过详细,导致整理的过程占用太长的时间了,这篇之后博客重要的是掌握实现过程,关于基础的理论会更多的放上别人写得更好的文章. 参考:[Unity Shader编程]之十五 屏幕高斯模糊 ...

  4. Unity Shader入门学习(5):基础屏幕后处理

    1.后处理基类 //屏幕后处理,顾名思义,通常指的是在渲染完整个场景得到屏幕图像后,再对这个图像进行一系列操作,实现各种屏幕特效. //基类的作用有二:检测平台是否支持后处理效果,及创建一个用于处理渲 ...

  5. Unity Shader - 模仿RenderImage制作全屏Quad,可以制作自定义后处理的流程

    文章目录 先尝试GL类来制作 Shader CSharp 画个三角型 画个全屏的Quad 发现GL没有RenderTarget之类的 使用CommandBuffer来绘制全屏的Quad GL渲染到目标 ...

  6. Unity shader学习之屏幕后期处理效果之高斯模糊

    高斯模糊,见 百度百科. 也使用卷积来实现,每个卷积元素的公式为: 其中б是标准方差,一般取值为1. x和y分别对应当前位置到卷积中心的整数距离. 由于需要对高斯核中的权重进行归一化,即使所有权重相加 ...

  7. 【Unity Shader编程】之十五 屏幕高斯模糊(Gaussian Blur)后期特效的实现

    本系列文章由@浅墨_毛星云 出品,转载请注明出处.   文章链接: http://blog.csdn.net/poem_qianmo/article/details/51871531 作者:毛星云(浅 ...

  8. (一)unity shader在实际项目中出现的问题————unity的后处理插件景深效果在某些低档机(如三星)无效的解决方案

    本专栏主要解决一些移动平台上unity shader效果异常的问题.很多情况下我们发现unity中的shader在PC平台效果正常,但是在移动平台上效果不对,或者部分机型效果不对的问题,尤其是低档老年 ...

  9. 【Unity Shader】屏幕后处理2.0:实现Sobel边缘检测

    边缘检测是描边效果的一种实现方法,关于描边效果其实还有更好的基于深度+法线纹理实现的方法,这里就先以边缘检测为主进行学习. 1 理解卷积 参考:深入理解卷积(卷积核到底要不要翻卷) [深度学习]深度学 ...

  10. Unity Shader学习笔记(5)基于摄像机深度和法线的后处理描边效果

    文章目标 : 主要参考书籍为<Unity Shader入门精要>,本文主要注重于整理,方便后续直接调用. 渲染效果图: 主要相关代码: 摄像机脚本文件: using System.Coll ...

最新文章

  1. 值得mark的11个开源机器学习项目 .
  2. linux ssh服务的优化,SSH服务端配置、优化加速、安全防护
  3. android自定义图标下载,charts
  4. 18.抽象模板方法———获取程序运行的时间
  5. wpf和winform的那点区别
  6. Oracle中函数/过程返回结果集的几种方式
  7. java求第几位数字_怎么得到一个数的第n位数字 急求大神帮助
  8. 计算机仿真实验报告实验原理简述,数控编程实验报告总结
  9. Microsoft Visio 2003下载地址
  10. 硬件加密芯片介绍 及 加密芯片选择(加密IC) 加密芯片原理
  11. 基于SSM【爱校图书馆管理系统】附源码+论文
  12. 双11后,第一批买家秀曝光……
  13. 有了域名空间服务器怎么做网站,有了域名和空间怎么建网站?
  14. 《虚拟仿真实验教学解决方案(BJBR)》(Yanlz+Unity+SteamVR+VR+AR+MR+HR+??BR??+??CR??+??DR??+??ER??+虚拟仿真+人机交互+立钻哥哥+==)
  15. JavaScript网页设计:用户登录页面
  16. OpenCV显示中文字体
  17. java集合之TreeMap 构造器 方法 比较器
  18. 计算机网络(ISP,因特网组成,分组交换,计算机网络性能,网络体系机构)
  19. 微信服务号开发----发送消息
  20. React中antd日期选择框,指定区间禁用时间

热门文章

  1. 【技术栈——00061】搭建关于python项目docker镜像的Dockerfile文件示例(自己的)
  2. UnityHub 下载unity 卡在最后不动,已解决
  3. 云计算时代,数据中心运维应该注意哪些问题?
  4. 《咸鱼分享》DNS反向解析
  5. 微信小程序产品定位及功能介绍
  6. 将电脑调成护眼色不一定起到护眼的功能
  7. 计算二维紧束缚模型费米面和nesting程序新思路
  8. 七个实用的分布式开源框架
  9. Poco C++库简介
  10. Codeforces Round #548 (Div. 2) C. Edgy Trees(dfs || 并查集)