おもちゃラボ

Unityで遊びを作ってます

【Unityシェーダ入門】トゥーンシェーダを自作してみる

Unityではフォトリアルな映像を作るのは簡単ですが、風ノ旅ビトやGravity dazeなどの独自にデフォルメされた映像を作るためにはシェーダを使う必要があります。

f:id:nn_hokuson:20170327192144j:plain

その中でアニメ風の映像を作るためには、トゥーンシェーダ(Toon Shader)を使います。UnityにはEffectパッケージにToonシェーダが用意されているのですが、ここでは勉強のためにトゥーンシェーダを自作してみましょう。

Unityに標準で付属するToonシェーダについては、↓の記事で非常にわかりやすく説明されていますよ〜!
tips.hecomi.com

Toonシェーダのアルゴリズム

今回作るトゥーンシェーダではRampテクスチャと呼ばれる、色をサンプリングするための専用テクスチャを使います。

f:id:nn_hokuson:20170327201313p:plain:w300

オブジェクト表面の暗い所はこの色、明るい所はこの色、といった感じでRampテクスチャから色を取得していきます。

f:id:nn_hokuson:20170327193602p:plain:w400

では、どのようにして暗い・明るいを計算するのでしょうか。
オブジェクト表面の明るさを計算するためには、ライトの方向とオブジェクトの法線の内積を取ります。これにより、ライトと法線方向が一致する場合は値が大きくなり、ライトと法線が垂直に近づくにつれて値が小さくなります。

f:id:nn_hokuson:20170327193238p:plain:w500

ライトの方向とオブジェクトの法線の内積で明るさが計算できたら、それを0〜1にスケーリングしてRampテクスチャのu座標とします。
Rampテクスチャはu座標に沿って段階的に明るくなるため、アニメのような明るさの変化がしっかり出る表現になります。

Toonシェーダファイルを作る

まずはトゥーンシェーダ用のシェーダファイルと、シェーダをアタッチしたマテリアルを作ります。

プロジェクトビューで「Create」→「Shader」→「Standard Surface Shader」を選択し、「Toon」というファイル名で保存します。
このファイルを選択した状態で、「Create」→「Material」を選択し、Toonマテリアルを作成してください。作成したマテリアルにToonシェーダが使われていればOKです。

f:id:nn_hokuson:20170327192212p:plain:w400

Toonシェーダのファイルに次のシェーダプログラムを入力してください。

Shader "Custom/Toon" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _RampTex ("Ramp", 2D) = "white"{}
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
        
        CGPROGRAM
        #pragma surface surf ToonRamp
        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _RampTex;

        struct Input {
            float2 uv_MainTex;
        };

        fixed4 _Color;

        fixed4 LightingToonRamp (SurfaceOutput s, fixed3 lightDir, fixed atten)
        {
            half d = dot(s.Normal, lightDir)*0.5 + 0.5;
            fixed3 ramp = tex2D(_RampTex, fixed2(d, 0.5)).rgb;
            fixed4 c;
            c.rgb = s.Albedo * _LightColor0.rgb * ramp;
            c.a = 0;
            return c;
        }

        void surf (Input IN, inout SurfaceOutput o) {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

今回作成するToonシェーダの主要な部分はLightingToonRampメソッドです。このメソッドでは上のアルゴリズムどおり、ライトの方向とオブジェクトの法線の内積を取り、その値に応じてRampテクスチャから値を取得しています。

このLightingToonRampメソッドはカスタムライティングを行うためのメソッドです。どのようにして、カスタムライティングのためのメソッドを作るのかを下で紹介します。

サーフェイスシェーダでカスタムライティングを使う

トゥーンシェーダのプログラムは、これまで出てきたsurfシェーダだけでは作れません。サーフェイスシェーダに追加するかたちで、ライティングの工程をフックする必要があります。

前の記事でも書いたとおり、Unityのサーフェイスシェーダは頂点シェーダ(Vertex)とマテリアルの表面の色を定義するサーフェイスシェーダ(Surf)、ライティングシェーダ(Lighting)の3つに分かれていましたね。

nn-hokuson.hatenablog.com

これまでは、VertexとLightingシェーダはUnityにお任せしていました。

f:id:nn_hokuson:20170401082008p:plain

今回は自分でライティングを行いたいのでライティングの工程をフックします。

f:id:nn_hokuson:20170401082156p:plain

ライティングの工程をフックするためには次の3つの手順が必要になります。

  1. ライティング用のメソッド(Lighting◯◯◯)を作る
  2. メソッド名をUnityに伝える
  3. StandardSurfaceOutputを使わないようにする

まずはライティング用のメソッドを作ります。ライティング用のメソッド名はLightingから始める必要があります。ここではLightingToonRampという名前にしました。

fixed4 LightingToonRamp (SurfaceOutput s, fixed3 lightDir, fixed atten)

また、ライティングのメソッドをフックしたことをUnityに伝えるため、#pragmaの行にライティングのメソッド名の◯◯◯の部分を追加します。

#pragma surface surf ToonRamp

surfシェーダでライティングの工程をフックした場合には、surfの出力にはSurfaceOutputStandard型を使うことができません。ここではSurfaceOutput型に書き換えています。
SurfaceOutput型にはEmmisionやSmoothnessは定義されていないので、surfメソッドからこれらの行も削除します。

Rampテクスチャをマテリアルにセットする

最後にToonシェーダのマテリアルにRampテクスチャをセットします。プロジェクトビューでToonマテリアルを選択し、インスペクタからRampの欄にRampテクスチャをドラッグ&ドロップして下さい。

f:id:nn_hokuson:20170327201900p:plain:w300

実行すると次のようなシェーダの表現になりました。Rampテクスチャを差し替えるだけで、さまざまな絵作りが出来るので、試してみて下さい!
f:id:nn_hokuson:20170327192812p:plain:w500