【Unity】UnlitShader/textureのコードを追いかける【Shader : 1】

## はじめに
この記事は連載記事です。過去に説明した扱いになっている部分については深く触れません。
記事を読むにあたって**レンダリングパイプラインでどのような処理を行っているのか？**を知っている事は前提になります。
非常に分かりやすい資料がありますので、レンダリングパイプラインを知らない方は読む前にご覧ください。

<a target="blank" href="https://www.youtube.com/watch?v=wUx_Y9BgC7k">Unity道場 2019.2 シェーダを書けるプログラマになろう #1 シェーダを理解しよう</a>
<a target="blank" href="https://www.youtube.com/watch?v=-Ki1Q2CAqEc">Unity道場 2019.2 シェーダを書けるプログラマになろう #2 GPUの神秘</a>

**アーカイブ**
<a target=”_blank” href="https://qiita.com/Nekomasu/items/6620ab19bfdaeb7743f2">Unity、Shaderことはじめ【Shader : 0】</a>
<a target=”_blank” href="https://qiita.com/Nekomasu/items/d50a4409e48ad77bc7f2">Unity、UVScroll(UVの処理)をやってみる【Shader : 2】</a>

## 概要
UnityのUnlitShaderのコードをしっかりと追いかけます。
UnlitShaderとは、陰影を付ける処理を行わず、テクスチャの色をそのまま表示する非常にシンプルなShaderです。
まずは基礎として最もシンプルなShaderをちゃんと理解する所からはじめます。

## ShaderLabとHLSLの関係

