模糊处理
本文章参考毛星云大佬的文章学习,十分推荐看大佬的原文
降采样是常见的优化性能的方法减少中间贴图的尺寸大小
产生的中间贴图一定要记得释放内存,否则造成内存泄漏
在for循环的中间贴图也要记得释放内存
课程的4种常用的模糊算法
盒状模糊或均值模糊(Box Blur)
最简单的模糊方式,取一个2*2的卷积核,四个数权重都为0.25,取一个像素的四角的像素叠加乘0.25

(-1,-1)(1,-1)(-1,1)(1,1)四个点位叠加除4;
当然卷积核的值可自己调
代码
BoxBlur.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| using System.Collections; using System.Collections.Generic; using UnityEngine;
[ExecuteInEditMode()] public class BoxBlur : MonoBehaviour { public Material material; [Range(0, 10)] public int _Iteration = 4; [Range(0, 15)] public float _BlurRadius = 5.0f; [Range(1, 10)] public float _DownSample = 2.0f;
void Start () { if (material == null || SystemInfo.supportsImageEffects == false || material.shader == null || material.shader.isSupported == false) { enabled = false; return; } }
void OnRenderImage(RenderTexture source, RenderTexture destination) { int width = (int)(source.width / _DownSample); int height = (int)(source.height / _DownSample); RenderTexture RT1 = RenderTexture.GetTemporary(width,height); RenderTexture RT2 = RenderTexture.GetTemporary(width, height);
Graphics.Blit(source, RT1);
material.SetVector("_BlurOffset", new Vector4(_BlurRadius / source.width, _BlurRadius / source.height, 0,0)); for (int i = 0; i < _Iteration; i++) { Graphics.Blit(RT1, RT2, material, 0); Graphics.Blit(RT2, RT1, material, 0); }
Graphics.Blit(RT1, destination);
RenderTexture.ReleaseTemporary(RT1); RenderTexture.ReleaseTemporary(RT2); } }
|
BoxBlur.shader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| Shader "Hidden/BoxBlur" { CGINCLUDE #include "UnityCG.cginc"
sampler2D _MainTex; float4 _BlurOffset;
half4 frag_BoxFilter_4Tap(v2f_img i) : SV_Target { half4 d = _BlurOffset.xyxy * half4(-1,-1,1,1); half4 s = 0; s += tex2D(_MainTex, i.uv + d.xy); s += tex2D(_MainTex, i.uv + d.zy); s += tex2D(_MainTex, i.uv + d.xw); s += tex2D(_MainTex, i.uv + d.zw); s *= 0.25; return s; }
half4 frag_BoxFilter_9Tap(v2f_img i) : SV_Target { half4 d = _BlurOffset.xyxy * half4(-1, -1, 1, 1);
half4 s = 0; s = tex2D(_MainTex, i.uv);
s += tex2D(_MainTex, i.uv + d.xy); s += tex2D(_MainTex, i.uv + d.zy); s += tex2D(_MainTex, i.uv + d.xw); s += tex2D(_MainTex, i.uv + d.zw);
s += tex2D(_MainTex, i.uv + half2(0.0, d.w)); s += tex2D(_MainTex, i.uv + half2(0.0, d.y)); s += tex2D(_MainTex, i.uv + half2(d.z,0.0)); s += tex2D(_MainTex, i.uv + half2(d.x, 0.0));
s = s/ 9.0; return s; }
ENDCG
Properties { _MainTex ("Texture", 2D) = "white" {} _BlurOffset("BlurOffset",Float) = 1 } SubShader { Cull Off ZWrite Off ZTest Always Pass { CGPROGRAM #pragma vertex vert_img #pragma fragment frag_BoxFilter_4Tap ENDCG } Pass { CGPROGRAM #pragma vertex vert_img #pragma fragment frag_BoxFilter_9Tap ENDCG } } }
|
高斯模糊(Gaussian Blur)
特定的权重,离像素点越远权重越小
下图为高斯函数的3维图示:

0.05 0.25 0.40 0.25 0.05权值
和盒状模糊差不多,效果会好一点
代码
GaussianBlur.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| using System.Collections; using System.Collections.Generic; using UnityEngine;
[ExecuteInEditMode()] public class GaussianBlur : MonoBehaviour { public Material material; [Range(0, 10)] public int _Iteration = 4; [Range(0, 15)] public float _BlurRadius = 5.0f; [Range(1, 10)] public float _DownSample = 2.0f;
void Start () { if (material == null || SystemInfo.supportsImageEffects == false || material.shader == null || material.shader.isSupported == false) { enabled = false; return; } }
void OnRenderImage(RenderTexture source, RenderTexture destination) { int width = (int)(source.width / _DownSample); int height = (int)(source.height / _DownSample); RenderTexture RT1 = RenderTexture.GetTemporary(width,height); RenderTexture RT2 = RenderTexture.GetTemporary(width, height);
Graphics.Blit(source, RT1);
material.SetVector("_BlurOffset", new Vector4(_BlurRadius / source.width, _BlurRadius / source.height, 0,0)); for (int i = 0; i < _Iteration; i++) { Graphics.Blit(RT1, RT2, material, 0); Graphics.Blit(RT2, RT1, material, 1); }
Graphics.Blit(RT1, destination);
RenderTexture.ReleaseTemporary(RT1); RenderTexture.ReleaseTemporary(RT2); } }
|
GaussianBlur.shader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| Shader "Hidden/GaussianBlur" { CGINCLUDE #include "UnityCG.cginc"
sampler2D _MainTex; float4 _BlurOffset;
half4 frag_HorizontalBlur(v2f_img i) : SV_Target { half2 uv1 = i.uv + _BlurOffset.xy * half2(1, 0) * -2.0; half2 uv2 = i.uv + _BlurOffset.xy * half2(1, 0) * -1.0; half2 uv3 = i.uv; half2 uv4 = i.uv + _BlurOffset.xy * half2(1, 0) * 1.0; half2 uv5 = i.uv + _BlurOffset.xy * half2(1, 0) * 2.0;
half4 s = 0; s += tex2D(_MainTex, uv1) * 0.05; s += tex2D(_MainTex, uv2) * 0.25; s += tex2D(_MainTex, uv3) * 0.40; s += tex2D(_MainTex, uv4) * 0.25; s += tex2D(_MainTex, uv5) * 0.05; return s; }
half4 frag_VerticalBlur(v2f_img i) : SV_Target { half2 uv1 = i.uv + _BlurOffset.xy * half2(0, 1) * -2.0; half2 uv2 = i.uv + _BlurOffset.xy * half2(0, 1) * -1.0; half2 uv3 = i.uv; half2 uv4 = i.uv + _BlurOffset.xy * half2(0, 1) * 1.0; half2 uv5 = i.uv + _BlurOffset.xy * half2(0, 1) * 2.0;
half4 s = 0; s += tex2D(_MainTex, uv1) * 0.05; s += tex2D(_MainTex, uv2) * 0.25; s += tex2D(_MainTex, uv3) * 0.40; s += tex2D(_MainTex, uv4) * 0.25; s += tex2D(_MainTex, uv5) * 0.05; return s; }
ENDCG
Properties { _MainTex ("Texture", 2D) = "white" {} _BlurOffset("BlurOffset",Float) = 1 } SubShader { Cull Off ZWrite Off ZTest Always Pass { CGPROGRAM #pragma vertex vert_img #pragma fragment frag_HorizontalBlur ENDCG } Pass { CGPROGRAM #pragma vertex vert_img #pragma fragment frag_VerticalBlur ENDCG } } }
|
双重模糊
双重的核心思想是先降采样,然后升采样。
相较于Kawase Blur在两个大小相等的纹理之间进行乒乓blit的的思路,Dual Kawase Blur的核心思路在于blit过程中进行降采样和升采样,即对RT进行了降采样以及升采样。如下图所示:

双重 Kawase 模糊(Dual Kawase Blur)
双重 Kawase 模糊和双重盒状模糊模糊是最常用的,前者常用于移动端,后者都行,两个的性能和质量都优于之前两种算法
Kawase模糊
Kawase Blur的思路是对距离当前像素越来越远的地方对四个角进行采样

所以中间的权重为4/8,其余四角为1/8;
单独的Kawase算法性能太差了,淘汰。
Dual Kawase Blur
Dual Kawase Blur,简称Dual Blur,是SIGGRAPH 2015上ARM团队提出的一种衍生自Kawase Blur的模糊算法。其由两种不同的Blur Kernel构成,如下图所示。

这个贴图怕是只有我看的懂了,图一的1图代表红色的框框,第二个代表整个框框
代码
DualKawaseBlur.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| using System; using UnityEngine; using System.Collections.Generic;
[RequireComponent(typeof(Camera))] [ExecuteInEditMode]
public class DualKawaseBlur : MonoBehaviour {
public Material material; [Range(0,15)] public float _BlurRadius = 5.0f; [Range(0, 10)] public int _Iteration = 4; [Range(1, 10)] public float _DownSample = 2.0f;
List<RenderTexture> _tempRTList = new List<RenderTexture>();
void Start() { if (!SystemInfo.supportsImageEffects || null == material || null == material.shader || !material.shader.isSupported) { enabled = false; return; } }
void OnRenderImage(RenderTexture source, RenderTexture destination) { int RTWidth = (int)(source.width / _DownSample); int RTHeight = (int)(source.height / _DownSample); RenderTexture RT1 = RenderTexture.GetTemporary(RTWidth, RTHeight, 0); RenderTexture RT2 = null; material.SetFloat("_Offset", _BlurRadius); Graphics.Blit(source, RT1, material, 0);
for (int i = 0; i < _Iteration; i++) { RenderTexture.ReleaseTemporary(RT2); RTWidth = RTWidth / 2; RTHeight = RTHeight / 2; RT2 = RenderTexture.GetTemporary(RTWidth, RTHeight); Graphics.Blit(RT1, RT2, material, 0);
RTWidth = RTWidth / 2; RTHeight = RTHeight / 2; RenderTexture.ReleaseTemporary(RT1); RT1 = RenderTexture.GetTemporary(RTWidth, RTHeight); Graphics.Blit(RT2, RT1, material, 0); }
for (int i = 0; i < _Iteration; i++) { RenderTexture.ReleaseTemporary(RT2); RTWidth = RTWidth * 2; RTHeight = RTHeight * 2; RT2 = RenderTexture.GetTemporary(RTWidth, RTHeight); Graphics.Blit(RT1, RT2, material, 1);
RTWidth = RTWidth * 2; RTHeight = RTHeight * 2; RenderTexture.ReleaseTemporary(RT1); RT1 = RenderTexture.GetTemporary(RTWidth, RTHeight); Graphics.Blit(RT2, RT1, material, 1); }
Graphics.Blit(RT1, destination, material, 1);
RenderTexture.ReleaseTemporary(RT1); RenderTexture.ReleaseTemporary(RT2); } }
|
DualKawaseBlur.shader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
| Shader "Hidden/DualKawaseBlur" { CGINCLUDE #include "UnityCG.cginc" uniform sampler2D _MainTex; uniform float4 _MainTex_TexelSize; uniform half _Offset; struct v2f_DownSample { float4 pos: SV_POSITION; float2 uv: TEXCOORD1; float4 uv01: TEXCOORD2; float4 uv23: TEXCOORD3; }; struct v2f_UpSample { float4 pos: SV_POSITION; float4 uv01: TEXCOORD1; float4 uv23: TEXCOORD2; float4 uv45: TEXCOORD3; float4 uv67: TEXCOORD4; }; v2f_DownSample Vert_DownSample(appdata_img v) { v2f_DownSample o; o.pos = UnityObjectToClipPos(v.vertex); _MainTex_TexelSize = 0.5 * _MainTex_TexelSize; float2 uv = v.texcoord; o.uv = uv; o.uv01.xy = uv - _MainTex_TexelSize * float2(1 + _Offset, 1 + _Offset); o.uv01.zw = uv + _MainTex_TexelSize * float2(1 + _Offset, 1 + _Offset); o.uv23.xy = uv - float2(_MainTex_TexelSize.x, -_MainTex_TexelSize.y) * float2(1 + _Offset, 1 + _Offset); o.uv23.zw = uv + float2(_MainTex_TexelSize.x, -_MainTex_TexelSize.y) * float2(1 + _Offset, 1 + _Offset); return o; } half4 Frag_DownSample(v2f_DownSample i): SV_Target { half4 sum = tex2D(_MainTex, i.uv) * 4; sum += tex2D(_MainTex, i.uv01.xy); sum += tex2D(_MainTex, i.uv01.zw); sum += tex2D(_MainTex, i.uv23.xy); sum += tex2D(_MainTex, i.uv23.zw); return sum * 0.125; } v2f_UpSample Vert_UpSample(appdata_img v) { v2f_UpSample o; o.pos = UnityObjectToClipPos(v.vertex); float2 uv = v.texcoord; _MainTex_TexelSize = 0.5 * _MainTex_TexelSize; _Offset = float2(1 + _Offset, 1 + _Offset); o.uv01.xy = uv + float2(-_MainTex_TexelSize.x * 2, 0) * _Offset; o.uv01.zw = uv + float2(-_MainTex_TexelSize.x, _MainTex_TexelSize.y) * _Offset; o.uv23.xy = uv + float2(0, _MainTex_TexelSize.y * 2) * _Offset; o.uv23.zw = uv + _MainTex_TexelSize * _Offset; o.uv45.xy = uv + float2(_MainTex_TexelSize.x * 2, 0) * _Offset; o.uv45.zw = uv + float2(_MainTex_TexelSize.x, -_MainTex_TexelSize.y) * _Offset; o.uv67.xy = uv + float2(0, -_MainTex_TexelSize.y * 2) * _Offset; o.uv67.zw = uv - _MainTex_TexelSize * _Offset; return o; } half4 Frag_UpSample(v2f_UpSample i): SV_Target { half4 sum = 0; sum += tex2D(_MainTex, i.uv01.xy); sum += tex2D(_MainTex, i.uv01.zw) * 2; sum += tex2D(_MainTex, i.uv23.xy); sum += tex2D(_MainTex, i.uv23.zw) * 2; sum += tex2D(_MainTex, i.uv45.xy); sum += tex2D(_MainTex, i.uv45.zw) * 2; sum += tex2D(_MainTex, i.uv67.xy); sum += tex2D(_MainTex, i.uv67.zw) * 2; return sum * 0.0833; } ENDCG Properties { _MainTex("", 2D) = "white" {} } SubShader { Cull Off ZWrite Off ZTest Always Pass { CGPROGRAM #pragma vertex Vert_DownSample #pragma fragment Frag_DownSample ENDCG } Pass { CGPROGRAM #pragma vertex Vert_UpSample #pragma fragment Frag_UpSample ENDCG } } }
|
双重盒状模糊(Dual Box Blur)
对原来的Box进行双重采样就行,最常用的算法。
代码
DualBoxBlur.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| using System.Collections; using System.Collections.Generic; using UnityEngine;
[ExecuteInEditMode()] public class DualBoxBlur : MonoBehaviour { public Material material; [Range(0, 10)] public int _Iteration = 4; [Range(0, 15)] public float _BlurRadius = 5.0f; [Range(1, 10)] public float _DownSample = 2.0f;
void Start () { if (material == null || SystemInfo.supportsImageEffects == false || material.shader == null || material.shader.isSupported == false) { enabled = false; return; } }
void OnRenderImage(RenderTexture source, RenderTexture destination) { int width = (int)(source.width / _DownSample); int height = (int)(source.height / _DownSample); RenderTexture RT1 = RenderTexture.GetTemporary(width,height); RenderTexture RT2 = RenderTexture.GetTemporary(width, height);
Graphics.Blit(source, RT1);
material.SetVector("_BlurOffset", new Vector4(_BlurRadius / source.width, _BlurRadius / source.height, 0,0)); for (int i = 0; i < _Iteration; i++) { RenderTexture.ReleaseTemporary(RT2); width = width / 2; height = height / 2; RT2 = RenderTexture.GetTemporary(width, height); Graphics.Blit(RT1, RT2, material, 0);
RenderTexture.ReleaseTemporary(RT1); width = width / 2; height = height / 2; RT1 = RenderTexture.GetTemporary(width, height); Graphics.Blit(RT2, RT1, material, 0); } for (int i = 0; i < _Iteration; i++) { RenderTexture.ReleaseTemporary(RT2); width = width * 2; height = height * 2; RT2 = RenderTexture.GetTemporary(width, height); Graphics.Blit(RT1, RT2, material, 0);
RenderTexture.ReleaseTemporary(RT1); width = width * 2; height = height * 2; RT1 = RenderTexture.GetTemporary(width, height); Graphics.Blit(RT2, RT1, material, 0); }
Graphics.Blit(RT1, destination);
RenderTexture.ReleaseTemporary(RT1); RenderTexture.ReleaseTemporary(RT2); } }
|
就之前的BoxBlur.shader。
其余算法
可以参考毛星云大佬的其他算法。