知識0からのUnityShader勉強

知識0からのUnityShader勉強

UnityのShaderをメインとして、0から学んでいくブログです。

【UnityShader】トゥーンシェーダー #17

前回の成果

soramamenatan.hatenablog.com

3種類のノイズを描画できるようになった。

今回やること

トゥーンシェーダー(Toon Shader)を自作してみようと思います。

nn-hokuson.hatenablog.com

ToonShaderとは

3次元コンピュータグラフィックスの一種で、2次元の手描きアニメーション、あるいは漫画やイラスト風の作画(いわゆるアニメ絵)で非写実的レンダリングさせる技術である

f:id:soramamenatan:20190809184947j:plain

トゥーンレンダリング - Wikipedia:より引用

とあります。
こちらの画像では、右側がトゥーンシェーダーを使用して描画されています。

f:id:soramamenatan:20190809185200p:plain

トゥーンレンダリング - Wikipedia:より引用

前準備

f:id:soramamenatan:20190814115805p:plain

Scene上にオブジェクトを配置します。

f:id:soramamenatan:20190815152046p:plain

【Unityシェーダ入門】トゥーンシェーダを自作してみる - おもちゃラボ:より引用

こちらの画像を今回制作するMaterialの_RampTexにアタッチします。

ソースコード

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 diff = dot(s.Normal, lightDir);
            fixed3 ramp = tex2D(_RampTex, fixed2(diff, diff)).rgb;
            fixed4 c;
            c.rgb = s.Albedo * _LightColor0.rgb * ramp * atten;
            c.a = s.Alpha;
            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"
}

surf関数の中身はTextureの情報を取得しているだけのようですが、LightingToonRamp関数が呼び出しされていません。

#pragma surface surf ToonRamp

以前までは、

#pragma surface surf Standard

#pragma surface surf Standard fullforwardshadows

が多かったと思います。

これらは、 VertexとLightingをUnityに任せていたからです。

f:id:soramamenatan:20190814120844p:plain

【Unityシェーダ入門】トゥーンシェーダを自作してみる - おもちゃラボ:より引用

ですが、今回は自分でライティングを行うことによってトゥーンシェーダーを実装します。

f:id:soramamenatan:20190814180522p:plain

【Unityシェーダ入門】トゥーンシェーダを自作してみる - おもちゃラボ:より引用

ですので、

#pragma surface surf ToonRamp

と書きます。

#6で一度紹介したのですが、

soramamenatan.hatenablog.com

// 元の文
#pragma surface surf ToonRamp
// 説明
pragma宣言 サーフェスシェーダーを使うよ 関数名 ライティングモデル (オプションパラメータ)

となっています。
このライティングモデルにメソッド名を記述するとカスタムライティングとなります。
この時、注意して欲しいのは関数を定義する際にはLightingを頭につける必要があります

// カスタムライティングの定義
#pragma surface surf ToonRamp

// 定義した名前(ToonRamp)の頭にLightingをつける
fixed4 LightingToonRamp (SurfaceOutput s, fixed3 lightDir, fixed atten)

// これだと動かない
fixed4 ToonRamp (SurfaceOutput s, fixed3 lightDir, fixed atten)

また、カスタムライティングを使用する際にはSurfaceOutputStandardを使用することができません
なので、SurfaceOutputに置き換えます。

カスタムライティングの引数

引数の種類は3種類あります。

Forward rendering

half4 LightingName(SurfaceOutput s、half3 lightDir、half atten){}

この関数は、ビューの方向が不要な場合のフォワードレンダリングに使用されます。

今回はこれを使用しています。

ViewDirectionあり

half4 LightingName(SurfaceOutput s、half3 lightDir、half3 viewDir、half atten){}

この関数は、ビューの方向が必要な場合のフォワードレンダリングで使用されます。

Deferred rendering

half4 LightingName_PrePass (SurfaceOutput s, half4 light){}

この関数は、プロジェクトで遅延レンダリングを使用している場合に使用されます。

qiita.com

ちなみに、attenはattenuationの略で、ライトの減衰率のことです。

_LightColor0

戻り値がfixed4型のライトの色です。

docs.unity3d.com

LightingToonRampのアルゴリズム

step1

half diff = dot(s.Normal, lightDir);

オブジェクトのピクセル毎の法線とライト方向の内積を出します。
dot(内積)は、-1~1の間で出力されます。

step2

fixed3 ramp = tex2D(_RampTex, fixed2(diff, diff)).rgb;

step1で出された内積の値で、_RampTexの色を取得します。

f:id:soramamenatan:20190815152046p:plain

UV値(内積の値)
0~0.333...
茶色 0.333...~0.666...
0.666...~1

負の数になった場合は黒になります。
以上の2つの手順からトゥーンシェーダーができます。

結果

f:id:soramamenatan:20190815160012p:plain

f:id:soramamenatan:20190815160025p:plain

表も裏もしっかり色を変更させることができています。

ソースコードにコメントを付与

Shader "Custom/toon" {
    // プロパティ
    Properties {
        // ベースとなる色
        _Color("Color", Color) = (1, 1, 1, 1)
        // メインテクスチャ
        _MainTex("Albedo(RGB)", 2D) = "white" {}
        // rampテクスチャ
        _RampTex("Ramp", 2D) = "white" {}
    }

    // Shaderの中身を記述
    SubShader {
        // 一般的なShaderを使用
        Tags {"RenderType" = "Opaque"}
        // しきい値
        LOD 200

        // cg言語記述
        CGPROGRAM
        // メソッド名がLightingToonRampのカスタムライティング宣言
        #pragma surface surf ToonRamp
        // Shader Model
        #pragma target 3.0

        // メインテクスチャ
        sampler2D _MainTex;
        // rampテクスチャ
        sampler2D _RampTex;

        // input構造体
        struct Input {
            // uv座標
            float2 uv_MainTex;
        };

        // ベースとなる色
        fixed4 _Color;

        // カスタムライティング
        fixed4 LightingToonRamp(SurfaceOutput s, fixed3 lightDir, fixed atten) {
            // 内積を取得
            half diff = dot(s.Normal, lightDir);
            // rampテクスチャのuv値を取得
            fixed3 ramp = tex2D(_RampTex, fixed2(diff, diff)).rgb;
            // rampテクスチャのuv値から色を取得
            fixed4 c;
            c.rgb = s.Albedo * _LightColor0.rgb * ramp * atten;
            c.a = s.Alpha;
            return c;
        }

        // surf関数
        void surf(Input IN, inout SurfaceOutput o) {
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        // Shaderの記述終了
        ENDCG
    }
    // SubShaderが失敗した時に呼ばれる
    Fallback "Diffuse"
}

今回は以上となります。
次回はvertexShaderについて触れていこうと思います。