后处理技术(中)

后处理技术(中)

二月 22, 2022

模糊处理

本文章参考毛星云大佬的文章学习,十分推荐看大佬的原文

降采样是常见的优化性能的方法减少中间贴图的尺寸大小

产生的中间贴图一定要记得释放内存,否则造成内存泄漏

在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);

//release
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,0,0);
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)); // 0 1
s += tex2D(_MainTex, i.uv + half2(0.0, d.y)); // 0 -1
s += tex2D(_MainTex, i.uv + half2(d.z,0.0)); // 1 0
s += tex2D(_MainTex, i.uv + half2(d.x, 0.0)); // -1 0

s = s/ 9.0;
return s;
}

ENDCG

Properties
{
_MainTex ("Texture", 2D) = "white" {}
_BlurOffset("BlurOffset",Float) = 1
}
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
//0pass 4tap
Pass
{
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag_BoxFilter_4Tap
ENDCG
}
//1pass 9tap
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);

//release
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
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
//0
Pass
{
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag_HorizontalBlur
ENDCG
}
//1
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);

// release
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);//top right
o.uv01.zw = uv + _MainTex_TexelSize * float2(1 + _Offset, 1 + _Offset);//bottom left
o.uv23.xy = uv - float2(_MainTex_TexelSize.x, -_MainTex_TexelSize.y) * float2(1 + _Offset, 1 + _Offset);//top left
o.uv23.zw = uv + float2(_MainTex_TexelSize.x, -_MainTex_TexelSize.y) * float2(1 + _Offset, 1 + _Offset);//bottom right

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;//-2,0
o.uv01.zw = uv + float2(-_MainTex_TexelSize.x, _MainTex_TexelSize.y) * _Offset;//-1 1
o.uv23.xy = uv + float2(0, _MainTex_TexelSize.y * 2) * _Offset;//0 2
o.uv23.zw = uv + _MainTex_TexelSize * _Offset;//1 1
o.uv45.xy = uv + float2(_MainTex_TexelSize.x * 2, 0) * _Offset;//2 0
o.uv45.zw = uv + float2(_MainTex_TexelSize.x, -_MainTex_TexelSize.y) * _Offset;//1 -1
o.uv67.xy = uv + float2(0, -_MainTex_TexelSize.y * 2) * _Offset;//0 -2
o.uv67.zw = uv - _MainTex_TexelSize * _Offset;// -1 -1

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);

//release
RenderTexture.ReleaseTemporary(RT1);
RenderTexture.ReleaseTemporary(RT2);
}
}

就之前的BoxBlur.shader。

其余算法

可以参考毛星云大佬的其他算法。