Unity Shader屏幕后处理3.0:均值模糊和高斯模糊
Posted 九九345
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity Shader屏幕后处理3.0:均值模糊和高斯模糊相关的知识,希望对你有一定的参考价值。
发现之前学习记录的太过详细,导致整理的过程占用太长的时间了,这篇之后博客重要的是掌握实现过程,关于基础的理论会更多的放上别人写得更好的文章。
参考:【Unity Shader编程】之十五 屏幕高斯模糊(Gaussian Blur)后期特效的实现
1 基础概念
1.1 模糊
高斯模糊只是各种模糊方式中的一种。模糊,从图像处理角度可以理解成每个像素都取周围像素的均值,数值上会呈现一种“平滑”的效果,图形上就是中心像素失去细节,达到模糊效果!显然,既然是取均值,那自然这个均值的范围越大,模糊效果越厉害。
那么问题就来了——卷积核内每个网格内的权重该怎么分配?
1.2 均值模糊
是直接均匀分配吗?如果周围像素取均值权重都一样,也就是取算数平均值模糊,这种模糊方式叫做均值模糊,均值模糊的卷积核很简单:(下图来自忘了哪一篇的参考文章)
1.3 什么是降采样
哦对了!在实践均值/高斯模糊之前,还需要知道一个概念——降采样(DownSample)。字面理解其实就是降低采样率,一般会给个降采样系数。例如降采样系数取2的话,就是在源图像上每行每列每隔2个像素组成一幅图像。降采样系数越大,我们需要处理的像素就少了,那么图片就会越模糊,但如果太大了,得到的结果可能直接像素化了。
1.4 什么是高斯模糊
什么是高斯模糊?——使用正态分布作为卷积权重表的像素平滑方法。
正态分布
这是正态分布公式:
这是正态分布图像:
正态分布我们再熟悉不过了, 数学中常常被我们拿来处理各种概率计算问题,而且正态分布计算非常简单,可以快速得出结果,非要详细学习的话,推荐看看:正态分布在日常生活中的应用(一) - 知乎 (zhihu.com),这里不再赘述这些基础知识。
高斯函数
上面给出的正态分布,都是一维的关系,而我们想基于图像做正态分布,那就需要二维的正态分布。正态分布密度函数叫做高斯函数(Gaussian function),高斯函数的二维形式可以根据一维形式推导出来,结果如下:
有了这个函数我们能干嘛?还记得吗,第1节中我们探讨了“权重”的问题,而这个函数刚好能帮我们解决权重问题,因此接下来我们需要计算权重矩阵&模糊值,这一部分直接参考真正搞懂均值模糊、中值模糊、高斯模糊、双边模糊中的内容就行。而经过一系列操作后获得的权重集合,就可以当作进行高斯模糊卷积操作的卷积核。
拆分高斯卷积核
本身高斯函数就是正态分布的密度函数,而二维的正态分布函数本身就可以拆分成x、y两个轴向上的一维正态分布函数, 所以将卷积核拆分成两个一维的卷积核是可行的。 详细解释可参考:高斯模糊用到的二维高斯卷积核,为什么可以拆成两个一维的高斯卷积核?
行,理论知识点到为止,这里就先简单实践一下均值模糊和高斯模糊的效果:
2 实现均值模糊
实现模糊效果,一般会给用户提供的可以调整的参数有
- 迭代次数
- 模糊范围
- 降采样系数
实现均值模糊很简单,我取了3X3的卷积核,C#代码和Shader代码如下:
2.1 C#脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
[ExecuteInEditMode]
public class BoxBlur : MonoBehaviour
public Shader blurShader;
public Material blurMaterial;
[Header("迭代次数")]
[Range(0, 4)]
public int blurIterations = 1;
[Header("模糊范围")]
[Range(0.2f, 3.0f)]
public float blurSpread = 0.2f;
[Header("降采样系数")]
[Range(1, 8)]
public int downSample = 2;
private void OnRenderImage(RenderTexture source, RenderTexture destination)
if(blurMaterial != null)
int rtW = source.width / downSample;
int rtH = source.height / downSample;
//定义缓存rt
RenderTexture rt = RenderTexture.GetTemporary(rtW, rtH, 0);
//把source缩放后,存到了rt上
Graphics.Blit(source, rt, blurMaterial);
//开始迭代模糊
for (int i = 0; i < blurIterations; i++)
blurMaterial.SetFloat("_BlurSize", blurSpread);
Graphics.Blit(rt, source, blurMaterial);
Graphics.Blit(source, rt, blurMaterial);
//输出结果
Graphics.Blit (rt, destination, blurMaterial);
//释放缓存
RenderTexture.ReleaseTemporary(rt);
else
Debug.Log("Please input your Material");
Graphics.Blit(source, destination);
2.2 Shader代码
Shader "Unity Shaders Book/Chapter 12/BoxBlur"
Properties
_MainTex ("Texture", 2D) = "white"
SubShader
ZTest Always
Cull Off
ZWrite Off
Pass
CGPROGRAM
#pragma vertex vert
#pragma fragment BoxBlur
#include "UnityCG.cginc"
//properties
sampler2D _MainTex;
half4 _MainTex_TexelSize;
float _BlurSize;
struct v2f
float4 pos : SV_POSITION;
half2 uv[9] : TEXCOORD0;
;
v2f vert(appdata_img v)
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
//加入_BlurSize控制采样距离
o.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1, -1) * _BlurSize;
o.uv[1] = uv + _MainTex_TexelSize.xy * half2(0, -1) * _BlurSize;
o.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1) * _BlurSize;
o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0) * _BlurSize;
o.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0) * _BlurSize;
o.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0) * _BlurSize;
o.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1) * _BlurSize;
o.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1) * _BlurSize;
o.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 1) * _BlurSize;
return o;
fixed3 BoxBlur(v2f i) : SV_Target
fixed4 color = fixed4(0, 0, 0, 0);
for(int j=0;j<9;j++)
color +=tex2D(_MainTex, i.uv[j]);
return color/9.0;
ENDCG
2.3 呈现效果
3 实现高斯模糊
没啥好说的,就是用了两个Pass分别做了水平和竖直向的高斯模糊处理。
3.1 C#脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
[ExecuteInEditMode]
public class GaussianBlur : MonoBehaviour
public Shader blurShader;
public Material blurMaterial;
[Header("迭代次数")]
[Range(0, 4)]
public int blurIterations = 1;
[Header("模糊范围")]
[Range(0.2f, 3.0f)]
public float blurSpread = 0.2f;
[Header("降采样系数")]
[Range(1, 8)]
public int downSample = 2;
private void OnRenderImage(RenderTexture source, RenderTexture destination)
if(blurMaterial != null)
int rtW = source.width / downSample;
int rtH = source.height / downSample;
//定义缓存rt
RenderTexture rt0 = RenderTexture.GetTemporary(rtW, rtH, 0);
rt0.filterMode = FilterMode.Bilinear; //设置滤波模式
//把source缩放后,存到了rt0上
Graphics.Blit(source, rt0, blurMaterial);
//开始迭代模糊
for (int i = 0; i < blurIterations; i++)
blurMaterial.SetFloat("_BlurSize", blurSpread);
RenderTexture rt1 = RenderTexture.GetTemporary(rtW, rtH, 0);
//第一个Pass
Graphics.Blit(rt0, rt1, blurMaterial, 0);
RenderTexture.ReleaseTemporary(rt0); //把rt0释放
rt0 = rt1; //把第一个Pass的结果给rt0
rt1 = RenderTexture.GetTemporary(rtW, rtH, 0); //创建个新的rt1
//第二个Pass
Graphics.Blit(rt0, rt1, blurMaterial, 1);
RenderTexture.ReleaseTemporary(rt0);
rt0 = rt1; //继续进行下一个迭代的(如果有的话)
//输出结果
Graphics.Blit (rt0, destination, blurMaterial);
//释放缓存
RenderTexture.ReleaseTemporary(rt0);
else
Debug.Log("Please input your Material");
Graphics.Blit(source, destination);
3.2 Shader代码
Shader "Unity Shaders Book/Chapter 12/GaussianBlur"
Properties
_MainTex ("Texture", 2D) = "white"
SubShader
//CGINCLUDE--ENDCG中定义的可以定义一系列Pass中会用到的函数,相当于一个头文件的作用:
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
half4 _MainTex_TexelSize;
float _BlurSize;
struct v2f
float4 pos : SV_POSITION;
half2 uv[5] : TEXCOORD0;
;
//水平
v2f vertBlurVertical(appdata_img v)
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv;
o.uv[1] = uv + float2(0.0f, _MainTex_TexelSize.y * 1.0 * _BlurSize);
o.uv[2] = uv - float2(0.0f, _MainTex_TexelSize.y * 1.0 * _BlurSize);
o.uv[3] = uv + float2(0.0f, _MainTex_TexelSize.y * 2.0 * _BlurSize);
o.uv[4] = uv - float2(0.0f, _MainTex_TexelSize.y * 2.0 * _BlurSize);
return o;
//竖直
v2f vertBlurHorizontal(appdata_img v)
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv;
o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0 * _BlurSize, 0.0f);
o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0 * _BlurSize, 0.0f);
o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0 * _BlurSize, 0.0f);
o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0 * _BlurSize, 0.0f);
return o;
fixed4 GaussianBlur(v2f i) : SV_Target
float weight[3] = 0.4026, 0.2442, 0.0545;
fixed3 color = tex2D(_MainTex, i.uv[0]).rgb * weight[0];
for(int j=1;j<3;j++)
color += tex2D(_MainTex, i.uv[2*j-1]).rgb * weight[j];
color += tex2D(_MainTex, i.uv[2*j]).rgb * weight[j];
return fixed4(color, 1.0);
ENDCG
ZTest Always
Cull Off
ZWrite Off
//水平的Pass
Pass
NAME "GAUSSIANBLUR_BLUR_VERTICAL"
CGPROGRAM
#pragma vertex vertBlurVertical
#pragma fragment GaussianBlur
ENDCG
//竖直
Pass
NAME "GAUSSIANBLUR_BLUR_HORIZONTAL"
CGPROGRAM
#pragma vertex vertBlurHorizontal
#pragma fragment GaussianBlur
ENDCG
FallBack Off
3.3 呈现效果
感觉均值模糊和高斯模糊看上去区别不是很明显,可能是我对比的问题~当然,细说的话高斯模糊变化的肯定更平滑,因为均值是直接暴力求均值的,忽略了连续性。
Unity shader学习之屏幕后期处理效果之高斯模糊
高斯模糊,见 百度百科。
也使用卷积来实现,每个卷积元素的公式为:
其中б是标准方差,一般取值为1。
x和y分别对应当前位置到卷积中心的整数距离。
由于需要对高斯核中的权重进行归一化,即使所有权重相加为1,因此e前面的系数实际不会对结果产生任何影响。
转载请注明出处:http://www.cnblogs.com/jietian331/p/7238032.html
综上,公式简化为:
G(x,y) = e-(x*x+y*y)/2
因此,高斯核计算代码如下:
1 using System; 2 3 namespace TestShell 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 Console.WriteLine("输入需要得到的高斯卷积核的维数(如3,5,7...):"); 10 11 string input = Console.ReadLine(); 12 int size; 13 14 if (!int.TryParse(input, out size)) 15 { 16 Console.WriteLine("不是数字..."); 17 return; 18 } 19 20 // 计算 21 double[] r2 = null; 22 double[,] r = null; 23 try 24 { 25 r = CalcGaussianBlur(size, out r2); 26 } 27 catch (Exception ex) 28 { 29 Console.WriteLine("错误: " + ex.Message); 30 } 31 32 if (r != null && r2 != null) 33 { 34 // 卷积如下: 35 Console.WriteLine(); 36 Console.WriteLine("{0}x{0}的高斯卷积核如下:", size); 37 for (int i = 0; i < r.GetLongLength(0); i++) 38 { 39 for (int j = 0; j < r.GetLongLength(1); j++) 40 { 41 Console.Write("{0:f4}\\t", r[i, j]); 42 } 43 Console.WriteLine(); 44 } 45 Console.WriteLine(); 46 47 Console.WriteLine("可拆成2个一维的数组:"); 48 for (int i = 0; i < r2.Length; i++) 49 { 50 Console.Write("{0:f4}\\t", r2[i]); 51 } 52 Console.WriteLine(); 53 Console.WriteLine(); 54 55 Console.WriteLine("验证,使用这2个一维的数组也可以得到同样的结果:"); 56 for (int i = 0; i < size; i++) 57 { 58 for (int j = 0; j < size; j++) 59 { 60 Console.Write("{0:f4}\\t", r2[i] * r2[j]); 61 62 } 63 Console.WriteLine(); 64 } 65 } 66 67 Console.WriteLine(); 68 Console.WriteLine("按任意键结束..."); 69 Console.ReadKey(); 70 } 71 72 static double[,] CalcGaussianBlur(int size, out double[] r2) 73 { 74 if (size < 3) 75 throw new ArgumentException("size < 3"); 76 if (size % 2 != 1) 77 throw new ArgumentException("size % 2 != 1"); 78 79 double[,] r = new double[size, size]; 80 r2 = new double[size]; 81 int center = (int)Math.Floor(size / 2f); 82 double sum = 0; 83 84 for (int i = 0; i < size; i++) 85 { 86 for (int j = 0; j < size; j++) 87 { 88 int x = Math.Abs(i - center); 89 int y = Math.Abs(j - center); 90 double d = CalcItem(x, y); 91 r[i, j] = d; 92 sum += d; 93 } 94 } 95 96 for (int i = 0; i < size; i++) 97 { 98 for (int j = 0; j < size; j++) 99 { 100 r[i, j] /= sum; 101 if (i == j) 102 r2[i] = Math.Sqrt(r[i, i]); 103 } 104 } 105 106 return r; 107 } 108 109 static double CalcItem(int x, int y) 110 { 111 return Math.Pow(Math.E, -(x * x + y * y) / 2d); 112 } 113 } 114 }
工具在: http://files.cnblogs.com/files/jietian331/CalcGaussianBlur.zip
一个5 x 5的高斯核如下:
使用2个一维数组可简化计算量,提高性能,通过观察可知,只需要计3个数:
使用unity shader屏幕后期处理来实现高斯模糊,代码如下。
子类:
1 using UnityEngine; 2 3 public class GaussianBlurRenderer : PostEffectRenderer 4 { 5 [Range(1, 8)] 6 [SerializeField] 7 public int m_downSample = 2; // 降采样率 8 [Range(0, 4)] 9 [SerializeField] 10 public int m_iterations = 3; // 迭代次数 11 [Range(0.2f, 3f)] 12 [SerializeField] 13 public float m_blurSpread = 0.6f; // 模糊扩散量 14 15 protected override void OnRenderImage(RenderTexture src, RenderTexture dest) 16 { 17 int w = (int)(src.width / m_downSample); 18 int h = (int)(src.height / m_downSample); 19 RenderTexture buffer0 = RenderTexture.GetTemporary(w, h); 20 RenderTexture buffer1 = RenderTexture.GetTemporary(w, h); 21 buffer0.filterMode = FilterMode.Bilinear; 22 buffer1.filterMode = FilterMode.Bilinear; 23 Graphics.Blit(src, buffer0); 24 25 for (int i = 0; i < m_iterations; i++) 26 { 27 Mat.SetFloat("_BlurSpread", 1 + i * m_blurSpread); 28 29 Graphics.Blit(buffer0, buffer1, Mat, 0); 30 Graphics.Blit(buffer1, buffer0, Mat, 1); 31 } 32 33 Graphics.Blit(buffer0, dest); 34 RenderTexture.ReleaseTemporary(buffer0); 35 RenderTexture.ReleaseTemporary(buffer1); 36 } 37 38 protected override string ShaderName 39 { 40 get { return "Custom/Gaussian Blur"; } 41 } 42 }
shader:
1 // Upgrade NOTE: replaced \'mul(UNITY_MATRIX_MVP,*)\' with \'UnityObjectToClipPos(*)\' 2 3 Shader "Custom/Gaussian Blur" 4 { 5 Properties 6 { 7 _MainTex("Main Texture", 2D) = "white" {} 8 _BlurSpread("Blur Spread", float) = 1 9 } 10 11 SubShader 12 { 13 CGINCLUDE 14 15 sampler2D _MainTex; 16 float4 _MainTex_TexelSize; 17 uniform float _BlurSpread; 18 19 struct appdata 20 { 21 float4 vertex : POSITION; 22 float2 uv : TEXCOORD0; 23 }; 24 25 struct v2f 26 { 27 float4 pos : SV_POSITION; 28 float2 uv[5] : TEXCOORD0; 29 }; 30 31 v2f vertHorizontal(appdata v) 32 { 33 v2f o; 34 o.pos = UnityObjectToClipPos(v.vertex); 35 float tsx = _MainTex_TexelSize.x * _BlurSpread; 36 o.uv[0] = v.uv + float2(tsx * -2, 0); 37 o.uv[1] = v.uv + float2(tsx * -1, 0); 38 o.uv[2] = v.uv; 39 o.uv[3] = v.uv + float2(tsx * 1, 0); 40 o.uv[4] = v.uv + float2(tsx * 2, 0); 41 return o; 42 } 43 44 v2f vertVertical(appdata v) 45 { 46 v2f o; 47 o.pos = UnityObjectToClipPos(v.vertex); 48 float tsy = _MainTex_TexelSize.y * _BlurSpread; 49 o.uv[0] = v.uv + float2(0, tsy * -2); 50 o.uv[1] = v.uv + float2(0, tsy * -1); 51 o.uv[2] = v.uv; 52 o.uv[3] = v.uv + float2(0, tsy * 1); 53 o.uv[4] = v.uv + float2(0, tsy * 2); 54 return o; 55 } 56 57 fixed4 frag(v2f i) : SV_TARGET 58 { 59 float g[3] = {0.0545, 0.2442, 0.4026}; 60 fixed4 col = tex2D(_MainTex, i.uv[2]) * g[2]; 61 for(int k = 0; k < 2; k++) 62 { 63 col += tex2D(_MainTex, i.uv[k]) * g[k]; 64 col += tex2D(_MainTex, i.uv[4 - k]) * g[k]; 65 } 66 return col; 67 } 68 69 ENDCG 70 71 Pass 72 { 73 Name "HORIZONTAL" 74 ZTest Always 75 ZWrite Off 76 Cull Off 77 78 CGPROGRAM 79 #pragma vertex vertHorizontal 80 #pragma fragment frag 81 ENDCG 82 } 83 84 Pass 85 { 86 Name "VERTICAL" 87 ZTest Always 88 ZWrite Off 89 Cull Off 90 91 CGPROGRAM 92 #pragma vertex vertVertical 93 #pragma fragment frag 94 ENDCG 95 } 96 } 97 98 Fallback Off 99 }
调整参数:
DownSample,即降采样率,越大性能越好,图像越模糊,但过大可能会使图像像素化。
Iteraitions, 即迭代次数,越大图像模糊效果越好,但性能也会下降。
BlurSpread,即模糊扩散量,越大图像越模糊,但过大会造成虚影。
效果如下:
以上是关于Unity Shader屏幕后处理3.0:均值模糊和高斯模糊的主要内容,如果未能解决你的问题,请参考以下文章
Unity Shader 屏幕后处理5.0:讨论双重模糊的Bloom