回答編集履歴
3
σとBlurの対応図を追記
answer
CHANGED
@@ -351,4 +351,8 @@
|
|
351
351
|
ノイズ効果と組み合わせれば、粗が目立たなくなってマシなように思います。
|
352
352
|
|
353
353
|

|
354
|
-

|
354
|
+

|
355
|
+
|
356
|
+
#_Blurとσの対応
|
357
|
+

|
358
|
+

|
2
軽量化について検討
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
|
+

|
344
|
+
|
345
|
+
FrostCompositionでは、Blurに応じて下図のような比率で各ぼかし画像を混合しました。
|
346
|
+
|
347
|
+

|
348
|
+
|
349
|
+
旧Frostと比較するとプロファイラーで見た際のレンダリング負荷(黄緑色領域)が削減されましたが、新Frostは低解像度のぼかし画像を引き伸ばしたものですので、よく見るとピクセル由来のブロック感が出ています。
|
350
|
+
σに応じて広い範囲をきっちりサンプリングしていた旧Frostに対し、新Frostは複数のぼかし画像を混ぜているだけですので、画質面では劣ってしまいます。一応ガウス分布っぽい広がりのぼかしにはしたつもりですが、「ガウシアンブラー」だと言い張るにはちょっと怪しいかもしれません...
|
2
|
-
|
351
|
+
ノイズ効果と組み合わせれば、粗が目立たなくなってマシなように思います。
|
352
|
+
|
353
|
+

|
354
|
+

|
1
操作ミスによる投稿
answer
CHANGED
@@ -1,1 +1,2 @@
|
|
1
|
+
すみません、軽量化について検討していましたが、操作ミスにより誤投稿してしまいました。
|
1
|
-
|
2
|
+
調査に進展がありましたら、追って編集いたします。
|