【Unity】【シェーダ】モデルのアウトラインを描画する(法線方向モデル拡大法)

Unityのシェーダでモデルのアウトラインを描画する方法を紹介します。
アウトラインの描画方法はいくつかありますが、本記事ではモデルを法線方向に拡大する手法についてまとめます。

法線方向モデル拡大法?

さて本記事ではアウトラインの描画方法のうち、モデルを法線方向に拡大する手法についてまとめます。
また本サイトではこれを便宜的に法線方向モデル拡大法と呼ぶことにします。

考え方はシンプルで、まず元のモデルをちょっと拡大してアウトラインを描画してから、元の大きさで通常通り描画するだけです。

f:id:halya_11:20180409232530p:plain

また、拡大したアウトライン部分を描画する際、前面をカリングします。
これをやらないと元の大きさで描画を行った際に、描画した部分がアウトライン部分に隠れてしまいます。

シェーダ

シェーダは次のようにします。

Shader "Custom/Outline" {
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _OutlineWidth ("Outline Width", float) = 0.1
        _OutlineColor ("Outline Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry" }

        Pass
        {

            // 前面をカリング
            Cull Front

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"

            struct appdata
            {
                half4 vertex : POSITION;
                half3 normal : NORMAL;
            };

            struct v2f
            {
                half4 pos : SV_POSITION;
            };

            half _OutlineWidth;
            half4 _OutlineColor;
            
            v2f vert (appdata v)
            {
                v2f o = (v2f)0;

                // アウトラインの分だけ法線方向に拡大する
                o.pos = UnityObjectToClipPos(v.vertex + v.normal * _OutlineWidth);

                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                return _OutlineColor;
            }
            ENDCG
        }

        // 2パス目は好きなようにレンダリングする
        Pass
        {
            Tags { "LightMode"="ForwardBase" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"

            struct appdata
            {
                half4 vertex : POSITION;
                half3 normal : NORMAL;
            };

            struct v2f
            {
                half4 pos : SV_POSITION;
                half3 normal: TEXCOORD1;
            };

            half4 _Color;
            half4 _LightColor0;
            
            v2f vert (appdata v)
            {
                v2f o = (v2f)0;

                o.pos = UnityObjectToClipPos(v.vertex);
                o.normal = UnityObjectToWorldNormal(v.normal);

                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                half3 diff = max(0, dot(i.normal, _WorldSpaceLightPos0.xyz)) * _LightColor0;

                fixed4 col;
                col.rgb = _Color * diff;
                return col;
            }
            ENDCG
        }
    }
}

説明はコメントの通りです。

結果

レンダリング結果は次のようになります。

f:id:halya_11:20180409233229p:plain

アウトラインの太さや色は自由に変更できます。

f:id:halya_11:20180409233250p:plain

課題1

この手法には課題があります。
一つ目は、同じ位置に複数の法線があるようなオブジェクトに適用すると描画がおかしくなるというものです。

f:id:halya_11:20180409233751p:plain

これは法線方向にモデルを拡大しているためです。
解決策としては、法線を修正したモデルを作ればいいです。

qiita.com

が、モデルが増えるのでこの手法が使えるかはケースバイケースです。

ちなみにモデルの形状とアウトラインの太さ次第では、アウトライン描画時のカリングをOffにすることで綺麗に描画できることもあります(もちろん描画順を制御する必要はありますが)。

課題2

2点目の課題はアウトラインを持つ二つのオブジェクトを近い位置に配置したときに綺麗にアウトラインが表示されないというものです。

f:id:halya_11:20180409233448p:plain

これは単純にZTestの影響です。
レンダリングの動作としては正しいですが、望んだ結果ではありません。

これに関してはステンシルバッファを使えば解決できます(記事書いたらリンクします)。