フラグメントシェーダー関数
ここからフラグメントシェーダー関数になります。
float4 frag(VertexOutput i, float facing : VFACE) : SV_Target{
入力は、VertexOutput構造体と、VFACEセマンティクスがバインドされたfacingです。VFACEは浮動小数点スカラーで、描画対象のピクセルが負ならカメラに対して裏向き、正なら表向きであることを意味します。数値が割合を示すのかはぱっと調べた限りではわかりませんでした(シェーダー本の記述ミスだったかも)。また、2.0.3コードでは、このfacingは使っていないようです。
//頂点シェーダーアウトプット struct VertexOutput { float4 pos : SV_POSITION; //POSITION float2 uv0 : TEXCOORD0; };
VertexOutput構造体はなぜか解説を忘れてました。SV_POSITIONはフラグメントシェーダーの入力セマンティクスに含まれないために使えません(値が不定)。TEXCOORD0には、テクスチャのuv座標が入っています。
float4 _BaseMap_var = tex2D( _BaseMap, TRANSFORM_TEX(i.uv0, _BaseMap) );
ベースマップ(_BaseMap)からカラー値を取得。ここで取得されるのは、本来そのピクセルが持つべき値と同じです(プリミティブ自体は若干大きくなってますが)。
float3 Set_BaseColor = lerp( _BaseColor.rgb * _BaseMap_var.rgb, _BaseColor.rgb * _BaseMap_var.rgb * _LightColor0.rgb, _Is_LightColor_Base );
_BaseColorはインスペクター上でBaseColorとして設定できる値で、BaseMapに乗算されるカラー値です。輪郭線のα値は常に0にするので、これ以降はfloat3で演算します。
Is_LightColor_Baseはインスペクターでトグルで表示され、基本色に対しライトカラーを有効にするかどうかを表します。型はFloatですがトグルなので0.0か1.0のどちらかとなり、lerp関数では0.0なら前者(ベースマップ×ベースカラーのみ)、1.0なら後者(ライトカラーも乗算する)が選択されます。
lerp関数はかなり高速に動作するようで(良く知りません)、こんな風に条件分岐の代わりに使われることが多い印象があります。
float3 Set_Outline_Color = lerp( _Outline_Color.rgb, _Outline_Color.rgb * Set_BaseColor * Set_BaseColor, _Is_BlendBaseColor );
_Outline_Colorはインスペクター上でOutline_Colorとして設定できる値で、輪郭線のカラー値です。さっきと同じように、Is_BlendBaseColorトグルのオンオフによってlerpの前者(輪郭線カラー値のみ)と後者(輪郭線カラー値にベースカラーを乗算)のどちらかがカラー値として算出されます。
Set_BaseColorが2回乗算されてるのはなんでだろう? なにかのイディオムなのかな(ごめんなさいわかりません)。
return fixed4(Set_Outline_Color,0); }
算出された輪郭線カラー値を戻り値に返します。
Cull Front
「え? もう終わり? なんでこれで輪郭線が描画されるの? このままだと元のモデルを一回り大きな単色のモデルが覆っちゃうんじゃない?」と正直思ったんですが、確認したら輪郭線のPassでは以下のようにカリングで裏面のみを描画するように設定されていました。
Cull Front
乱暴に言えば後ろ半分だけ描画され、かつ、それらのプリミティブは常に元のモデルよりも奥側に配置されるために、上手い事輪郭線部分だけがフレームバッファに記録されるのですね。Passの順序を入れ替えれば、早期Zテストが機能して、もっと効率化できるかもしれません(機会があったら計測してみます。)
輪郭線部まとめ
2.0.3では頂点を外側に動かしてその部分を指定したカラー値で塗りつぶすことで輪郭線を描画する、比較的シンプルな手法(「押し出し法」と言うようです)を使っていました。2.0.4でどうなってるかはわかりませんが、ひとまず輪郭線描画の理解が進みました!><