Unity の HLSL シェーダー：シンプルなトゥーンシェーダーを実装する：陰影編

# Unity の HLSL シェーダー：シンプルなトゥーンシェーダーを実装する：陰影編

こんにちは、@studio_meowtoon です。今回は Unity でシンプルなトゥーンシェーダーを実装する方法を紹介します。
![hlsl_in_unity.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/fa31790d-8484-4a14-45a3-6a2c8bff2eb1.png)

## 実現すること

Unity で HLSL 言語を使用したシンプルなトゥーンシェーダーを作成します。

### HLSL とは？

:::note info
Unity では、シェーダープログラムを記述するために HLSL というプログラミング言語を使用します。HLSL (High Level Shading Language) は元々はマイクロソフトによって開発された、Direct3D (DirectX) で使われるプログラマブルシェーダーのためのプロプライエタリなシェーディング言語です。
:::

## これまでの記事でできたこと
Unity でシンプルなカラーシェーダーを実装することができました。

:::note info
こちらの関連記事で実装方法がご確認いただけます。
:::

https://qiita.com/studio_meowtoon/items/0ef1315b4e001625030b

## トゥーンシェーダーとは？

:::note info
トゥーンシェーディングはアニメのセル画のような、輪郭線やはっきりした陰影をつけるシェーディングの総称であり、セルシェーディングと呼ばれることもあります。
:::

:::note alert
今回の記事では、平板で境界線のはっきりした陰影をつけるシェーディングを実装しています。
:::

## Unity のシェーダーを実装する

次のシェーダーは、Unity のシェーダープログラムで書かれたシンプルなトゥーンシェーダーです。

```hlsl:Assets\Shaders\Germio\ColorShade.shader
// シェーダー名
Shader "Germio/ColorShade"
{
    // プロパティセクション
    Properties
    {
        // カスタムプロパティ "_Color" の宣言
        _Color("Color", Color) = (1, 1, 1, 1)
        
        // カスタムプロパティ "_Strength" の宣言
        _Strength("Strength", Range(0, 1)) = 0.4
    }

    // サブシェーダーセクション
    SubShader
    {
        // タグの設定: レンダリングキューを透明に設定
        Tags { "Queue" = "Transparent" }
        
        // ブレンドモードの設定: アルファブレンド
        Blend SrcAlpha OneMinusSrcAlpha
        
        // シェーダーのパスを定義
        Pass
        {
            // パスの名前
            Name "COLOR_SHADE"
            
            // プログラマブルなシェーダーの開始宣言
            CGPROGRAM
            
            // 頂点シェーダーの指定
            #pragma vertex vert
            
            // フラグメントシェーダーの指定
            #pragma fragment frag
            
            // UnityCG.cginc ライブラリをインクルード
            #include "UnityCG.cginc"

            // プロパティで定義されたシェーダーの強度を定義する変数
            float _Strength;

            // 頂点情報を格納する構造体の宣言
            struct appdata
            {
                float4 vertex : POSITION; // 頂点の座標情報
                float3 normal : NORMAL; // 頂点の法線情報
            };

            // 頂点シェーダーからフラグメントシェーダーにデータを渡す構造体の宣言
            struct v2f
            {
                float4 pos : SV_POSITION; // 頂点のスクリーン座標
                float3 worldNormal : TEXCOORD0; // ワールド空間の法線情報
            };

            // 頂点シェーダー関数の宣言
            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex); // 頂点位置をクリップ座標に変換
                o.worldNormal = UnityObjectToWorldNormal(v.normal); // 法線情報をワールド空間に変換
                return o;
            }

            // プロパティで定義されたカラー情報を格納する変数
            float4 _Color;

            // フラグメントシェーダー関数の宣言
            fixed4 frag(v2f i) : SV_Target
            {
                float3 l = normalize(_WorldSpaceLightPos0.xyz); // ライトの方向ベクトルを計算
                float3 n = normalize(i.worldNormal); // 法線ベクトルを計算
                float interpolation = step(dot(n, l), 0); // ライトと法線ベクトルの内積を取得し、補間値を計算
                float4 final_color = lerp(_Color, (1 - _Strength) * _Color, interpolation); // 最終的なカラーを計算
                final_color.a = _Color.a; // アルファ値をカスタムプロパティのアルファ値に設定
                return final_color; // 最終出力カラーを返す
            }
            
            // プログラマブルなシェーダーの終了宣言
            ENDCG
        }
    }

    // フォールバック
    Fallback "Standard"
}
```

### シェーダーで描画する

こちらは今回実装したシンプルなトゥーンシェーダー(マテリアル)で描画した立方体の3Dモデルです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/6c86f85d-e370-b874-b8e1-781d636cf623.png)

### 各セクションの説明

以下、各部分について詳しく説明します。

```hlsl
// シェーダー名
Shader "Germio/ColorShade"
{
    // プロパティセクション: カスタムプロパティの定義
    Properties
    {
        // "_Color" プロパティはシェーダーのカラーを指定します。
        _Color("Color", Color) = (1, 1, 1, 1)
        
        // "_Strength" プロパティはシェーダーの強度を指定します。範囲は0から1です。
        _Strength("Strength", Range(0, 1)) = 0.4
    }
}
```

