回答編集履歴

1

変数名変更、ループ展開版を追記

2018/10/02 21:55

投稿

Bongo
Bongo

スコア10807

test CHANGED
@@ -26,7 +26,7 @@
26
26
 
27
27
  // _Blur("Blur", Float) = 0.005 // Blurは廃止、サンプリング位置ずらしはテクセルサイズに基づいた形に変更
28
28
 
29
- _Sigma("Sigma", Range(0.01, 8.0)) = 1.0 // σを追加
29
+ _Sigma("Sigma", Range(0.01, 8.0)) = 1.0 // σを追加
30
30
 
31
31
  }
32
32
 
@@ -36,7 +36,327 @@
36
36
 
37
37
  {
38
38
 
39
- // スカイボックスも含めてグラブするため、キューをTransparentに変更
39
+ // スカイボックスも含めてグラブするため、キューをTransparentに変更
40
+
41
+ Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" }
42
+
43
+
44
+
45
+ Cull Off
46
+
47
+
48
+
49
+ GrabPass{ "_Frost" }
50
+
51
+
52
+
53
+ CGINCLUDE
54
+
55
+ #include "UnityCG.cginc"
56
+
57
+
58
+
59
+ half4 _Color;
60
+
61
+
62
+
63
+ sampler2D _MainTex;
64
+
65
+ float4 _MainTex_ST;
66
+
67
+
68
+
69
+ sampler2D _Frost;
70
+
71
+
72
+
73
+ // グラブテクスチャのテクセルサイズを追加
74
+
75
+ float4 _Frost_TexelSize;
76
+
77
+
78
+
79
+ sampler2D _Noise;
80
+
81
+ float4 _Noise_ST;
82
+
83
+
84
+
85
+ half _Range;
86
+
87
+ // half _Blur; // _Blurは廃止
88
+
89
+ float _Sigma; // _Sigmaを追加
90
+
91
+
92
+
93
+ // 重み計算用関数
94
+
95
+ // ご質問者さんの重み関数 1/(2*π*σ)*exp(-(x^2+y^2)/(2*σ^2)) と同様ですが、係数は1に変更しました
96
+
97
+ // 係数を付けて正規化した重みで有限の範囲をたたみ込むと、重みの総和が1より小さくなってしまうため
98
+
99
+ // できあがったぼかし画像は、もとの画像より暗くなってしまうと思われます
100
+
101
+ // そこで重み関数には係数を付けず、たたみ込みの際に重みの総和を求めて、最後にたたみ込み結果を
102
+
103
+ // 重みの総和で割ることで明るさが維持されるようにしました
104
+
105
+ inline float getWeight(float2 xy)
106
+
107
+ {
108
+
109
+ return exp(-dot(xy, xy) / (2.0 * _Sigma * _Sigma));
110
+
111
+ }
112
+
113
+
114
+
115
+ // カーネルサイズ計算用関数
116
+
117
+ // たたみ込み範囲の片側幅...カーネルの一辺の長さ2*n+1のnをいくつにするかですが、さしあたり
118
+
119
+ // 中心から最も遠い点(カーネルの角)における重みが十分小さくなる(0.0001を切る)大きさにしました
120
+
121
+ // σ=1でn=4となり、サンプリング回数は(2*4+1)^2=81回になります(多分...)
122
+
123
+ // σ=2で225回、σ=4で729回、σ=8で2601回...といった具合に、2乗のオーダーでサンプリング回数が
124
+
125
+ // 増大しますので、あんまりσを大きくしすぎるのは控えた方がいいでしょう
126
+
127
+ // ぼかしを縦方向と横方向の二段階に分けることで、サンプリング回数の増大を1乗のオーダーに
128
+
129
+ // 抑えるテクニックもありますので、負荷を軽減したい場合は採用を検討してみてもいいでしょう
130
+
131
+ inline int getKernelN()
132
+
133
+ {
134
+
135
+ return (int)ceil(_Sigma * sqrt(-log(0.0001)));
136
+
137
+ }
138
+
139
+
140
+
141
+ ENDCG
142
+
143
+
144
+
145
+ Pass
146
+
147
+ {
148
+
149
+ CGPROGRAM
150
+
151
+ #pragma target 3.0
152
+
153
+ #pragma vertex vert
154
+
155
+ #pragma fragment frag
156
+
157
+
158
+
159
+ struct v2f
160
+
161
+ {
162
+
163
+ float4 pos : SV_POSITION;
164
+
165
+
166
+
167
+ float3 uv : TEXCOORD;
168
+
169
+ float4 screenPos : TEXCOORD1;
170
+
171
+ float3 ray : TEXCOORD2;
172
+
173
+ };
174
+
175
+
176
+
177
+ v2f vert(appdata_full v)
178
+
179
+ {
180
+
181
+ v2f o;
182
+
183
+ o.pos = UnityObjectToClipPos(v.vertex);
184
+
185
+ o.uv = v.texcoord;
186
+
187
+ o.screenPos = ComputeGrabScreenPos(o.pos);
188
+
189
+ o.ray = UnityObjectToViewPos(v.vertex).xyz * float3(-1, -1, 1);
190
+
191
+ return o;
192
+
193
+ }
194
+
195
+
196
+
197
+ half4 frag(v2f i) : SV_Target
198
+
199
+ {
200
+
201
+ // rayは使用していないようですが、一応残しておきました
202
+
203
+ i.ray = i.ray * (_ProjectionParams.z / i.ray.z);
204
+
205
+
206
+
207
+ float2 uv = i.screenPos.xy / i.screenPos.w;
208
+
209
+
210
+
211
+ float2 frostUV = tex2D(_Noise, i.uv * _Noise_ST.xy + _Noise_ST.zw).xy;
212
+
213
+
214
+
215
+ frostUV -= 0.5;
216
+
217
+ frostUV *= _Range;
218
+
219
+ frostUV += uv;
220
+
221
+
222
+
223
+ // 霜のついたガラスを表現するためサンプリング位置にノイズを加えているようですが、
224
+
225
+ // もし純粋にぼかし効果だけをかけたい場合は、uvをそのまま使うといいでしょう
226
+
227
+ // frostUV = uv;
228
+
229
+
230
+
231
+ int kernelN = getKernelN();
232
+
233
+ float weightSum = 0.0;
234
+
235
+ float4 frost = 0.0;
236
+
237
+
238
+
239
+ // 注目しているピクセルを中心に、-kernelN ~ +kernelNの範囲をたたみ込む
240
+
241
+ for (int m = -kernelN; m <= kernelN; m++)
242
+
243
+ {
244
+
245
+ for (int n = -kernelN; n <= kernelN; n++)
246
+
247
+ {
248
+
249
+ float2 texelOffset = float2(n, m);
250
+
251
+ float weight = getWeight(texelOffset);
252
+
253
+ weightSum += weight;
254
+
255
+ frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy);
256
+
257
+ }
258
+
259
+ }
260
+
261
+
262
+
263
+ // 最後に、重みの総和で割る
264
+
265
+ frost /= weightSum;
266
+
267
+
268
+
269
+ half4 diffuse = tex2D(_MainTex, i.uv * _MainTex_ST.xy + _MainTex_ST.zw);
270
+
271
+
272
+
273
+ half alpha = _Color.a * diffuse.a;
274
+
275
+
276
+
277
+ return half4(frost.xyz + (diffuse.rgb * _Color.rgb * alpha), 1);
278
+
279
+ }
280
+
281
+
282
+
283
+ ENDCG
284
+
285
+ }
286
+
287
+ }
288
+
289
+
290
+
291
+ Fallback Off
292
+
293
+ }
294
+
295
+ ```
296
+
297
+
298
+
299
+ コード中のコメントでもちょっと言及しましたが、このままではσを大きくすると、毎フレーム描画では重さが気になるかもしれません。
300
+
301
+ 追加のレンダーテクスチャが必要になるためコードはちょっとややこしくなるでしょうが、[wgld.org | WebGL: gaussian フィルタ |](https://wgld.org/d/webgl/w057.html)で紹介されているような縦横二段掛け方式とか、[品質とパフォーマンスを両立する独自の技術|YEBIS 3](https://www.siliconstudio.co.jp/middleware/yebis/jp/features/processing_algorithms/)で解説されているような低解像度化してぼかし処理を行うテクニックを用いれば、もっと軽量化できるかもしれませんね。
302
+
303
+
304
+
305
+ σ = 1、ノイズ効果なし
306
+
307
+ ![1](ccbf520945b052ca6f82bca2fec63008.png)
308
+
309
+ σ = 1、ノイズ効果あり
310
+
311
+ ![F1](f97d3d3faa656cc5a8db3702cc13ff2d.png)
312
+
313
+ σ = 8、ノイズ効果なし
314
+
315
+ ![8](59f729096d3d242d5c5e10ae710db25b.png)
316
+
317
+ σ = 8、ノイズ効果あり
318
+
319
+ ![F8](edd8232aac26f3a693c9d12261d30d8d.png)
320
+
321
+
322
+
323
+ ### ループ展開版
324
+
325
+ 非展開版と比べると、シェーダーのインポートに時間がかかるかもしれません。
326
+
327
+ `KERNEL_N_MAX`をもっと小さくすると、対応可能なぼかし幅は小さくなってしまうものの、インポート速度・動作速度は改善するかと思います。
328
+
329
+ ```ShaderLab
330
+
331
+ Shader "Custom/Frost"
332
+
333
+ {
334
+
335
+ Properties
336
+
337
+ {
338
+
339
+ _Color("Color", Color) = (1, 1, 1, 1)
340
+
341
+
342
+
343
+ _MainTex("Diffuse", 2D) = "white" {}
344
+
345
+ _Noise("Noise", 2D) = "black" {}
346
+
347
+
348
+
349
+ _Range("Range", Float) = 0.025
350
+
351
+ _Sigma("Sigma", Range(0.01, 8.0)) = 1.0
352
+
353
+ }
354
+
355
+
356
+
357
+ SubShader
358
+
359
+ {
40
360
 
41
361
  Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" }
42
362
 
@@ -68,10 +388,6 @@
68
388
 
69
389
  sampler2D _Frost;
70
390
 
71
-
72
-
73
- // グラブテクスチャのテクセルサイズを追加
74
-
75
391
  float4 _Frost_TexelSize;
76
392
 
77
393
 
@@ -84,24 +400,12 @@
84
400
 
85
401
  half _Range;
86
402
 
87
- // half _Blur; // _Blurは廃止
88
-
89
- float _Sigma; // _Sigmaを追加
403
+ float _Sigma;
90
404
 
91
405
 
92
406
 
93
407
  // 重み計算用関数
94
408
 
95
- // ご質問者さんの重み関数 1/(2*π*σ)*exp(-(x^2+y^2)/(2*σ^2)) と同様ですが、係数は1に変更しました
96
-
97
- // 係数を付けて正規化した重みで有限の範囲をたたみ込むと、重みの総和が1より小さくなってしまうため
98
-
99
- // できあがったぼかし画像は、もとの画像より暗くなってしまうと思われます
100
-
101
- // そこで重み関数には係数を付けず、たたみ込みの際に重みの総和を求めて、最後にたたみ込み結果を
102
-
103
- // 重みの総和で割ることで明るさが維持されるようにしました
104
-
105
409
  inline float getWeight(float2 xy)
106
410
 
107
411
  {
@@ -114,20 +418,6 @@
114
418
 
115
419
  // カーネルサイズ計算用関数
116
420
 
117
- // たたみ込み範囲の片側幅...カーネルの一辺の長さ2*n+1のnをいくつにするかですが、さしあたり
118
-
119
- // 中心から最も遠い点(カーネルの角)における重みが十分小さくなる(0.0001を切る)大きさにしました
120
-
121
- // σ=1でn=4となり、サンプリング回数は(2*4+1)^2=81回になります(多分...)
122
-
123
- // σ=2で225回、σ=4で729回、σ=8で2601回...といった具合に、2乗のオーダーでサンプリング回数が
124
-
125
- // 増大しますので、あんまりσを大きくしすぎるのは控えた方がいいでしょう
126
-
127
- // ぼかしを縦方向と横方向の二段階に分けることで、サンプリング回数の増大を1乗のオーダーに
128
-
129
- // 抑えるテクニックもありますので、負荷を軽減したい場合は採用を検討してみてもいいでしょう
130
-
131
421
  inline int getKernelN()
132
422
 
133
423
  {
@@ -138,6 +428,12 @@
138
428
 
139
429
 
140
430
 
431
+ // 最大kernelN...(int)ceil(8 * sqrt(-ln(0.0001)))
432
+
433
+ #define KERNEL_N_MAX 25
434
+
435
+
436
+
141
437
  ENDCG
142
438
 
143
439
 
@@ -198,8 +494,6 @@
198
494
 
199
495
  {
200
496
 
201
- // rayは使用していないようですが、一応残しておきました
202
-
203
497
  i.ray = i.ray * (_ProjectionParams.z / i.ray.z);
204
498
 
205
499
 
@@ -218,50 +512,112 @@
218
512
 
219
513
  frostUV += uv;
220
514
 
221
-
222
-
223
- // 霜のついたガラスを表現するためサンプリング位置にノイズを加えているようですが、
224
-
225
- // もし純粋にぼかし効果だけをかけたい場合は、uvをそのまま使うといいでしょう
226
-
227
515
  // frostUV = uv;
228
516
 
229
517
 
230
518
 
231
519
  int kernelN = getKernelN();
232
520
 
521
+
522
+
523
+ // ループ展開版
524
+
525
+ float2 texelOffset = float2(0, 0);
526
+
527
+ float weight = getWeight(texelOffset);
528
+
233
- float weightSum = 0.0;
529
+ float weightSum = weight;
234
-
530
+
235
- float4 frost = 0.0;
531
+ float4 frost = weight * tex2D(_Frost, frostUV);
236
-
237
-
238
-
532
+
533
+
534
+
239
- // 注目しているピクセルを中心に、-kernelN +kernelNの範囲をたたみ込む
535
+ [unroll(KERNEL_N_MAX)]
240
-
536
+
241
- for (int j = -kernelN; j <= kernelN; j++)
537
+ for (int n = 0; n < kernelN; n++)
538
+
539
+ {
540
+
541
+ int x = n + 1;
542
+
543
+
544
+
545
+ texelOffset = float2(x, 0);
546
+
547
+ weight = getWeight(texelOffset);
548
+
549
+ weightSum += 2 * weight;
550
+
551
+ frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy);
552
+
553
+ texelOffset = float2(-x, 0);
554
+
555
+ frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy);
556
+
557
+ }
558
+
559
+
560
+
561
+ [unroll(KERNEL_N_MAX)]
562
+
563
+ for (int m = 0; m < kernelN; m++)
242
564
 
243
565
  {
244
566
 
567
+ int y = m + 1;
568
+
569
+
570
+
571
+ texelOffset = float2(0, y);
572
+
573
+ weight = getWeight(texelOffset);
574
+
575
+ weightSum += 2 * weight;
576
+
577
+ frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy);
578
+
579
+ texelOffset = float2(0, -y);
580
+
581
+ frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy);
582
+
583
+
584
+
585
+ [unroll(KERNEL_N_MAX)]
586
+
245
- for (int i = -kernelN; i <= kernelN; i++)
587
+ for (int n = 0; n < kernelN; n++)
246
588
 
247
589
  {
248
590
 
591
+ int x = n + 1;
592
+
593
+
594
+
249
- float2 texelOffset = float2(i, j);
595
+ texelOffset = float2(x, y);
250
-
596
+
251
- float weight = getWeight(texelOffset);
597
+ weight = getWeight(texelOffset);
252
-
598
+
253
- weightSum += weight;
599
+ weightSum += 4 * weight;
254
600
 
255
601
  frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy);
256
602
 
603
+ texelOffset = float2(-x, y);
604
+
605
+ frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy);
606
+
607
+ texelOffset = float2(x, -y);
608
+
609
+ frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy);
610
+
611
+ texelOffset = float2(-x, -y);
612
+
613
+ frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy);
614
+
257
615
  }
