土屋つかさの技術ブログは今か無しか

土屋つかさが主にプログラミングについて語るブログです。

ユニティちゃんシェーダーを読む(トゥーンシェード1)

 準備運動も終わったので本丸のトゥーンシェード部のPass(Name "FORWARD")を見て行きます。今回は頂点シェーダー部

入出力セマンティクスと頂点シェーダー

struct VertexInput {
    float4 vertex : POSITION;
    float3 normal : NORMAL;
    float4 tangent : TANGENT;
    float2 texcoord0 : TEXCOORD0;
};

struct VertexOutput {
    float4 pos : SV_POSITION;
    float2 uv0 : TEXCOORD0;
    float4 posWorld : TEXCOORD1;
    float3 normalDir : TEXCOORD2;
    float3 tangentDir : TEXCOORD3;
    float3 bitangentDir : TEXCOORD4;
    LIGHTING_COORDS(5,6)
    UNITY_FOG_COORDS(7)
};

VertexOutput vert (VertexInput v) {
    VertexOutput o = (VertexOutput)0;

 頂点シェーダーvertはVertexInput構造体を入力、VertexOutput構造体を出力としています。LIGHTING_COORDSマクロはTEXCOORD5/TEXCOORD6にライト周りのテクスチャとして設定し、UNITY_FOG_COORDSマクロはTEXCOORD7にフォグ周りのテクスチャを設定しています(これのマクロ展開についてはまた別の時にやります)。

o.uv0 = v.texcoord0;
o.normalDir = UnityObjectToWorldNormal(v.normal);

 uvはそのまま出力に渡しています。
 出力normalDirは、頂点法線の「ワールド空間」の値になります。これ、覚えておいた方が良いかもです。

o.tangentDir = normalize( 
	mul( unity_ObjectToWorld, 
		 float4( v.tangent.xyz, 0.0 ) ).xyz 
	);

 接線(タンジェント)ベクトルもワールド空間に変換して出力します。mulに渡す前に同次座標wを削除してるのはなんでなんだろ。wに値が入ってるのかな?(よくわかってない)

                o.bitangentDir = normalize(
                	cross(o.normalDir, o.tangentDir) * v.tangent.w);

 crossはベクトルの外積を返す関数で、ここでは法線と接線から従法線(バイタンジェント)ベクトルを算出しています。さっきサクったwはここで使っているのですね。
※この辺のwの使い方実はいまいち把握できてないので今度ちゃんと調べます。

                o.posWorld = mul(unity_ObjectToWorld, v.vertex);

 頂点座標自体もワールド空間の物を出力しておきます。

                float3 lightColor = _LightColor0.rgb;

 このライトのカラー値はマクロで使用しています。

                o.pos = UnityObjectToClipPos(v.vertex );

 最後に、頂点のクリップ空間座標を出力します。

                UNITY_TRANSFER_FOG(o,o.pos);
                TRANSFER_VERTEX_TO_FRAGMENT(o)
                return o;
            }

 組み込みマクロを2個実行して、構造体を返します。頂点シェーダーはここまで。

余談

 そういえば、Unity2017.3から(なのか?)、"mul(UNITY_MATRIX_MVP,〜)"を含むシェーダーコードを書くと、該当部分が自動的に"UnityObjectToClipPos(〜)"に置換され、ファイルの先頭に以下の注釈が追記されるようになりました。

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

 なにかしらの演算を省略する最適化があるのかな?(UnityObjectToClipPosの中身が更新されたのかはまだ見てない)。前者の方が直感的で分かりやすいと思っていたのですが、保存ができないので今後は後者を使っていきます。

2018/9/5追記
上記の現象について、下記に対応方法が記載されていました。
https://docs.unity3d.com/ja/current/Manual/SL-BuiltinMacros.html

自動アップグレードを無効にする
UNITY_SHADER_NO_UPGRADE を使うと、Unity が自動的にシェーダーファイルをアップグレードするのを無効にすることができます。

 UNITY_SHADER_NO_UPGRADEはどうやって使うのかがわからなかったのですが、ググったり自身で確認した限り、コメントで書いておく(!)のが正しいみたいです。マジか!?