この部分ではシェーダーのプロパティセクションが定義されています。プロパティはシェーダーの外部から調整可能なパラメータを提供します。_Color プロパティはカラーを、_Strength プロパティは強度を調整します。

```hlsl
// サブシェーダーセクション: レンダリング設定
SubShader
{
    // タグの設定: レンダリングキューを透明に設定し、描画の順序を制御します。
    Tags { "Queue" = "Transparent" }
    
    // ブレンドモードの設定: アルファブレンドを使用して透明なオブジェクトをレンダリングします。
    Blend SrcAlpha OneMinusSrcAlpha
```

ここではシェーダーのサブシェーダーセクションが定義されており、レンダリング設定が行われています。Tags セクションでは描画の順序を制御するためにキューを透明に設定し、Blend セクションではアルファブレンドが指定されています。

```hlsl
// シェーダーのパスを定義
Pass
{
    // パスの名前
    Name "COLOR_SHADE"
    
    // プログラマブルなシェーダーの開始宣言
    CGPROGRAM
    
    // 頂点シェーダーの指定
    #pragma vertex vert
    
    // フラグメントシェーダーの指定
    #pragma fragment frag
    
    // UnityCG.cginc ライブラリをインクルード
    #include "UnityCG.cginc"

    // プロパティで定義されたシェーダーの強度を定義する変数
    float _Strength;
```

この部分ではシェーダーのパスが定義されており、プログラマブルなシェーダーが開始されています。CGPROGRAM セクションでは頂点シェーダーとフラグメントシェーダーが指定され、UnityCG.cginc ライブラリがインクルードされて便利な関数や変数が利用可能になります。_Strength 変数はシェーダーの強度を格納します。

```hlsl
// 頂点情報を格納する構造体の宣言
struct appdata
{
    // 頂点の座標情報
    float4 vertex : POSITION;

    // 頂点の法線情報
    float3 normal : NORMAL;
};
```

この部分では頂点情報を格納するための構造体 appdata が定義されています。この構造体には頂点の座標、法線が含まれています。

```hlsl
// 頂点シェーダーからフラグメントシェーダーにデータを渡す構造体の宣言
struct v2f
{
    // 頂点のスクリーン座標
    float4 pos : SV_POSITION;

    // ワールド空間の法線情報
    float3 worldNormal : TEXCOORD0;
};
```

ここでは頂点シェーダーからフラグメントシェーダーにデータを渡すための構造体 v2f が定義されています。これにより、頂点シェーダーで計算されたデータがフラグメントシェーダーに渡されます。

```hlsl
// 頂点シェーダー関数の宣言
v2f vert(appdata v)
{
    v2f o;
    // 頂点位置をクリップ座標に変換
    o.pos = UnityObjectToClipPos(v.vertex);

    // 法線情報をワールド空間に変換
    o.worldNormal = UnityObjectToWorldNormal(v.normal);
    return o;
}
```

この部分では頂点シェーダー関数 vert が宣言されており、頂点情報を受け取って変換し、v2f 構造体に格納して返します。これにより、頂点情報がフラグメントシェーダーに渡される準備が行われます。

```hlsl
// プロパティで定義されたカラー情報を格納する変数
float4 _Color;
```

```hlsl
// フラグメントシェーダー関数の宣言
fixed4 frag(v2f i) : SV_Target
{
    // ライトの方向ベクトルを計算
    float3 l = normalize(_WorldSpaceLightPos0.xyz);
    
    // 法線ベクトルを計算
    float3 n = normalize(i.worldNormal);
    
    // ライトと法線ベクトルの内積を取得し、補間値を計算
    float interpolation = step(dot(n, l), 0);
    
    // 最終的なカラーを計算
    float4 final_color = lerp(_Color, (1 - _Strength) * _Color, interpolation);
    
    // アルファ値をカスタムプロパティのアルファ値に設定
    final_color.a = _Color.a;
    
    // 最終出力カラーを返す
    return final_color;
}
```

この部分ではフラグメントシェーダー関数 frag が宣言されており、ライトと法線ベクトルの内積を計算してライトの影響を取得し、最終的なカラーを計算します。このカラーにはカスタムプロパティ _Color と _Strength が影響を与えます。最終的に、このカラーがシェーダーの出力として返されます。

## まとめ

Unity で HLSL 言語を使用したシンプルなトゥーンシェーダーを作成することができました。

:::note alert
この記事の実装例は一つのアプローチに過ぎず、必ずしも正しい方法とは限りません。他にも多様な方法がありますので、さまざまな情報を照らし合わせて検討してみてください。
:::

どうでしたか？ Window 11 の Unity で3Dゲームを開発する環境を手軽に構築することができます、ぜひお試しください。今後も Unity の開発トピックなどを紹介していきますので、ぜひお楽しみにしてください。

## 推奨コンテンツ

https://qiita.com/studio_meowtoon/items/1d06ab57a6e62bedf092

## 参考資料

https://docs.unity3d.com/ja/2023.2/Manual/SL-VertexFragmentShaderExamples.html