Unity の HLSL シェーダー：テクスチャを表示するシェーダーを実装する

# Unity の HLSL シェーダー：テクスチャを表示するシェーダーを実装する

こんにちは、@studio_meowtoon です。今回は Unity でテクスチャを表示するシェーダーを実装する方法を紹介します。
![hlsl_in_unity.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/51eb64b3-01db-a6bd-7261-dae4e12b8bf9.png)

## 実現すること

Unity で HLSL 言語を使用したテクスチャを表示するシェーダーを作成します。

### HLSL とは？

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

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

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

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

## 開発用の素材

今回の記事では以下の素材を使用しています。

### 3Dモデル

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/167e4e2a-430b-e92c-54ca-05f20e3f5a37.png)
FBX ファイル形式の3Dモデルを用意します。

### テクスチャ画像

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/7dca4c90-7e37-3877-cf0d-f87bbf6a5288.png)
PNG ファイル形式の画像を用意します。

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

次のシェーダーは、Unity のシェーダープログラムで書かれたテクスチャを表示するシェーダーです。

```hlsl:Assets\Shaders\Germio\Texture.shader
// シェーダー名
Shader "Germio/Texture"
{
    // プロパティセクション
    Properties
    {
        // カスタムプロパティ "_Color" の宣言
        _Color("Color", Color) = (1, 1, 1, 0.5)
        
        // カスタムプロパティ "_MainTex" の宣言
        [NoScaleOffset] _MainTex("Main Texture", 2D) = "white" {}
    }

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

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

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

            // プロパティで定義されたメインテクスチャを格納する変数
            sampler2D _MainTex;
            
            // メインテクスチャの座標変換行列を格納する変数
            float4 _MainTex_ST;

            // 頂点シェーダー関数の宣言
            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex); // 頂点の位置をクリップ空間に変換
                o.worldNormal = UnityObjectToWorldNormal(v.normal); // 法線ベクトルをワールド空間に変換
                o.uv = TRANSFORM_TEX(v.uv, _MainTex); // テクスチャUV座標を変換
                return o;
            }

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

            // フラグメントシェーダー関数の宣言
            fixed4 frag(v2f i) : SV_Target
            {
                float4 texture_color = tex2D(_MainTex, i.uv); // メインテクスチャから色をサンプリング
                float4 final_color = texture_color * _Color; // テクスチャ色とカラープロパティを掛け合わせる
                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/684645f0-e6ff-73a0-c6b1-2bf9cfdd6696.png)

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

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

```hlsl
Shader "Germio/Texture"
{
    // プロパティセクション
    Properties
    {
        // カスタムプロパティ "_Color" の宣言
        _Color("Color", Color) = (1, 1, 1, 0.5)
        
        // カスタムプロパティ "_MainTex" の宣言
        [NoScaleOffset] _MainTex("Main Texture", 2D) = "white" {}
    }
```

ここではシェーダーのプロパティを定義しています。

|項目|内容|
|:--|:--|
|_Color("Color", Color) = (1, 1, 1, 0.5)|このプロパティはカラー値を表し、既定値は (1, 1, 1, 0.5) です。インスペクターでこのプロパティを変更することで、マテリアルの色を調整できます。|
|[NoScaleOffset] _MainTex("Main Texture", 2D) = "white" {}|このプロパティは 2D テクスチャを表し、テクスチャの既定値は "white" です。また、[NoScaleOffset] 属性がついており、テクスチャのスケールとオフセットが無効化されています。このプロパティを変更することで、マテリアルに適用されるメインテクスチャを設定できます。|

プロパティはインスペクターで調整できるため、マテリアルの外観を柔軟にカスタマイズできます。

```hlsl
// サブシェーダーセクション
SubShader
{
    // タグの設定: レンダリングキューを透明に設定
    Tags { "Queue" = "Transparent" }
    
    // ブレンドモードの設定: アルファブレンド
    Blend SrcAlpha OneMinusSrcAlpha
```

ここではシェーダーのサブシェーダーを定義しています。

|項目|内容|
|:--|:--|
|Tags { "Queue" = "Transparent" }|タグは、このサブシェーダーがどのように描画キュー内で処理されるかを指定します。ここでは、"Queue" タグを "Transparent" に設定しています。これは、透明オブジェクトを描画するための描画キューを指定しています。つまり、このサブシェーダーは透明オブジェクトの描画に使用されることを意味します。|
|Blend SrcAlpha OneMinusSrcAlpha|ブレンドモードは、オブジェクトの描画時に透明度を制御するためのものです。SrcAlpha は、ソース (描画中のオブジェクト自体) のアルファ値を使用し、OneMinusSrcAlpha は、背後のオブジェクトのアルファ値の逆数を使用します。これにより、透明なオブジェクトが正しく重ねられ、アルファブレンドが設定されます。|

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

ここではシェーダーのパスを定義しています。

|項目|内容|
|:--|:--|
|Name "TEXTURE"|描画パスが "TEXTURE" という名前を持ちます。この名前は、後でシェーダーを選択する際に使用されます。|
|#pragma vertex vert|この行は、頂点シェーダーを vert 関数として指定しています。頂点シェーダーはオブジェクトの頂点位置を変換し、データをフラグメントシェーダーに渡す役割を担います。
|#pragma fragment frag|この行は、フラグメントシェーダーを frag 関数として指定しています。フラグメントシェーダーはピクセルごとに実行され、色などの情報を計算し、最終的な描画結果を生成します。|
|#include "UnityCG.cginc"|この行は、Unity のシェーダーコアライブラリである UnityCG.cginc をインクルードしています。このライブラリは、シェーダー内でよく使用される関数や定義を提供し、シェーダーの記述を簡略化します。このインクルードにより、シェーダーが Unity の統合機能を利用できるようになります。|

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

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

    // テクスチャUV座標
    float4 uv : TEXCOORD0;
};
```

ここでは頂点情報を格納するために使用される appdata 構造体を定義しています。

|項目|内容|
|:--|:--|
|float4 vertex : POSITION|頂点の位置情報を表すメンバーで、4つの浮動小数点数 (float4) で表現されています。頂点の3D座標情報が含まれています。|
|float3 normal : NORMAL|頂点の法線ベクトル情報を表すメンバーで、3つの浮動小数点数 (float3) で表現されています。法線ベクトルは、光の反射や陰影の計算に重要な情報です。|
|float4 uv : TEXCOORD0|テクスチャのUV座標情報を表すメンバーで、4つの浮動小数点数 (float4) で表現されています。テクスチャの座標変換に使用され、テクスチャマッピングを行うのに役立ちます。|

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

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

    // テクスチャUV座標
    float2 uv : TEXCOORD0;
};
```

ここでは頂点シェーダーからフラグメントシェーダーにデータを渡すために使用される v2f 構造体を定義しています。

|項目|内容|
|:--|:--|
|float4 pos : SV_POSITION|頂点のスクリーン座標情報を表すメンバーで、4つの浮動小数点数 (float4) で表現されています。この情報は、頂点がスクリーン上のどの位置に描画されるかを指定します。|
|float3 worldNormal : NORMAL|ワールド空間の法線ベクトル情報を表すメンバーで、3つの浮動小数点数 (float3) で表現されています。フラグメントシェーダー内での照明計算などで使用されます。|
|float2 uv : TEXCOORD0|テクスチャのUV座標情報を表すメンバーで、2つの浮動小数点数 (float2) で表現されています。フラグメントシェーダー内でテクスチャからピクセルの色をサンプリングする際に使用されます。|

```hlsl
// プロパティで定義されたメインテクスチャを格納する変数
sampler2D _MainTex;

// メインテクスチャの座標変換行列を格納する変数
float4 _MainTex_ST;
```

|項目|内容|
|:--|:--|
|sampler2D _MainTex|プロパティで定義されたメインテクスチャをシェーダー内で使用するために必要です。sampler2D 型は、2D テクスチャをサンプリングするためのタイプです。|
|float4 _MainTex_ST|テクスチャの座標変換 (スケール、オフセット、回転など) を保持します。テクスチャの座標変換は、テクスチャをオブジェクトに適切にマッピングする為に使用されます。|

```hlsl
// 頂点シェーダー関数の宣言
v2f vert(appdata v)
{
    v2f o;

    // 頂点の位置をクリップ空間に変換
    o.pos = UnityObjectToClipPos(v.vertex);

    // 法線ベクトルをワールド空間に変換
    o.worldNormal = UnityObjectToWorldNormal(v.normal);

    // テクスチャUV座標を変換
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    
    return o;
}
```

ここでは頂点シェーダー関数 vert を宣言しています。この関数は、入力として appdata 構造体を受け取り、出力として v2f 構造体を返します。

|項目|内容|
|:--|:--|
|o.pos = UnityObjectToClipPos(v.vertex)|入力頂点の位置情報をクリップ空間に変換し、出力の o.pos に格納します。クリップ空間では、頂点が最終的にスクリーン上にどの位置に描画されるかが決定されます。|
|o.worldNormal = UnityObjectToWorldNormal(v.normal)|入力頂点の法線ベクトルをワールド空間に変換し、出力の o.worldNormal に格納します。これは光の反射や陰影の計算に使用されます。|
|o.uv = TRANSFORM_TEX(v.uv, _MainTex)|入力のテクスチャUV座標を _MainTex の座標変換行列 _MainTex_ST を考慮して変換し、出力の o.uv に格納します。これにより、テクスチャがオブジェクトに正しくマッピングされます。|

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

// フラグメントシェーダー関数の宣言
fixed4 frag(v2f i) : SV_Target
{
    // メインテクスチャから色をサンプリング
    float4 texture_color = tex2D(_MainTex, i.uv);
    
    // テクスチャ色とカラープロパティを掛け合わせる
    float4 final_color = texture_color * _Color;

    // アルファ値をカラープロパティのアルファ値に設定
    final_color.a = _Color.a;

    // 最終的な色を返す
    return final_color;
}
```

ここではフラグメントシェーダー関数 frag を定義しています。フラグメントシェーダーは、各ピクセル (フラグメント) の色情報を計算し、最終的な描画色を生成します。

|項目|内容|
|:--|:--|
|float4 _Color|この変数は、シェーダーのプロパティから設定されたカラー情報を保持するために使用されます。|
|fixed4 frag(v2f i) : SV_Target|この関数は、入力として v2f 構造体を受け取り、出力として fixed4 型 (カラー情報) を返します。SV_Target は、この関数がフラグメントの最終的な出力色を示すことを示しています。|
|float4 texture_color = tex2D(_MainTex, i.uv)|メインテクスチャから色をサンプリングし、その結果を texture_color に格納しています。tex2D 関数は、指定したテクスチャ _MainTex から指定したUV座標 i.uv に対応する色情報を取得します。|
|float4 final_color = texture_color * _Color|テクスチャの色情報とプロパティから設定された _Color の色情報を掛け合わせ、最終的な描画色を final_color に格納します。これにより、テクスチャの色がカラー情報と結合されます。|
|final_color.a = _Color.a|最終的な色のアルファ値を、プロパティから設定された _Color のアルファ値に設定します。これにより、描画色の透明度がカラー情報の透明度に合わせられます。|
|return final_color|最終的な描画色 final_color を返します。この色がフラグメントの出力として使用され、ピクセルが描画されます。|

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

このシェーダーが使用できない場合、デフォルトの "Standard" シェーダーを使用します。

## まとめ

Unity で HLSL 言語を使用したテクスチャを表示するシェーダーを作成することができました。

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

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

## 参考資料

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