おもちゃラボ

Unityで遊びを作っていきます

【Unityシェーダ入門】アウトラインシェーダを作る

トゥーンシェーダなどを使ってアニメ調の塗りをする場合、モデルにアウトラインを付けたいことがあります。ここでは、モデルと背景の境界線上にアウトラインを表示するシェーダを作る方法を紹介します。

f:id:nn_hokuson:20180205200341j:plain

今回の記事の内容は次のとおりです。

アウトラインの概要

モデルにアウトラインをつけるには次のように幾つかの方法があります。

  • ステンシルバッファを使う
  • 法線情報から境界を計算する
  • 少し膨らませたモデルを使う
  • ポストエフェクトで輪郭抽出する

ここでは直感的に分かりやすい3つめの「少し膨らませたモデルを使う」方法を紹介したいと思います。この方法は2パス(モデルを2回描画する)でアウトラインを描画します。

まず1パス目では、法線方向にモデルを少し膨らませてから、モデルの裏面を塗りつぶしで描画します。これが輪郭線になります。

f:id:nn_hokuson:20180206194757p:plain:w500

続いて2パス目では通常通りモデルを描画します。このときモデルの表面のみを描画するようにします。1パス目では少し大きめのモデルを描画しているため、この部分がはみ出てアウトラインになります

f:id:nn_hokuson:20180206194804p:plain:w500

このように、1パス目で法線方向に拡大したモデルを塗りつぶして描画し、2パス目で通常通り描画することで、アウトラインのついたモデルを作ることが出来ます。直感的に理解しやすいですね!

また、ステンシルバッファを使ってアウトラインを表示する方法は(Unityについての記事ではありませんが)こちらの記事が参考になるかと思います。

wgld.org

ポストエフェクトとして輪郭抽出する方法は「DirectX 9 シェーダプログラミングブック」で詳しく解説されています(こちらもUnityではありませが)

DirectX 9 シェーダプログラミングブック

DirectX 9 シェーダプログラミングブック

それでは実際にシェーダを書いていきましょう。

シェーダファイルの準備

続いてアウトライン用のシェーダを作ります。プロジェクトビューで右クリック→Create→Shader→Unlit Shaderを選択し、作成したファイル名をoutline.shaderに変更します。

続いてこのシェーダファイルをアタッチするマテリアルも作成しましょう。outline.shaderを選択した状態で右クリック→Create→Materialを選択すると、Unlit_outlineというマテリアルが作成されます。

f:id:nn_hokuson:20180205200457p:plain:w200

outline.shaderが作成できたら、次のシェーダプログラムを入力してください。ここでは頂点・フラグメントシェーダを使って記述しています。

Shader "outline"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            Cull Front

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                v.vertex += float4(v.normal * 0.04f, 0);   
                o.vertex = UnityObjectToClipPos(v.vertex); 
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = fixed4(0.1,0.1,0.1,1);                
                return col;
            }
            ENDCG
        }

        Pass
        {
            Cull Back

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 normal : NORMAL;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.normal = UnityObjectToWorldNormal(v.normal);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {                
                half nl = max(0, dot(i.normal, _WorldSpaceLightPos0.xyz));
                if( nl <= 0.01f ) nl = 0.1f;
                else if( nl <= 0.3f ) nl = 0.3f;
                else nl = 1.0f;
                fixed4 col = fixed4(nl, nl, nl, 1);
                return col;
            }
            ENDCG
        }
    }

1パス目の頂点シェーダではモデルを頂点方向にすこし膨らませています。ここでは裏面のみを描画するため「Cull Front」を指定していることに注意してください。

頂点シェーダに入力される頂点座標と法線方向はローカル座標系です。そこでローカル座標系でモデルを膨張させてから、UnityObjectToClipPos関数を使って頂点座標をワールド座標系に変換しています。

1パス目のフラグメントシェーダは、特にシェーディングの計算などはせず、黒色でベタ塗りしているだけです。このフラグメントシェーダで指定した色がアウトラインの色になります。

続いて2パス目ではモデルを通常通り描画します。ここではトゥーン調の表示にするためフラグメントシェーダでランバートの計算をした後、3段階に階調化して表示しています。トゥーンシェーダについては次の記事で解説しているので合わせて参考にしてください。

nn-hokuson.hatenablog.com

モデルにマテリアルをアタッチする

アウトラインシェーダが作れたら、マテリアルをモデルにアタッチしてみてください。ここでは球とトーラスをシーンに配置して、そこにoutlineマテリアルをドラッグ&ドロップしました。
f:id:nn_hokuson:20180205201651j:plain:w570

アウトラインシェーダの描画結果はこのようになりました。アウトラインがあるだけで、通常のトゥーンシェーダよりも引き締まって見えますね!

f:id:nn_hokuson:20180205200341j:plain:w550

まとめ

今回はトゥーンシェーダと合わせて使えるアウトラインシェーダを紹介しました。アウトラインを描画するには2パス必要にはなるものの、シェーダプログラムは分かりやすかったのではないでしょうか。次はステンシルバッファを使ったアウトラインシェーダも紹介したいと思います。

Unity 5.x Shaders and Effects Cookbook

Unity 5.x Shaders and Effects Cookbook

Mastering Unity Shaders and Effects

Mastering Unity Shaders and Effects