【First Steps #001】Unity Shaderの輪郭を掴む

どうも！記念すべき初記事です！

まずは自己紹介から。
今はテックスタートアップでディレクターをしています。僕がいる業界・環境ではUnityを使って開発することが多く、「僕もそれを使いたいな〜」と思ったのがUnityを始めようと思ったきっかけです。

そうはいいつつ、僕は初心者。
ということで「First Steps」という名前で、初心者ながらに日々学んだことを書いていきたいと思います。

記念すべき初回のテーマは「Unity Shader」!
近くでUnity Shaderをバリバリ書いている方がいて、その方へのリスペクトもありUnity Shaderから初めてみたいと思います。

とはいえ、Unity Shader。初見ではなんもわからん。。。
ということで、まずはUnity Shaderの輪郭を掴むところから始めたいと思います！

:::message
この記事はUnityエンジニア向けではないです！
あくまでもUnity初心者の自分への備忘録的に書いています。
なので、内容にはあまり期待をしないでください！！
:::


# バイブル的な動画の紹介
Unity Shaderを始める上で、僕のバイブル的な動画を紹介します。
Unityの安原さん（＊僕にとっては神のような存在）がShaderの基本を紹介してくれている動画が公開されています！

<iframe width="560" height="315" src="https://www.youtube.com/embed/wUx_Y9BgC7k" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>


実はこの動画を見る前に、UnityのShaderを理解しようとして、「ふっふらぐめんと？ばーてっくす？」となった思い出があります。。
そんな不明点を解消してくれるのがこの動画です！

では動画内での説明と僕の理解を交えて見てみます！
（「僕の理解」部分について間違っていたらすみません。。）


# Shaderの基本8ステップ
### ステップ1：3Dモデルを準備する
前提中の大前提、Unity Shaderはそれ単体では存在しません。
かならず何かしらの3Dモデルの上で存在しています。

そしてUnityでは3Dモデルのポリゴンは3角形です。（Blenderなどは四角。）

なので、3Dモデルの1ポリゴン、1面は3角形で構成されます。
この後で、ポリゴンごとに頂点を動かすときは、必ずこの3頂点を意識する必要があります。

### ステップ2:Transformの値を4×4行列に変換
ちょっと小難しいですが、3DモデルのTransformの値を行列として捉え直します。
これが「モデル行列」と呼ばれるものです。
![Frame 2 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1585621/fd6591cb-3091-136d-dcd1-0e7d1e2e2d85.png)

こちらの解説もUnity Technologiesの安原さんが記事で説明をしてくれています。（ほんとに神様みたいなお方です。。）

https://qiita.com/yuji_yasuhara/items/8d63455d1d277af4c270

### ステップ３：頂点ごとに描画位置を算出 【頂点シェーダ】
ステップ2のモデル行列とステップ1の3Dモデルの頂点情報にカメラの情報を加えることで、どこに頂点が描画されるか、位置を算出します。

この部分がいわゆる「頂点シェーダ」です。頂点の位置を算出することで、3Dモデルの見た目の形状を変えることができます。

### ステップ４：3点が揃ったら裏表を調べて、裏なら描画しない
ステップ3で3頂点の位置が算出されたら、3頂点で囲まれた面（ポリゴン）の表裏が決まります。
つまり、表がわかるので、描画される面が決まります。

### ステップ5：描画点を確定
続いて、3頂点を線で結び、それらに囲まれた範囲と画素を確定します。
3つの点で囲まれた範囲を塗りつぶすイメージですね。

### ステップ6：描画点を深度バッファと比較
描画する表面や範囲がわかったら、次にすでに描画されているものと比較します。
ここで出てくる「深度バッファ」とは、カメラから3Dモデル・オブジェクトまでの距離の情報を持つバッファ（記憶領域）のことです。Unityではこの深度の値がZ値とも呼ばれるので、「Zバッファ」としても知られています。

この深度バッファを元に、すでに描画済みの点と描画されようとしている点を比較し、手前にある点しか描画しないようにします。

### ステップ7：描画点に打つべき「色」を確定 【フラグメントシェーダ】
描画点に打つべき「点」の色を考えます。
この色には色々な要素が含まれています。ライティングやテクスチャにシャドウ、etx。
これらの影響を踏まえて点の色が決定します。

そしてこれが、「フラグメントシェーダ」と呼ばれています。見た目の色を決めたり変えたりするものになります。

### ステップ8：点を打つ 
そうして最後に点を打ちます。
このタイミングでブレンド関数の指定やアルファ値の調節も可能となります。
あとは深度バッファの更新も可能とのこと。
（ここらへんはまだ正直わからない、、、）

これが一旦のShaderの流れです。
では次に、UnityのデフォルトのShaderのコードを見ていきます。

# この記事がこれ以降の内容を完全解説してくれています！

ちなみに本気でShaderの内容を知りたい方はこちらの記事を参照してください！
天神いなさんがまとめてくれたUnlit Shaderの中身の全解説です。
これから下に展開される僕の解釈よりも300倍詳しくわかりやすいです。

https://amagamina.jp/unity-shader/

# まずは見てみましょう！

以下なんの変哲もない、Unityで「Unlit Shader」を選ぶと自動で生成されるコードになります。
今回はライティングの影響を受けないシンプルなUnlitを題材にShaderコードの中身を見ていきます。

```cs
Shader "Unlit/NewUnlitShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}
```
一度に見るとパンクするので、部分的に見ていきたいと思います。

