回答編集履歴
1
変数名変更、ループ展開版を追記
answer
CHANGED
@@ -12,12 +12,172 @@
|
|
12
12
|
|
13
13
|
_Range("Range", Float) = 0.025
|
14
14
|
// _Blur("Blur", Float) = 0.005 // Blurは廃止、サンプリング位置ずらしはテクセルサイズに基づいた形に変更
|
15
|
-
|
15
|
+
_Sigma("Sigma", Range(0.01, 8.0)) = 1.0 // σを追加
|
16
16
|
}
|
17
17
|
|
18
18
|
SubShader
|
19
19
|
{
|
20
|
-
|
20
|
+
// スカイボックスも含めてグラブするため、キューをTransparentに変更
|
21
|
+
Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" }
|
22
|
+
|
23
|
+
Cull Off
|
24
|
+
|
25
|
+
GrabPass{ "_Frost" }
|
26
|
+
|
27
|
+
CGINCLUDE
|
28
|
+
#include "UnityCG.cginc"
|
29
|
+
|
30
|
+
half4 _Color;
|
31
|
+
|
32
|
+
sampler2D _MainTex;
|
33
|
+
float4 _MainTex_ST;
|
34
|
+
|
35
|
+
sampler2D _Frost;
|
36
|
+
|
37
|
+
// グラブテクスチャのテクセルサイズを追加
|
38
|
+
float4 _Frost_TexelSize;
|
39
|
+
|
40
|
+
sampler2D _Noise;
|
41
|
+
float4 _Noise_ST;
|
42
|
+
|
43
|
+
half _Range;
|
44
|
+
// half _Blur; // _Blurは廃止
|
45
|
+
float _Sigma; // _Sigmaを追加
|
46
|
+
|
47
|
+
// 重み計算用関数
|
48
|
+
// ご質問者さんの重み関数 1/(2*π*σ)*exp(-(x^2+y^2)/(2*σ^2)) と同様ですが、係数は1に変更しました
|
49
|
+
// 係数を付けて正規化した重みで有限の範囲をたたみ込むと、重みの総和が1より小さくなってしまうため
|
50
|
+
// できあがったぼかし画像は、もとの画像より暗くなってしまうと思われます
|
51
|
+
// そこで重み関数には係数を付けず、たたみ込みの際に重みの総和を求めて、最後にたたみ込み結果を
|
52
|
+
// 重みの総和で割ることで明るさが維持されるようにしました
|
53
|
+
inline float getWeight(float2 xy)
|
54
|
+
{
|
55
|
+
return exp(-dot(xy, xy) / (2.0 * _Sigma * _Sigma));
|
56
|
+
}
|
57
|
+
|
58
|
+
// カーネルサイズ計算用関数
|
59
|
+
// たたみ込み範囲の片側幅...カーネルの一辺の長さ2*n+1のnをいくつにするかですが、さしあたり
|
60
|
+
// 中心から最も遠い点(カーネルの角)における重みが十分小さくなる(0.0001を切る)大きさにしました
|
61
|
+
// σ=1でn=4となり、サンプリング回数は(2*4+1)^2=81回になります(多分...)
|
62
|
+
// σ=2で225回、σ=4で729回、σ=8で2601回...といった具合に、2乗のオーダーでサンプリング回数が
|
63
|
+
// 増大しますので、あんまりσを大きくしすぎるのは控えた方がいいでしょう
|
64
|
+
// ぼかしを縦方向と横方向の二段階に分けることで、サンプリング回数の増大を1乗のオーダーに
|
65
|
+
// 抑えるテクニックもありますので、負荷を軽減したい場合は採用を検討してみてもいいでしょう
|
66
|
+
inline int getKernelN()
|
67
|
+
{
|
68
|
+
return (int)ceil(_Sigma * sqrt(-log(0.0001)));
|
69
|
+
}
|
70
|
+
|
71
|
+
ENDCG
|
72
|
+
|
73
|
+
Pass
|
74
|
+
{
|
75
|
+
CGPROGRAM
|
76
|
+
#pragma target 3.0
|
77
|
+
#pragma vertex vert
|
78
|
+
#pragma fragment frag
|
79
|
+
|
80
|
+
struct v2f
|
81
|
+
{
|
82
|
+
float4 pos : SV_POSITION;
|
83
|
+
|
84
|
+
float3 uv : TEXCOORD;
|
85
|
+
float4 screenPos : TEXCOORD1;
|
86
|
+
float3 ray : TEXCOORD2;
|
87
|
+
};
|
88
|
+
|
89
|
+
v2f vert(appdata_full v)
|
90
|
+
{
|
91
|
+
v2f o;
|
92
|
+
o.pos = UnityObjectToClipPos(v.vertex);
|
93
|
+
o.uv = v.texcoord;
|
94
|
+
o.screenPos = ComputeGrabScreenPos(o.pos);
|
95
|
+
o.ray = UnityObjectToViewPos(v.vertex).xyz * float3(-1, -1, 1);
|
96
|
+
return o;
|
97
|
+
}
|
98
|
+
|
99
|
+
half4 frag(v2f i) : SV_Target
|
100
|
+
{
|
101
|
+
// rayは使用していないようですが、一応残しておきました
|
102
|
+
i.ray = i.ray * (_ProjectionParams.z / i.ray.z);
|
103
|
+
|
104
|
+
float2 uv = i.screenPos.xy / i.screenPos.w;
|
105
|
+
|
106
|
+
float2 frostUV = tex2D(_Noise, i.uv * _Noise_ST.xy + _Noise_ST.zw).xy;
|
107
|
+
|
108
|
+
frostUV -= 0.5;
|
109
|
+
frostUV *= _Range;
|
110
|
+
frostUV += uv;
|
111
|
+
|
112
|
+
// 霜のついたガラスを表現するためサンプリング位置にノイズを加えているようですが、
|
113
|
+
// もし純粋にぼかし効果だけをかけたい場合は、uvをそのまま使うといいでしょう
|
114
|
+
// frostUV = uv;
|
115
|
+
|
116
|
+
int kernelN = getKernelN();
|
117
|
+
float weightSum = 0.0;
|
118
|
+
float4 frost = 0.0;
|
119
|
+
|
120
|
+
// 注目しているピクセルを中心に、-kernelN ~ +kernelNの範囲をたたみ込む
|
121
|
+
for (int m = -kernelN; m <= kernelN; m++)
|
122
|
+
{
|
123
|
+
for (int n = -kernelN; n <= kernelN; n++)
|
124
|
+
{
|
125
|
+
float2 texelOffset = float2(n, m);
|
126
|
+
float weight = getWeight(texelOffset);
|
127
|
+
weightSum += weight;
|
128
|
+
frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy);
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
132
|
+
// 最後に、重みの総和で割る
|
133
|
+
frost /= weightSum;
|
134
|
+
|
135
|
+
half4 diffuse = tex2D(_MainTex, i.uv * _MainTex_ST.xy + _MainTex_ST.zw);
|
136
|
+
|
137
|
+
half alpha = _Color.a * diffuse.a;
|
138
|
+
|
139
|
+
return half4(frost.xyz + (diffuse.rgb * _Color.rgb * alpha), 1);
|
140
|
+
}
|
141
|
+
|
142
|
+
ENDCG
|
143
|
+
}
|
144
|
+
}
|
145
|
+
|
146
|
+
Fallback Off
|
147
|
+
}
|
148
|
+
```
|
149
|
+
|
150
|
+
コード中のコメントでもちょっと言及しましたが、このままではσを大きくすると、毎フレーム描画では重さが気になるかもしれません。
|
151
|
+
追加のレンダーテクスチャが必要になるためコードはちょっとややこしくなるでしょうが、[wgld.org | WebGL: gaussian フィルタ |](https://wgld.org/d/webgl/w057.html)で紹介されているような縦横二段掛け方式とか、[品質とパフォーマンスを両立する独自の技術|YEBIS 3](https://www.siliconstudio.co.jp/middleware/yebis/jp/features/processing_algorithms/)で解説されているような低解像度化してぼかし処理を行うテクニックを用いれば、もっと軽量化できるかもしれませんね。
|
152
|
+
|
153
|
+
σ = 1、ノイズ効果なし
|
154
|
+

|
155
|
+
σ = 1、ノイズ効果あり
|
156
|
+

|
157
|
+
σ = 8、ノイズ効果なし
|
158
|
+

|
159
|
+
σ = 8、ノイズ効果あり
|
160
|
+

|
161
|
+
|
162
|
+
### ループ展開版
|
163
|
+
非展開版と比べると、シェーダーのインポートに時間がかかるかもしれません。
|
164
|
+
`KERNEL_N_MAX`をもっと小さくすると、対応可能なぼかし幅は小さくなってしまうものの、インポート速度・動作速度は改善するかと思います。
|
165
|
+
```ShaderLab
|
166
|
+
Shader "Custom/Frost"
|
167
|
+
{
|
168
|
+
Properties
|
169
|
+
{
|
170
|
+
_Color("Color", Color) = (1, 1, 1, 1)
|
171
|
+
|
172
|
+
_MainTex("Diffuse", 2D) = "white" {}
|
173
|
+
_Noise("Noise", 2D) = "black" {}
|
174
|
+
|
175
|
+
_Range("Range", Float) = 0.025
|
176
|
+
_Sigma("Sigma", Range(0.01, 8.0)) = 1.0
|
177
|
+
}
|
178
|
+
|
179
|
+
SubShader
|
180
|
+
{
|
21
181
|
Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" }
|
22
182
|
|
23
183
|
Cull Off
|
@@ -33,41 +193,29 @@
|
|
33
193
|
float4 _MainTex_ST;
|
34
194
|
|
35
195
|
sampler2D _Frost;
|
36
|
-
|
37
|
-
// グラブテクスチャのテクセルサイズを追加
|
38
196
|
float4 _Frost_TexelSize;
|
39
197
|
|
40
198
|
sampler2D _Noise;
|
41
199
|
float4 _Noise_ST;
|
42
200
|
|
43
201
|
half _Range;
|
44
|
-
// half _Blur; // _Blurは廃止
|
45
|
-
float _Sigma;
|
202
|
+
float _Sigma;
|
46
203
|
|
47
204
|
// 重み計算用関数
|
48
|
-
// ご質問者さんの重み関数 1/(2*π*σ)*exp(-(x^2+y^2)/(2*σ^2)) と同様ですが、係数は1に変更しました
|
49
|
-
// 係数を付けて正規化した重みで有限の範囲をたたみ込むと、重みの総和が1より小さくなってしまうため
|
50
|
-
// できあがったぼかし画像は、もとの画像より暗くなってしまうと思われます
|
51
|
-
// そこで重み関数には係数を付けず、たたみ込みの際に重みの総和を求めて、最後にたたみ込み結果を
|
52
|
-
// 重みの総和で割ることで明るさが維持されるようにしました
|
53
205
|
inline float getWeight(float2 xy)
|
54
206
|
{
|
55
207
|
return exp(-dot(xy, xy) / (2.0 * _Sigma * _Sigma));
|
56
208
|
}
|
57
209
|
|
58
210
|
// カーネルサイズ計算用関数
|
59
|
-
// たたみ込み範囲の片側幅...カーネルの一辺の長さ2*n+1のnをいくつにするかですが、さしあたり
|
60
|
-
// 中心から最も遠い点(カーネルの角)における重みが十分小さくなる(0.0001を切る)大きさにしました
|
61
|
-
// σ=1でn=4となり、サンプリング回数は(2*4+1)^2=81回になります(多分...)
|
62
|
-
// σ=2で225回、σ=4で729回、σ=8で2601回...といった具合に、2乗のオーダーでサンプリング回数が
|
63
|
-
// 増大しますので、あんまりσを大きくしすぎるのは控えた方がいいでしょう
|
64
|
-
// ぼかしを縦方向と横方向の二段階に分けることで、サンプリング回数の増大を1乗のオーダーに
|
65
|
-
// 抑えるテクニックもありますので、負荷を軽減したい場合は採用を検討してみてもいいでしょう
|
66
211
|
inline int getKernelN()
|
67
212
|
{
|
68
213
|
return (int)ceil(_Sigma * sqrt(-log(0.0001)));
|
69
214
|
}
|
70
215
|
|
216
|
+
// 最大kernelN...(int)ceil(8 * sqrt(-ln(0.0001)))
|
217
|
+
#define KERNEL_N_MAX 25
|
218
|
+
|
71
219
|
ENDCG
|
72
220
|
|
73
221
|
Pass
|
@@ -98,7 +246,6 @@
|
|
98
246
|
|
99
247
|
half4 frag(v2f i) : SV_Target
|
100
248
|
{
|
101
|
-
// rayは使用していないようですが、一応残しておきました
|
102
249
|
i.ray = i.ray * (_ProjectionParams.z / i.ray.z);
|
103
250
|
|
104
251
|
float2 uv = i.screenPos.xy / i.screenPos.w;
|
@@ -108,28 +255,59 @@
|
|
108
255
|
frostUV -= 0.5;
|
109
256
|
frostUV *= _Range;
|
110
257
|
frostUV += uv;
|
111
|
-
|
112
|
-
// 霜のついたガラスを表現するためサンプリング位置にノイズを加えているようですが、
|
113
|
-
// もし純粋にぼかし効果だけをかけたい場合は、uvをそのまま使うといいでしょう
|
114
258
|
// frostUV = uv;
|
115
259
|
|
116
260
|
int kernelN = getKernelN();
|
117
|
-
float weightSum = 0.0;
|
118
|
-
float4 frost = 0.0;
|
119
261
|
|
262
|
+
// ループ展開版
|
263
|
+
float2 texelOffset = float2(0, 0);
|
264
|
+
float weight = getWeight(texelOffset);
|
265
|
+
float weightSum = weight;
|
120
|
-
|
266
|
+
float4 frost = weight * tex2D(_Frost, frostUV);
|
267
|
+
|
268
|
+
[unroll(KERNEL_N_MAX)]
|
121
|
-
|
269
|
+
for (int n = 0; n < kernelN; n++)
|
270
|
+
{
|
271
|
+
int x = n + 1;
|
272
|
+
|
273
|
+
texelOffset = float2(x, 0);
|
274
|
+
weight = getWeight(texelOffset);
|
275
|
+
weightSum += 2 * weight;
|
276
|
+
frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy);
|
277
|
+
texelOffset = float2(-x, 0);
|
278
|
+
frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy);
|
279
|
+
}
|
280
|
+
|
281
|
+
[unroll(KERNEL_N_MAX)]
|
282
|
+
for (int m = 0; m < kernelN; m++)
|
122
283
|
{
|
284
|
+
int y = m + 1;
|
285
|
+
|
286
|
+
texelOffset = float2(0, y);
|
287
|
+
weight = getWeight(texelOffset);
|
288
|
+
weightSum += 2 * weight;
|
289
|
+
frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy);
|
290
|
+
texelOffset = float2(0, -y);
|
291
|
+
frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy);
|
292
|
+
|
293
|
+
[unroll(KERNEL_N_MAX)]
|
123
|
-
for (int
|
294
|
+
for (int n = 0; n < kernelN; n++)
|
124
295
|
{
|
296
|
+
int x = n + 1;
|
297
|
+
|
125
|
-
|
298
|
+
texelOffset = float2(x, y);
|
126
|
-
|
299
|
+
weight = getWeight(texelOffset);
|
127
|
-
weightSum += weight;
|
300
|
+
weightSum += 4 * weight;
|
128
301
|
frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy);
|
302
|
+
texelOffset = float2(-x, y);
|
303
|
+
frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy);
|
304
|
+
texelOffset = float2(x, -y);
|
305
|
+
frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy);
|
306
|
+
texelOffset = float2(-x, -y);
|
307
|
+
frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy);
|
129
308
|
}
|
130
309
|
}
|
131
310
|
|
132
|
-
// 最後に、重みの総和で割る
|
133
311
|
frost /= weightSum;
|
134
312
|
|
135
313
|
half4 diffuse = tex2D(_MainTex, i.uv * _MainTex_ST.xy + _MainTex_ST.zw);
|
@@ -145,16 +323,4 @@
|
|
145
323
|
|
146
324
|
Fallback Off
|
147
325
|
}
|
148
|
-
```
|
149
|
-
|
150
|
-
コード中のコメントでもちょっと言及しましたが、このままではσを大きくすると、毎フレーム描画では重さが気になるかもしれません。
|
151
|
-
追加のレンダーテクスチャが必要になるためコードはちょっとややこしくなるでしょうが、[wgld.org | WebGL: gaussian フィルタ |](https://wgld.org/d/webgl/w057.html)で紹介されているような縦横二段掛け方式とか、[品質とパフォーマンスを両立する独自の技術|YEBIS 3](https://www.siliconstudio.co.jp/middleware/yebis/jp/features/processing_algorithms/)で解説されているような低解像度化してぼかし処理を行うテクニックを用いれば、もっと軽量化できるかもしれませんね。
|
152
|
-
|
153
|
-
σ = 1、ノイズ効果なし
|
154
|
-

|
155
|
-
σ = 1、ノイズ効果あり
|
156
|
-

|
157
|
-
σ = 8、ノイズ効果なし
|
158
|
-

|
159
|
-
σ = 8、ノイズ効果あり
|
160
|
-

|
326
|
+
```
|