本系列文章由@浅墨_毛星云 出品,转载请注明出处。  
 文章链接: http://blog.csdn.net/poem_qianmo/article/details/51871531
 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 
 本文工程使用的Unity3D版本: 5.2.1 
 
 
 
 一、降采样与高斯模糊的原理
  
 
   
  首先梳理一下在Unity中实现高斯模糊效果需用到的几个图像处理的知识点,说起来也很巧,正好和之前我写过一个关于OpenCV的系列博客里的这篇文章(http://blog.csdn.net/poem_qianmo/article/details/22745559)涉及的知识点类似。
   
   
  
 
  
 
    
  
 
  降采样(Downsample)也称下采样(Subsample),按字面意思理解即是降低采样频率。对于一幅N*M的图像来说,如果降采样系数为k,则降采样即是在原图中每行每列每隔k个点取一个点组成一幅图像的一个过程。
  不难得出,降采样系数K值越大,则需要处理的像素点越少,运行速度越快。
  
 
  
 
  
 
    
  
 
  高斯模糊(Gaussian Blur),也叫高斯平滑,高斯滤波,其通常用它来减少图像噪声以及降低细节层次,常常也被用于对图像进行模糊。
  通俗的讲,高斯模糊就是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值经过加权平均后得到。高斯模糊的具体操作是:用一个模板(或称卷积、掩模)扫描图像中的每一个像素,用模板确定的邻域内像素的加权平均灰度值去替代模板中心像素点的值。
  高斯分布的数学表示如下:
  
  其中,x为到像素中心的距离,σ为标准差。
   
   
  
  高斯分布(正态分布曲线)
  分条来说明一下高斯模糊的几个要点:
  
  
 - 从数学的角度来看,图像的高斯模糊过程就是图像与正态分布做卷积。
- 由于正态分布又叫作高斯分布,所以这项技术就叫作高斯模糊。
- 高斯模糊能够把某一点周围的像素色值按高斯曲线统计起来,采用数学上加权平均的计算方法得到这条曲线的色值
- 所谓"模糊",可以理解成每一个像素都取周边像素的平均值。
- 图像与圆形方框模糊做卷积将会生成更加精确的焦外成像效果。由于高斯函数的傅立叶变换是另外一个高斯函数,所以高斯模糊对于图像来说就是一个低通滤波器。
 高斯模糊的原理大致如此。若各位还想进一步了解,可以参考高斯模糊的wiki,以及《Real-Time Rendering 3rd》,或各种图像处理的书籍。相关参考内容见附录中的reference。
  下面主要来一起看一下高斯模糊特效在Unity中的实现。
  
 
   
  
 
  
 
  
 
 二、高斯模糊特效在Unity中的实现
  
 
  
 
  
 
  
 
  Unity中的屏幕特效,通常分为两部分来实现:
 
 
- Shader代码实现部分
- C#/javascript代码实现部分
 
   上述两者结合起来,便可以在Unity中实现具有很强可控性和灵活性的屏幕后期特效。
  下面即是从这两个方面对高斯模糊的特效进行实现。其实现思路类似Standard Assets/Image Effect中的Blur,但是本文的实现更简洁,有更大的可控性。
   
   
  
 
   
 
  
 
  本次的高斯模糊Shader包含逐行注释后约200多行。
  书写思路方面,采用了3个通道(Pass)各司其职,他们分别是:
  
 - 通道0:降采样通道。
- 通道1:垂直方向模糊处理通道。
- 通道2:水平方向模糊处理通道。
 
  而三个通道中共用的变量、函数和结构体的代码位于CGINCLUDE和ENDCG之间。
  以下贴出经过详细注释的Shader源码:
   
   -  Shader "Learning Unity Shader/Lecture 15/RapidBlurEffect"  
-  {  
-        
-      Properties  
-      {  
-            
-          _MainTex("Base (RGB)", 2D) = "white" {}  
-      }  
-    
-        
-      SubShader  
-      {  
-          ZWrite Off  
-          Blend Off  
-    
-            
-            
-          Pass  
-          {  
-              ZTest Off  
-              Cull Off  
-    
-              CGPROGRAM  
-    
-                
-              #pragma vertex vert_DownSmpl  
-                
-              #pragma fragment frag_DownSmpl  
-    
-              ENDCG  
-    
-          }  
-    
-            
-            
-          Pass  
-          {  
-              ZTest Always  
-              Cull Off  
-    
-              CGPROGRAM  
-    
-                
-              #pragma vertex vert_BlurVertical  
-                
-              #pragma fragment frag_Blur  
-    
-              ENDCG  
-          }  
-    
-            
-            
-          Pass  
-          {  
-              ZTest Always  
-              Cull Off  
-    
-              CGPROGRAM  
-    
-                
-              #pragma vertex vert_BlurHorizontal  
-                
-              #pragma fragment frag_Blur  
-    
-              ENDCG  
-          }  
-      }  
-    
-    
-        
-      CGINCLUDE  
-    
-        
-      #include "UnityCG.cginc"  
-    
-        
-      sampler2D _MainTex;  
-        
-      uniform half4 _MainTex_TexelSize;  
-        
-      uniform half _DownSampleValue;  
-    
-        
-      struct VertexInput  
-      {  
-            
-          float4 vertex : POSITION;  
-            
-          half2 texcoord : TEXCOORD0;  
-      };  
-    
-        
-      struct VertexOutput_DownSmpl  
-      {  
-            
-          float4 pos : SV_POSITION;  
-            
-          half2 uv20 : TEXCOORD0;  
-            
-          half2 uv21 : TEXCOORD1;  
-            
-          half2 uv22 : TEXCOORD2;  
-            
-          half2 uv23 : TEXCOORD3;  
-      };  
-    
-    
-        
-      static const half4 GaussWeight[7] =  
-      {  
-          half4(0.0205,0.0205,0.0205,0),  
-          half4(0.0855,0.0855,0.0855,0),  
-          half4(0.232,0.232,0.232,0),  
-          half4(0.324,0.324,0.324,1),  
-          half4(0.232,0.232,0.232,0),  
-          half4(0.0855,0.0855,0.0855,0),  
-          half4(0.0205,0.0205,0.0205,0)  
-      };  
-    
-    
-        
-      VertexOutput_DownSmpl vert_DownSmpl(VertexInput v)  
-      {  
-            
-          VertexOutput_DownSmpl o;  
-    
-            
-            
-          o.pos = mul(UNITY_MATRIX_MVP, v.vertex);  
-            
-          o.uv20 = v.texcoord + _MainTex_TexelSize.xy* half2(0.5h, 0.5h);;  
-          o.uv21 = v.texcoord + _MainTex_TexelSize.xy * half2(-0.5h, -0.5h);  
-          o.uv22 = v.texcoord + _MainTex_TexelSize.xy * half2(0.5h, -0.5h);  
-          o.uv23 = v.texcoord + _MainTex_TexelSize.xy * half2(-0.5h, 0.5h);  
-    
-            
-          return o;  
-      }  
-    
-        
-      fixed4 frag_DownSmpl(VertexOutput_DownSmpl i) : SV_Target  
-      {  
-            
-          fixed4 color = (0,0,0,0);  
-    
-            
-          color += tex2D(_MainTex, i.uv20);  
-          color += tex2D(_MainTex, i.uv21);  
-          color += tex2D(_MainTex, i.uv22);  
-          color += tex2D(_MainTex, i.uv23);  
-    
-            
-          return color / 4;  
-      }  
-    
-        
-      struct VertexOutput_Blur  
-      {  
-            
-          float4 pos : SV_POSITION;  
-            
-          half4 uv : TEXCOORD0;  
-            
-          half2 offset : TEXCOORD1;  
-      };  
-    
-        
-      VertexOutput_Blur vert_BlurHorizontal(VertexInput v)  
-      {  
-            
-          VertexOutput_Blur o;  
-    
-            
-            
-          o.pos = mul(UNITY_MATRIX_MVP, v.vertex);  
-            
-          o.uv = half4(v.texcoord.xy, 1, 1);  
-            
-          o.offset = _MainTex_TexelSize.xy * half2(1.0, 0.0) * _DownSampleValue;  
-    
-            
-          return o;  
-      }  
-    
-        
-      VertexOutput_Blur vert_BlurVertical(VertexInput v)  
-      {  
-            
-          VertexOutput_Blur o;  
-    
-            
-            
-          o.pos = mul(UNITY_MATRIX_MVP, v.vertex);  
-            
-          o.uv = half4(v.texcoord.xy, 1, 1);  
-            
-          o.offset = _MainTex_TexelSize.xy * half2(0.0, 1.0) * _DownSampleValue;  
-    
-            
-          return o;  
-      }  
-    
-        
-      half4 frag_Blur(VertexOutput_Blur i) : SV_Target  
-      {  
-            
-          half2 uv = i.uv.xy;  
-    
-            
-          half2 OffsetWidth = i.offset;  
-            
-          half2 uv_withOffset = uv - OffsetWidth * 3.0;  
-    
-            
-          half4 color = 0;  
-          for (int j = 0; j< 7; j++)  
-          {  
-                
-              half4 texCol = tex2D(_MainTex, uv_withOffset);  
-                
-              color += texCol * GaussWeight[j];  
-                
-              uv_withOffset += OffsetWidth;  
-          }  
-    
-            
-          return color;  
-      }  
-    
-        
-      ENDCG  
-    
-      FallBack Off  
-  }  
   
  
 
  
 
  
 
  
 
   
 
  
 
  C#脚本文件的代码可以从我们之前的几篇分析屏幕特效实现的文章中重用(如这篇实现屏幕油画特效的文章:http://blog.csdn.net/poem_qianmo/article/details/49719247),只用稍微改一点细节即可。 
  贴出详细注释的配合Shader实现此特效的C#脚本:
  
   -  using UnityEngine;  
-  using System.Collections;  
-    
-    
-  [ExecuteInEditMode]  
-    
-  [AddComponentMenu("Learning Unity Shader/Lecture 15/RapidBlurEffect")]  
-  public class RapidBlurEffect : MonoBehaviour  
-  {  
-        
-      #region Variables  
-        
-        
-      private string ShaderName = "Learning Unity Shader/Lecture 15/RapidBlurEffect";  
-    
-        
-      public Shader CurShader;  
-      private Material CurMaterial;  
-    
-        
-      public static int ChangeValue;  
-      public static float ChangeValue2;  
-      public static int ChangeValue3;  
-    
-        
-      [Range(0, 6), Tooltip("[降采样次数]向下采样的次数。此值越大,则采样间隔越大,需要处理的像素点越少,运行速度越快。")]  
-      public int DownSampleNum = 2;  
-        
-      [Range(0.0f, 20.0f), Tooltip("[模糊扩散度]进行高斯模糊时,相邻像素点的间隔。此值越大相邻像素间隔越远,图像越模糊。但过大的值会导致失真。")]  
-      public float BlurSpreadSize = 3.0f;  
-        
-      [Range(0, 8), Tooltip("[迭代次数]此值越大,则模糊操作的迭代次数越多,模糊效果越好,但消耗越大。")]  
-      public int BlurIterations = 3;  
-    
-      #endregion  
-    
-        
-      #region MaterialGetAndSet  
-      Material material  
-      {  
-          get  
-          {  
-              if (CurMaterial == null)  
-              {  
-                  CurMaterial = new Material(CurShader);  
-                  CurMaterial.hideFlags = HideFlags.HideAndDontSave;  
-              }  
-              return CurMaterial;  
-          }  
-      }  
-      #endregion  
-    
-      #region Functions  
-        
-        
-        
-      void Start()  
-      {  
-            
-          ChangeValue = DownSampleNum;  
-          ChangeValue2 = BlurSpreadSize;  
-          ChangeValue3 = BlurIterations;  
-    
-            
-          CurShader = Shader.Find(ShaderName);  
-    
-            
-          if (!SystemInfo.supportsImageEffects)  
-          {  
-              enabled = false;  
-              return;  
-          }  
-      }  
-    
-        
-        
-        
-      void OnRenderImage(RenderTexture sourceTexture, RenderTexture destTexture)  
-      {  
-            
-          if (CurShader != null)  
-          {  
-                
-                
-              float widthMod = 1.0f / (1.0f * (1 << DownSampleNum));  
-                
-              material.SetFloat("_DownSampleValue", BlurSpreadSize * widthMod);  
-                
-              sourceTexture.filterMode = FilterMode.Bilinear;  
-                
-              int renderWidth = sourceTexture.width >> DownSampleNum;  
-              int renderHeight = sourceTexture.height >> DownSampleNum;  
-    
-                
-                
-              RenderTexture renderBuffer = RenderTexture.GetTemporary(renderWidth, renderHeight, 0, sourceTexture.format);  
-                
-              renderBuffer.filterMode = FilterMode.Bilinear;  
-                
-              Graphics.Blit(sourceTexture, renderBuffer, material, 0);  
-    
-                
-              for (int i = 0; i < BlurIterations; i++)  
-              {  
-                    
-                    
-                  float iterationOffs = (i * 1.0f);  
-                    
-                  material.SetFloat("_DownSampleValue", BlurSpreadSize * widthMod + iterationOffs);  
-    
-                    
-                    
-                  RenderTexture tempBuffer = RenderTexture.GetTemporary(renderWidth, renderHeight, 0, sourceTexture.format);  
-                    
-                  Graphics.Blit(renderBuffer, tempBuffer, material, 1);  
-                    
-                  RenderTexture.ReleaseTemporary(renderBuffer);  
-                    
-                   renderBuffer = tempBuffer;  
-    
-                    
-                    
-                  tempBuffer = RenderTexture.GetTemporary(renderWidth, renderHeight, 0, sourceTexture.format);  
-                    
-                  Graphics.Blit(renderBuffer, tempBuffer, CurMaterial, 2);  
-    
-                    
-                    
-                  RenderTexture.ReleaseTemporary(renderBuffer);  
-                    
-                  renderBuffer = tempBuffer;  
-              }  
-    
-                
-              Graphics.Blit(renderBuffer, destTexture);  
-                
-              RenderTexture.ReleaseTemporary(renderBuffer);  
-    
-          }  
-    
-            
-          else  
-          {  
-                
-              Graphics.Blit(sourceTexture, destTexture);  
-          }  
-      }  
-    
-    
-        
-        
-        
-      void OnValidate()  
-      {  
-            
-          ChangeValue = DownSampleNum;  
-          ChangeValue2 = BlurSpreadSize;  
-          ChangeValue3 = BlurIterations;  
-      }  
-    
-        
-        
-        
-      void Update()  
-      {  
-            
-          if (Application.isPlaying)  
-          {  
-                
-              DownSampleNum = ChangeValue;  
-              BlurSpreadSize = ChangeValue2;  
-              BlurIterations = ChangeValue3;  
-          }  
-            
-  #if UNITY_EDITOR  
-          if (Application.isPlaying != true)  
-          {  
-              CurShader = Shader.Find(ShaderName);  
-          }  
-  #endif  
-    
-      }  
-    
-        
-        
-        
-      void OnDisable()  
-      {  
-          if (CurMaterial)  
-          {  
-                
-              DestroyImmediate(CurMaterial);  
-          }  
-    
-      }  
-    
-   #endregion  
-    
-  }  
   
 
  将此C#代码拖拽到场景的主摄像机之上, 且你的工程中也存在2.1节中贴出的Shader代码,那么就可以在Game窗口中看到经过了屏幕模糊特效的处理后的镜头效果。
   
  而Inspector中可得到如下所示的脚本选项。
   
  其中,有3个选项可以调节,他们分别是:
  
 - [Down Sample Num] – 降采样的次数。此值越大,则采样间隔越大,需要处理的像素点越少,运行速度越快。
- [Blur Speread Size] -模糊扩散度。进行高斯模糊时,相邻像素点的间隔。此值越大相邻像素间隔越远,图像越模糊。但过大的值会导致失真。
- [Blur Iterations] -迭代次数。此值越大,则模糊操作的迭代次数越多,模糊效果越好,但消耗越大。
 调节这三个参数,便可以在场景中定制出自己需要的模糊特效。
   
  
 
  
 
  
 
   
 
  
 
  这边推荐几组效果出色较为出色的参数预设,方便有需要的朋友定制出适合自己的效果。
   
  
 
  
 
  
 
  
 
  
   
  
 
  
 
  
 
   
  
 
   
  
 
 三、最终实现的效果图示
  
 
  
 
  
 
  
 
   
   
  
 
  
 
  
 
  
  
 
  
 
  
 
   
 
  
   
  
 
  
   
  
 
  
   
   
  
 
   
 
   
   
  
 
  
 
  
 
   
  
   
   
  附1、本文配套源码下载链接
  
 
  
     【Github】本文Shader源码
  
   
  
 
 附2、Reference
   
 
 
   [1] https://en.wikipedia.org/wiki/Gaussian_blur
 
   [2] http://www.cnblogs.com/foxianmo/p/4931507.html
 
   [3]《Real-Time Rendering 3rd》,p467-p473.