GLSL の移植
重要な API
バッファーとシェーダー オブジェクトを作成して構成するコードが完成したら、それらのシェーダー内のコードを OpenGL ES 2.0 の GL シェーダー言語 (GLSL) から Direct3D 11 の上位レベル シェーダー言語 (HLSL) に移植します。
OpenGL ES 2.0 では、シェーダーは gl_Position、gl_FragColor、gl_FragData[n] (n は特定のレンダー ターゲットのインデックス) などの組み込みを使って実行した後にデータを返します。 Direct3D では、特定の組み込みメソッドはなく、シェーダーはそれぞれの main() 関数の戻り値の型としてデータを返します。
頂点の位置や法線など、シェーダー ステージ間で補間されるデータは、varying 宣言を使って処理します。 ただし、Direct3D にはこの宣言がありません。そのため、シェーダー ステージ間で受け渡されるデータは HLSL セマンティクスでマークする必要があります。 選んだ特定のセマンティクスはデータの目的を示します。 たとえば、フラグメント シェーダー間で補完される頂点データは次のように宣言します。
float4 vertPos : POSITION;
または
float4 vertColor : COLOR;
POSITION は、頂点の位置データを示すために使うセマンティクスです。 また、POSITION は特殊なケースで、補間後にピクセル シェーダーからアクセスできません。 そのため、SV_POSITION を使用してピクセル シェーダーへの入力を指定する必要があります。補完された頂点データは、その変数に配置されます。
float4 position : SV_POSITION;
セマンティクスは、シェーダーの body (main) メソッドで宣言できます。 ピクセル シェーダーでは、body メソッドにレンダー ターゲットを示す SV_TARGET[n] が必要です (数値サフィックスのない SV_TARGET は、既定でレンダー ターゲット インデックス 0 に設定されます)。
また、頂点シェーダーでは、SV_POSITION システム値セマンティクスを出力する必要があります。 このセマンティクスは頂点の位置データを座標値に解決します。x は -1 ~ 1 の値に、y は -1 ~ 1 の値になり、z は元の同次座標 w の値で割られ (z/w)、w は 1 を元の w の値で割った値 (1/w) になります。 ピクセル シェーダーでは、SV_POSITION システム値セマンティクスを使って、画面上のピクセル位置を取得します。x は 0 からレンダー ターゲットの幅までの値に、y は 0 からレンダー ターゲットの高さまでの値になります (各オフセットは 0.5 単位)。 機能レベル 9_x ピクセル シェーダーでは、SV_POSITION 値から読み取ることができません。
定数バッファーは cbuffer を使って宣言し、検索のために特定の開始レジスタに関連付ける必要があります。
Direct3D 11: HLSL での定数バッファーの宣言
cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
matrix mvp;
};
ここでは、定数バッファーはレジスタ b0 を使って、パックされたバッファーを保持します。 b# という形式ですべてのレジスタを参照します。 HLSL での定数バッファー、レジスタ、データ パッキングの実装について詳しくは、「シェーダー定数 (HLSL)」をご覧ください。
手順
手順 1: 頂点シェーダーの移植
この簡単な OpenGL ES 2.0 の例では、頂点シェーダーに 3 つの入力があります。1 つの定数のモデル ビュー プロジェクション 4x4 マトリックスと 2 つの 4 座標ベクトルです。 これら 2 つのベクトルには、頂点の位置と色が含まれます。 シェーダーでは、ラスタライズするために、位置ベクトルをパースペクティブ座標に変換し、それを gl_Position 組み込みに割り当てます。 また、頂点の色は、ラスタライズ時に補間のために varying 変数にコピーされます。
OpenGL ES 2.0: 立方体オブジェクトの頂点シェーダー (GLSL)
uniform mat4 u_mvpMatrix;
attribute vec4 a_position;
attribute vec4 a_color;
varying vec4 destColor;
void main()
{
gl_Position = u_mvpMatrix * a_position;
destColor = a_color;
}
次に、Direct3D では、レジスタ b0 にパックされた定数バッファーに定数のモデル ビュー プロジェクション マトリックスを格納し、頂点の位置と色をそれぞれ適切な HLSL セマンティクス (POSITION と COLOR) 使って明確にマークします。 入力レイアウトはこれら 2 つの頂点の値の特定の配置を示しているため、それらの値を保持する構造体を作成し、シェーダーの body 関数 (main) でその構造体を入力パラメーターの型として宣言します (それらの値を 2 つのパラメーターとして指定することもできますが、そうすると扱いにくくなる場合があります)。また、補完された位置と色を格納するこのステージの出力の型を指定し、それを頂点シェーダーの body 関数の戻り値として宣言します。
Direct3D 11: 立方体オブジェクトの頂点シェーダー (HLSL)
cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
matrix mvp;
};
// Per-vertex data used as input to the vertex shader.
struct VertexShaderInput
{
float3 pos : POSITION;
float3 color : COLOR;
};
// Per-vertex color data passed through the pixel shader.
struct PixelShaderInput
{
float3 pos : SV_POSITION;
float3 color : COLOR;
};
PixelShaderInput main(VertexShaderInput input)
{
PixelShaderInput output;
float4 pos = float4(input.pos, 1.0f); // add the w-coordinate
pos = mul(mvp, projection);
output.pos = pos;
output.color = input.color;
return output;
}
出力のデータ型 PixelShaderInput は、ラスタライズ時に設定され、フラグメント (ピクセル) シェーダーに渡されます。
手順 2: フラグメント シェーダーの移植
GLSL のフラグメント シェーダーの例は非常に簡単です。補完された色値を gl_FragColor 組み込みに渡します。 OpenGL ES 2.0 では、それを既定のレンダー ターゲットに書き込みます。
OpenGL ES 2.0: 立方体オブジェクトのフラグメント シェーダー (GLSL)
varying vec4 destColor;
void main()
{
gl_FragColor = destColor;
}
Direct3D も同じくらい簡単です。 大きな違いは、ピクセル シェーダーの body 関数で値を返す必要があることだけです。 色が 4 座標 (RGBA) の浮動小数点値であるため、戻り値の型として float4 を指定し、SV_TARGET システム値セマンティクスとして既定のレンダー ターゲットを指定します。
Direct3D 11: 立方体オブジェクトのピクセル シェーダー (HLSL)
struct PixelShaderInput
{
float4 pos : SV_POSITION;
float3 color : COLOR;
};
float4 main(PixelShaderInput input) : SV_TARGET
{
return float4(input.color, 1.0f);
}
位置のピクセルの色はレンダー ターゲットに書き込まれます。 次に、「画面への描画」でそのレンダー ターゲットのコンテンツを表示する方法を見ていきます。
前の手順
次のステップ
注釈
HLSL セマンティクスと定数バッファーのパッキングについて理解すると、デバッグの苦労がいくらか少なくなるだけでなく、最適化できるようにもなります。 機会があれば、「変数の構文 (HLSL)」、「Direct3D 11 のバッファーについて」、「定数バッファーを作成する方法」をご覧ください。 機会がない場合は、次のセマンティクスと定数バッファーについての基本的なヒントを心に留めておいてください。
- 必ずレンダラーの Direct3D 構成コードを見直して、定数バッファーの構造体が HLSL の cbuffer 構造体の宣言と一致し、コンポーネントのスカラー型が両方の宣言で一致していることを確認する。
- レンダラーの C++ コードでは、データ パッキングが適切に行われるように、定数バッファーの宣言で DirectXMath 型を使う。
- 定数バッファーを効率的に使う最良の方法として、更新頻度に応じてシェーダーの変数を定数バッファーにまとめる。 たとえば、フレームごとに 1 回更新される uniform データと、カメラが移動したときにだけ更新される uniform データがある場合は、それらのデータを 2 つの定数バッファーに分けることを考えます。
- セマンティクスの適用し忘れや誤った適用は、シェーダー コンパイル (FXC) エラーのよくある原因である。 よく見直してください。 以前のページやサンプルの多くでは Direct3D 11 より前のさまざまなバージョンの HLSL セマンティクスを参照しているため、ドキュメントが混乱を招くことがあります。
- 各シェーダーのターゲットとする Direct3D 機能レベルを確認する。 機能レベル 9_* のセマンティクスは、11_1 のセマンティクスとは異なります。
- SV_POSITION セマンティクスは、関連する補間後の位置データを座標値に解決します。x は 0 からレンダー ターゲットの幅までの値に、y は 0 からレンダー ターゲットの高さまでの値になり、z は元の同次座標 w の値で割られ (z/w)、w は 1 を元の w の値で割った値 (1/w) になります。
関連トピック
簡単な OpenGL ES 2.0 レンダラーを Direct3D 11 に移植する方法