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