シェーダープログラミングしない人のためのシェーダー入門


# 前書き
ソーシャルコンピューティング研究者の@Yam3939です。
[ACCESS Advent Calender 2022年](https://qiita.com/advent-calendar/2022/access)の15日目へ投稿します。
昨日は、トニオ（@tonionagauzzi）さんのAndroid アプリ UnusedAppFinder をリファクタリングした話でした。
この記事は社内勉強会用に準備していた内容をマークダウンに書き直しました。

最近、これからWeb3だ、メタバースという声に誘われて3次元のレンダリングの勉強を始めました。その中に「シェーダー」の勉強もあります。
シェーダーという言葉は聞いたことがあるが、それが何をするかはまるでわからないという人のために、シェーダー入門の話をします。

# 想定する読者
・UnityもUnreal EngineもBlenderも触ったことがない人
・ColaboratoryでGPUオンにする以外 GPUを触ったことがない人
・PCにグラフィックカードを載せたことがない人
・3Dモデリングに縁がない人
・でもシェーダーって何ということに興味がある人

# シェーダーって何？
## シェーダーの定義はいろいろ
とりあえず調べたところでは人によってピンキリのようです。例えば：
- 物体の見栄えを決めるプログラム
- 陰影処理（シェードは影）
- GPUのプログラム（音のシェーダもある）

結局、私の現在の解釈としては：
- 最初は陰影を付けるのが計算量的に難しかったのでGPUに任せた。陰影のつけかたは多様なのでプログラマブルにした。GPUがプログラマブルになった瞬間に影だけでなくていろいろなイフェクトをつけられるようになった。GPUの進化と普及とともに「シェード（陰影）」はごくごく一部分になっている。

とはいえ、すべてのGPUプログラミングをカバーすることはできないので、とりあえず、ゲームエンジンのUnityでのシェーダーって何、というのを勉強していきます。

## シェーダーとは何でないか？
シェーダーでないものを知ることによってシェーダーとは何かを知ることができます。
- 直線があったときにピクセル上でかくかくしないようにするのはシェーダー以前で対応する
- 物体の3Dモデルに対して、カメラの位置を決め、光をあてて、ピクセルの色を決めるのがシェーダー

## シェーダーはプログラム
- Unityで見た目を作るためにモデルに付け加えるいろいろなプログラムがシェーダー。UnityのAPIを使っていろいろプログラムする。
- マテリアル、テクスチャーとともに見栄えを制御（下図参照）
![shader20221214a.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323085/1db5395a-c251-703d-d6c3-7b07d6a738e9.png)
- グラフィックAPIを読んでプログラムするのがシェーダー
![20221214qiita-b.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323085/dfad63d0-f259-408e-bac7-1a9c8d812271.png)

グラフィックAPIとはDirectX, OpenCV, Metal, Vulkan, ...というライブラリ群が提供するものです。当然、当該デバイスが対応するライブラリをサポートしていなければAPIを呼んでプログラムしても動きません。

## レンダリングパイプラインからデバイスまで流れは
レンダリングパイプラインからデバイスまでの流れは下図のとおりです。
![20221214qiita-c.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323085/424ebe94-285e-89e1-2b40-b7e6d3cea7eb.png)
デバイスで対応のAPIをサポートしていないとAPIを書いても動きません。
マルチデバイス用にはサブシェーダーと言って
シェーダー内にAPI用にそれぞれシェーダーを書いたりします（実行可能なものだけが実行されます）

# Unityのレンダリングエンジンとシェーダー
## Unityのレンダリングパイプライン
Unityのレンダリングパイプラインは3つあって、下図の通りです。
![20221214qiita-d2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323085/4ccf0211-df23-3d18-2246-d27337b45934.png)
ユーザがシェーダーをかけるのはUnity SRP (Scriptable Rendering Engine)と言います。紛らわしいですが、これはレンダリングエンジンのクラスの呼称です。 Unity のSRPは上図のURP, HDRPです。
Unity 2019.3でLW RP(Light Weight)がURPという名前になりました。今後は URPが推奨されます。BRPの機能が今後URPで実装されるようです。

## Unityのシェーダーグラフ
シェーダーグラフというのはグラフ表現でシェーダーの機能を実現するUIです。
グラフィックUIでデザイナーがシェーダーを使えるようにしたUnity上のシンタックスシュガーです。
例は下図のとおりです。
![20221214qiita-e.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323085/ca1f7b16-40b1-3e36-299e-ed4e957774f6.png)
出典: https://unity.com/ja/features/shader-graph

以下の例のようにノードとノードをむすぶさまざまな機能が提供されています。
- One Minus 0-1を反転
- Step　閾値
- Remap 範囲調整
- Time 経過時間
- Multiply 掛け算
- Lerp　線形補間
・・・関数の数だけグラフがある：プログラマには苦痛に見えます。もっとも自分でシェーダープログラムすればそれはそれではまる予想ができます。
関数毎にノードが用意されているので面倒で、デモ動画でも
グラフが錯綜して何度も整理することになっていました。

## 3Dモデルのレンダリングの全体像とシェーダーの位置づけ
- ステップ１：3Dモデルを準備（描画とはピクセルに点を打つこと）
- ステップ２：Transformの値を4x4に変換（モデル行列を渡す、グローバル座標に変換）
- ステップ３：頂点毎に描画位置を算出（モデル、モデル行列、カメラで決まる）
- ステップ４：表裏を調べ、裏なら描画しない（3点の右回り、左回りで表が決まる）
- ステップ５：描画点を確定（頂点以外の点が3頂点の中に入っているかを決める）
- ステップ６：描画点を深度バッファと比較（手前に点があれば書かない）
- ステップ７：色を確定（ライティング、テクスチャ、シャドウ、フォグ）
- ステップ８：点を打つ（ブレン関数を適用可、半透明、深度バッファ更新）
ステップ１、２はCPU、ステップ３から８はGPUの仕事です。
ステップ３が頂点シェーダーで、
ステップ７がフラグメントシェーダーの仕事です。

## 頂点シェーダーの例
ステップ３：頂点毎に描画位置を算出（モデル、モデル行列、カメラで決まる）
シェーダーといっても頂点の計算をするだけ：
```
UnityObjectToClipPos(v.vertex); //頂点の座標をクリップ空間での座標に変換
fixed4 col = tex2D(_MainTex, i.uv);
```
このあと、カリング、ラスタライズを行います。
- カリング：隠れて表示されないものを取り除く
- ラスタライズ：3頂点の間にピクセルを埋める

## フラグメントシェーダーの例
ラスタライズの後、フラグメントシェーダーを実行します（
DirectXではピクセルシェーダーというらしい）。

ステップ７：色を確定（ライティング、テクスチャ、シャドウ、フォグ）
- シェーダーの名前を付け
- Propertiesを宣言し
- SubShaderでPassを定義（複数あってもいい）
-- Passはシェーダーのマテリアルとレンダリングされるオブジェクトの頂点コードとフラグメントコードの実行を表す
-- CGPROGRAM .. ENDCGで囲まれる
フラグメントシェーダーの例は以下の通り（出典： https://docs.unity3d.com/ja/2019.4/Manual/SL-VertexFragmentShaderExamples.html）
```
Shader "Unlit/SimpleUnlitTexturedShader"
{
    Properties
    {
        [NoScaleOffset] _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            // "vert" 関数を頂点シェーダーとして使います
            #pragma vertex vert
            // "frag" 関数をピクセル (フラグメント) シェーダーとして使います
            #pragma fragment frag

            // 頂点シェーダー入力
            struct appdata
            {
                float4 vertex : POSITION; // 頂点位置
                float2 uv : TEXCOORD0; // テクスチャ座標
            };

            // vertex shader outputs ("vertex to fragment")
            struct v2f
            {
                float2 uv : TEXCOORD0; // テクスチャ座標
                float4 vertex : SV_POSITION; // クリップスペース位置
            };
        // 頂点シェーダー
            v2f vert (appdata v)
            {
                v2f o;
                // クリップスペースへの変換位置
                // (モデル*ビュー*プロジェクション行列で乗算)
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                // 単にテクスチャ座標を渡します
                o.uv = v.uv;
                return o;
            }
            
            // サンプリングするテクスチャ
            sampler2D _MainTex;

            // ピクセルシェーダー; 低精度を返します ("fixed4" 型)
            // color ("SV_Target" セマンティック)
            fixed4 frag (v2f i) : SV_Target
            {
                // テクスチャをサンプリングして、それを返します
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

```

## シェーダーの暗黙の了解
プログラムしている人が作法に従っていればこうなります。
作法に従っていれば何をやっているかを推定できます。
- Unlit 光を考慮しない
- Lit 光を考慮するのは当然なのでわざわざつけることはない
- Diffuse 拡散。ざらざら。てかてかしていない。
- Specular 鏡面反射。反射光も調節できる。
- Transparent 透明。透明にするのは処理が重い。
- Cutout 　切り取り。切ったところは透明にするので重い
- Emission　発光。発光するのには準備が必要。
- Bumpt でこぼこにする。実は平坦。ポリゴン増やさない工夫。
- Toon　アニメ調、コミック調
動くところを見たい方はこちら　→　[実例→Unity道場2D編 Shader Graph はじめてみよう！基礎編（6月23日号）- Unityステーション](https://www.youtube.com/watch?v=3TXHMS8xFJ0)

## シェーダーグラフによるエファクトの例
![20221214qiita-f.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323085/04b48f30-55ad-f249-a58d-56f35f97c245.png)

出典: Unityとシェーダーグラフで簡単なポストエフェクトを作ってみよう！（チュートリアル動画 #1） https://www.youtube.com/watch?v=AQGgwgo51lo 14m09s
動いていないと面白くないので
動いているところを見たい方は上記YouTubeをご参照ください。

## シェーダーとゲームの連結
シェーダーで描いたものをゲームに組み込む手順は下図のとおり
![20221214qiita-g.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323085/8aad4da2-1706-e6ad-1d7c-76ef2e7e5106.png)
トランジションとか（次のレベルへ行く）にエフェクトをかけてかっこよくします。

出典: 
- Unityとシェーダーグラフで簡単なポストエフェクトを作ってみよう！（チュートリアル動画 #1） https://www.youtube.com/watch?v=AQGgwgo51lo 14m09s
- Unityとシェーダーグラフで凝ったポストエフェクトを作る！（チュートリアル動画 #2）https://www.youtube.com/watch?v=Ylq2y6Qez3s 15m57s 



# その他のレンダリングエンジンの紹介
## Unreal Engine
Unreal Engineの最新版はUnreal Engine 5です。
最新の機能は以下の2つです。
![20221214qiita-h.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323085/1c89fd19-7c8d-44c9-144b-a5058e7d4e35.png)
Naniteは仮想化マイクロポリゴンジオメトリを使い3Dモデルを効率的かつ高速にレンダリングする高性能なシステムです。

Unreal Engineのレンダリングパイプラインは以下のとおりです。
![20221214qiita-i.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323085/c624aeb8-42e2-91bd-ec60-cf4b42419a5b.png)
出典:https://blog.jp.uwa4d.com/2021/11/05/ue5%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0%E3%81%AE%E7%B4%B9%E4%BB%8B%EF%BC%9Ananite%E7%B7%A8/

GPUの進化に合わせてG-buffer, Visibility-bufferの2つを使って
レンダリングパイプラインを構成しています。

これについてはシェーダーを勉強するところまで到達しませんでした。

## Blenderのレンダリングパイプライン
Blenderのレンダリングパイプラインは下図に示すように3つです。
![20221214qiita-j.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323085/e13ad42c-7826-2953-0e06-d613af62ae2e.png)

レンダリングはレイヤとパスに分割され、複数組み合わせたコンポジットが提供されています。
サードパーティからAdd onとしてレンダラーが提供されています。例えば、
Turbo Tools V2 (Cycles高速化Add on)です。

Blenderのシェーダーは：
- サーフェスシェーダーは物体の面を扱う
- ボリュームシェーダーは内部空間の密度を扱う
- 特殊シェーダーは面ではなく全体や結果に作用する
具体的には下図に示すようなものがあります。
![20221214qiita-k.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323085/038acfe9-998c-cb06-a5f8-e07e2bc879d1.png)

これも今回はシェーダーの詳細には至りませんでした。

# むすび
シェーダープログラミング未経験でシェーダーについて調べてみました。
ブラウザのエンジンとゲームエンジンは似通ったところがあるので、今後、メタバースのリアルワールドエンジンもこれらの延長上に作られるのではないかと思います。
引き続き、勉強していきたいと思います。

明日は@aRyoKurodaさんです。

# 参考文献
- Unityとシェーダーグラフで簡単なポストエフェクトを作ってみよう！（チュートリアル動画 #1）
 https://www.youtube.com/watch?v=AQGgwgo51lo 14m09s
- Unityとシェーダーグラフで凝ったポストエフェクトを作る！（チュートリアル動画 #2）https://www.youtube.com/watch?v=Ylq2y6Qez3s 15m57s 
- Unityとビジュアルスクリプトを使ってポストエフェクトを組み込んでみる！（チュートリアル動画 #3）
https://www.youtube.com/watch?v=E_oDKQU3e94 17m15s
- 第1回 レンダリングパイプラインとUnityシェーダ【Unityシェーダプログラム入門】　https://www.youtube.com/watch?v=8WsWNYNUyeE 23m57s
- 第2回 ShaderLabでUnityシェーダを書く【Unityシェーダプログラム入門】
　https://www.youtube.com/watch?v=602VNAdZZe4 24m58s
- Unityのマテリアルとシェーダー【初心者入門講座】【ゲームの作り方】#11
 https://www.youtube.com/watch?v=8EjRH89MjVs 10m26s
- シェーダを書けるプログラマになろう #1 シェーダを理解しよう - Unity道場2019 2月 https://www.youtube.com/watch?v=wUx_Y9BgC7k 25m23s
- シェーダを書けるプログラマになろう #2 GPUの神秘 - Unity道場2019 2月
　https://www.youtube.com/watch?v=-Ki1Q2CAqEc 21m41s
UIもshaderで盛る！ 〜 shaderとanimationで作るリッチなUI演出
https://www.youtube.com/watch?v=T_dYC4LvFJk  15m03s