Shader入門メモ


Shader基礎

・UnityにおけるGPU制御処理をシェーダープログラミングと呼んでいます。

・シェーダープログラミングを習得するためには、「行列とベクトルの数学知識」「3DCGの基礎知識」「レンダリングパイプラインの仕組み」など広範囲を学ぶ必要がある。

・レンダリングパイプラインとは、GPU内で頂点情報が色情報に変わるまでの工程をいいます。レンダリングパイプラインは複数のシェーダーステージから構成されている。

・レンダリングパスとは、実際にどのタイミングでどんな演算を行うかのソフトウェア上の機能の流れを示すものがある。

・プリミティブとは、複数の頂点から構成されるポリゴンの最小単位のこと。ゲームでは通常。3つの頂点から構成される三角形のプリミティブを使用している。

プリミティブは厳密に表と裏が決まっていて、カメラに対して裏向きになっているプリミティブは画面に表示しない。

・uv座標とは、あるモデルのプリミティブが、そのモデルに貼り付けるテクスチャのどの部分に相当するのかは、そのモデルのデータ内にマッピング情報として同梱されている。これを「uv情報」「uvテクスチャマップ」 と呼びます。

・uvというのは、このテクスチャへのマッピングをuv座標系で指定することに由来します。uvテクスチャマップはモデルごとに複数持つことが可能で、Unityでは一つのモデルにつき4個まで保持できる。



ライティング

・Foward Renderingはレンダリングパスの種類の一つ。

全てのオブジェクトについて、シーン内にあるライト一個ごとに一回レンダリングします。例えば、シーン内にライトが4つあれば同じプリミティブが4回レンダリングされる。

・Pass

上記の1回分のレンダリング処理のことを呼びます。ユーザーは個々のPassをシェーダーコードをプログラミングできます。

Passが実行されるたびにフレームバッファに値が書き込まれます。次のPassでは前のPassの結果を踏まえて値を更新していき、それによって全てのライトの効果が反映される。

・あるオブジェクトを複数のライトが照らしている時、それらのライトはオブジェクトに対して照度の高い順に3種類に分類されます。

種類
その種類に分類されるライトの個数

ピクセル単位ライト
環境設定のPixel Light Countで指定された数(最大)

頂点単位ライト
4つ(最大)

SH(球面調和)ライト
上記以外全て

例えば、オブジェクトに影響しているライトが9個あり、それらのライトをオブジェクトへの照度が強い順にA、B、C、D、E、F、G、Hとした場合、それぞれのライトは以下のように分類されます。

・ピクセル単位ライト:A B C D

・頂点単位ライト:D E F G

・SH(球面調和)ライト:G H

この重複は近似値の使用によって見た目が変らないように、ライト間の値をなじませる為の処置になります。

・ピクセル単位ライト

ライトに応じたPassが実行されます。Passには「Foward Base Pass」と「Foward Add Pass」の2種類があり、オブジェクトにもっとも強い効果を発しているピクセル単位ライト1個に対して「Foward Base Pass」が実行され、それ以外のピクセル単位ライトに対しては「Foward Add Pass」がライト1個につき実行されます。

・頂点単位ライト

これに分類されたライト情報は、「Foward Base Pass」実行時に取得できます。頂点単位ライトは最大4個まで設定され、その個々の情報を取得できます。

・SH(球面調和)ライト

頂点単位ライトよりも効果が小さいライトは、それら全てを1つに合成して、球面調和関数という特殊な式の係数に変換します。これをSHライトと呼びます。

SHライトの情報はFoward Base Pass実行時に、取得できます。複数のライトの情報をひとまとめにしている為に精度は落ちますが、非常に高速に動作する。


Shaderの構文

Shader "Unlit/NewUnlitShader"

{
Properties
{

}
SubShader
{

        }
        
        FallBack "ShaderName"
        
        CustomEditor "EditorName"
}

・Properties宣言

Inspectorビューに表示する項目



Cg/HLSLについて


構文仕様

シェーダーコードでは、関数の仮引数や戻り値を介してアプリケーションやGPUと値をやりとりします。シェーダーからGPUに値を返す時、通常は戻り値を使うが、仮引数を介してGPUに値を返す事もできる。

仮引数名にin/out/inout演算子を付与することで、どの引数が入出力のいずれかで使われるかを明示できる。

演算子
説明

in
引数を入力として扱う(デフォルト。)

out
引数を出力として扱う

inout
引数を入出力として扱う(GPUから受け取る値と返す値が同じ要素(セマンティクス)を指す場合に使用できる)

float4 frag(in float a, out float b, inout float c) {

}


データ型

テクスチャサンプル型

シェーダープログラミングでテクスチャのデータを参照する場合、テクスチャサンプル型という特殊な型を使用します。