![ShaderLab.png](https://qiita-image-store.s3.amazonaws.com/0/149449/3e74b39a-f807-007e-b198-7a719ef8dcb3.png)

UnityのShaderで使われてる言語は**ShaderLab**と呼ばれています。
そしてShader本体となる部分である `CGPROGRAM` からはじまり `ENDCG` で終わるまでの間は**HLSL**で書かれています。

### HLSL
HLSLとは**DirectXで使われているシェーダー言語**です。
そもそも、DirectXとは…Microsoftが作ったゲーム・マルチメディアの処理に必要なAPIの集合です。
**APIとは何だ…？**という話ですが、APIは**アプリケーションプログラミングインターフェース**です。
端的に言って**「アプリケーションインターフェース？は？」**という感じなので、相関図を書いてみます。

![HLSL相関図.jpg](https://qiita-image-store.s3.amazonaws.com/0/149449/269a0953-d2a2-d936-d6fc-a188c0dd77c6.jpeg)
>※DirectX8の時にプログマブルシェーダー登場ただし言語がプリミティブだった。DirectX9になって言語がバージョンアップしてHLSLとして登場した。

まず、そもそも**プログラムに色を指示する命令があったとしても、ハードがそれに対応してなければ意味がありません。**
白黒のドットしか映せないハードに対して、色を表示する命令を送っても、色は表示されないわけです。
つまり、**ハードを作るにも、プログラムを作るにも、両者が対応している仕様で作る必要がある**わけですね。
そこで出てくるのが**API**というやつで…インターフェースというとわけわかんないですが**「情報をやり取りするルール」**です。

![ジャンケンAPI.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149449/ff644f8f-478a-d6a8-8801-3cfd700f534a.png)
例えば、**ジャンケン**で考えてみます。
ジャンケンで使う**「手の形と形の名称」**を決めます。これがハードの仕様にあたります。
そして、ハードの仕様が分かってるので**「グーはチョキに勝つ、パーはグーに勝つ、チョキはパーに勝つ、同じ場合は引き分け」**という風にソフトを実行すると、ジャンケンができるわけです。
この取り決めがAPIなわけです。
そして、**色々な人がジャンケンAPIに合わせてジャンケンをすると、知らない人ともお互いにジャンケンができて、勝敗を決める事ができる**というわけです。
ジャンケンAPIのライブラリを持ってない人とはジャンケンができません。この場合は相手にジャンケンのルールを教える事で、APIをインストールするという事です。

ちょっと長くなりましたが…
**DirectX(API)とはハードと、ハードを動かす為のプログラムを繋ぐ取り決め**という事です。
例えば、色に関する命令の定義とか、データ構造とかを決めて、受けた命令をハードでどう解釈すればいいのか理解できるので、ちゃんとレンダリングできるという事。

そして、DirectX7までは固定シェーダーだったのが、DirectX8でプログラマブルシェーダーになりました。
その際に　**シェーダーを命令する為の言語として作られたのがHLSL**　というわけです。（長かった）

**参考リンク**
<a target=”_blank” href="https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9">アプリケーションプログラミングインタフェース</a>
<a target=”_blank” href="https://ja.wikipedia.org/wiki/Microsoft_DirectX">Microsoft DirectX</a>
<a target=”_blank” href="https://ja.wikipedia.org/wiki/High_Level_Shading_Language">High Level Shading Language</a>

### ShaderLab
それに対して**「ShaderLabとは何だ？」**という話があります。
しれっと、UnityのShaderを選択するとインスペクターに**Shaderに必要なパラメータや、テクスチャが表示される**わけですが、それらは**Unityに依存している固有の機能**です。

```c:.shader
Properties
{
	// Unlit/texture の場合だとテクスチャ１枚だけのPropertiesが決められている。
	_MainTex ("Texture", 2D) = "white" {}

	//下記のように記述すれば、色(color)やfloatを入力にする事ができる。
	_Value ("Value", Range(0.0, 1.0)) = 0
	_Color ("Color", Color) = (0, 1, 0, 1)
}
```

この`Properties`の部分は、記述するとテクスチャやパラメータをshaderに必要な入力として受け取れるようになります。
ここがShaderLab シンタックスと呼ばれる領域で…要するに**HLSLだけではUnityのエディタからパラメータや、テクスチャなどの入力を渡せない**のをHLSLを拡張したShaderLabにすることで、より取り回しをよくしているわけです。

Propertyの他にも…下記のように色々あります。※まだまだあります。

```c:.shader
	//ポリゴンのカリングの設定
	Cull Off

	//デプスバッファテストモードを設定
	ZTest Greater

	//デプスバッファ書き込みモードを設定。
	ZWrite On
```

HLSLを含んでShaderLabですが、**HLSLとShaderLab固有の機能の決定的な違いは、HLSLはプログラマブルな制御に対して、ShaderLabの機能はあくまで設定を記述しているだけ**という点です。
例えば、**HLSLの結果を使って動的にPropertyを変更する**事はできません。
一応、C#からはPropertyを変更する事はできますが、これはあくまで外から設定を変えてるだけであって動的な処理ではありません。

本来、バーテクスシェーダーとフラグメントシェーダーを指す意味でのシェーダーが制御できる範囲を超えて、shaderLabでは色々な設定をできるようにしてくれているわけです。
これらの設定についてはshaderの責任範囲外でありますが、現実的に運用するにあたってほぼほぼ必要になりますし、shaderを決める側としても設定できた方が便利なわけです。

**参考リンク**
<a target=”_blank” href="https://docs.unity3d.com/ja/current/Manual/SL-Shader.html">ShaderLab シンタックス</a>

## HLSL スニペット
![pragma.png](https://qiita-image-store.s3.amazonaws.com/0/149449/636faf58-ba31-28b6-0866-a3f0fcf38b70.png)
HLSLとShaderLabの違いが分かった所で…本体となるUnlitShaderのHLSLを追っかけていきます。

まず最初にあるのが `#pragma` ではじまる部分です。
**HLSLスニペットと呼ばれる部分にコンパイラーディレクティブ**を書いてるらしいですが…重要なのは、ここの役割です。

```c:.shader
#pragma vertex hogevertex
#pragma fragment hogefragment
```

とした場合…
hogevertexという名前の関数はバーテクスシェーダーとして解釈され、hogefragmentという名前の関数はフラグメントシェーダーと解釈されます。
この部分では**「どの関数をどう解釈するか」**という事を宣言しています。
デフォルトのUnlit/Textureでは**fragがフラグメントシェーダー**として**vertがバーテクスシェーダーとして**宣言されています。
それによって、**レンダリングパイプラインの実行時に、レンダリングを行う側がそれぞれのタイミングで関数を呼び出します。**
<a target=”_blank” href="https://docs.unity3d.com/jp/current/Manual/SL-ShaderPrograms.html">HLSL スニペット</a>

その下にある `#include "UnityCG.cginc"` は関数の読み込みです。
`float4` とか `UnityObjectToClipPos` というオブジェクト空間からカメラのクリップ空間への変換を行ってくれる関数などがあります。
この読込をすると、そのプログラム上ではUnityCG.cgincに書かれている様々な関数や変数が使えるようになります。
<a target=”_blank” href="https://docs.unity3d.com/ja/current/Manual/SL-BuiltinFunctions.html">ビルトインシェーダーヘルパー機能</a>
<a target=”_blank” href="https://docs.unity3d.com/ja/current/Manual/SL-BuiltinIncludes.html">ビルトインのシェーダー include ファイル</a>

## UnlitShaderの処理

**HLSLの部分**
![unlit.png](https://qiita-image-store.s3.amazonaws.com/0/149449/8bdc0bbb-c7a1-69d4-f350-21df581fed7e.png)
**めちゃくちゃ読みづらい！**の申し訳ない。

**上記の処理をイメージ図化**
![unlit.png](https://qiita-image-store.s3.amazonaws.com/0/149449/6eafc3bc-7000-66ae-9380-12b453a98b36.png)

```c:.shader
sampler2D _MainTex;
```
処理を追いかける前に、この部分。
先ほど、ShaderLabとHLSLの違いについて触れましたが、当然レンダリングする時にHLSLの中にテクスチャの情報が含まれてないといけないわけです。
なので、**Propertyで割り当てた値はちゃんとHLSLの中で再度宣言する必要**があります。(バーテクスシェーダーとフラグメントシェーダーの処理より上に書いた方がいいです。)
ShaderLabはあくまでUnityが操作できるにようにしてる設定値であって、**レンダリングの処理に必要な事はHLSLの中に全部書かないといけない。**というのをお忘れなく…。

そして、レンダリングの処理は主に４つの段階に分かれていると思います。

１．**struct appdata** では、下記の情報を保存する構造を定義します。
　　`float4 vertex : POSITION` で「頂点の空間座標」
　　`float2 uv : TEXCOORD0` で「頂点のUV座標」

２．**struct v2f** では、下記の情報を保存する構造を定義します。
　　`float4 vertex : SV_POSITION` で「頂点のクリップ座標」
　　`float2 uv : TEXCOORD0` で「頂点のUV座標」

※appdataの時は、純粋にモデルの形だけが保存されてますが v2f ではモデルをカメラで写した時のスクリーン上での座標に変換されるわけです。
v2fはバーテクスシェーダーでワールド座標からスクリーン上の座標に変換された後の構造という事です。

３．そして**バーテクスシェーダーで appdata を v2f に変換**しフラグメントシェーダーに渡します。

４．最後に、**フラグメントシェーダーはバーテクスシェーダーから受け取ったv2fの情報を使ってピクセルの色を決めてます。**

疑問点になりうる所を解説してゆきます。

**「struct appdata の中でfloat4は配列じゃないのにオブジェクトの情報が保存できるの？」**という所ですが `float4 vertex : POSITION` と表記される事で、**ハードウェア側で**ただのfloat4ではなくオブジェクトの頂点座標と解釈されるます。
そして、実行時には**float4はオブジェクトの全ての頂点配列**として保持されます。
UVについても同様です。

このように…**変数に意味・役割をつける事で、ハードウェアに変数に意味・役割を伝える構文**をシェーダーセマンティクスといいます。
<a target=”_blank” href="https://docs.unity3d.com/ja/current/Manual/SL-ShaderSemantics.html">シェーダーセマンティクス</a>

次に**「フラグメントシェーダーがどうしてピクセルの色を塗れるの？」**という所ですが…
まず、バーテクスシェーダーから渡されるv2fには、**スクリーンにおけるオブジェクト(頂点)の位置と、各頂点が参照しているUV座標**が保持されています。
なので、スクリーンで色を塗るべき位置は、頂点の情報を参照すれば分かりますし、**テクスチャの色は頂点が参照しているUV空間**を見てくればいいわけです。
（フラグメントシェーダー実行→v2fの頂点位置→頂点が持ってるUV情報→UV空間にアクセス、テクスチャの色が参照できる）

![fragment.png](https://qiita-image-store.s3.amazonaws.com/0/149449/6844ff93-d540-10b6-036a-171c4675f122.png)

**※FOG、LOD、Tag について触れてませんが、あまりUnlitShaderの処理そのものとは関係ない部分なので省略します。**

## おわり
これで、１番基本と思われる UnlitShader/texture を読み終えました。
DirectXの本とか見てるともっと大変そうなので、3Dのレンダリングは相当大変な事なんだなと…。

次回以降は、UnlitShaderをベースにカスタムなんかできたらいいなと思います。

## 次
<a target=”_blank” href="https://qiita.com/Nekomasu/items/d50a4409e48ad77bc7f2">Unity、UVScroll(UVの処理)をやってみる【Shader : 2】</a>