untiyBlur方案探索

需求

手游,对3D及某些UI的渲染结果进行高斯模糊,在模糊的结果上显示另一层UI弹出框。

方案探索过程

GrabPass 2Pass法(纯shader)

简单介绍就是两个Pass先横后竖两次模糊,第二次抓取了第一次的结果。
但上面提到的shader算法,如果直接搬运会发现效率很差,因为shader计算用到了pow,于是个人做了优化,高斯的权重计算结果可以缓存,需要的见:“源码汇总”

GrabPass 1Pass法(纯shader)

  • 好处:同上
  • 坏处:效率比上面还差,说明瓶颈并不在GrabPass本身,而在于每个像素都得过量取样。
    而GrabPass本身不能降低分辨率而跳过一半像素去计算,如果真的一定要沿这个思路走就得用CommandBuffer了。

后处理法 OnRenderImage

  • 好处:可以先降分辨率再计算,最终真机就掉了5fps。
  • 坏处:不太灵活。
    对overlay无效,以及需要将需要模糊的和不需要模糊的UI强行分两个Canvas,项目内本身的弹出框上再弹出多层弹出框还想模糊就比较蛋疼了。
    方案参考:
    毛星云版github

其它 - RT、CommandBuffer

其实也可以输出到RT再插入Image,还有说使用CommandBuffer,因为后处理法过得去所以未再细究,而CommandBuffer据说性能会跟GrabPass差不多差所以更不用试了。

参考资料

原理篇

高斯模糊原理:
https://zh.wikipedia.org/wiki/%E9%AB%98%E6%96%AF%E6%A8%A1%E7%B3%8A

GrabPass
https://docs.unity3d.com/2018.4/Documentation/Manual/SL-GrabPass.html

Graphics.Blit
https://docs.unity3d.com/2018.4/Documentation/ScriptReference/Graphics.Blit.html

CommandBuffer 命令缓冲
https://docs.unity3d.com/2018.4/Documentation/Manual/GraphicsCommandBuffers.html

实践篇

@浅墨_毛星云:
【Unity Shader编程】之十五 屏幕高斯模糊(Gaussian Blur)后期特效的实现 https://blog.csdn.net/poem_qianmo/article/details/51871531
高品质后处理:十种图像模糊算法的总结与实现 https://zhuanlan.zhihu.com/p/125744132

Unity Shader-后处理:高斯模糊
https://blog.csdn.net/puppet_master/article/details/52783179

grabpass纯shader @ruccho_vector 一篇日文blog
https://qiita.com/ruccho_vector/items/651f760b3240a253bd1d

CommandBuffer版
https://github.com/andydbc/unity-frosted-glass

性能相关篇

Unity中GrabPass、OnRenderImage、TargetTexture性能测试
https://zhuanlan.zhihu.com/p/83507625

Unity引擎后处理性能优化方案解析

OnRenderImage 效率探索原理-2
http://www.gamethk.com/news/detail/4751/6.html

tbr(解释为何Grap Pass慢)
https://zhuanlan.zhihu.com/p/301155768

题外话

  • opengl 2.0
    都说OnRenderImage对opengl 2.0 会特别慢,但现在机型几乎是3.0以上了,so不考虑。而c#内也可判断SystemInfo.supportsImageEffects
    2.0替代方案: https://zhuanlan.zhihu.com/p/38520983

unity back buffer

Graphics.PresentAndSync

