Unityシェーダーチュートリアル Vertex/Fragment Shader の基本
シェーダー
今回はもう一つの記述形式である Vertex/Fragment Shader について解説していきます。
字が多めなので頑張って下さい。
Unityにおける Surface Shader と Vertex/Fragment Shader の大まかな使い分けは、
ライトの影響を受けるモノには Surface Shader で、受けないモノには Vertex/Fragment Shader です。
が、勘違いしてはいけません。「Surface Shader > Vertex/Fragment Shader」ではなく、
むしろシェーダーの基本は Vertex/Fragment の方です。(以降 V/F Shader と書きます)
V/F Shader でもライトやリフレクションプローブの影響を与えることは可能です。
と言うより、Unityの内部的に Surface Shader は全て V/F Shader に自動変換されています。
様々な環境の影響を受けるシェーダーは、それなりの知識を持ってゴリゴリと書く必要があり、
とてもシェーダー初心者に太刀打ちできるものではありません。
そこで、初心者でも比較的簡単にシェーダーが作れるように、
Surface Shader という仕組みが用意されています。
筆者も、エフェクトやUI,簡単なポストプロセスを作りたい時に、
ちょびっと V/F Shader を使う程度です。
Unityにおける V/F Shader の基本構造
プロジェクトウィンドウでシェーダーを新規作成する際に、Unlit Shader を選択すると
V/F Shader の雛型が出来上がります。
ただし、この雛型コードの中にはフォグの影響やUVのオプションが最初から組み込まれていて、
少しだけ難しくなっています。
可能な限りミニマムな方が分かりやすいと思うので、
それらのオプションを外して、最軽量な状態の V/F Shader から見てみましょう。
(投稿時に使用した Unity のバージョンは 5.6.0f3)
Pass
まず最初に Surface Shader と違うところは、
CGPROGRAM ~ ENDCG を Pass { } で囲んでいるところです。
囲まなければエラーになります。何故と思わずとりあえず囲んでおきましょう。
#pragma vertex / #pragma fragment
Surface Shader の時は、こうでした。
#pragma surface surf Standard
V/F Shader の時は、こう書きます。
#pragma vertex vert
#pragma fragment frag
Vertexシェーダー に vert関数を、
Fragmentシェーダー に frag関数を使いますよ、と言う指定です。
コードの下の方を見ると vert 関数と frag 関数が記述されています。
(勿論、vert frag の名前を変えても問題ありません。)
V/F Shader の処理の流れ
だいぶ語弊があるかもしれませんが、V/F Shader の流れをかなりざっくり言うとこんな感じです。
① appdataモデルが持っているどの情報を使うのかを指定。
② v2f描画のために必要な情報を指定。
③ vertv2fで指定した情報を計算・確定。
④ fragvertで確定したv2fの情報をもとにピクセル描画
簡単で直観的に伝えたいのですが、なかなか難しい・・・
今回のミニマム V/F Shader で例えるとこんな感じ?
① appdataモデルの頂点座標を参照するよ。
② v2fOK。頂点座標だね。準備は良いかい、vert君?
③ vertなるほど。カメラから見た時の画面上の頂点位置はここだぜ。
④ fragよしきた。じゃぁ、その中を塗りつぶすよ。
みたいな・・・
appdata
描画にあたり、モデルのどの情報が必要なのかを指定します。
今回のミニマムコードでもそうですが、頂点座標は必須です。
描画したいのに頂点が無いのでは話になりません。
struct appdata {
float4 vertex : POSITION;
};
: POSITION が頂点座標だよ、という意味で、(セマンティクスと呼ばれています)
vertex がそれを格納する変数です。(絶対にfloat4)
セマンティクス名は自由に変えられません。座標を取得したい場合は絶対に : POSITION です。
変数名は何でも良いです。
ちなみに Unity4 の時のデフォルトはこうでした。
struct appdata {
float4 pos : POSITION;
};
頂点座標以外にもUVとか法線とかいろいろと取得できますが、それはまた後日。
v2f
v2f と言うのは、 vertex から fragment へ という意味でしょう。きっと。
描画時に必要な情報を指定します。
ほとんどの場合、appdata の中身と同じ感じになります。「Aだよ」「Aだね」みたいな。
struct v2f {
float4 vertex : SV_POSITION;
};
appdata と同様に、頂点座標の指定は必須です。
ただし、ここでは : SV_POSITION となっています。DX11互換対策らしい。
「ふ~ん」と思ってこう書いてください。
変数名は何でも良いです。appdata の 変数名と合わせる必要もありません。
vert
ここが Vertexシェーダー です。頂点情報をもとに ゴニョゴニョ します。
v2f vert (appdata v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
変数・関数名をデフォルトと同じにしている限り、赤ハイライトの部分は定型です。
その間に、必要な情報の計算を入れます。
今回の例では頂点の座標位置を決定しているのみです。
o.vertex = UnityObjectToClipPos(v.vertex);
この一文も定型だと思って、そのまま記述してください。
これを説明するには、行列変換が云々とかなり難しい話になってしまいます。
そんなことはエンジンの開発者に任せて、我々はおまじないだと思って、ただコピペすれば良いのです。
ちなみに、やたらと vertex の文字が出てきて混乱していませんか?
デフォルトでは appdata でも v2f でも vertex という変数名なので区別にしくいですね。
敢えて変数名を変えて書くとこんな感じです。
struct appdata {
float4 input : POSITION;
};
struct v2f {
float4 output : SV_POSITION;
};
v2f vert (appdata v) {
v2f o;
o.output = UnityObjectToClipPos(v.input);
return o;
}
① appdataモデルの頂点座標はここだよ。(input)
② v2fOK。頂点座標が必要なんだね。じゃぁ、vert君。計算結果をここに入れてね。(output)
③ vertなるほど。・・・ほら、画面上の位置が計算できたぜ。(input → output)
frag
最後に、Vertexシェーダーで計算された情報を利用して、
該当ピクセルを塗りつぶす役割の Fragment シェーダーが働きます。
Vertex シェーダーによって、画面上の頂点位置が特定されたので、
そのエリア内を塗りつぶします。
fixed4 frag (v2f i) : SV_Target {
return _Color;
}
変数・関数名をデフォルトと同じにしている限り、赤ハイライトの部分は定型です。
そのまま記述してください。
そして、Surface Shader とは違い、単純に RGBA を return します。
今回の例では、Propaties で設定した _Color で、単純に塗りつぶします。
おわりに
最軽量な V/F Shader を見てきましたが、なんとなくの流れはつかめましたか。
ここに、テクスチャ/UVや頂点カラーなどを足していって、より複雑なピクセル描画を作っていきます。