あまがみ's diary

ゲーム & Webプログラミングな日常

【Unity】Sprite用アウトラインシェーダ

f:id:ina-amagami:20170423173042p:plain:w300

以下の記事で紹介されているシェーダを参考に、改良版を2種類作成してみました。
UnityのSprite用シェーダ(2本) · M.Ike

  • 線の色は固定、SpriteRendererでスプライトの色を変更できる(Sprites/Outline)
  • スプライトの色は固定、SpriteRendererで線の色を変更できる(Sprites/OutlineColor)

影とアウトラインの機能が両方含まれていたので、負荷を抑えるためアウトラインのみ残しています。
uGUIではOutlineコンポーネントを使えるので、SpriteRendererを使うケース専用です。

半透明や縦長、横長のスプライトだとアウトラインが綺麗に付かない場合があります。
アウトラインを別画像として作る方法が使えるケースでは使わない方が良いかもしれません。
 

準備:スプライト設定

Sprite Mode -> Mesh Typeを「Tight」にしている場合、
ワイヤフレームで見た時に上のUnityロゴはこんな感じになっています。

f:id:ina-amagami:20170423173138p:plain:w300

今回のシェーダでは線を描く範囲を確保するためにポリゴン内のテクスチャを縮小するため、
画像のようにポリゴンが分離している場合は変な方向にずれて縮小されてしまいます。
Mesh Typeを「Full Rect」に変更すると四角形のポリゴンになるため、アウトラインが綺麗に付くようになります。
ただし、余白部分まで描画処理が発生する分、負荷が高くなる点に注意して下さい。
 

Sprites/Outline

SpriteRendererで指定するColorは、スプライトの色に影響し、線の色には影響しません。

プロパティ

Outline Spread…線の太さ
Outline Color…線の色(アルファ値は無効)
Outline Smoothness…線のなめらかさ

実装(SpritesOutline.shader)
Shader "Sprites/Outline"
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
        _OutLineSpread ("Outline Spread", Range(0, 0.1)) = 0
        _OutLineColor ("Outline Color", Color) = (1, 1, 1, 1)
        _Smoothness ("Outline Smoothness", Range(0, 0.5)) = 0.1
    }

    SubShader
    {
        Tags
        { 
            "Queue"="Transparent" 
            "IgnoreProjector"="True" 
            "RenderType"="Transparent" 
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Cull Off
        Lighting Off
        ZWrite Off
        Fog { Mode Off }
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile DUMMY PIXELSNAP_ON
            #include "UnityCG.cginc"
            
            struct appdata
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
            };
            
            sampler2D _MainTex;
            half _OutLineSpread;
            fixed4 _OutLineColor;
            fixed _Smoothness;

            v2f vert(appdata IN)
            {
                fixed scale = 1 + _OutLineSpread * 2;

                float2 tex = IN.texcoord * scale;
                tex -= (scale - 1) / 2;

                v2f OUT;
                OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex);
                OUT.texcoord = tex;
                OUT.color = IN.color;
                #ifdef PIXELSNAP_ON
                OUT.vertex = UnityPixelSnap (OUT.vertex);
                #endif

                return OUT;
            }

            sampler2D _AlphaTex;
            float _AlphaSplitEnabled;

            fixed4 SampleSpriteTexture (float2 uv)
            {
                fixed4 color = tex2D (_MainTex, uv);

#if UNITY_TEXTURE_ALPHASPLIT_ALLOWED
                if (_AlphaSplitEnabled)
                {
                    color.a = tex2D (_AlphaTex, uv).r;
                }
#endif

                return color;
            }

            fixed4 frag(v2f IN) : SV_Target
            {
                fixed4 base = SampleSpriteTexture(IN.texcoord) * IN.color;

                fixed4 out_col = _OutLineColor;
                out_col.a = 1;
                half2 line_w = half2(_OutLineSpread, 0);
                fixed4 line_col = SampleSpriteTexture(IN.texcoord + line_w.xy)
                                + SampleSpriteTexture(IN.texcoord - line_w.xy)
                                + SampleSpriteTexture(IN.texcoord + line_w.yx)
                                + SampleSpriteTexture(IN.texcoord - line_w.yx);
                out_col *= line_col.a;
                out_col.rgb = _OutLineColor.rgb;
                out_col = lerp(base, out_col, max(0, sign(_OutLineSpread)));

                fixed4 main_col = base;
                main_col = lerp(main_col, out_col, (1 - main_col.a));
                main_col.a = IN.color.a * max(0, sign(main_col.a - _Smoothness));
                return main_col;
            }
        ENDCG
        }
    }
}

 

Sprites/OutlineColor

アウトラインの色はSpriteRendererのColorで指定します。

実装(SpritesOutlineColor.shader)
Shader "Sprites/OutlineColor"
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)
        [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
        _OutLineSpread ("Outline Spread", Range(0, 0.1)) = 0
        _Smoothness ("Outline Smoothness", Range(0, 0.5)) = 0
    }

    SubShader
    {
        Tags
        { 
            "Queue"="Transparent" 
            "IgnoreProjector"="True" 
            "RenderType"="Transparent" 
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Cull Off
        Lighting Off
        ZWrite Off
        Fog { Mode Off }
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile DUMMY PIXELSNAP_ON
            #include "UnityCG.cginc"
            
            struct appdata
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
            };
            
            sampler2D _MainTex;
            fixed4 _Color;
            half _OutLineSpread;
            fixed _Smoothness;

            v2f vert(appdata IN)
            {
                fixed scale = 1 + _OutLineSpread * 2;

                float2 tex = IN.texcoord * scale;
                tex -= (scale - 1) / 2;

                v2f OUT;
                OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex);
                OUT.texcoord = tex;
                OUT.color = IN.color;
                #ifdef PIXELSNAP_ON
                OUT.vertex = UnityPixelSnap (OUT.vertex);
                #endif

                return OUT;
            }

            sampler2D _AlphaTex;
            float _AlphaSplitEnabled;

            fixed4 SampleSpriteTexture (float2 uv)
            {
                fixed4 color = tex2D (_MainTex, uv);

#if UNITY_TEXTURE_ALPHASPLIT_ALLOWED
                if (_AlphaSplitEnabled)
                {
                    color.a = tex2D (_AlphaTex, uv).r;
                }
#endif

                return color;
            }

            fixed4 frag(v2f IN) : SV_Target
            {
                fixed4 base = SampleSpriteTexture(IN.texcoord);
                base.rgb *= _Color.rgb;

                fixed4 out_col = IN.color;
                out_col.a = 1;
                half2 line_w = half2(_OutLineSpread, 0);
                fixed4 line_col = SampleSpriteTexture(IN.texcoord + line_w.xy)
                                + SampleSpriteTexture(IN.texcoord - line_w.xy)
                                + SampleSpriteTexture(IN.texcoord + line_w.yx)
                                + SampleSpriteTexture(IN.texcoord - line_w.yx);
                out_col.a *= line_col.a;
                out_col = lerp(base, out_col, max(0, sign(_OutLineSpread)));

                fixed4 main_col = base;
                main_col = lerp(main_col, out_col, (1 - main_col.a));
                main_col.a = IN.color.a * max(0, sign(main_col.a - _Smoothness));
                return main_col;
            }
        ENDCG
        }
    }
}