シェーダーステージ入出力の自動管理
上級 プログラマー
HLSL シェーダーを書く際には、頂点属性を正確に定義し、最終シェーダーのステージに慎重に渡す必要があります。
ここでは、頂点の色を使用するシンプルな HLSL シェーダーの例を示します。
struct VS_IN
{
float4 pos : POSITION;
float4 col : COLOR;
};
struct PS_IN
{
float4 pos : SV_POSITION;
float4 col : COLOR;
};
PS_IN VS( VS_IN input )
{
PS_IN output = (PS_IN)0;
output.pos = input.pos;
output.col = input.col;
return output;
}
float4 PS( PS_IN input ) : SV_Target
{
return input.col;
}
technique10 Render
{
pass P0
{
SetGeometryShader( 0 );
SetVertexShader( CompileShader( vs_4_0, VS() ) );
SetPixelShader( CompileShader( ps_4_0, PS() ) );
}
}
モデルに法線を追加し、法線に応じて結果の色を変えたくなったとします。色を計算するコードを修正し、頂点シェーダーからピクセルシェーダーに属性を渡すために中間構造の調整をしなければなりません。また、使用するセマンティクスにも注意しなければなりません。
コード: 修正後の HLSL シェーダー
struct VS_IN
{
float4 pos : POSITION;
float4 col : COLOR;
float3 normal : NORMAL;
};
struct PS_IN
{
float4 pos : SV_POSITION;
float4 col : COLOR;
float3 normal : TEXCOORD0;
};
PS_IN VS( VS_IN input )
{
PS_IN output = (PS_IN)0;
output.pos = input.pos;
output.col = input.col;
output.normal = input.normal;
return output;
}
float4 PS( PS_IN input ) : SV_Target
{
return input.col * max(input.normal.z, 0.0);
}
technique10 Render
{
pass P0
{
SetGeometryShader( 0 );
SetVertexShader( CompileShader( vs_4_0, VS() ) );
SetPixelShader( CompileShader( ps_4_0, PS() ) );
}
}
この例はシンプルです。実際のプロジェクトにはもっとたくさんのシェーダーがあるので、1回の変更でたくさんのシェーダーや構造などを書き換えることになるかもしれません。
図式的には、新しい属性を追加する際には、頂点入力から、属性を使用する最後のステージまでのすべてのステージと中間構造を修正する必要があります。
SDSL (Stride Shading Language)
SDSL には、シェーダーのさまざまなステージで横断的にパラメーターを渡す便利な方法があります。ストリーム(stream)変数の概要は以下の通りです。
- 変数です。
- 他のシェーダーメンバと同じように定義され、
stream
というキーワードを持ちます。 - stream プレフィックスと一緒に使用します(省略するとコンパイルエラーになります)。ストリームが曖昧な(同じ名前である)場合は、変数の前にシェーダー名を指定する必要があります(例:
streams.<my_shader>.<my_variable>
)。
ストリームは、属性、変化(varying)、出力を1つの概念にまとめています。
- 属性とは、頂点シェーダーで書き込まれる前に読み込まれるストリームのことです。
- 変化とは、シェーダーステージ全体で横断的に存在するストリームのことです。
- 出力とは、読み込まれる前に割り当てられるストリームのことです。
ストリームは、関数のパラメーターとして指定しなくてもどこからでもアクセスできるグローバルオブジェクトだと考えてください。
Note
これらの変数のセマンティックを作成する必要はありません。コンパイラが自動的に作成します。ただし、同じセマンティックを共有する変数は最後のシェーダーでマージされることに注意してください。この仕様は、ストリーム変数を宣言したシェーダーを継承せずに、ローカルで使用したい場合に便利です。
ストリームを宣言した後は、シェーダーのどの段階からでもアクセスすることができます。シェーダーコンパイラがすべてを処理してくれます。ストリーム変数は、他の変数と同じように、呼び出し元のコード(つまり継承階層の中)から見えるようにする必要があります。
コード: ストリームの定義と使用
shader BaseShader
{
stream float3 myVar;
float3 Compute()
{
return streams.myVar;
}
};
コード: ストリームの仕様
shader StreamShader
{
stream float3 myVar;
};
shader ShaderA : BaseShader, StreamShader
{
float3 Test()
{
return streams.BaseShader.myVar + streams.StreamShader.myVar;
}
}
SDSL シェーダーの例
最初の例と同じ HLSL シェーダーを、SDSL で見てみましょう。
コード: SDSL でのシェーダー
shader MyShader : ShaderBase
{
stream float4 pos : POSITION;
stream float4 col : COLOR;
override void VSMain()
{
streams.ShadingPosition = streams.pos;
}
override void PSMain()
{
streams.ColorTarget = streams.col;
}
};
では、法線の計算を追加してみましょう。
コード: 修正後の SDSL シェーダー
shader MyShader : ShaderBase
{
stream float4 pos : POSITION;
stream float4 col : COLOR;
stream float3 normal : NORMAL;
override void VSMain()
{
streams.ShadingPosition = streams.pos;
}
override void PSMain()
{
streams.ColorTarget = streams.col * max(streams.normal.z, 0.0);
}
};
SDSL では、新しい属性の追加は、ストリームのプールに追加して必要な場所で使用するだけという簡単さです。