## 卵の殻から見てみる
卵に例えて、まずは殻から見てみます。
すると意外と簡単な様子（？）

「Shader "Unlit/NewUnlitShader"」が一番外側にいます。
これは触らなくて良さそうですね。

次に中を見てみると「Properties」と「SubShader」の要素が見て取れます。
どうやら「Properties」は、このShader内で使う要素を最初に定義しているもののようです。

そしてどうやら本命は「SubShader」のようです。
ここで色々なことをうにゃむにゃやっている様子。
ということで、次にこの部分を深掘って行きます。

```cs
Shader "Unlit/NewUnlitShader"
{
    Properties
    {
       
    }
    SubShader
    {
        
    }
}
```
## 卵の中身を割ってみてみると
SubShaderの中身をブロックごとに見ていきたいと思います。

### TagsとLOD、Shaderの基本設定
SubShaderの中の一番上に書いてある「Tags」と「LOD」、これらはShaderの基本的な設定を示すものです。

Tagsはこれから書くシェーダの設定・種類を示すもので、この場合レンダーのタイプが「Opaque（不透明）」であることを示しています。ちなみに、半透明にする場合は、「Transparent（透過）」を選ぶといい感じになります。

そして次のLODは、 Level of Detail の略。ゲームなどで条件に応じてグラフィックスのクオリティを設定するために使います。一旦はスルーで行きましょう。

```cs
	Tags { "RenderType"="Opaque" }
    LOD 100
```

### Shaderの本命「Pass」
LODの下にある「Pass」の中にShaderの中身がいます。卵でいうと黄身。
この中に先程紹介した頂点シェーダやフラグメントシェーダ、もしくはサーフェスシェーダなどなど、様々なシェーダが書かれます。

今回はデフォルトで書かれている「頂点シェーダ」と「フラグメントシェーダ」を見ていきます！

```cs
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
	
        }
    }
```
### 「CGPROGRAM」？？？ 何者？
「Pass」の一番上に書いてある「CGPROGRAM」。
この冒頭にある「CG」は「C for Graphics」というシェーダ言語の略になります。

つまり、「CGPROGRAM」は「C for Graphicsの言語でプログラム書くよ！」というアナウンスです。他のシェーダ言語でも書けるらしいのですが、今は無理をせずとりあえずCG言語を使いこなすことを優先します。

そしてこの「CGPROGRAM」の終わりを示しているのが、下のほうにある「END」です。
この範囲がシェーダ言語の範囲ということのようですね。

### たくさん出てきた「#pragma」
CGPRGRAMの下に複数個ある文字「#pragma」。
これらはどんなシェーダを書くのかを指定して、関数として命名するために書きます。

サンプルで書かれているものは以下の3つ。

```cs
	#pragma vertex vert
        #pragma fragment frag
        #pragma multi_compile_fog
```
「#pragma」の次に書いてあるものでどんなシェーダかを指定して、次で関数名を定義しています。

vertexは頂点シェーダ
fragmentはフラグメントシェーダ
をそれぞれ定義しています。

3つ目の「multi_compile_fog」ですが、「multi_comile」から始まるものはシェーダバリアントと呼ばれるものらしく、意味合いが少し違うようです。今は深追いせずに、いつか戻ってきましょう。

### 縁の下の力持ち的な 「include」

「include」はその後に書いてあるファイルを呼び出せる機能です。
デフォルトでは、Unityが事前に用意してくれている「UnityCG.cginc」を呼び出しています。ここにはUnityが用意してくれたShaderを扱う上で便利な関数が詰め込まれています！

ありがたし！

```cs
#include "UnityCG.cginc"
```

### 次はシェーダの定義
これ以降の中身は細かい部分（書き方など）を一度に説明するのは今の僕には難しいので、なかでどのようなプロセスが通っているのか、にしぼって見てみます！

「struct」後に定義する名前を書いて、ブロックの中身に定義する内容を書き込んでいきます。


```cs
	struct appdata
	{
	     //頂点シェーダへの入力定義
        };

        struct v2f
        {
	     //フラグメントシェーダへ受け渡すデータの定義
        };
```


### そうしてようやく頂点・フラグメントシェーダへ
これまで長々と準備をしてきましたが、ようやく本題の頂点シェーダとフラグメントシェーダの中身を書いていく部分です。

頂点シェーダ・フラグメントシェーダ、それぞれがブロックを持っています。
先程紹介をしたステップの中で、1つ目のブロックが頂点シェーダを担当するステップ3、2つ目のブロックがフラグメントシェーダを担当するステップ7に当たります。

```cs
        v2f vert (appdata v)
        {
	     //頂点シェーダの処理（＊ステップ3）
        }
	
        fixed4 frag (v2f i) : SV_Target
        {
	     //フラグメントシェーダの処理（＊ステップ7）
        }
```

# Shaderの輪郭を掴めた！かな？？
（色々肝心なところを端折ってしまった気もしますが、）一旦これでUnityの「Unlit Shader」を開いた中身の超略解説となります！

そもそもシェーダのプロセスから確認をして、Unityのベースのシェーダコードを見てみました。
見て分かる通り、ほとんどのステップをUnity側でよしなにやってくれています！
もちろん準備は大変ですが、要所要所でUnityが自動でやってくれているので、だいぶ楽になっているかなと思います。

次回はこのUnlitシェーダをいじって何かを作ってみようと思います！

＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝
はじめての記事なので、優しく読んでもらえると嬉しいです。。
これからも自分の備忘録的な扱いで書いていきたいなと思っています。慣れるまでは・・