質問するログイン新規登録

回答編集履歴

3

σとBlurの対応図を追記

2018/10/10 13:29

投稿

Bongo
Bongo

スコア10816

answer CHANGED
@@ -351,4 +351,8 @@
351
351
  ノイズ効果と組み合わせれば、粗が目立たなくなってマシなように思います。
352
352
 
353
353
  ![新旧Frostの比較](60ac59ad62229f8751e2c14cd368f86c.png)
354
- ![プロファイラー](4893b45ef6ccf1032a2092e8e14d0b81.png)
354
+ ![プロファイラー](4893b45ef6ccf1032a2092e8e14d0b81.png)
355
+
356
+ #_Blurとσの対応
357
+ ![対応グラフ1](0e30e4e8704be9dd0c37370c51e77ef6.png)
358
+ ![対応グラフ2](aba411a3cdd40a9df347d0d57c790c69.png)

2

軽量化について検討

2018/10/10 13:29

投稿

Bongo
Bongo

スコア10816

answer CHANGED
@@ -1,2 +1,354 @@
1
+ # 軽量化について
2
+ 文字数の限界につき別回答ですがご容赦ください。
3
+ 下記のようなアプローチを試してみました。
4
+
5
+ - シェーダーはぼかし処理を担当するFrostBlurと、ぼかし画像合成を担当するFrostCompositionに分ける。
6
+ FrostCompositionは旧Frostを改造、FrostBlurはイメージエフェクトシェーダーに近い作りになっている。
7
+ - カメラが不透明オブジェクト・背景の描画を終え、透明オブジェクトに取りかかる前の位置にコマンドバッファを挿入し、ここでぼかし処理を行う。
8
+ - 画面全体をレンダーテクスチャにコピーしぼかしをかける。
9
+ ぼかしカーネルは一次元9点で固定、重みもシェーダー内に直書きし、これを縦横二段掛けとして高速化を狙った。
10
+ - ぼかし幅が固定されている代わりに、ぼかし量の異なる多段階の画像を作っておくことで可変ぼかしに対応できるようにする。
11
+ ぼかし後の画像を縦横半分にし、再びぼかしをかける操作を繰り返し、解像度が半々に小さくなるぼかし画像群を得る。
12
+ - 曇りガラスオブジェクトの描画の際には、各ぼかしレベルから採った色をBlurに応じて混合する。ノイズテクスチャによるサンプリング位置ずらしは旧Frostと同様に行う。
13
+
14
+ **コマンドバッファ挿入用スクリプト**
15
+ これを曇りガラスオブジェクトにアタッチした上で、さらにマテリアルはFrostCompositionを使用する。
16
+ ```C#
17
+ using System.Collections.Generic;
18
+ using UnityEngine;
19
+ using UnityEngine.Rendering;
20
+
21
+ [ExecuteInEditMode]
22
+ public class Frost : MonoBehaviour
23
+ {
24
+ private const CameraEvent FrostBlurEvent = CameraEvent.AfterImageEffectsOpaque;
25
+ private static Material blurMaterial;
26
+ private static readonly Dictionary<Camera, CameraInfo> Cameras = new Dictionary<Camera, CameraInfo>();
27
+ private static readonly int GrabTex = Shader.PropertyToID("_GrabTex");
28
+ private static readonly int[] FrostTex =
29
+ {
30
+ Shader.PropertyToID("_FrostTex0"),
31
+ Shader.PropertyToID("_FrostTex1"),
32
+ Shader.PropertyToID("_FrostTex2"),
33
+ Shader.PropertyToID("_FrostTex3")
34
+ };
35
+ private static readonly int[] FrostTexT =
36
+ {
37
+ Shader.PropertyToID("_FrostTex0T"),
38
+ Shader.PropertyToID("_FrostTex1T"),
39
+ Shader.PropertyToID("_FrostTex2T"),
40
+ Shader.PropertyToID("_FrostTex3T")
41
+ };
42
+
43
+ private static void AddCommandBufferIfNeeded(Camera cam)
44
+ {
45
+ if (Cameras.ContainsKey(cam))
46
+ {
47
+ return;
48
+ }
49
+
50
+ var cb = new CommandBuffer
51
+ {
52
+ name = "FrostBlur"
53
+ };
54
+ cam.AddCommandBuffer(FrostBlurEvent, cb);
55
+ Cameras.Add(cam, new CameraInfo(cb, 0, 0));
56
+ }
57
+
58
+ private static void CleanCameras()
59
+ {
60
+ foreach (var pair in Cameras)
61
+ {
62
+ if (pair.Key != null)
63
+ {
64
+ pair.Key.RemoveCommandBuffer(FrostBlurEvent, pair.Value.CommandBuffer);
65
+ }
66
+ }
67
+
68
+ Cameras.Clear();
69
+ }
70
+
71
+ private void OnEnable()
72
+ {
73
+ CleanCameras();
74
+ }
75
+
76
+ private void OnDisable()
77
+ {
78
+ CleanCameras();
79
+ }
80
+
81
+ private void OnWillRenderObject()
82
+ {
83
+ if (!this.enabled || !this.gameObject.activeInHierarchy)
84
+ {
85
+ CleanCameras();
86
+ return;
87
+ }
88
+ var cam = Camera.current;
89
+ if (cam == null)
90
+ {
91
+ return;
92
+ }
93
+ AddCommandBufferIfNeeded(cam);
94
+ var camInfo = Cameras[cam];
95
+ var width = cam.pixelWidth;
96
+ var height = cam.pixelHeight;
97
+ if ((width == camInfo.Width) && (height == camInfo.Height))
98
+ {
99
+ return;
100
+ }
101
+ var cb = camInfo.CommandBuffer;
102
+ Cameras[cam] = new CameraInfo(cb, width, height);
103
+ var blur = blurMaterial;
104
+ if (blur == null)
105
+ {
106
+ blur = new Material(Shader.Find("Hidden/FrostBlur"));
107
+ blurMaterial = blur;
108
+ }
109
+
110
+ // 必要に応じ(画面サイズが変わった場合)コマンドバッファを再構成する
111
+ cb.Clear();
112
+ // 一時テクスチャを取得
113
+ // _GrabTexは画面と同サイズ、_FrostTex0~4と_FrostTex0T~4Tは
114
+ // 0が画面と同サイズで、以降はサイズを半々に小さくする
115
+ cb.GetTemporaryRT(GrabTex, width, height, 0, FilterMode.Bilinear);
116
+ for (int i = 0, w = width, h = height; i < 4; i++, w >>= 1, h >>= 1)
117
+ {
118
+ cb.GetTemporaryRT(FrostTex[i], w, h, 0, FilterMode.Bilinear);
119
+ cb.GetTemporaryRT(FrostTexT[i], w, h, 0, FilterMode.Bilinear);
120
+ }
121
+ // 現在のレンダリング結果を_GrabTexにコピー
122
+ cb.Blit(BuiltinRenderTextureType.CurrentActive, GrabTex);
123
+ // _FrostTex0に_GrabTexをぼかしたものを格納
124
+ cb.Blit(GrabTex, FrostTex[0]); // そのままコピー
125
+ cb.Blit(FrostTex[0], FrostTexT[0], blur, 0); // 水平ぼかしコピー
126
+ cb.Blit(FrostTexT[0], FrostTex[0], blur, 1); // 垂直ぼかしコピー
127
+ // 段階的に縮小テクスチャへコピーしつつ、さらにぼかしをかけていく
128
+ for (var i = 1; i < 4; i++)
129
+ {
130
+ cb.Blit(FrostTex[i - 1], FrostTex[i]); // 縮小コピー
131
+ cb.Blit(FrostTex[i], FrostTexT[i], blur, 0); // 水平ぼかしコピー
132
+ cb.Blit(FrostTexT[i], FrostTex[i], blur, 1); // 垂直ぼかしコピー
133
+ }
134
+ // Blitなどのコストを無視すれば、ぼかしテクスチャ総面積は画面面積×1.33で
135
+ // サンプリング回数はピクセルあたり9×縦横2回で18回、さらに後でグラブテクスチャと
136
+ // 各ぼかしテクスチャから1回ずつサンプリングするので合計23回となり、
137
+ // 総サンプリング回数はおよそ画面面積×30.5回となるはず...
138
+ // 当初の方式でのσ=8クラスのぼかし(およそ描画面積×2600回のサンプリングが必要)と
139
+ // 比べると、同等のぼかし量ながらだいぶ処理コストを削減できたのではないでしょうか?
140
+ }
141
+
142
+ private struct CameraInfo
143
+ {
144
+ public readonly CommandBuffer CommandBuffer;
145
+ public readonly int Width;
146
+ public readonly int Height;
147
+
148
+ public CameraInfo(CommandBuffer cb, int w, int h)
149
+ {
150
+ this.CommandBuffer = cb;
151
+ this.Width = w;
152
+ this.Height = h;
153
+ }
154
+ }
155
+ }
156
+ ```
157
+
158
+ **ぼかし処理担当シェーダー**
159
+ 上記Frostスクリプトが内部で使用する。
160
+ ```ShaderLab
161
+ Shader "Hidden/FrostBlur"
162
+ {
163
+ Properties
164
+ {
165
+ _MainTex ("Texture", 2D) = "white" {}
166
+ }
167
+ SubShader
168
+ {
169
+ Cull Off ZWrite Off ZTest Always
170
+
171
+ CGINCLUDE
172
+ #include "UnityCG.cginc"
173
+ struct appdata
174
+ {
175
+ float4 vertex : POSITION;
176
+ float2 uv : TEXCOORD0;
177
+ };
178
+ struct v2f
179
+ {
180
+ float2 uv : TEXCOORD0;
181
+ float4 vertex : SV_POSITION;
182
+ };
183
+ v2f vert (appdata v)
184
+ {
185
+ v2f o;
186
+ o.vertex = UnityObjectToClipPos(v.vertex);
187
+ o.uv = v.uv;
188
+ return o;
189
+ }
190
+ sampler2D _MainTex;
191
+ float4 _MainTex_TexelSize;
192
+ // 二項係数に基づく重み
193
+ #define WS 256.0
194
+ #define W0 (70.0 / WS)
195
+ #define W1 (56.0 / WS)
196
+ #define W2 (28.0 / WS)
197
+ #define W3 (8.0 / WS)
198
+ #define W4 (1.0 / WS)
199
+ ENDCG
200
+
201
+ // パス0...水平ぼかし
202
+ Pass
203
+ {
204
+ CGPROGRAM
205
+ #pragma vertex vert
206
+ #pragma fragment frag
207
+ fixed4 frag (v2f i) : SV_Target
208
+ {
209
+ float2 scale = _MainTex_TexelSize.xy;
210
+ fixed4 col = W0 * tex2D(_MainTex, i.uv);
211
+ col += W1 * tex2D(_MainTex, i.uv + scale * float2(1, 0));
212
+ col += W1 * tex2D(_MainTex, i.uv + scale * float2(-1, 0));
213
+ col += W2 * tex2D(_MainTex, i.uv + scale * float2(2, 0));
214
+ col += W2 * tex2D(_MainTex, i.uv + scale * float2(-2, 0));
215
+ col += W3 * tex2D(_MainTex, i.uv + scale * float2(3, 0));
216
+ col += W3 * tex2D(_MainTex, i.uv + scale * float2(-3, 0));
217
+ col += W4 * tex2D(_MainTex, i.uv + scale * float2(4, 0));
218
+ col += W4 * tex2D(_MainTex, i.uv + scale * float2(-4, 0));
219
+ return col;
220
+ }
221
+ ENDCG
222
+ }
223
+
224
+ // パス1...垂直ぼかし
225
+ Pass
226
+ {
227
+ CGPROGRAM
228
+ #pragma vertex vert
229
+ #pragma fragment frag
230
+ fixed4 frag (v2f i) : SV_Target
231
+ {
232
+ float2 scale = _MainTex_TexelSize.xy;
233
+ fixed4 col = W0 * tex2D(_MainTex, i.uv);
234
+ col += W1 * tex2D(_MainTex, i.uv + scale * float2(0, 1));
235
+ col += W1 * tex2D(_MainTex, i.uv + scale * float2(0, -1));
236
+ col += W2 * tex2D(_MainTex, i.uv + scale * float2(0, 2));
237
+ col += W2 * tex2D(_MainTex, i.uv + scale * float2(0, -2));
238
+ col += W3 * tex2D(_MainTex, i.uv + scale * float2(0, 3));
239
+ col += W3 * tex2D(_MainTex, i.uv + scale * float2(0, -3));
240
+ col += W4 * tex2D(_MainTex, i.uv + scale * float2(0, 4));
241
+ col += W4 * tex2D(_MainTex, i.uv + scale * float2(0, -4));
242
+ return col;
243
+ }
244
+ ENDCG
245
+ }
246
+ }
247
+ }
248
+ ```
249
+
250
+ **ぼかしテクスチャ合成担当シェーダー**
1
- すみません、軽量化ついて検討していましたが操作ミスにより誤投稿してしまいました。
251
+ 旧Frostと同様に、曇りガラオブジェクトはこれを使っマテリアルをセットする
252
+ ```ShaderLab
253
+ Shader "Custom/FrostComposition"
254
+ {
255
+ Properties
256
+ {
257
+ _Color("Color", Color) = (1, 1, 1, 1)
258
+
259
+ _MainTex("Diffuse", 2D) = "white" {}
260
+ _Noise("Noise", 2D) = "black" {}
261
+
262
+ _Range("Range", Float) = 0.025
263
+ _Blur("Blur", Range(0.0, 1.0)) = 0.5
264
+ }
265
+
266
+ SubShader
267
+ {
268
+ Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" }
269
+
270
+ Cull Off
271
+
272
+ CGINCLUDE
273
+ #include "UnityCG.cginc"
274
+ half4 _Color;
275
+ sampler2D _MainTex;
276
+ float4 _MainTex_ST;
277
+ sampler2D _GrabTex;
278
+ sampler2D _FrostTex0;
279
+ sampler2D _FrostTex1;
280
+ sampler2D _FrostTex2;
281
+ sampler2D _FrostTex3;
282
+ sampler2D _Noise;
283
+ float4 _Noise_ST;
284
+ half _Range;
285
+ float _Blur;
286
+ ENDCG
287
+
288
+ Pass
289
+ {
290
+ CGPROGRAM
291
+ #pragma target 3.0
292
+ #pragma vertex vert
293
+ #pragma fragment frag
294
+
295
+ struct v2f
296
+ {
297
+ float4 pos : SV_POSITION;
298
+ float3 uv : TEXCOORD;
299
+ float4 screenPos : TEXCOORD1;
300
+ };
301
+
302
+ v2f vert(appdata_full v)
303
+ {
304
+ v2f o;
305
+ o.pos = UnityObjectToClipPos(v.vertex);
306
+ o.uv = v.texcoord;
307
+ o.screenPos = ComputeGrabScreenPos(o.pos);
308
+ return o;
309
+ }
310
+
311
+ half4 frag(v2f i) : SV_Target
312
+ {
313
+ float2 uv = i.screenPos.xy / i.screenPos.w;
314
+ float2 frostUV = tex2D(_Noise, i.uv * _Noise_ST.xy + _Noise_ST.zw).xy;
315
+
316
+ frostUV -= 0.5;
317
+ frostUV *= _Range;
318
+ frostUV += uv;
319
+ // frostUV = uv;
320
+
321
+ float t = pow(_Blur, 0.5) * 4;
322
+ float4 frost = smoothstep(1, 0, t) * tex2D(_GrabTex, frostUV);
323
+ frost += smoothstep(1, 0, abs(t - 1)) * tex2D(_FrostTex0, frostUV);
324
+ frost += smoothstep(1, 0, abs(t - 2)) * tex2D(_FrostTex1, frostUV);
325
+ frost += smoothstep(1, 0, abs(t - 3)) * tex2D(_FrostTex2, frostUV);
326
+ frost += smoothstep(1, 0, 4 - t) * tex2D(_FrostTex3, frostUV);
327
+
328
+ half4 diffuse = tex2D(_MainTex, i.uv * _MainTex_ST.xy + _MainTex_ST.zw);
329
+
330
+ half alpha = _Color.a * diffuse.a;
331
+
332
+ return half4(frost.xyz + (diffuse.rgb * _Color.rgb * alpha), 1);
333
+ }
334
+ ENDCG
335
+ }
336
+ }
337
+ Fallback Off
338
+ }
339
+ ```
340
+
341
+ ぼかし画像を作る過程は下図のようになります。GIFでも階調を表現できるよう、白黒で撮影しました。
342
+
343
+ ![ぼかし過程](35a19eb339ed44fa7bc75d148dfeb26e.gif)
344
+
345
+ FrostCompositionでは、Blurに応じて下図のような比率で各ぼかし画像を混合しました。
346
+
347
+ ![ぼかし混合比率](a0b38034897205b8cd2b21616b2ceec7.png)
348
+
349
+ 旧Frostと比較するとプロファイラーで見た際のレンダリング負荷(黄緑色領域)が削減されましたが、新Frostは低解像度のぼかし画像を引き伸ばしたものですので、よく見るとピクセル由来のブロック感が出ています。
350
+ σに応じて広い範囲をきっちりサンプリングしていた旧Frostに対し、新Frostは複数のぼかし画像を混ぜているだけですので、画質面では劣ってしまいます。一応ガウス分布っぽい広がりのぼかしにはしたつもりですが、「ガウシアンブラー」だと言い張るにはちょっと怪しいかもしれません...
2
- 調査に進展ありましら、追って編集たします。
351
+ ノイズ効果と組み合わせれば、粗目立なくなってマシなように思います。
352
+
353
+ ![新旧Frostの比較](60ac59ad62229f8751e2c14cd368f86c.png)
354
+ ![プロファイラー](4893b45ef6ccf1032a2092e8e14d0b81.png)

1

操作ミスによる投稿

2018/10/04 21:07

投稿

Bongo
Bongo

スコア10816

answer CHANGED
@@ -1,1 +1,2 @@
1
+ すみません、軽量化について検討していましたが、操作ミスにより誤投稿してしまいました。
1
- ガウスぼかとしの正確性は劣るでょうが、
2
+ 調査に進展がありまたら、追っ編集いたます。