Shader, VRChat, 備忘録
Shaderを理解したい。Pass編
Shaderを理解したいシリーズ2です。
Passにいろんな処理を書いてそうなのだがいまいちつかめない。 一体なんのためにあってなにをしているのだろうか。 我々はジャングルの奥地へと進んだ。
GPUに対して「このオブジェクトを1回どう描くか」を命令する単位
前回のワイヤーフレームシェーダーのPassをもとに確認していく。
Pass
{
CGPROGRAM
#pragma target 4.0
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
#include "UnityCG.cginc"
float4 _WireColor;
float4 _BaseColor;
float _WireWidth;
struct appdata
{
float4 vertex : POSITION;
};
struct v2g
{
float4 pos : SV_POSITION;
};
struct g2f
{
float4 pos : SV_POSITION;
float3 bary : TEXCOORD0;
};
v2g vert(appdata v)
{
v2g o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
[maxvertexcount(3)]
void geom(triangle v2g input[3], inout TriangleStream<g2f> stream)
{
g2f o;
o.pos = input[0].pos;
o.bary = float3(1, 0, 0);
stream.Append(o);
o.pos = input[1].pos;
o.bary = float3(0, 1, 0);
stream.Append(o);
o.pos = input[2].pos;
o.bary = float3(0, 0, 1);
stream.Append(o);
}
fixed4 frag(g2f i) : SV_Target
{
float edge = min(min(i.bary.x, i.bary.y), i.bary.z);
float wire = smoothstep(_WireWidth, _WireWidth * 0.5, edge);
float3 color = lerp(_BaseColor.rgb, _WireColor.rgb, wire);
return fixed4(color, 1);
}今回のPassは1つのみですがこれが3つだったり4つだったり複数あることもある。 1回目は影用に書く 2回目は通常の色で書く 3回目は輪郭線だけ書く など複数のPassを分けて書くこともある。
今回のワイヤーフレームシェーダーの場合
Passは一つしかないので一回だけ描く。 その一回で 頂点処理にvert 三角形加工にgeom ピクセルの色決定にfrag
#pragma target 4.0はなにか。
シェーダーを動かすために必要なGPU機能レベルを指定する命令。 targetは2.0, 3.0, 4.0, 4.5, 5.0がある。 通常は3.0足りそう。
#include "UnityCG.cginc"とは
unityでいうusingに近い。 これを書くことでUnityの関数が使用できるようになる。 今回でいうとo.pos = UnityObjectToClipPos(v.vertex); で使用している。
変数の定義
float4 _WireColor;
float4 _BaseColor;
float _WireWidth;変数を定義する。 定義した変数はPropertiesで初期化できる。
Unityのメッシュから頂点座標を取得する
struct appdata
{
float4 vertex : POSITION;
};・struct シェーダーではメッシュから受け取るデータをstructで定義する。 ・appdata 構造名、任意 ・float4 vertex (x, y, z, w)の変数を宣言 ・POSITION セマンティクスと呼ばれる、Unity/GPUに伝えるラベルのようなもの。 POSITIONはMeshの頂点位置を受け取るという意味。 ほかにも ・法線 ・UV ・頂点カラー ・接線 などある。
Vertex ShaderからGeometry Shaderへ渡す
struct v2g
{
float4 pos : SV_POSITION;
};先ほどMeshからPOSITIONを取得したがそれとはまた違う。 先ほどのvertexは ”メッシュのローカル頂点座標”を取得した。 一方で今回のv2g, float4 posは画面に描くために座標となる。 ではSV_POSOTIONとはなにか。 これもセマンティクスである。 これは最終的に画面上の頂点位置として使う座標であり、SVはSystem Valueの略だ。
ただここで疑問がある。 POSITIONとSV_POSITOINって一緒では?
一緒なようで一緒ではない。 正確には POSITIONはMeshの頂点座標を見る に対して、 SV_POSITIONはカメラから見た頂点座標 になる。 SV_POSITIONは単なるカメラから見た座標ではなく、カメラ・投影変換まで終わった画面描画用の座標である。 もっと具体的に言うと float4 vertex : POSITION; ↓ この頂点はモデル内でどこにあるか float4 pos : SV_POSITION; ↓ この頂点を最終的に画面上のどこへ投影するか が決められる。 なんとなく理解できたであろう。
Geometry ShaderからFragment Shaderへ渡すデータの形を定義する。
struct g2f
{
float4 pos : SV_POSITION;
float3 bary : TEXCOORD0;
};今まではVertex Shader, Geometry Shaderへデータを渡してきた。 次はFragment Shaderへ渡す。
float4 pos : SV_POSITION;
先ほど出てきたv2gでもあった。ので省略。
float3 bary : TEXCOORD0;
これが今回のワイヤーフレーム用の重要部分である。 いまのところbaryは定義しているだけでなにも入っていない。 TEXCOORDは値を受け渡しするだけの箱のようなもの。
勘違いしていたこと
今まで解説していた。
float4 vertex : POSITION;
float3 bary : TEXCOORD0;
などはまだ値に何も入っていなく、空らしい。 これはfloat4のbaryという変数でTEXOORD0という箱で定義しているということ。 SV_POSITIONとして扱うという定義など。 プログラミングとは違い型のほかに "どこへ渡すか" という定義が必要らしい。 その際に必要なのが POSITION TEXCOORD0 らしい。 やっと理解できた...
長文になっていたのでいったんここまで。 次からは実際の計算や処理に移っていく。
https://rin0700.com/blog/fb147cbc-7b69-4b2f-a530-0e9b2cf20ccd