258
616
 
259
617
  }
260
618
 
261
619
 
262
620
 
263
- // 最後に、重みの総和で割る
264
-
265
621
  frost /= weightSum;
266
622
 
267
623
 
@@ -293,27 +649,3 @@
293
649
  }
294
650
 
295
651
  ```
296
-
297
-
298
-
299
- コード中のコメントでもちょっと言及しましたが、このままではσを大きくすると、毎フレーム描画では重さが気になるかもしれません。
300
-
301
- 追加のレンダーテクスチャが必要になるためコードはちょっとややこしくなるでしょうが、[wgld.org | WebGL: gaussian フィルタ |](https://wgld.org/d/webgl/w057.html)で紹介されているような縦横二段掛け方式とか、[品質とパフォーマンスを両立する独自の技術|YEBIS 3](https://www.siliconstudio.co.jp/middleware/yebis/jp/features/processing_algorithms/)で解説されているような低解像度化してぼかし処理を行うテクニックを用いれば、もっと軽量化できるかもしれませんね。
302
-
303
-
304
-
305
- σ = 1、ノイズ効果なし
306
-
307
- ![1](ccbf520945b052ca6f82bca2fec63008.png)
308
-
309
- σ = 1、ノイズ効果あり
310
-
311
- ![F1](f97d3d3faa656cc5a8db3702cc13ff2d.png)
312
-
313
- σ = 8、ノイズ効果なし
314
-
315
- ![8](59f729096d3d242d5c5e10ae710db25b.png)
316
-
317
- σ = 8、ノイズ効果あり
318
-
319
- ![F8](edd8232aac26f3a693c9d12261d30d8d.png)