テクスチャサンプル型は、シェーダーコードの中では生成できず、プロパティを通じてのみ受け取ることができる。


ベクトル型と行列型

シェーダープログラミングの中で行われる演算のほとんどは「ある座標Aに、変換行列Bを左辺から乗算して、座標Cに変換する」というものになる。このような演算を「座標変換」と呼ぶ。

上記の説明の中に登場する「ある座標A」は4要素からなるベクトル。

「変換行列」は4行4列の行列表すのが一般的で、GPUはこの2つの要素を乗算するのにハードウェア設計を最適化しています。そのため可能な限り、GPU上で行う演算は「4行4列の変換行列 × 4要素のベクトル」という形で記述するのがいい。


行列型

複数のベクトル型をひとつにまとめた物が行列型です。シェーダープログラミングでは、主に座標を変換するための変換行列を格納するために使用します。

行列型は、スカラー型(float、halfなど)の名前の後に"A×B"という形式の識別子を付与することで表します。

Aは行数、Bは列の数を指し、それぞれ1から4まで任意に指定できます。

float1×4 test1;  // 1行4列のfloatからなる行列型 

float4×2 test2; // 4行2列のfloatからなる行列型

座標変換に必要な行列型はfloat4×4が主です。


行列スウィズル型演算子

行列の各要素へのサクセスには行列スウィズル演算子を使用する。

行列スウィズル演算子は、".(ドット)"の後に"_mAB"という文字列を設定する。Aは行数、Bは列数を示し、それぞれ1から4の数字を設定します。

float4の行列変数から3行2列の要素を取得したい場合以下のように記述します。

float myFloatScalar = myMatrix._m32;

行列スウィズル演算子は連結して記述できる。以下では、myMatrixから4つの要素を取得し、float4に格納しています。

float4 myFloatVec4 = myMatrix._m00_m11_m22_m33;


プロパティから変数へのマッピング

ShaderLab言語のPropertiesブロックで宣言したプロパティを経由して、アプリケーションからシェーダーコードに値を渡す場合は、プロパティと対応する変数をCg/HLSL内で宣言する必要がある。

以下のようにPropertiesブロックで宣言した場合、

_Color ("Color", Color) = (1,1,1,1)

_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
_Snow("Snow", Range(0, 2)) = 0.0

シェーダーコードで以下のように変数を宣言すると、同名のプロパティに設定された値が、各変数に自動的にマップ(対応付け)され、参照できるようになる。他のメソッド内とかで使用できる。

fixed4 _Color;

sampler2D _MainTex;
half _Glossiness;
half _Metallic;
half _Snow;


セマンティクス

各シェーダーステージは、GPUから複数の値を受け取り、それを演算して結果として出力される複数の値を再びGPUに返します。例えば頂点シェーダーでは、GPUから頂点のモデル空間座標を受け取り、それをクリップ空間座標に変換してGPUに返しています。

GPUとシェーダーがやりとりする値には幾つもの種類があり、シェーダーコードによって必要とする物が変わってきます。GPUとシェーダーコードが効率的にやりとりするために、Cg/HLSLでは「セマンティクス」という仕組みが用意さえている。

セマンティクスは、Cg/HLSL内の変数宣言に定義済みの名前を付記することで、その変数がGPUから値を入力される、あるいは、GPUへ値を出力する役割を持つことを明示します。

このように変数に役割を結びつける(バインディングする)仕組みを「バインディングセマンティクス」と言います。

バインディングセマンティクスは「関数の仮引数」、「関数の戻り値」、「構造体の変数」に対して行える。

セマンティクスには「入力セマンティクス」、「出力セマンティクス」の2種類があり、あるセマンティクスが入力/出力のどちらでバインディングされるかは、そのセマンティクスに結び付けられた変数がin/out/inoutのいずれに相当するかに依存する。


頂点シェーダー

全てのポリゴンの頂点ごとに、#pragma vertxtで定義された頂点シェーダー関数が呼び出される。

頂点シェーダー関数の主な役割は、入力されたモデル空間座標をワールド空間座標 -> ビュー空間座標 -> クリップ空間座標に変換して出力することにあります。この座標変換処理は行列とベクトルの乗算を複数回行うことで実現している。

頂点シェーダー関数では、頂点座標の他に、テクスチャのUV座標や、頂点を照らしている光源を計算した結果のライティング情報などをレンダリングパイプラインに出力できます。

ある変数にバインディングされたセマンティクスが入力と出力のどちらの扱いになるかは、その変数が頂点シェーダー関数の入力に使われるか、出力に使われるかで決まります。

mul(UNITY_MATRIX_MVP, v.vertex) // 第二引数の頂点座標を第一引数の指定によりモデル空間座標からクリップ空間座標に変換する。