【UnityShader】Imageにアウトラインをつける #49
前回の成果
ノイズで背景を歪めました。
今回やること
UGUIのImageの画像にアウトラインをつけます。
事前準備
Scene上にImageを配置します。
Imageに画像と今回制作するマテリアルをアタッチします。
Sceneのキャプチャ
使用した画像
ソースコード
Shader "Unlit/outlineImage" { Properties { _MainTex ("Base (RGB), Alpha (A)", 2D) = "white" {} _BlurColor ("Blur Color", Color) = (1, 1, 1, 1) _BlurSize ("Blur Size", float) = 1 } CGINCLUDE struct appdata { float4 vertex : POSITION; float2 texcoord : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; half2 texcoord : TEXCOORD0; float4 worldPosition : TEXCOORD1; }; #include "UnityCG.cginc" sampler2D _MainTex; float4 _MainTex_TexelSize; fixed4 _BlurColor; float _BlurSize; v2f vert (appdata v) { v2f o; o.worldPosition = v.vertex; o.vertex = UnityObjectToClipPos(o.worldPosition); o.texcoord = v.texcoord; return o; } fixed4 frag(v2f v) : SV_Target { half4 color = (tex2D(_MainTex, v.texcoord)); return color; } fixed4 frag_blur (v2f v) : SV_Target { int k = 1; float2 blurSize = _BlurSize * _MainTex_TexelSize.xy; float blurAlpha = 0; float2 tempCoord = float2(0,0); float tempAlpha; for (int px = -k; px <= k; px++) { for (int py = -k; py <= k; py++) { tempCoord = v.texcoord; tempCoord.x += px * blurSize.x; tempCoord.y += py * blurSize.y; tempAlpha = tex2D(_MainTex, tempCoord).a; blurAlpha += tempAlpha; } } half4 blurColor = _BlurColor; blurColor.a *= blurAlpha; return blurColor; } ENDCG SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" } Cull Off Lighting Off ZWrite Off ZTest [unity_GUIZTestMode] Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag_blur ENDCG } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag ENDCG } } }
2つのパスがありますが、いつもと異なる書き方をしています。
複数のパスでの共通処理
CGINCLUDEを使用すると、CGINCLUDE~ENDCGまでの間を各パスでの共通処理とすることができます。
実例
複数パスで色を変える
色を変えるだけの複数パスのシェーダーを用意します。
Shader "Unlit/cgincludePass" { Properties { _Color ("Color", Color) = (0, 0, 0, 0) } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; float4 _Color; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { return _Color; } ENDCG } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; float4 _Color; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex - 1); return o; } fixed4 frag (v2f i) : SV_Target { return _Color + 1; } ENDCG } } }
結果としては、以下の画像のようになります。
しかし変数や構造体で同じことを書いてしまい冗長です。
CGINCLUDEを使う
そこで、CGINCLUDEを使用します。
Shader "Unlit/cgincludePass" { Properties { _Color ("Color", Color) = (0, 0, 0, 0) } CGINCLUDE #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; float4 _Color; v2f vertFirst (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 fragFirst (v2f i) : SV_Target { return _Color; } v2f vertSecond (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex - 1); return o; } fixed4 fragSecond (v2f i) : SV_Target { return _Color + 1; } ENDCG SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vertFirst #pragma fragment fragFirst ENDCG } Pass { CGPROGRAM #pragma vertex vertSecond #pragma fragment fragSecond ENDCG } } }
CGINCLUDEを使用し2つのパスで共通の処理であったものをまとめることでソースコードを綺麗にすることができます。
もちろん、描画結果は同じとなります。
各用語の説明
_MainTex_TexelSize
テクスチャのサイズを扱う変数です。
テクスチャ名_TexelSize
で定義することができます。
xyzw | 意味 |
---|---|
x | 1.0 / width |
y | 1.0 / height |
z | width |
w | height |
IgnoreProjector
値がTrueの場合、シェーダーを使用しているオブジェクトはプロジェクターの影響を受けません。
部分的に透過なオブジェクトに使用します。
PreviewType
マテリアルのインスペクターでどのようにプレビューされるかを変更します。
デフォルトはsphereです。
デフォルト
"PreviewType"="Plane"
CanUseSpriteAtlas
シェーダーがスプライトに用いられる場合にで、何か不具合があるときにFalseに設定されます。
ZTest [unity_GUIZTestMode]
描画されるマテリアルのCanvasのRenderModeの設定によって自動的に変化するものです。
RenderMode | unity_GUIZTestModeの値 |
---|---|
Screen Space - Overlay | Always |
Screen Space - Camera | LEqual |
World Space | LEqual |
frag_blur関数の説明
今回のシェーダーの肝はfrag_blur関数なので解説していこうと思います。
変数の定義
主に前半部分です。
int k = 1; float2 blurSize = _BlurSize * _MainTex_TexelSize.xy; float blurAlpha = 0; float2 tempCoord = float2(0,0); float tempAlpha;
int k = 1;
kは後のfor文で使用するループの回数(-k~k)になります。
今回の場合、-1~1でループさせたいので1としています。
float2 blurSize = _BlurSize * _MainTex_TexelSize.xy;
ブラーの大きさを決めてます。
_MainTex_TexelSize.xyは1.0/画像の大きさなので気をつけてください。
僕はここでハマりました。
アウトラインをつける
今回の肝の部分となります。
for (int px = -k; px <= k; px++) { for (int py = -k; py <= k; py++) { tempCoord = v.texcoord; tempCoord.x += px * blurSize.x; tempCoord.y += py * blurSize.y; tempAlpha = tex2D(_MainTex, tempCoord).a; blurAlpha += tempAlpha; } } half4 blurColor = _BlurColor; blurColor.a *= blurAlpha; return blurColor;
for (int px = -k; px <= k; px++) { for (int py = -k; py <= k; py++) { tempCoord = v.texcoord; tempCoord.x += px * blurSize.x; tempCoord.y += py * blurSize.y; tempAlpha = tex2D(_MainTex, tempCoord).a; blurAlpha += tempAlpha; } }
for文では、テクスチャをblurSize分ズラしたときのアルファ値を求めています。
イメージは以下となります。
イメージ1
元画像のUV値をもらいます。
イメージ2
テクスチャをズラします。
この時、pxは-1でpyは-1~1になります。
イメージ2のズラす値
それぞれblurSize分ズラします。
これを繰り返すことによって、アウトラインが表示できます。
今回のやり方はテクスチャの大きさを拡大しているので、
Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag ENDCG }
をコメントアウトすると以下のようになります。
この方法はズラして拡大しているので、blurSizeが大きいと表示がおかしくなってしまうので気をつけてください。
blurSizeが20の場合
ズラす値が大きくなっているので、うさぎの耳のアウトラインが3つになってしまっている。
half4 blurColor = _BlurColor;
blurColor.a *= blurAlpha;
return blurColor;
最後にアルファ値を計算して終了となります。
結果
Imageにアウトラインがつけば成功です。
ソースコードにコメントを付与
Shader "Unlit/outlineImage" { Properties { _MainTex ("Base (RGB), Alpha (A)", 2D) = "white" {} _BlurColor ("Blur Color", Color) = (1, 1, 1, 1) _BlurSize ("Blur Size", float) = 1 } // passで共通処理をまとめる CGINCLUDE struct appdata { float4 vertex : POSITION; float2 texcoord : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; half2 texcoord : TEXCOORD0; float4 worldPosition : TEXCOORD1; }; #include "UnityCG.cginc" sampler2D _MainTex; // MainTexのサイズを扱う // x : 1.0 / width // y : 1.0 / height // z : width // w : height float4 _MainTex_TexelSize; fixed4 _BlurColor; float _BlurSize; v2f vert (appdata v) { v2f o; o.worldPosition = v.vertex; o.vertex = UnityObjectToClipPos(o.worldPosition); o.texcoord = v.texcoord; return o; } fixed4 frag(v2f v) : SV_Target { half4 color = (tex2D(_MainTex, v.texcoord)); return color; } fixed4 frag_blur (v2f v) : SV_Target { // -1 ~ 1でforを回す int k = 1; float2 blurSize = _BlurSize * _MainTex_TexelSize.xy; float blurAlpha = 0; float2 tempCoord = float2(0,0); float tempAlpha; // xy方向にblurSize分ズラし、アルファ値を計算 for (int px = -k; px <= k; px++) { for (int py = -k; py <= k; py++) { tempCoord = v.texcoord; tempCoord.x += px * blurSize.x; tempCoord.y += py * blurSize.y; tempAlpha = tex2D(_MainTex, tempCoord).a; blurAlpha += tempAlpha; } } half4 blurColor = _BlurColor; blurColor.a *= blurAlpha; return blurColor; } ENDCG SubShader { Tags { "Queue"="Transparent" // プロジェクターの影響を受けないように "IgnoreProjector"="True" "RenderType"="Transparent" // マテリアルのincpecterでの表示をplaneに "PreviewType"="Plane" // spriteに不具合がある場合にfalseになる "CanUseSpriteAtlas"="True" } Cull Off Lighting Off ZWrite Off // CanvasのRenderModeによって動的に変化 // Screen Space - Overlay : Always // Screen Space - Camera : LEqual // World Space : LEqual ZTest [unity_GUIZTestMode] Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag_blur ENDCG } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag ENDCG } } }
今回は以上となります。
ここまでご視聴ありがとうございました。