高斯模糊是图像模糊处理中非常经典和常见的一种算法,也是Bloom屏幕效果的基础。
实现高斯模糊同样用到了卷积的概念,关于卷积的概念和原理详见我的另一篇博客:
https://cloud.tencent.com/developer/article/1601300
通过高斯方程计算出的卷积核称为高斯核,一个5*5的高斯核对它进行权重归一化如下:
0.0030 | 0.0133 | 0.0219 | 0.0133 | 0.0030 |
---|---|---|---|---|
0.0133 | 0.0596 | 0.0983 | 0.0596 | 0.0133 |
0.0219 | 0.0983 | 0.1621 | 0.0983 | 0.0219 |
0.0133 | 0.0596 | 0.0983 | 0.0596 | 0.0133 |
0.0030 | 0.0133 | 0.0219 | 0.0133 | 0.0030 |
通过表也可以很清楚的看到,离原点越近的点模糊程度影响越大,反之越小。
为了优化计算,可以将这个5*5矩阵简化为两个矩阵分别计算,得到的效果是相同的。
它们分别是一个1*5的横向矩阵和一个5*1的纵向矩阵,这样我们只需要对横纵向矩阵分别进行一次采样既可,这样可以很大程度的减少计算量。
拆分之后结果如下:
我们发现,最终的计算只需要记录3个权重值既可,它们是weight3={0.4026,0.2442,0.0545};
具体实现:
1.实现调整高斯模糊参数的脚本。
为了进一步优化计算,这里加入了降采样系数,模糊范围缩放;为此,需要在外部增加模糊采样的迭代次数,具体如下:
1 using UnityEngine;
2
3 public class GaussianBlurCtrl : ScreenEffectBase
4 {
5 private const string _BlurSize = "_BlurSize";//只有模糊范围需要在GPU中计算
6
7 [Range(0, 4)]
8 public int iterations = 3;//迭代次数
9 [Range(0.2f, 3)]
10 public float blurSize = 0.6f;//模糊范围
11 [Range(1, 8)]
12 public int downSample = 2;//降采样系数
13
14 private void OnRenderImage(RenderTexture source, RenderTexture destination)
15 {
16 if (Material != null)
17 {
18 //得到屏幕的渲染纹理后直接除以降采样系数以成倍减少计算量,但过大时模糊效果不佳
19 int rtw = source.width/downSample;
20 int rth = source.height/downSample;
21
22 RenderTexture buffer0 = RenderTexture.GetTemporary(rtw, rth, 0);
23 buffer0.filterMode = FilterMode.Bilinear;
24
25 Graphics.Blit(source, buffer0);
26
27 //利用迭代次数对模糊范围加以控制,用到了类似于双缓冲的方式对纹理进行处理
28 for (int i = 0; i < iterations; i++)
29 {
30 //设置采样范围,根据迭代次数范围增加,之后会与纹理坐标进行乘积操作,固基础值为1
31 Material.SetFloat(_BlurSize, blurSize*i+1);
32
33 RenderTexture buffer1 = RenderTexture.GetTemporary(rtw, rth, 0);
34 Graphics.Blit(buffer0, buffer1, Material, 0);
35 //每次处理完立即释放相应缓存,因为Unity内部已经对此做了相应的优化
36 RenderTexture.ReleaseTemporary(buffer0);
37 buffer0 = RenderTexture.GetTemporary(rtw, rth, 0);
38 Graphics.Blit(buffer1, buffer0,Material, 1);
39 RenderTexture.ReleaseTemporary(buffer1);
40 }
41 Graphics.Blit(buffer0, destination);
42 RenderTexture.ReleaseTemporary(buffer0);
43 }
44 else
45 Graphics.Blit(source, destination);
46 }
47 }
基类脚本见:
https://cloud.tencent.com/developer/article/1601301
2.在Shader中分别进行横向和纵向的模糊计算,分为两个Pass进行,具体如下:
1 Shader "MyUnlit/GaussianBlur"
2 {
3 Properties
4 {
5 _MainTex ("Texture", 2D) = "white" {}
6 }
7 SubShader
8 {
9 Tags { "RenderType"="Opaque" }
10
11 //CGINCLUDE中的代码可被其他Pass重复调用,用于简化不必要的重复代码
12 CGINCLUDE
13
14 #pragma multi_compile_fog
15 #include "UnityCG.cginc"
16
17 struct appdata
18 {
19 float4 vertex : POSITION;
20 float2 uv : TEXCOORD0;
21 };
22
23 struct v2f
24 {
25 half2 uv[5] : TEXCOORD0;
26 UNITY_FOG_COORDS(1)
27 float4 pos : SV_POSITION;
28 };
29
30 sampler2D _MainTex;
31 float4 _MainTex_TexelSize;
32 float _BlurSize;
33
34 //用于计算纵向模糊的纹理坐标元素
35 v2f vert_v(appdata v)
36 {
37 v2f o;
38 o.pos = UnityObjectToClipPos(v.vertex);
39 half2 uv = v.uv;
40
41 //以扩散的方式对数组进行排序,只偏移y轴,其中1和2,3和4分别位于原始点0的上下,且距离1个单位和2个像素单位
42 //得到的最终偏移与模糊范围的控制参数进行乘积
43 o.uv[0] = uv;
44 o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y*1.0)*_BlurSize;
45 o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y*1.0)*_BlurSize;
46 o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y*2.0)*_BlurSize;
47 o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y*2.0)*_BlurSize;
48
49 UNITY_TRANSFER_FOG(o, o.vertex);
50 return o;
51 }
52
53 //用于计算横向模糊的纹理坐标元素
54 v2f vert_h(appdata v)
55 {
56 v2f o;
57 o.pos = UnityObjectToClipPos(v.vertex);
58 half2 uv = v.uv;
59
60 //与上面同理,只不过是x轴向的模糊偏移
61 o.uv[0] = uv;
62 o.uv[1] = uv + float2( _MainTex_TexelSize.x*1.0,0.0)*_BlurSize;
63 o.uv[2] = uv - float2( _MainTex_TexelSize.x*1.0,0.0)*_BlurSize;
64 o.uv[3] = uv + float2( _MainTex_TexelSize.x*2.0,0.0)*_BlurSize;
65 o.uv[4] = uv - float2( _MainTex_TexelSize.x*2.0,0.0)*_BlurSize;
66
67 UNITY_TRANSFER_FOG(o, o.vertex);
68 return o;
69 }
70
71 //在片元着色器中进行最终的模糊计算,此过程在每个Pass中都会进行一次计算,但计算方式是统一的
72 fixed4 frag(v2f i) : SV_Target
73 {
74 float weights[3] = {0.4026,0.2442,0.0545};
75
76 fixed4 col = tex2D(_MainTex, i.uv[0]);
77
78 fixed3 sum = col.rgb*weights[0];
79
80 //对采样结果进行对应纹理偏移坐标的权重计算,以得到模糊的效果
81 for (int it = 1; it < 3; it++)
82 {
83 sum += tex2D(_MainTex, i.uv[2 * it - 1]).rgb*weights[it];//对应1和3,也就是原始像素的上方两像素
84 sum += tex2D(_MainTex, i.uv[2 * it]).rgb*weights[it];//对应2和4,下方两像素
85 }
86 fixed4 color = fixed4(sum, 1.0);
87 UNITY_APPLY_FOG(i.fogCoord, color);
88 return color;
89 }
90
91 ENDCG
92
93 ZTest Always
94 Cull Off
95 ZWrite Off
96
97 //纵向模糊Pass,直接用指令调用上面的函数
98 Pass
99 {
100 NAME "GAUSSIANBLUR_V"
101 CGPROGRAM
102 #pragma vertex vert_v
103 #pragma fragment frag
104
105 ENDCG
106 }
107
108 //横向模糊Pass
109 Pass
110 {
111 NAME "GAUSSIANBLUR_H"
112 CGPROGRAM
113 #pragma vertex vert_h
114 #pragma fragment frag
115
116 ENDCG
117 }
118 }
119 Fallback Off
120 }
效果如下: