本日はMRTKのHandTrianglesShaderの勉強枠です。今回はフラグメントシェーダーを見ていきます。
〇HandTrianglesShaderのフラグメントシェーダー
fixed4 fragment_main(FragmentInput fragInput) : SV_Target
{
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(fragInput);
float4 result;
half inLine_Q168;
Edges_B168(_Edge_Width_,_Filter_Width_,fragInput.extra3,inLine_Q168);
half4 Color_Q172;
half Alpha_Q172;
Split_Color_Alpha_B172(fragInput.extra2,Color_Q172,Alpha_Q172);
// Multiply_Colors
half4 Product_Q170 = _Fill_Color_ * Color_Q172;
// Scale_Color
half4 Result_Q169 = Alpha_Q172 * _Line_Color_;
half4 Fill_Color_Q171;
half4 Line_Color_Q171;
Transition_B171(_Pulse_Line_Fuzz_,Product_Q170,Result_Q169,fragInput.extra3,fragInput.extra1,_Pulse_Amplify_Leading_,_Line_End_Time_,_Fill_Start_Time_,Fill_Color_Q171,Line_Color_Q171);
// Mix_Colors
half4 Color_At_T_Q173 = lerp(Fill_Color_Q171, Line_Color_Q171,float4( inLine_Q168, inLine_Q168, inLine_Q168, inLine_Q168));
float4 Out_Color = Color_At_T_Q173;
float Clip_Threshold = 0.00;
bool To_sRGB = false;
result = Out_Color;
float clipVal = (Out_Color.a<Clip_Threshold) ? -1 : 1;
clip(clipVal);
return result;
}
fixed4 fragment_main(FragmentInput fragInput) : SV_Target{}
FragmentInputからデータを受け取りSV_Target(ピクセル色)として使用されるという意味です。
〇FragmentInput
struct FragmentInput {
float4 pos : SV_POSITION;
float3 posWorld : TEXCOORD8;
float4 extra1 : TEXCOORD4;
float4 extra2 : TEXCOORD5;
float4 extra3 : TEXCOORD2;
UNITY_VERTEX_OUTPUT_STEREO
};
FragmentInput構造体はposはシステム上の座標、posWorldはUV座標、extra1、extra2、extra3はそれぞれUV座標、UNITY_CERTEX_OUTPUT_STEREOは両目のレンダリングに関するパスのデータを持つ構造体です。
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(fragInput);
これはジオメトリシェーダーでもありましたが、xR特有の右目、左目の両目のレンダリングに関するものです。
float4 result;
half inLine_Q168;
Edges_B168(_Edge_Width_,_Filter_Width_,fragInput.extra3,inLine_Q168);
void Edges_B168(
half Edge_Width,
half Filter_Width,
half4 Edges,
out half inLine )
{
float3 fw = Filter_Width*fwidth(Edges.xyz)*max(Edge_Width,1.0);
float3 a = smoothstep(float3(0.0,0.0,0.0),fw,Edges.xyz);
inLine = (1.0-min(a.x,min(a.y,a.z)))*min(Edge_Width,1.0);
}
Edges_B168の引数は次のように値が入ります。
half Edge_Width=Edge_Width(プロパティで指定されるEdge_Widthの値 defaultでは1が入ります。)
half Filter_Width=Filter_Width(プロパティで指定されるEdge_Widthの値 defaultでは1.5が入ります)

half4 Edges=fragInput.extra3(FragmentInput構造体のextra3(UV座標))
out half inLine =inLine_Q168
float3 fw = Filter_Width*fwidth(Edges.xyz)*max(Edge_Width,1.0);
float3 a = smoothstep(float3(0.0,0.0,0.0),fw,Edges.xyz);
inLine = (1.0-min(a.x,min(a.y,a.z)))*min(Edge_Width,1.0);
fwはFilter_Width、fwidth(Edges.xyz)、max(Edge_Width,1.0)積が代入されます。
fwidth(Edges.xyz)はabs(ddx(Edges.xyz)) + abs(ddy(Edges.xyz)) を返します。 ddx(Edges.xyz),ddy(Edges.xyz)は隣接するピクセルとEdges.xyzの値の差分を返します。つまりx,yそれぞれの部分微分です。
abs関数は絶対値を返します。つまりfwidth(Edges.xyz)はEdges.xyzの部分微分の絶対値が返されます。
max(Edge_Width,1.0)はEdge_Widthと1.0との間で値が大きいほうが返されます。デフォルトでEdge_Widthの値は1なので1が入ります(Edge_Widthは0~1のレンジを持ちます)。
aにはfloat4(0,0,0,0)とfwとの値の間でEdge.xyzに関してエルミート補間が行われます。エルミート補間は、始点から徐々にスピードアップし、終点に向かって減速していく方法になります。
Edge.xyzが0以下の場合0、fw以上の場合1、間にある場合0~1の値が入ります。
inLineには(1.0-min(a.x,min(a.y,a.z)))とmin(Edge_Width,1.0)の積が代入されます。
min関数はmax関数の逆で値の小さいほうが抽出されます。
aのx,y,z成分のうち一番小さい値が1から引かれた値とEdge_Widthと1.0のうち小さい値の積が代入されます。
以上がEdges_B168()の中身になります。
nLineはinLine_Q168が代入されていました。
これは793行目で
half4 Color_At_T_Q173 = lerp(Fill_Color_Q171, Line_Color_Q171,float4( inLine_Q168, inLine_Q168, inLine_Q168, inLine_Q168));
で使用されています.
leap関数は線形補間を行います。次のようになります。
half4 Color_At_T_Q173=Fill_Color_Q171+float4( inLine_Q168, inLine_Q168, inLine_Q168, inLine_Q168)*(Line_Color_Q171-Fill_Color_Q171);
つまりEdges_B168()の処理は最終的に出力されるカラーの補正に使用されます。
〇もっと読み解いてみた
Edge_Width、Filter_WidthはHandTrianglesShaderの中でEdges_B168()のみで使用される変数です。
これらの値によって具体的にどのように処理が変わるのか見ていきます。
●Edge_Width=10、Filter_Width=1.5(デフォルト)の場合

Edge_B168(){
//fwidth(Edges.xyz)=UVの部分微分
//1.5*fwidth(Edges.xyz) *10
float3 fw = Filter_Width*fwidth(Edges.xyz)*max(Edge_Width,1.0);
//fwの値がデフォルトより大きいため補間の最大値が大きくなる
float3 a = smoothstep(float3(0.0,0.0,0.0),fw,Edges.xyz);
//inLine = (1.0-min(a.x,min(a.y,a.z)))
inLine = (1.0-min(a.x,min(a.y,a.z)))*min(Edge_Width,1.0);
}
以上からEdge_Widthの値によって
①aに代入されるエルミート補間の最大値が変化する
②inLineに計算される係数が変わる(1以下の場合inLineの値が小さくなる)
このふたつが分かります。
このエルミーと補間の最大値が変わることによって描く曲線の滑らかさが変わります。これによって塗りつぶされる色のグラデーションが変わりポリゴンのEdgeの太さが変わるようになっています。

また、Filter_Widthの値はfwのみにかかわっており、値が変わると曲線の最大値が変化します。Edge_Widthが小さい場合Fillter_Widthの値を変更してもそこまで変化が見られませんが、Edge_Widthが大きければ大きいほどfwの値が変動し、エルミート補間の最大値が変化します。

以上がEdges_B168での処理です。