質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.51%
Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

2回答

2294閲覧

ガウシアンブラーの実装

shiroshiro_me

総合スコア19

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

1クリップ

投稿2018/10/01 03:43

f(x,y)=1/(2πσ)exp(-(x^2+y^2)/(2σ^2))

unityのshaderで上記の式を基にしたガウシアンブラーを実装したいと考えています。フィルタサイズは気にしていませんが、σの値をプロパティで変えることで、ブラーの強さを調節するプログラムにしたいと考えています。
しかし、フィルタサイズは3×3で、σの値が不可変のshaderプログラムしか作成できませんでした。どのようにすればσの値が可変な上記の式を実装できるか分からず質問させていただきました。下記は現在のプログラムです。

shader

1Shader "Custom/Frost" 2{ 3 Properties 4 { 5 _Color("Color", Color) = (1, 1, 1, 1) 6 7 _MainTex("Diffuse", 2D) = "white" {} 8 _Noise("Noise", 2D) = "black" {} 9 10 _Range("Range", Float) = 0.025 11 _Blur("Blur", Float) = 0.005 12 } 13 14 SubShader 15 { 16 Cull Off 17 18 GrabPass{ "_Frost" } 19 20 CGINCLUDE 21 #include "UnityCG.cginc" 22 23 half4 _Color; 24 25 sampler2D _MainTex; 26 float4 _MainTex_ST; 27 28 sampler2D _Frost; 29 sampler2D _Noise; 30 float4 _Noise_ST; 31 32 half _Range; 33 half _Blur; 34 35 ENDCG 36 37 Pass 38 { 39 CGPROGRAM 40 #pragma target 3.0 41 #pragma vertex vert 42 #pragma fragment frag 43 44 struct v2f 45 { 46 float4 pos : SV_POSITION; 47 48 float3 uv : TEXCOORD; 49 float4 screenPos : TEXCOORD1; 50 float3 ray : TEXCOORD2; 51 }; 52 53 v2f vert(appdata_full v) 54 { 55 v2f o; 56 o.pos = UnityObjectToClipPos(v.vertex); 57 o.uv = v.texcoord; 58 o.screenPos = ComputeScreenPos(o.pos); 59 o.ray = UnityObjectToViewPos(v.vertex).xyz * float3(-1, -1, 1); 60 return o; 61 } 62 63 half4 frag(v2f i) : SV_Target 64 { 65 i.ray = i.ray * (_ProjectionParams.z / i.ray.z); 66 float2 uv = i.screenPos.xy / i.screenPos.w; 67 68 float2 frostUV = tex2D(_Noise, i.uv * _Noise_ST.xy + _Noise_ST.zw).xy; 69 70 frostUV -= 0.5; 71 frostUV *= _Range; 72 frostUV += uv; 73 74 half4 frost = 4 * tex2D(_Frost, frostUV); 75 frost += tex2D(_Frost, frostUV + float2(_Blur, _Blur)); 76 frost += 2 * tex2D(_Frost, frostUV + float2(_Blur, 0)); 77 frost += tex2D(_Frost, frostUV + float2(_Blur, -_Blur)); 78 frost += 2 * tex2D(_Frost, frostUV + float2(0, _Blur)); 79 frost += 2 * tex2D(_Frost, frostUV + float2(0, -_Blur)); 80 frost += tex2D(_Frost, frostUV + float2(-_Blur, _Blur)); 81 frost += 2 * tex2D(_Frost, frostUV + float2(-_Blur, 0)); 82 frost += tex2D(_Frost, frostUV + float2(-_Blur, -_Blur)); 83 frost *= 0.0625; 84 85 half4 diffuse = tex2D(_MainTex, i.uv * _MainTex_ST.xy + _MainTex_ST.zw); 86 87 half alpha = _Color.a * diffuse.a; 88 89 return half4(frost.xyz + (diffuse.rgb * _Color.rgb * alpha), 1); 90 } 91 92 ENDCG 93 } 94 } 95 96 Fallback Off 97} 98

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

こんな感じでやってみましたが、ご参考になりますでしょうか?

ShaderLab

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

コード中のコメントでもちょっと言及しましたが、このままではσを大きくすると、毎フレーム描画では重さが気になるかもしれません。
追加のレンダーテクスチャが必要になるためコードはちょっとややこしくなるでしょうが、wgld.org | WebGL: gaussian フィルタ |で紹介されているような縦横二段掛け方式とか、品質とパフォーマンスを両立する独自の技術|YEBIS 3で解説されているような低解像度化してぼかし処理を行うテクニックを用いれば、もっと軽量化できるかもしれませんね。

σ = 1、ノイズ効果なし
1
σ = 1、ノイズ効果あり
F1
σ = 8、ノイズ効果なし
8
σ = 8、ノイズ効果あり
F8

ループ展開版

非展開版と比べると、シェーダーのインポートに時間がかかるかもしれません。
KERNEL_N_MAXをもっと小さくすると、対応可能なぼかし幅は小さくなってしまうものの、インポート速度・動作速度は改善するかと思います。

ShaderLab

1Shader "Custom/Frost" 2{ 3 Properties 4 { 5 _Color("Color", Color) = (1, 1, 1, 1) 6 7 _MainTex("Diffuse", 2D) = "white" {} 8 _Noise("Noise", 2D) = "black" {} 9 10 _Range("Range", Float) = 0.025 11 _Sigma("Sigma", Range(0.01, 8.0)) = 1.0 12 } 13 14 SubShader 15 { 16 Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" } 17 18 Cull Off 19 20 GrabPass{ "_Frost" } 21 22 CGINCLUDE 23 #include "UnityCG.cginc" 24 25 half4 _Color; 26 27 sampler2D _MainTex; 28 float4 _MainTex_ST; 29 30 sampler2D _Frost; 31 float4 _Frost_TexelSize; 32 33 sampler2D _Noise; 34 float4 _Noise_ST; 35 36 half _Range; 37 float _Sigma; 38 39 // 重み計算用関数 40 inline float getWeight(float2 xy) 41 { 42 return exp(-dot(xy, xy) / (2.0 * _Sigma * _Sigma)); 43 } 44 45 // カーネルサイズ計算用関数 46 inline int getKernelN() 47 { 48 return (int)ceil(_Sigma * sqrt(-log(0.0001))); 49 } 50 51 // 最大kernelN...(int)ceil(8 * sqrt(-ln(0.0001))) 52 #define KERNEL_N_MAX 25 53 54 ENDCG 55 56 Pass 57 { 58 CGPROGRAM 59 #pragma target 3.0 60 #pragma vertex vert 61 #pragma fragment frag 62 63 struct v2f 64 { 65 float4 pos : SV_POSITION; 66 67 float3 uv : TEXCOORD; 68 float4 screenPos : TEXCOORD1; 69 float3 ray : TEXCOORD2; 70 }; 71 72 v2f vert(appdata_full v) 73 { 74 v2f o; 75 o.pos = UnityObjectToClipPos(v.vertex); 76 o.uv = v.texcoord; 77 o.screenPos = ComputeGrabScreenPos(o.pos); 78 o.ray = UnityObjectToViewPos(v.vertex).xyz * float3(-1, -1, 1); 79 return o; 80 } 81 82 half4 frag(v2f i) : SV_Target 83 { 84 i.ray = i.ray * (_ProjectionParams.z / i.ray.z); 85 86 float2 uv = i.screenPos.xy / i.screenPos.w; 87 88 float2 frostUV = tex2D(_Noise, i.uv * _Noise_ST.xy + _Noise_ST.zw).xy; 89 90 frostUV -= 0.5; 91 frostUV *= _Range; 92 frostUV += uv; 93 // frostUV = uv; 94 95 int kernelN = getKernelN(); 96 97 // ループ展開版 98 float2 texelOffset = float2(0, 0); 99 float weight = getWeight(texelOffset); 100 float weightSum = weight; 101 float4 frost = weight * tex2D(_Frost, frostUV); 102 103 [unroll(KERNEL_N_MAX)] 104 for (int n = 0; n < kernelN; n++) 105 { 106 int x = n + 1; 107 108 texelOffset = float2(x, 0); 109 weight = getWeight(texelOffset); 110 weightSum += 2 * weight; 111 frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy); 112 texelOffset = float2(-x, 0); 113 frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy); 114 } 115 116 [unroll(KERNEL_N_MAX)] 117 for (int m = 0; m < kernelN; m++) 118 { 119 int y = m + 1; 120 121 texelOffset = float2(0, y); 122 weight = getWeight(texelOffset); 123 weightSum += 2 * weight; 124 frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy); 125 texelOffset = float2(0, -y); 126 frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy); 127 128 [unroll(KERNEL_N_MAX)] 129 for (int n = 0; n < kernelN; n++) 130 { 131 int x = n + 1; 132 133 texelOffset = float2(x, y); 134 weight = getWeight(texelOffset); 135 weightSum += 4 * weight; 136 frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy); 137 texelOffset = float2(-x, y); 138 frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy); 139 texelOffset = float2(x, -y); 140 frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy); 141 texelOffset = float2(-x, -y); 142 frost += weight * tex2D(_Frost, frostUV + texelOffset * _Frost_TexelSize.xy); 143 } 144 } 145 146 frost /= weightSum; 147 148 half4 diffuse = tex2D(_MainTex, i.uv * _MainTex_ST.xy + _MainTex_ST.zw); 149 150 half alpha = _Color.a * diffuse.a; 151 152 return half4(frost.xyz + (diffuse.rgb * _Color.rgb * alpha), 1); 153 } 154 155 ENDCG 156 } 157 } 158 159 Fallback Off 160}

投稿2018/10/01 16:14

編集2018/10/02 21:55
Bongo

総合スコア10807

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

shiroshiro_me

2018/10/02 04:57

詳しい回答ありがとうございます。 一度回答者様が書いてくださったshaderのマテリアルを適用してみると再生前のgameビューではブラーが適用されていましたが、プロジェクトを再生してみるとブラーがなくなってしまいました。colorのalphaを最大にすれば再生しても可視化できています。 なぜブラーがなくなるのかお分かりになりますでしょうか?
Bongo

2018/10/02 06:09

ColorのAlphaが半透明状態だと、オブジェクトを通して見た背後の景色がぼかしのかかっていないくっきりとした映像になってしまうということでしょうか? Alphaはぼかしの効きには関与していないはずで、原因はすぐには思いつかないのですが、Alphaを変えてみるというのはまだ試していないため、帰宅後にちょっと調べてみようと思います。 参考としてお伺いしたいのですが、Unityのウィンドウのタイトルバーに、現在のターゲット環境が表示されているかと思いますが(「PC, Mac & Linux Standalone」や「Android」とか、「<DX11>」、「<Metal>」だのといった内容)、それはどうなっていますでしょうか? 経験的に、シェーダープログラムは普通のスクリプトよりも環境による動作のばらつきが出やすい気がしますので... その他、マテリアルの挙動について気付いた点がありましたら、ぜひコメントいただけますと参考になるかと思います。
shiroshiro_me

2018/10/02 06:28

プロジェクト再生前であればぼかしがきいているのですが、再生した瞬間にくっきりするようになってしまいます。ノイズをONにしてみると再生後にはノイズのみが残ったゆがんだ状態になります。 タイトルバーには「PC, Mac & Linux Standalone*<DX11>」となっています。バージョンは「unity 2018.2.1f1 personal(64bit)」です。
Bongo

2018/10/02 06:55

症状から想像するに、今のところ怪しいのはforループ部分でしょうかね... コンパイル時に回数の確定しないループは、シェーダープログラミングではあまり好まれない方法です。 過去のご質問を拝見するに、いろいろなサイトのシェーダー作例を調査なさったご様子ですが、forループを使わずにずらずらと反復操作を直書きしているコードをたびたび見かけたことかと思います。見た目も悪くて柔軟性に欠けますが、その方が古い環境にも対応でき、負荷も小さかったりして、利点も大きいようです。 ちょっとお試し願いたいのですが、シェーダーコードの中ほどにある「#pragma target 3.0」を「#pragma target 4.0」に変えると挙動に変化はありますでしょうか?
shiroshiro_me

2018/10/02 07:09

「#pragma target 4.0」また4.5, 5.0でも試してみましたが、表示にこれといった変化はありませんでした。 繰り返し処理ができないとなると、やはり一つずつ処理を書いていくしかないのでしょうか。
Bongo

2018/10/02 07:31

だめでしたか...ひとまずここから先は帰宅してから調べてみることにします。すみません... もし不定回数のループがだめでも、固定回数のループならコンパイラが自動的に直書きの形に展開してくれるかもしれません。つまり、常にカーネルサイズを最大にしてしまうのです。 たとえσが小さくとも結果に寄与しない遠くのピクセルまでサンプリングしてしまったり、コード内で決めた最大サイズより大きい範囲が必要なσの場合でも強制的にサンプリング範囲が制限されてしまったり...と非効率・不自由感はありますが、まったく動作しないよりはマシかもしれません。 とはいえ、非再生状態やAlpha完全不透明なら一応動くというのでしたら、ループに対応していないなんてことは考えづらく思えてきました。何か見落としているかもしれません。後ほどちゃんと見直してみます。
Bongo

2018/10/02 22:11 編集

Windows 10 バージョン1803、Unity 2018.2.10f1、PC, Mac & Linux Standalone <DX11>でやってみたものの、結局実行時にぼかし効果が消失する現象は再現できませんでした(ColorのAlphaを変えても、プレイモード・非プレイモードに関わらずぼかしの効きには影響しない)... 最初に投稿したコードではループ変数にiとjを使っていましたが、不確かですが変数名がフラグメントシェーダー入力変数のiと名前かぶりしていたのがまずかったかもしれません。念のため、ループ変数の名前をnとmに変えてみました。 また、ループ部分を展開するよう指示したバージョンも試しに作ってみました。一応全文を投稿しましたが、変更箇所は主にループ周辺のみです。 ループが最大回数で打ち切られるかもしれなくなったため、たたみ込みは注目ピクセルに近い部分から外側に向かって行い、ループが打ち切られても注目ピクセルがカーネル中心に位置するようにしてみました。
shiroshiro_me

2018/10/03 04:48

ループ展開版も試してみましたが、やはり再生時にブラーが消失してしまいました。 実は現在「FOVE0」というHMDを用いるコンテンツの開発を行っており、もしやと思いFOVEの公式サイトで与えられたFOVE専用のカメラのprefabをoffにし、通常のカメラで再生してみた結果gameビューでブラーの消失は観測されませんでした。 なので原因はHMDおよびそれ専用の特殊なカメラにありそうです。こちらで色々探ってみます。 また、このマテリアルを適用したオブジェクトを複数用いる予定のため、すべてオンにしてみた結果、フレームレートがおよそ14fpsに大幅に低下してしまいました。処理を軽くする方法も探りたいと考えています。
Bongo

2018/10/04 21:10

すでにご覧になっているかもしれませんが、同様のケースがないか検索してみたところ、https://support.getfove.com/hc/en-us/community/posts/360014764273-Shader-not-working-Unity-3D- に投稿されている症状がご質問者さんのものとよく似ているように思います。 投稿者の方が使おうとしたシェーダーもグラブパスで描画内容をキャプチャーし、それにぼかしをかけるもののようで、今回のトラブルと同じ原因なのかもしれません。 グラブパスがあるのが悪いのか、複数回テクスチャサンプリングを行っているのが悪いのか、あるいは他の原因なのかは、すみませんがFOVEを持っていないため、これについて調査するのは困難そうです。 Frost軽量化バージョンでは、グラブパスではなくコマンドバッファ挿入で画面キャプチャーを行っていますので、もしグラブパスが原因なら、こちらであればぼかしをかけられるかも知れません。 残念ながらこの投稿に対する解決策は寄せられていないようですが、回答によるとUnity 2018.xにおいてレンダリング関連のトラブル報告があるようで、もしかするとUnityをダウングレードすれば動くようになるかも...? FOVEの問題となるとあまりお力になれそうもなく、せめてと思い軽量化する案について別回答に投稿しました。 方針としては、こちらの回答で言及した「縦横二段掛け」と「低解像度化」に基づいています。また、シーン内のすべての曇りガラスマテリアルは、不透明オブジェクトレンダリング後に一回だけ生成されるぼかしテクスチャを合成するだけになりましたので、オブジェクトが多数存在する場合のパフォーマンス低下も小さくできるかと思います。
guest

0

軽量化について

文字数の限界につき別回答ですがご容赦ください。
下記のようなアプローチを試してみました。

  • シェーダーはぼかし処理を担当するFrostBlurと、ぼかし画像合成を担当するFrostCompositionに分ける。

FrostCompositionは旧Frostを改造、FrostBlurはイメージエフェクトシェーダーに近い作りになっている。

  • カメラが不透明オブジェクト・背景の描画を終え、透明オブジェクトに取りかかる前の位置にコマンドバッファを挿入し、ここでぼかし処理を行う。
  • 画面全体をレンダーテクスチャにコピーしぼかしをかける。

ぼかしカーネルは一次元9点で固定、重みもシェーダー内に直書きし、これを縦横二段掛けとして高速化を狙った。

  • ぼかし幅が固定されている代わりに、ぼかし量の異なる多段階の画像を作っておくことで可変ぼかしに対応できるようにする。

ぼかし後の画像を縦横半分にし、再びぼかしをかける操作を繰り返し、解像度が半々に小さくなるぼかし画像群を得る。

  • 曇りガラスオブジェクトの描画の際には、各ぼかしレベルから採った色をBlurに応じて混合する。ノイズテクスチャによるサンプリング位置ずらしは旧Frostと同様に行う。

コマンドバッファ挿入用スクリプト
これを曇りガラスオブジェクトにアタッチした上で、さらにマテリアルはFrostCompositionを使用する。

C#

1using System.Collections.Generic; 2using UnityEngine; 3using UnityEngine.Rendering; 4 5[ExecuteInEditMode] 6public class Frost : MonoBehaviour 7{ 8 private const CameraEvent FrostBlurEvent = CameraEvent.AfterImageEffectsOpaque; 9 private static Material blurMaterial; 10 private static readonly Dictionary<Camera, CameraInfo> Cameras = new Dictionary<Camera, CameraInfo>(); 11 private static readonly int GrabTex = Shader.PropertyToID("_GrabTex"); 12 private static readonly int[] FrostTex = 13 { 14 Shader.PropertyToID("_FrostTex0"), 15 Shader.PropertyToID("_FrostTex1"), 16 Shader.PropertyToID("_FrostTex2"), 17 Shader.PropertyToID("_FrostTex3") 18 }; 19 private static readonly int[] FrostTexT = 20 { 21 Shader.PropertyToID("_FrostTex0T"), 22 Shader.PropertyToID("_FrostTex1T"), 23 Shader.PropertyToID("_FrostTex2T"), 24 Shader.PropertyToID("_FrostTex3T") 25 }; 26 27 private static void AddCommandBufferIfNeeded(Camera cam) 28 { 29 if (Cameras.ContainsKey(cam)) 30 { 31 return; 32 } 33 34 var cb = new CommandBuffer 35 { 36 name = "FrostBlur" 37 }; 38 cam.AddCommandBuffer(FrostBlurEvent, cb); 39 Cameras.Add(cam, new CameraInfo(cb, 0, 0)); 40 } 41 42 private static void CleanCameras() 43 { 44 foreach (var pair in Cameras) 45 { 46 if (pair.Key != null) 47 { 48 pair.Key.RemoveCommandBuffer(FrostBlurEvent, pair.Value.CommandBuffer); 49 } 50 } 51 52 Cameras.Clear(); 53 } 54 55 private void OnEnable() 56 { 57 CleanCameras(); 58 } 59 60 private void OnDisable() 61 { 62 CleanCameras(); 63 } 64 65 private void OnWillRenderObject() 66 { 67 if (!this.enabled || !this.gameObject.activeInHierarchy) 68 { 69 CleanCameras(); 70 return; 71 } 72 var cam = Camera.current; 73 if (cam == null) 74 { 75 return; 76 } 77 AddCommandBufferIfNeeded(cam); 78 var camInfo = Cameras[cam]; 79 var width = cam.pixelWidth; 80 var height = cam.pixelHeight; 81 if ((width == camInfo.Width) && (height == camInfo.Height)) 82 { 83 return; 84 } 85 var cb = camInfo.CommandBuffer; 86 Cameras[cam] = new CameraInfo(cb, width, height); 87 var blur = blurMaterial; 88 if (blur == null) 89 { 90 blur = new Material(Shader.Find("Hidden/FrostBlur")); 91 blurMaterial = blur; 92 } 93 94 // 必要に応じ(画面サイズが変わった場合)コマンドバッファを再構成する 95 cb.Clear(); 96 // 一時テクスチャを取得 97 // _GrabTexは画面と同サイズ、_FrostTex0~4と_FrostTex0T~4Tは 98 // 0が画面と同サイズで、以降はサイズを半々に小さくする 99 cb.GetTemporaryRT(GrabTex, width, height, 0, FilterMode.Bilinear); 100 for (int i = 0, w = width, h = height; i < 4; i++, w >>= 1, h >>= 1) 101 { 102 cb.GetTemporaryRT(FrostTex[i], w, h, 0, FilterMode.Bilinear); 103 cb.GetTemporaryRT(FrostTexT[i], w, h, 0, FilterMode.Bilinear); 104 } 105 // 現在のレンダリング結果を_GrabTexにコピー 106 cb.Blit(BuiltinRenderTextureType.CurrentActive, GrabTex); 107 // _FrostTex0に_GrabTexをぼかしたものを格納 108 cb.Blit(GrabTex, FrostTex[0]); // そのままコピー 109 cb.Blit(FrostTex[0], FrostTexT[0], blur, 0); // 水平ぼかしコピー 110 cb.Blit(FrostTexT[0], FrostTex[0], blur, 1); // 垂直ぼかしコピー 111 // 段階的に縮小テクスチャへコピーしつつ、さらにぼかしをかけていく 112 for (var i = 1; i < 4; i++) 113 { 114 cb.Blit(FrostTex[i - 1], FrostTex[i]); // 縮小コピー 115 cb.Blit(FrostTex[i], FrostTexT[i], blur, 0); // 水平ぼかしコピー 116 cb.Blit(FrostTexT[i], FrostTex[i], blur, 1); // 垂直ぼかしコピー 117 } 118 // Blitなどのコストを無視すれば、ぼかしテクスチャ総面積は画面面積×1.33で 119 // サンプリング回数はピクセルあたり9×縦横2回で18回、さらに後でグラブテクスチャと 120 // 各ぼかしテクスチャから1回ずつサンプリングするので合計23回となり、 121 // 総サンプリング回数はおよそ画面面積×30.5回となるはず... 122 // 当初の方式でのσ=8クラスのぼかし(およそ描画面積×2600回のサンプリングが必要)と 123 // 比べると、同等のぼかし量ながらだいぶ処理コストを削減できたのではないでしょうか? 124 } 125 126 private struct CameraInfo 127 { 128 public readonly CommandBuffer CommandBuffer; 129 public readonly int Width; 130 public readonly int Height; 131 132 public CameraInfo(CommandBuffer cb, int w, int h) 133 { 134 this.CommandBuffer = cb; 135 this.Width = w; 136 this.Height = h; 137 } 138 } 139}

ぼかし処理担当シェーダー
上記Frostスクリプトが内部で使用する。

ShaderLab

1Shader "Hidden/FrostBlur" 2{ 3 Properties 4 { 5 _MainTex ("Texture", 2D) = "white" {} 6 } 7 SubShader 8 { 9 Cull Off ZWrite Off ZTest Always 10 11 CGINCLUDE 12 #include "UnityCG.cginc" 13 struct appdata 14 { 15 float4 vertex : POSITION; 16 float2 uv : TEXCOORD0; 17 }; 18 struct v2f 19 { 20 float2 uv : TEXCOORD0; 21 float4 vertex : SV_POSITION; 22 }; 23 v2f vert (appdata v) 24 { 25 v2f o; 26 o.vertex = UnityObjectToClipPos(v.vertex); 27 o.uv = v.uv; 28 return o; 29 } 30 sampler2D _MainTex; 31 float4 _MainTex_TexelSize; 32 // 二項係数に基づく重み 33 #define WS 256.0 34 #define W0 (70.0 / WS) 35 #define W1 (56.0 / WS) 36 #define W2 (28.0 / WS) 37 #define W3 (8.0 / WS) 38 #define W4 (1.0 / WS) 39 ENDCG 40 41 // パス0...水平ぼかし 42 Pass 43 { 44 CGPROGRAM 45 #pragma vertex vert 46 #pragma fragment frag 47 fixed4 frag (v2f i) : SV_Target 48 { 49 float2 scale = _MainTex_TexelSize.xy; 50 fixed4 col = W0 * tex2D(_MainTex, i.uv); 51 col += W1 * tex2D(_MainTex, i.uv + scale * float2(1, 0)); 52 col += W1 * tex2D(_MainTex, i.uv + scale * float2(-1, 0)); 53 col += W2 * tex2D(_MainTex, i.uv + scale * float2(2, 0)); 54 col += W2 * tex2D(_MainTex, i.uv + scale * float2(-2, 0)); 55 col += W3 * tex2D(_MainTex, i.uv + scale * float2(3, 0)); 56 col += W3 * tex2D(_MainTex, i.uv + scale * float2(-3, 0)); 57 col += W4 * tex2D(_MainTex, i.uv + scale * float2(4, 0)); 58 col += W4 * tex2D(_MainTex, i.uv + scale * float2(-4, 0)); 59 return col; 60 } 61 ENDCG 62 } 63 64 // パス1...垂直ぼかし 65 Pass 66 { 67 CGPROGRAM 68 #pragma vertex vert 69 #pragma fragment frag 70 fixed4 frag (v2f i) : SV_Target 71 { 72 float2 scale = _MainTex_TexelSize.xy; 73 fixed4 col = W0 * tex2D(_MainTex, i.uv); 74 col += W1 * tex2D(_MainTex, i.uv + scale * float2(0, 1)); 75 col += W1 * tex2D(_MainTex, i.uv + scale * float2(0, -1)); 76 col += W2 * tex2D(_MainTex, i.uv + scale * float2(0, 2)); 77 col += W2 * tex2D(_MainTex, i.uv + scale * float2(0, -2)); 78 col += W3 * tex2D(_MainTex, i.uv + scale * float2(0, 3)); 79 col += W3 * tex2D(_MainTex, i.uv + scale * float2(0, -3)); 80 col += W4 * tex2D(_MainTex, i.uv + scale * float2(0, 4)); 81 col += W4 * tex2D(_MainTex, i.uv + scale * float2(0, -4)); 82 return col; 83 } 84 ENDCG 85 } 86 } 87}

ぼかしテクスチャ合成担当シェーダー
旧Frostと同様に、曇りガラスオブジェクトにはこれを使ったマテリアルをセットする。

ShaderLab

1Shader "Custom/FrostComposition" 2{ 3 Properties 4 { 5 _Color("Color", Color) = (1, 1, 1, 1) 6 7 _MainTex("Diffuse", 2D) = "white" {} 8 _Noise("Noise", 2D) = "black" {} 9 10 _Range("Range", Float) = 0.025 11 _Blur("Blur", Range(0.0, 1.0)) = 0.5 12 } 13 14 SubShader 15 { 16 Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" } 17 18 Cull Off 19 20 CGINCLUDE 21 #include "UnityCG.cginc" 22 half4 _Color; 23 sampler2D _MainTex; 24 float4 _MainTex_ST; 25 sampler2D _GrabTex; 26 sampler2D _FrostTex0; 27 sampler2D _FrostTex1; 28 sampler2D _FrostTex2; 29 sampler2D _FrostTex3; 30 sampler2D _Noise; 31 float4 _Noise_ST; 32 half _Range; 33 float _Blur; 34 ENDCG 35 36 Pass 37 { 38 CGPROGRAM 39 #pragma target 3.0 40 #pragma vertex vert 41 #pragma fragment frag 42 43 struct v2f 44 { 45 float4 pos : SV_POSITION; 46 float3 uv : TEXCOORD; 47 float4 screenPos : TEXCOORD1; 48 }; 49 50 v2f vert(appdata_full v) 51 { 52 v2f o; 53 o.pos = UnityObjectToClipPos(v.vertex); 54 o.uv = v.texcoord; 55 o.screenPos = ComputeGrabScreenPos(o.pos); 56 return o; 57 } 58 59 half4 frag(v2f i) : SV_Target 60 { 61 float2 uv = i.screenPos.xy / i.screenPos.w; 62 float2 frostUV = tex2D(_Noise, i.uv * _Noise_ST.xy + _Noise_ST.zw).xy; 63 64 frostUV -= 0.5; 65 frostUV *= _Range; 66 frostUV += uv; 67 // frostUV = uv; 68 69 float t = pow(_Blur, 0.5) * 4; 70 float4 frost = smoothstep(1, 0, t) * tex2D(_GrabTex, frostUV); 71 frost += smoothstep(1, 0, abs(t - 1)) * tex2D(_FrostTex0, frostUV); 72 frost += smoothstep(1, 0, abs(t - 2)) * tex2D(_FrostTex1, frostUV); 73 frost += smoothstep(1, 0, abs(t - 3)) * tex2D(_FrostTex2, frostUV); 74 frost += smoothstep(1, 0, 4 - t) * tex2D(_FrostTex3, frostUV); 75 76 half4 diffuse = tex2D(_MainTex, i.uv * _MainTex_ST.xy + _MainTex_ST.zw); 77 78 half alpha = _Color.a * diffuse.a; 79 80 return half4(frost.xyz + (diffuse.rgb * _Color.rgb * alpha), 1); 81 } 82 ENDCG 83 } 84 } 85 Fallback Off 86}

ぼかし画像を作る過程は下図のようになります。GIFでも階調を表現できるよう、白黒で撮影しました。

ぼかし過程

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

ぼかし混合比率

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

新旧Frostの比較
プロファイラー

#_Blurとσの対応
対応グラフ1
対応グラフ2

投稿2018/10/03 22:17

編集2018/10/10 13:29
Bongo

総合スコア10807

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

shiroshiro_me

2018/10/10 04:02

返信が遅れてしまい申し訳ありません。 長文での詳しいご回答本当にありがとうございます。 試してみたところこちらでも動作の高速化が確認できました。 こんな書き方もあるのかと驚いております。 質問なのですが一応これもガウシアンブラーの手法にのっとった方法なのでしょうか。またBlurの値とσの値を対応づけはできるのでしょうか。 質問ばかりで申し訳ありません。
Bongo

2018/10/10 20:23 編集

痛いところを突いた鋭いご質問だと思います... 新Frostを作っているときは「σとの対応は大して考慮しなくても、Blurスライダーの量におおむね比例した見た目でぼかしがかかればいいだろう」などと考えていました。 ガウシアンブラーだと言ってもいいのは、個々のぼかし画像を作っている横ぼかし・縦ぼかし部分までかもしれません。縮小によるぼけ成分や、複数ぼかし画像合成による可変ぼかしもどきは、正統なガウシアンブラーからの誤差を生む要因になるはずです。 ですが、ある程度はσとBlurの対応付けも可能かと思います。 ぼかしの重みは二項係数に基づいていて、カーネルサイズ2n+1=9、つまりn =4ですので分散は2*4*0.5*0.5=2、よって標準偏差√2ですので、σ=√2...約1.4のガウシアンブラーと同程度の結果が得られると考えられます。 また、半分の解像度でガウシアンブラーを行うことは、サンプリング範囲が縦横2倍に広がったようなものですので、分散を4倍...σを2倍にするようなものと言えるでしょう。 そして、σaのガウシアンブラーがかかった画像にさらにσbのガウシアンブラーをかけると、分散が2つのブラーの和になりますので、σ=√(σa^2 + σb^2)のガウシアンブラーを1回かけるのと同等の効果が得られます。 ですので、各ぼかしレベルのσは... _GrabTex...ぼかしなし、極めて0に近い微小値 ≒ 0 _FrostTex0...√2 ≒ 1.41 _FrostTex1...√(8+2) = √10 ≒ 3.16 _FrostTex2...√(32+8+2) = √42 ≒ 6.48 _FrostTex3...√(128+32+8+2) = √170 ≒ 13.0 と見積もられます。これらをsmoothstepで合成して、Blurとの散布図としてプロットしたものを回答に追記しました。ご覧の通りぐにゃぐにゃしているものの、わりと正比例していますので... _Sigma ≒ 13 * _Blur _Blur ≒ _Sigma / 13 で、だいたいの換算ができそうです。 一応近似多項式も図に入れていますが(_Blur→_Sigmaは10次近似でけっこうフィットしましたが、_Sigma→_Blurは次数を上げてもいまいちフィットしなかったので、5次までとしました)、そもそも_Sigmaの方は推定値ですし、あまり計算コストをかける必要はないのではないかと思います。
shiroshiro_me

2018/10/11 03:59

なるほど。確かに見た限り誤差の範囲内ですし、_Sigmaと_Blurの対応付けも可能で、これだけパフォーマンスが向上するのならお釣りがくるぐらいですね。 改めて詳しい説明ありがとうございました。勉強にもなりましたしとても助かりました。 コメントでの不躾な質問にも答えてくださり、感激しております。 shaderも勉強していきたいと思います。本当にありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.51%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問