回答編集履歴

3

σとBlurの対応図を追記

2018/10/10 13:29

投稿

Bongo
Bongo

スコア10807

test CHANGED
@@ -705,3 +705,11 @@
705
705
  ![新旧Frostの比較](60ac59ad62229f8751e2c14cd368f86c.png)
706
706
 
707
707
  ![プロファイラー](4893b45ef6ccf1032a2092e8e14d0b81.png)
708
+
709
+
710
+
711
+ #_Blurとσの対応
712
+
713
+ ![対応グラフ1](0e30e4e8704be9dd0c37370c51e77ef6.png)
714
+
715
+ ![対応グラフ2](aba411a3cdd40a9df347d0d57c790c69.png)

2

軽量化について検討

2018/10/10 13:29

投稿

Bongo
Bongo

スコア10807

test CHANGED
@@ -1,3 +1,707 @@
1
+ # 軽量化について
2
+
3
+ 文字数の限界につき別回答ですがご容赦ください。
4
+
5
+ 下記のようなアプローチを試してみました。
6
+
7
+
8
+
9
+ - シェーダーはぼかし処理を担当するFrostBlurと、ぼかし画像合成を担当するFrostCompositionに分ける。
10
+
11
+ FrostCompositionは旧Frostを改造、FrostBlurはイメージエフェクトシェーダーに近い作りになっている。
12
+
13
+ - カメラが不透明オブジェクト・背景の描画を終え、透明オブジェクトに取りかかる前の位置にコマンドバッファを挿入し、ここでぼかし処理を行う。
14
+
15
+ - 画面全体をレンダーテクスチャにコピーしぼかしをかける。
16
+
17
+ ぼかしカーネルは一次元9点で固定、重みもシェーダー内に直書きし、これを縦横二段掛けとして高速化を狙った。
18
+
19
+ - ぼかし幅が固定されている代わりに、ぼかし量の異なる多段階の画像を作っておくことで可変ぼかしに対応できるようにする。
20
+
21
+ ぼかし後の画像を縦横半分にし、再びぼかしをかける操作を繰り返し、解像度が半々に小さくなるぼかし画像群を得る。
22
+
23
+ - 曇りガラスオブジェクトの描画の際には、各ぼかしレベルから採った色をBlurに応じて混合する。ノイズテクスチャによるサンプリング位置ずらしは旧Frostと同様に行う。
24
+
25
+
26
+
27
+ **コマンドバッファ挿入用スクリプト**
28
+
29
+ これを曇りガラスオブジェクトにアタッチした上で、さらにマテリアルはFrostCompositionを使用する。
30
+
31
+ ```C#
32
+
33
+ using System.Collections.Generic;
34
+
35
+ using UnityEngine;
36
+
37
+ using UnityEngine.Rendering;
38
+
39
+
40
+
41
+ [ExecuteInEditMode]
42
+
43
+ public class Frost : MonoBehaviour
44
+
45
+ {
46
+
47
+ private const CameraEvent FrostBlurEvent = CameraEvent.AfterImageEffectsOpaque;
48
+
49
+ private static Material blurMaterial;
50
+
51
+ private static readonly Dictionary<Camera, CameraInfo> Cameras = new Dictionary<Camera, CameraInfo>();
52
+
53
+ private static readonly int GrabTex = Shader.PropertyToID("_GrabTex");
54
+
55
+ private static readonly int[] FrostTex =
56
+
57
+ {
58
+
59
+ Shader.PropertyToID("_FrostTex0"),
60
+
61
+ Shader.PropertyToID("_FrostTex1"),
62
+
63
+ Shader.PropertyToID("_FrostTex2"),
64
+
65
+ Shader.PropertyToID("_FrostTex3")
66
+
67
+ };
68
+
69
+ private static readonly int[] FrostTexT =
70
+
71
+ {
72
+
73
+ Shader.PropertyToID("_FrostTex0T"),
74
+
75
+ Shader.PropertyToID("_FrostTex1T"),
76
+
77
+ Shader.PropertyToID("_FrostTex2T"),
78
+
79
+ Shader.PropertyToID("_FrostTex3T")
80
+
81
+ };
82
+
83
+
84
+
85
+ private static void AddCommandBufferIfNeeded(Camera cam)
86
+
87
+ {
88
+
89
+ if (Cameras.ContainsKey(cam))
90
+
91
+ {
92
+
93
+ return;
94
+
95
+ }
96
+
97
+
98
+
99
+ var cb = new CommandBuffer
100
+
101
+ {
102
+
103
+ name = "FrostBlur"
104
+
105
+ };
106
+
107
+ cam.AddCommandBuffer(FrostBlurEvent, cb);
108
+
109
+ Cameras.Add(cam, new CameraInfo(cb, 0, 0));
110
+
111
+ }
112
+
113
+
114
+
115
+ private static void CleanCameras()
116
+
117
+ {
118
+
119
+ foreach (var pair in Cameras)
120
+
121
+ {
122
+
123
+ if (pair.Key != null)
124
+
125
+ {
126
+
127
+ pair.Key.RemoveCommandBuffer(FrostBlurEvent, pair.Value.CommandBuffer);
128
+
129
+ }
130
+
131
+ }
132
+
133
+
134
+
135
+ Cameras.Clear();
136
+
137
+ }
138
+
139
+
140
+
141
+ private void OnEnable()
142
+
143
+ {
144
+
145
+ CleanCameras();
146
+
147
+ }
148
+
149
+
150
+
151
+ private void OnDisable()
152
+
153
+ {
154
+
155
+ CleanCameras();
156
+
157
+ }
158
+
159
+
160
+
161
+ private void OnWillRenderObject()
162
+
163
+ {
164
+
165
+ if (!this.enabled || !this.gameObject.activeInHierarchy)
166
+
167
+ {
168
+
169
+ CleanCameras();
170
+
171
+ return;
172
+
173
+ }
174
+
175
+ var cam = Camera.current;
176
+
177
+ if (cam == null)
178
+
179
+ {
180
+
181
+ return;
182
+
183
+ }
184
+
185
+ AddCommandBufferIfNeeded(cam);
186
+
187
+ var camInfo = Cameras[cam];
188
+
189
+ var width = cam.pixelWidth;
190
+
191
+ var height = cam.pixelHeight;
192
+
193
+ if ((width == camInfo.Width) && (height == camInfo.Height))
194
+
195
+ {
196
+
197
+ return;
198
+
199
+ }
200
+
201
+ var cb = camInfo.CommandBuffer;
202
+
203
+ Cameras[cam] = new CameraInfo(cb, width, height);
204
+
205
+ var blur = blurMaterial;
206
+
207
+ if (blur == null)
208
+
209
+ {
210
+
211
+ blur = new Material(Shader.Find("Hidden/FrostBlur"));
212
+
213
+ blurMaterial = blur;
214
+
215
+ }
216
+
217
+
218
+
219
+ // 必要に応じ(画面サイズが変わった場合)コマンドバッファを再構成する
220
+
221
+ cb.Clear();
222
+
223
+ // 一時テクスチャを取得
224
+
225
+ // _GrabTexは画面と同サイズ、_FrostTex0~4と_FrostTex0T~4Tは
226
+
227
+ // 0が画面と同サイズで、以降はサイズを半々に小さくする
228
+
229
+ cb.GetTemporaryRT(GrabTex, width, height, 0, FilterMode.Bilinear);
230
+
231
+ for (int i = 0, w = width, h = height; i < 4; i++, w >>= 1, h >>= 1)
232
+
233
+ {
234
+
235
+ cb.GetTemporaryRT(FrostTex[i], w, h, 0, FilterMode.Bilinear);
236
+
237
+ cb.GetTemporaryRT(FrostTexT[i], w, h, 0, FilterMode.Bilinear);
238
+
239
+ }
240
+
241
+ // 現在のレンダリング結果を_GrabTexにコピー
242
+
243
+ cb.Blit(BuiltinRenderTextureType.CurrentActive, GrabTex);
244
+
245
+ // _FrostTex0に_GrabTexをぼかしたものを格納
246
+
247
+ cb.Blit(GrabTex, FrostTex[0]); // そのままコピー
248
+
249
+ cb.Blit(FrostTex[0], FrostTexT[0], blur, 0); // 水平ぼかしコピー
250
+
251
+ cb.Blit(FrostTexT[0], FrostTex[0], blur, 1); // 垂直ぼかしコピー
252
+
253
+ // 段階的に縮小テクスチャへコピーしつつ、さらにぼかしをかけていく
254
+
255
+ for (var i = 1; i < 4; i++)
256
+
257
+ {
258
+
259
+ cb.Blit(FrostTex[i - 1], FrostTex[i]); // 縮小コピー
260
+
261
+ cb.Blit(FrostTex[i], FrostTexT[i], blur, 0); // 水平ぼかしコピー
262
+
263
+ cb.Blit(FrostTexT[i], FrostTex[i], blur, 1); // 垂直ぼかしコピー
264
+
265
+ }
266
+
267
+ // Blitなどのコストを無視すれば、ぼかしテクスチャ総面積は画面面積×1.33で
268
+
269
+ // サンプリング回数はピクセルあたり9×縦横2回で18回、さらに後でグラブテクスチャと
270
+
271
+ // 各ぼかしテクスチャから1回ずつサンプリングするので合計23回となり、
272
+
273
+ // 総サンプリング回数はおよそ画面面積×30.5回となるはず...
274
+
275
+ // 当初の方式でのσ=8クラスのぼかし(およそ描画面積×2600回のサンプリングが必要)と
276
+
277
+ // 比べると、同等のぼかし量ながらだいぶ処理コストを削減できたのではないでしょうか?
278
+
279
+ }
280
+
281
+
282
+
283
+ private struct CameraInfo
284
+
285
+ {
286
+
287
+ public readonly CommandBuffer CommandBuffer;
288
+
289
+ public readonly int Width;
290
+
291
+ public readonly int Height;
292
+
293
+
294
+
295
+ public CameraInfo(CommandBuffer cb, int w, int h)
296
+
297
+ {
298
+
299
+ this.CommandBuffer = cb;
300
+
301
+ this.Width = w;
302
+
303
+ this.Height = h;
304
+
305
+ }
306
+
307
+ }
308
+
309
+ }
310
+
311
+ ```
312
+
313
+
314
+
315
+ **ぼかし処理担当シェーダー**
316
+
317
+ 上記Frostスクリプトが内部で使用する。
318
+
319
+ ```ShaderLab
320
+
321
+ Shader "Hidden/FrostBlur"
322
+
323
+ {
324
+
325
+ Properties
326
+
327
+ {
328
+
329
+ _MainTex ("Texture", 2D) = "white" {}
330
+
331
+ }
332
+
333
+ SubShader
334
+
335
+ {
336
+
337
+ Cull Off ZWrite Off ZTest Always
338
+
339
+
340
+
341
+ CGINCLUDE
342
+
343
+ #include "UnityCG.cginc"
344
+
345
+ struct appdata
346
+
347
+ {
348
+
349
+ float4 vertex : POSITION;
350
+
351
+ float2 uv : TEXCOORD0;
352
+
353
+ };
354
+
355
+ struct v2f
356
+
357
+ {
358
+
359
+ float2 uv : TEXCOORD0;
360
+
361
+ float4 vertex : SV_POSITION;
362
+
363
+ };
364
+
365
+ v2f vert (appdata v)
366
+
367
+ {
368
+
369
+ v2f o;
370
+
371
+ o.vertex = UnityObjectToClipPos(v.vertex);
372
+
373
+ o.uv = v.uv;
374
+
375
+ return o;
376
+
377
+ }
378
+
379
+ sampler2D _MainTex;
380
+
381
+ float4 _MainTex_TexelSize;
382
+
383
+ // 二項係数に基づく重み
384
+
385
+ #define WS 256.0
386
+
387
+ #define W0 (70.0 / WS)
388
+
389
+ #define W1 (56.0 / WS)
390
+
391
+ #define W2 (28.0 / WS)
392
+
393
+ #define W3 (8.0 / WS)
394
+
395
+ #define W4 (1.0 / WS)
396
+
397
+ ENDCG
398
+
399
+
400
+
401
+ // パス0...水平ぼかし
402
+
403
+ Pass
404
+
405
+ {
406
+
407
+ CGPROGRAM
408
+
409
+ #pragma vertex vert
410
+
411
+ #pragma fragment frag
412
+
413
+ fixed4 frag (v2f i) : SV_Target
414
+
415
+ {
416
+
417
+ float2 scale = _MainTex_TexelSize.xy;
418
+
419
+ fixed4 col = W0 * tex2D(_MainTex, i.uv);
420
+
421
+ col += W1 * tex2D(_MainTex, i.uv + scale * float2(1, 0));
422
+
423
+ col += W1 * tex2D(_MainTex, i.uv + scale * float2(-1, 0));
424
+
425
+ col += W2 * tex2D(_MainTex, i.uv + scale * float2(2, 0));
426
+
427
+ col += W2 * tex2D(_MainTex, i.uv + scale * float2(-2, 0));
428
+
429
+ col += W3 * tex2D(_MainTex, i.uv + scale * float2(3, 0));
430
+
431
+ col += W3 * tex2D(_MainTex, i.uv + scale * float2(-3, 0));
432
+
433
+ col += W4 * tex2D(_MainTex, i.uv + scale * float2(4, 0));
434
+
435
+ col += W4 * tex2D(_MainTex, i.uv + scale * float2(-4, 0));
436
+
437
+ return col;
438
+
439
+ }
440
+
441
+ ENDCG
442
+
443
+ }
444
+
445
+
446
+
447
+ // パス1...垂直ぼかし
448
+
449
+ Pass
450
+
451
+ {
452
+
453
+ CGPROGRAM
454
+
455
+ #pragma vertex vert
456
+
457
+ #pragma fragment frag
458
+
459
+ fixed4 frag (v2f i) : SV_Target
460
+
461
+ {
462
+
463
+ float2 scale = _MainTex_TexelSize.xy;
464
+
465
+ fixed4 col = W0 * tex2D(_MainTex, i.uv);
466
+
467
+ col += W1 * tex2D(_MainTex, i.uv + scale * float2(0, 1));
468
+
469
+ col += W1 * tex2D(_MainTex, i.uv + scale * float2(0, -1));
470
+
471
+ col += W2 * tex2D(_MainTex, i.uv + scale * float2(0, 2));
472
+
473
+ col += W2 * tex2D(_MainTex, i.uv + scale * float2(0, -2));
474
+
475
+ col += W3 * tex2D(_MainTex, i.uv + scale * float2(0, 3));
476
+
477
+ col += W3 * tex2D(_MainTex, i.uv + scale * float2(0, -3));
478
+
479
+ col += W4 * tex2D(_MainTex, i.uv + scale * float2(0, 4));
480
+
481
+ col += W4 * tex2D(_MainTex, i.uv + scale * float2(0, -4));
482
+
483
+ return col;
484
+
485
+ }
486
+
487
+ ENDCG
488
+
489
+ }
490
+
491
+ }
492
+
493
+ }
494
+
495
+ ```
496
+
497
+
498
+
499
+ **ぼかしテクスチャ合成担当シェーダー**
500
+
1
- すみません、軽量化ついて検討していましたが操作ミスにより誤投稿してしまいました。
501
+ 旧Frostと同様に、曇りガラオブジェクトはこれを使っマテリアルをセットする
502
+
2
-
503
+ ```ShaderLab
504
+
505
+ Shader "Custom/FrostComposition"
506
+
507
+ {
508
+
509
+ Properties
510
+
511
+ {
512
+
513
+ _Color("Color", Color) = (1, 1, 1, 1)
514
+
515
+
516
+
517
+ _MainTex("Diffuse", 2D) = "white" {}
518
+
519
+ _Noise("Noise", 2D) = "black" {}
520
+
521
+
522
+
523
+ _Range("Range", Float) = 0.025
524
+
525
+ _Blur("Blur", Range(0.0, 1.0)) = 0.5
526
+
527
+ }
528
+
529
+
530
+
531
+ SubShader
532
+
533
+ {
534
+
535
+ Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" }
536
+
537
+
538
+
539
+ Cull Off
540
+
541
+
542
+
543
+ CGINCLUDE
544
+
545
+ #include "UnityCG.cginc"
546
+
547
+ half4 _Color;
548
+
549
+ sampler2D _MainTex;
550
+
551
+ float4 _MainTex_ST;
552
+
553
+ sampler2D _GrabTex;
554
+
555
+ sampler2D _FrostTex0;
556
+
557
+ sampler2D _FrostTex1;
558
+
559
+ sampler2D _FrostTex2;
560
+
561
+ sampler2D _FrostTex3;
562
+
563
+ sampler2D _Noise;
564
+
565
+ float4 _Noise_ST;
566
+
567
+ half _Range;
568
+
569
+ float _Blur;
570
+
571
+ ENDCG
572
+
573
+
574
+
575
+ Pass
576
+
577
+ {
578
+
579
+ CGPROGRAM
580
+
581
+ #pragma target 3.0
582
+
583
+ #pragma vertex vert
584
+
585
+ #pragma fragment frag
586
+
587
+
588
+
589
+ struct v2f
590
+
591
+ {
592
+
593
+ float4 pos : SV_POSITION;
594
+
595
+ float3 uv : TEXCOORD;
596
+
597
+ float4 screenPos : TEXCOORD1;
598
+
599
+ };
600
+
601
+
602
+
603
+ v2f vert(appdata_full v)
604
+
605
+ {
606
+
607
+ v2f o;
608
+
609
+ o.pos = UnityObjectToClipPos(v.vertex);
610
+
611
+ o.uv = v.texcoord;
612
+
613
+ o.screenPos = ComputeGrabScreenPos(o.pos);
614
+
615
+ return o;
616
+
617
+ }
618
+
619
+
620
+
621
+ half4 frag(v2f i) : SV_Target
622
+
623
+ {
624
+
625
+ float2 uv = i.screenPos.xy / i.screenPos.w;
626
+
627
+ float2 frostUV = tex2D(_Noise, i.uv * _Noise_ST.xy + _Noise_ST.zw).xy;
628
+
629
+
630
+
631
+ frostUV -= 0.5;
632
+
633
+ frostUV *= _Range;
634
+
635
+ frostUV += uv;
636
+
637
+ // frostUV = uv;
638
+
639
+
640
+
641
+ float t = pow(_Blur, 0.5) * 4;
642
+
643
+ float4 frost = smoothstep(1, 0, t) * tex2D(_GrabTex, frostUV);
644
+
645
+ frost += smoothstep(1, 0, abs(t - 1)) * tex2D(_FrostTex0, frostUV);
646
+
647
+ frost += smoothstep(1, 0, abs(t - 2)) * tex2D(_FrostTex1, frostUV);
648
+
649
+ frost += smoothstep(1, 0, abs(t - 3)) * tex2D(_FrostTex2, frostUV);
650
+
651
+ frost += smoothstep(1, 0, 4 - t) * tex2D(_FrostTex3, frostUV);
652
+
653
+
654
+
655
+ half4 diffuse = tex2D(_MainTex, i.uv * _MainTex_ST.xy + _MainTex_ST.zw);
656
+
657
+
658
+
659
+ half alpha = _Color.a * diffuse.a;
660
+
661
+
662
+
663
+ return half4(frost.xyz + (diffuse.rgb * _Color.rgb * alpha), 1);
664
+
665
+ }
666
+
667
+ ENDCG
668
+
669
+ }
670
+
671
+ }
672
+
673
+ Fallback Off
674
+
675
+ }
676
+
677
+ ```
678
+
679
+
680
+
681
+ ぼかし画像を作る過程は下図のようになります。GIFでも階調を表現できるよう、白黒で撮影しました。
682
+
683
+
684
+
685
+ ![ぼかし過程](35a19eb339ed44fa7bc75d148dfeb26e.gif)
686
+
687
+
688
+
689
+ FrostCompositionでは、Blurに応じて下図のような比率で各ぼかし画像を混合しました。
690
+
691
+
692
+
693
+ ![ぼかし混合比率](a0b38034897205b8cd2b21616b2ceec7.png)
694
+
695
+
696
+
697
+ 旧Frostと比較するとプロファイラーで見た際のレンダリング負荷(黄緑色領域)が削減されましたが、新Frostは低解像度のぼかし画像を引き伸ばしたものですので、よく見るとピクセル由来のブロック感が出ています。
698
+
699
+ σに応じて広い範囲をきっちりサンプリングしていた旧Frostに対し、新Frostは複数のぼかし画像を混ぜているだけですので、画質面では劣ってしまいます。一応ガウス分布っぽい広がりのぼかしにはしたつもりですが、「ガウシアンブラー」だと言い張るにはちょっと怪しいかもしれません...
700
+
3
- 調査に進展ありましら、追って編集たします。
701
+ ノイズ効果と組み合わせれば、粗目立なくなってマシなように思います。
702
+
703
+
704
+
705
+ ![新旧Frostの比較](60ac59ad62229f8751e2c14cd368f86c.png)
706
+
707
+ ![プロファイラー](4893b45ef6ccf1032a2092e8e14d0b81.png)

1

操作ミスによる投稿

2018/10/04 21:07

投稿

Bongo
Bongo

スコア10807

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