【UnityShader】CommandBufferでブラーをかける 【1】#66
前回の成果
ガウシアンブラーを理解した。
今回やること
前回のガウシアンブラーをCommand Bufferから行います。
事前準備
まず、Scene上に適当な大きさのCubeを配置します。
次に、Cubeが覆いかぶさるくらいのuGUIのImageを配置します。
Imageに今回制作する、GaussianUIBlurマテリアルをアタッチします。
最後に空のオブジェクトに今回制作するスクリプトをアタッチします。
ブラーシェーダー
Shader "Unlit/GaussianUIBlur" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma fragmentoption ARB_precision_hint_fastest #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; half4 _Offset; static const int samplingCount = 10; half _Weights[samplingCount]; half _Intencity; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = 0; [unroll] for (int j = samplingCount - 1; j > 0; j--) { col += tex2D(_MainTex, i.uv - (_Offset.xy * j * _Intencity)) * _Weights[j]; } [unroll] for (int j = 0; j < samplingCount; j++) { col += tex2D(_MainTex, i.uv + (_Offset.xy * j * _Intencity)) * _Weights[j]; } return col; } ENDCG } } }
こちらのシェーダーの解説は以下で行っています。
以下との変更点は、
重みを計算する際に_Intencity変数を乗算してあげて、ブラーの強度をInspectorから変更出来るようにしただけになります。
UIシェーダー
Shader "Unlit/GaussianUI" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} _Color ("Tint", Color) = (1,1,1,1) _StencilComp ("Stencil Comparison", Float) = 8 _Stencil ("Stencil ID", Float) = 0 _StencilOp ("Stencil Operation", Float) = 0 _StencilWriteMask ("Stencil Write Mask", Float) = 255 _StencilReadMask ("Stencil Read Mask", Float) = 255 _ColorMask ("Color Mask", Float) = 15 [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0 } SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" } Stencil { Ref [_Stencil] Comp [_StencilComp] Pass [_StencilOp] ReadMask [_StencilReadMask] WriteMask [_StencilWriteMask] } Cull Off Lighting Off ZWrite Off ZTest [unity_GUIZTestMode] Blend SrcAlpha OneMinusSrcAlpha ColorMask [_ColorMask] Pass { Name "Default" CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 2.0 #include "UnityCG.cginc" #include "UnityUI.cginc" #pragma multi_compile_local _ UNITY_UI_CLIP_RECT #pragma multi_compile_local _ UNITY_UI_ALPHACLIP struct appdata_t { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; float4 worldPosition : TEXCOORD1; float4 pos : TEXCOORD2; UNITY_VERTEX_OUTPUT_STEREO }; sampler2D _MainTex; fixed4 _Color; fixed4 _TextureSampleAdd; float4 _ClipRect; float4 _MainTex_ST; sampler2D _GrabBlurTexture; v2f vert(appdata_t v) { v2f OUT; UNITY_SETUP_INSTANCE_ID(v); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT); OUT.worldPosition = v.vertex; OUT.vertex = UnityObjectToClipPos(OUT.worldPosition); OUT.pos = ComputeScreenPos(OUT.vertex); OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex); OUT.color = v.color * _Color; return OUT; } fixed4 frag(v2f IN) : SV_Target { float2 uv = IN.pos.xy / IN.pos.w; uv.y = 1.0 - uv.y; half4 color = (tex2D(_GrabBlurTexture, uv) + _TextureSampleAdd) * IN.color; #ifdef UNITY_UI_CLIP_RECT color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect); #endif #ifdef UNITY_UI_ALPHACLIP clip (color.a - 0.001); #endif half4 mask = tex2D(_MainTex, IN.texcoord); color.a *= mask.a; return color; } ENDCG } } }
Unityのビルドインシェーダーに今回のブラーで行うことを追記したものになります。
ビルドインシェーダー
ビルドインシェーダーは、以下のUnity公式サイトから取得することができます。
対象のUnityバージョンを選択して、以下画像のようにダウンロードしてください。
変更した点
ビルドインシェーダーから変更した点で、重要なものを説明します。
スクリーン座標に変換
v2f vert(appdata_t v) { // 省略 OUT.worldPosition = v.vertex; OUT.vertex = UnityObjectToClipPos(OUT.worldPosition); OUT.pos = ComputeScreenPos(OUT.vertex); // 省略 } fixed4 frag(v2f IN) : SV_Target { float2 uv = IN.pos.xy / IN.pos.w; uv.y = 1.0 - uv.y; // 省略
ここでやっていることは、クリップ座標をスクリーン座標に変換してその値をUV値として使用しています。
ComputeScreenPos
スクリーン座標に変換するUnityで定義済の関数となります。
定義は以下となります。
inline float4 ComputeScreenPos(float4 pos) { float4 o = ComputeNonStereoScreenPos(pos); #if defined(UNITY_SINGLE_PASS_STEREO) o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w); #endif return o; }
UNITY_SINGLE_PASS_STEREOはVRの時の処理ですので、今回は割愛します。
ComputeNonStereoScreenPosも定義済の関数となります。
つまり、クリップ座標が以下の関数に入ることになります。
inline float4 ComputeNonStereoScreenPos(float4 pos) { float4 o = pos * 0.5f; o.xy = float2(o.x, o.y * _ProjectionParams.x) + o.w; o.zw = pos.zw; return o; }
では、この関数について説明します。
ComputeNonStereoScreenPos
この関数もスクリーン座標に変換する関数となります。
float4 o = pos * 0.5f;
posはクリップ座標空間ですので、xとyには-w~wの値が入っていることになります。
0.5を乗算することにより、この範囲を-0.5w~0.5wにしています。
o.xy = float2(o.x, o.y * _ProjectionParams.x) + o.w;
xyと同様に半分になったwを加算します。
そうすることで、先程-0.5w~0.5wだったものを0~wにしています。
こうすることで、wで除算すると0~1の範囲となります。
0~1の範囲にすることにより、UV値として扱うことが出来ます。
_ProjectionParams.xは、プラットフォームごとのyの向きを変更するために-1.0もしくは1.0が入っています。
o.zw = pos.zw;
計算の過程で半分になってしまったzwをもとの値に戻します。
fragmentでの処理
float2 uv = IN.pos.xy / IN.pos.w;
先程も説明しましたが、IN.pos.xyはIN.pos.wで乗算することによりUV値として扱うことができます。
uv.y = 1.0 - uv.y;
最後にキャプチャ時にyが反転しているので正常な向きへと戻します。
もっと詳しく知りたい方は以下のサイト様が非常に参考になりました。
シェーダーにコメントを付与
Shader "Unlit/GaussianUI" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} _Color ("Tint", Color) = (1,1,1,1) _StencilComp ("Stencil Comparison", Float) = 8 _Stencil ("Stencil ID", Float) = 0 _StencilOp ("Stencil Operation", Float) = 0 _StencilWriteMask ("Stencil Write Mask", Float) = 255 _StencilReadMask ("Stencil Read Mask", Float) = 255 _ColorMask ("Color Mask", Float) = 15 [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0 } SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" } Stencil { Ref [_Stencil] Comp [_StencilComp] Pass [_StencilOp] ReadMask [_StencilReadMask] WriteMask [_StencilWriteMask] } Cull Off Lighting Off ZWrite Off ZTest [unity_GUIZTestMode] Blend SrcAlpha OneMinusSrcAlpha ColorMask [_ColorMask] Pass { Name "Default" CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 2.0 #include "UnityCG.cginc" #include "UnityUI.cginc" #pragma multi_compile_local _ UNITY_UI_CLIP_RECT #pragma multi_compile_local _ UNITY_UI_ALPHACLIP struct appdata_t { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; float4 worldPosition : TEXCOORD1; float4 pos : TEXCOORD2; UNITY_VERTEX_OUTPUT_STEREO }; sampler2D _MainTex; fixed4 _Color; fixed4 _TextureSampleAdd; float4 _ClipRect; float4 _MainTex_ST; sampler2D _GrabBlurTexture; v2f vert(appdata_t v) { v2f OUT; UNITY_SETUP_INSTANCE_ID(v); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT); OUT.worldPosition = v.vertex; OUT.vertex = UnityObjectToClipPos(OUT.worldPosition); // クリップ座標をスクリーン座標に変換 // 下記、ComputeScreenPosの処理 // inline float4 ComputeNonStereoScreenPos(float4 pos) { // // -w ~ wで入ってきているので、-0.5w ~ 0.5wへと変換 // float4 o = pos * 0.5f; // // 0 ~ wへと変換 // o.xy = float2(o.x, o.y * _ProjectionParams.x) + o.w; // o.zw = pos.zw; // return o; // } OUT.pos = ComputeScreenPos(OUT.vertex); OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex); OUT.color = v.color * _Color; return OUT; } fixed4 frag(v2f IN) : SV_Target { // 0 ~ w なので、wで除算してUV値として扱う float2 uv = IN.pos.xy / IN.pos.w; // y軸反転 uv.y = 1.0 - uv.y; // command bufferでキャプチャした画像でtex2D half4 color = (tex2D(_GrabBlurTexture, uv) + _TextureSampleAdd) * IN.color; #ifdef UNITY_UI_CLIP_RECT color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect); #endif #ifdef UNITY_UI_ALPHACLIP clip (color.a - 0.001); #endif // uGUIのImageをマスクとして使用 half4 mask = tex2D(_MainTex, IN.texcoord); color.a *= mask.a; return color; } ENDCG } } }
今回は以上となります。
ここまでご視聴ありがとうございました。