源码汇总

  1. GrabPass 2Pass法
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
Shader "Unlit/UIBlur"
{
Properties
{
//_MainTex("Texture", 2D) = "white" {}
_Blur("Blur", Float) = 10
//_Color ("Tint", Color) = (1,1,1,1)

[HideInInspector]_StencilComp ("Stencil Comparison", Float) = 8
[HideInInspector]_Stencil ("Stencil ID", Float) = 0
[HideInInspector]_StencilOp ("Stencil Operation", Float) = 0
[HideInInspector]_StencilWriteMask ("Stencil Write Mask", Float) = 255
[HideInInspector]_StencilReadMask ("Stencil Read Mask", Float) = 255

[HideInInspector]_ColorMask ("Color Mask", Float) = 15
}
SubShader
{

Tags{ "Queue" = "Transparent" }

GrabPass
{
}

Pass
{
CGPROGRAM

#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
fixed4 color : COLOR;
};

struct v2f
{
float4 grabPos : TEXCOORD0;
float4 pos : SV_POSITION;
float4 vertColor : COLOR;
};

v2f vert(appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.grabPos = ComputeGrabScreenPos(o.pos);
o.vertColor = v.color;
return o;
}

sampler2D _GrabTexture;
fixed4 _GrabTexture_TexelSize;

float _Blur;


static const half GaussWeight[6] = {0.93291196,0.757465128,0.93291196,0.757465128,0.93291196,0.757465128};

half4 frag(v2f i) : SV_Target
{
float blur = _Blur;
blur = max(1, blur);

fixed4 col = (0, 0, 0, 0);
float weight_total = 0;

[loop]
for (float x = -blur; x <= blur; x += 1)
{
//float distance_normalized = abs(x / blur);

//todo 可优化
//float weight = exp(-0.5 * pow(distance_normalized, 2) * 5.0);
float weight = GaussWeight[abs(x)];
weight_total += weight;
col += tex2Dproj(_GrabTexture, i.grabPos + float4(x * _GrabTexture_TexelSize.x, 0, 0, 0)) * weight;
}

col /= weight_total;
return col;
}
ENDCG
}
GrabPass
{
}

Pass
{
CGPROGRAM

#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
fixed4 color : COLOR;
};

struct v2f
{
float4 grabPos : TEXCOORD0;
float4 pos : SV_POSITION;
float4 vertColor : COLOR;
};

v2f vert(appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.grabPos = ComputeGrabScreenPos(o.pos);
o.vertColor = v.color;
return o;
}

sampler2D _GrabTexture;
fixed4 _GrabTexture_TexelSize;

//fixed4 _Color;

float _Blur;
static const half GaussWeight[6] = {0.93291196,0.757465128,0.93291196,0.757465128,0.93291196,0.757465128};

half4 frag(v2f i) : SV_Target
{
float blur = _Blur;
blur = max(1, blur);

fixed4 col = (0, 0, 0, 0);
float weight_total = 0;

[loop]
for (float y = -blur; y <= blur; y += 1)
{
//float distance_normalized = abs(y / blur);
//float weight = exp(-0.5 * pow(distance_normalized, 2) * 5.0);
float weight = GaussWeight[abs(y)];
weight_total += weight;
col += tex2Dproj(_GrabTexture, i.grabPos + float4(0, y * _GrabTexture_TexelSize.y, 0, 0)) * weight;
}

col /= weight_total;
//加灰度
col.xyz =col.xyz * (1 -i.vertColor.a);
return col;
}
ENDCG
}

}

}
  1. GrabPass 1Pass法
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
Shader "Unlit/UIBlur"
{
Properties
{
//_MainTex("Texture", 2D) = "white" {}
_Blur("Blur", Float) = 10
//_Color ("Tint", Color) = (1,1,1,1)

[HideInInspector]_StencilComp ("Stencil Comparison", Float) = 8
[HideInInspector]_Stencil ("Stencil ID", Float) = 0
[HideInInspector]_StencilOp ("Stencil Operation", Float) = 0
[HideInInspector]_StencilWriteMask ("Stencil Write Mask", Float) = 255
[HideInInspector]_StencilReadMask ("Stencil Read Mask", Float) = 255

[HideInInspector]_ColorMask ("Color Mask", Float) = 15
}
SubShader
{

Tags{ "Queue" = "Transparent" }

GrabPass
{
}

Pass
{
CGPROGRAM

#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
fixed4 color : COLOR;
};

struct v2f
{
float4 grabPos : TEXCOORD0;
float4 pos : SV_POSITION;
float4 vertColor : COLOR;
};

v2f vert(appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.grabPos = ComputeGrabScreenPos(o.pos);
o.vertColor = v.color;
return o;
}

sampler2D _GrabTexture;
fixed4 _GrabTexture_TexelSize;

float _Blur;

static const half GaussWeight[7][7] = {
{1.00000000,0.93291196,0.75746513,0.53526143,0.32919299,0.17620431,0.08208500 },
{0.93291196,0.87032473,0.70664828,0.49935179,0.30710808,0.16438311,0.07657808 },
{0.75746513,0.70664828,0.57375342,0.40544187,0.24935221,0.13346862,0.06217652 },
{0.53526143,0.49935179,0.40544187,0.28650480,0.17620431,0.09431537,0.04393693 },
{0.32919299,0.30710808,0.24935221,0.17620431,0.10836802,0.05800522,0.02702181 },
{0.17620431,0.16438311,0.13346862,0.09431537,0.05800522,0.03104796,0.01446373 },
{0.08208500,0.07657808,0.06217652,0.04393693,0.02702181,0.01446373,0.00673795 },
};

half4 frag(v2f i) : SV_Target
{
float blur = _Blur;
blur = max(1, blur);

fixed4 col = fixed4(0, 0, 0, 0);
float weight_total = 0;

[loop]
for (float x = -blur; x <= blur; x += 1)
{
//可优化
//float distance_normalized = abs(x / blur);
//float weight = exp(-0.5 * pow(distance_normalized, 2) * 5.0);
for (float y = -blur; y<=blur; y += 1) {
float weight = GaussWeight[abs(x)][abs(y)];
weight_total += weight;
col += tex2Dproj(_GrabTexture, i.grabPos + float4(x * _GrabTexture_TexelSize.x, y * _GrabTexture_TexelSize.y, 0, 0)) * weight;
}
}
col /= weight_total;

return col;
}
ENDCG
}
}

}
  1. 后处理法
    毛星云版github