ストリーム出力ステージの基礎知識 (Direct3D 10)
ストリーム出力ステージを初期化し、実行するには、次の手順を実行します。
- ジオメトリ シェーダーのコンパイル
- ストリーム出力によるジオメトリ シェーダー オブジェクトの作成
- 出力ターゲットの設定
ジオメトリ シェーダーのコンパイル
「チュートリアル 13」のジオメトリ シェーダー (GS) は各三角形の面の法線を計算し、位置、法線、およびテクスチャーの座標データを出力します。
struct GSPS_INPUT { float4 Pos : SV_POSITION; float3 Norm : TEXCOORD0; float2 Tex : TEXCOORD1; }; [maxvertexcount(12)] void GS( triangle GSPS_INPUT input[3], inout TriangleStream<GSPS_INPUT> TriStream ) { GSPS_INPUT output; // // Calculate the face normal // float3 faceEdgeA = input[1].Pos - input[0].Pos; float3 faceEdgeB = input[2].Pos - input[0].Pos; float3 faceNormal = normalize( cross(faceEdgeA, faceEdgeB) ); float3 ExplodeAmt = faceNormal*Explode; // // Calculate the face center // float3 centerPos = (input[0].Pos.xyz + input[1].Pos.xyz + input[2].Pos.xyz)/3.0; float2 centerTex = (input[0].Tex + input[1].Tex + input[2].Tex)/3.0; centerPos += faceNormal*Explode; // // Output the pyramid // for( int i=0; i<3; i++ ) { output.Pos = input[i].Pos + float4(ExplodeAmt,0); output.Pos = mul( output.Pos, View ); output.Pos = mul( output.Pos, Projection ); output.Norm = input[i].Norm; output.Tex = input[i].Tex; TriStream.Append( output ); int iNext = (i+1)%3; output.Pos = input[iNext].Pos + float4(ExplodeAmt,0); output.Pos = mul( output.Pos, View ); output.Pos = mul( output.Pos, Projection ); output.Norm = input[iNext].Norm; output.Tex = input[iNext].Tex; TriStream.Append( output ); output.Pos = float4(centerPos,1) + float4(ExplodeAmt,0); output.Pos = mul( output.Pos, View ); output.Pos = mul( output.Pos, Projection ); output.Norm = faceNormal; output.Tex = centerTex; TriStream.Append( output ); TriStream.RestartStrip(); } for( int i=2; i>=0; i-- ) { output.Pos = input[i].Pos + float4(ExplodeAmt,0); output.Pos = mul( output.Pos, View ); output.Pos = mul( output.Pos, Projection ); output.Norm = -input[i].Norm; output.Tex = input[i].Tex; TriStream.Append( output ); } TriStream.RestartStrip(); }
このコードでは、ジオメトリ シェーダーが頂点シェーダーやピクセル シェーダーとよく似ていることがわかります。ただし、関数の戻り型、入力パラメーターの宣言、および組み込み関数が異なります。
関数の戻り型
関数の戻り型は、シェーダーから出力可能な頂点の最大数を宣言するだけです。この場合は、maxvertexcount(12) によって、出力が最大 12 つの頂点となるように定義されています。入力パラメーターの宣言
この関数は、2 つの入力パラメーターを取得します。triangle GSPS_INPUT input[3] , inout TriangleStream<GSPS_INPUT> TriStream
最初のパラメーターは頂点 (この場合は 3 つ) の配列で、GSPS_INPUT 構造体によって定義されています (この構造体は、頂点単位のデータを位置、法線、およびテクスチャーの座標として定義します)。また、最初のパラメーターは、triangle キーワードを使用しています。これは、入力アセンブラー ステージで三角形プリミティブ タイプのいずれか (三角形リストまたは三角形ストリップ) としてジオメトリ シェーダーにデータを出力する必要があることを意味します。
2 番目のパラメーターは、TriangleStream<GSPS_INPUT> 型によって定義された三角形ストリームです。これは、このパラメーターが三角形の配列で、それぞれが 3 つの頂点で構成されていることを意味します (各三角形は GSPS_INPUT のメンバーからのデータを含みます)。
triangle および TriangleStream キーワードを使用して、GS で個別の三角形または三角形のストリームを示します。
組み込み関数
シェーダー関数内のコードの行は、共通シェーダー コア HLSL 組み込み関数を使用しています。ただし、Append および RestartStrip を呼び出す最後の 2 行を除きます。これらの関数は、ジオメトリ シェーダーのみで使用できます。Append は、ジオメトリ シェーダーで、現在のストリップに出力を追加するように指定します。RestartStrip は、新しいプリミティブ ストリップを作成します。新しいストリップは、GS ステージのすべての呼び出しで暗黙的に作成されます。
シェーダーの残りの部分は、頂点シェーダーやピクセル シェーダーと非常に類似しています。ジオメトリ シェーダーは、構造体を使用して入力パラメーターを宣言し、SV_POSITION セマンティクスで Position メンバーをマークすることで、これが位置データであることをハードウェアに知らせます。入力構造体は、他の 2 つのパラメーターをテクスチャー座標として示します (一方のパラメーターが面の法線を含む場合でも同様です)。必要に応じて、面の法線に独自のカスタム セマンティクスを使用することもできます。
ジオメトリ シェーダーの設計が完了したら、D3D10CompileShader を呼び出して、次のコード例のようにコンパイルします。
DWORD dwShaderFlags = D3D10_SHADER_ENABLE_STRICTNESS; ID3D10Blob** ppShader; D3D10CompileShader( pSrcData, sizeof( pSrcData ), "Tutorial13.fx", NULL, NULL, "GS", "gs_4_0", dwShaderFlags, &ppShader, NULL );
頂点シェーダーやピクセル シェーダーと同様に、シェーダー フラグで、シェーダーのコンパイル方法 (デバッグ用途や速度の最適化など)、エントリ ポイント関数、および検証対象のシェーダー モデルをコンパイラに指示する必要があります。この例では、GS 関数を使用して、Tutorial13.fx ファイルから構築されたジオメトリ シェーダーを作成しています。シェーダーは、シェーダー モデル 4.0 用にコンパイルされます。
ストリーム出力によるジオメトリ シェーダー オブジェクトの作成
データがジオメトリから出力されることがわかり、シェーダーを正しくコンパイルしたら、次に ID3D10Device::CreateGeometryShaderWithStreamOutput を呼び出して、ジオメトリ シェーダー オブジェクトを作成します。
ただし、最初に、ストリーム出力 (SO) ステージの入力シグネチャを宣言する必要があります。このシグネチャは、オブジェクト作成時に GS 出力と SO 入力を照合または検証します。次のコードは、SO 宣言の例です。
D3D10_STREAM_OUTPUT_DECLARATION_ENTRY pDecl[] = { // semantic name, semantic index, start component, component count, output slot { "SV_POSITION", 0, 0, 4, 0 }, // output all components of position { "TEXCOORD0", 0, 0, 3, 0 }, // output the first 3 of the normal { "TEXCOORD1", 0, 0, 2, 0 }, // output the first 2 texture coordinates }; D3D10Device->CreateGeometryShaderWithStreamOut( pShaderBytecode, pDecl, 3, sizeof(pDecl), &pGS );
この関数は、次のパラメーターを取得します。
- コンパイル済みジオメトリ シェーダー (ジオメトリ シェーダーが存在せず、データが頂点シェーダーから直接ストリーム出力される場合は頂点シェーダー) へのポインター。このポインターの取得方法については、「コンパイル済みシェーダーへのポインターの取得」を参照してください。
- ストリーム出力ステージの入力データを記述する宣言の配列へのポインター(「D3D10_SO_DECLARATION_ENTRY」を参照してください)。SO ステージから出力される要素の型ごとに 1 つずつ、最大 64 の宣言を指定できます。宣言エントリの配列は、ストリーム出力にバインドされるのが 1 つのバッファーのみであるか、複数のバッファーであるかに関係なく、データ レイアウトを記述します。
- SO ステージによって書き出される要素の数。
- 作成されるジオメトリ シェーダー オブジェクトへのポインター (「ID3D10GeometryShader インターフェイス」を参照してください)。
ストリーム出力宣言は、データをバッファー リソースに書き込む方法を定義します。出力宣言には、必要な数の要素を追加できます。SO ステージを使用して、1 つのバッファー リソースまたは複数のバッファー リソースに書き込みます。1 つのバッファーの場合、SO ステージでは頂点ごとに多数の異なる要素を書き込むことができます。複数のバッファーの場合、SO ステージでは頂点単位のデータの 1 つ要素しか各バッファーに書き込むことができません。
ジオメトリ シェーダーを使用せずに SO ステージを使用するには、ID3D10Device::CreateGeometryShaderWithStreamOutput を呼び出し、pShaderBytecode パラメーターに頂点シェーダーへのポインターを渡します。
出力ターゲットの設定
最後の手順では、SO ステージ バッファーを設定します。データは、後で使用するために、メモリー内の 1 つまたは複数のバッファーに読み込むことができます。次のコードは、頂点データや、データを読み込む SO ステージに使用できる 1 つのバッファーの作成方法を示しています。
ID3D10Buffer *m_pBuffer; int m_nBufferSize = 1000000; D3D10_BUFFER_DESC bufferDesc = { m_nBufferSize, D3D10_USAGE_DEFAULT, D3D10_BIND_STREAM_OUTPUT, 0, 0 }; D3D10Device->CreateBuffer( &bufferDesc, NULL, &m_pBuffer );
ID3D10Device::CreateBuffer を呼び出してバッファーを作成します。この例は、デフォルトの使用方法を示しています。これは、CPU によってかなり頻繁に更新されることが予想されるバッファー リソースに一般的な方法です。バインド フラグは、リソースをバインドできるパイプラインのステージを示します。また、SO ステージによって使用されるリソースは、バインド フラグ D3D10_BIND_STREAM_OUTPUT を指定して作成されている必要があります。
バッファーを正しく作成したら、ID3D10Device::SOSetTargets を呼び出して現在のデバイスに設定します。
UINT offset[1] = 0; D3D10Device->SOSetTargets( 1, &m_pBuffer, offset );
この呼び出しは、バッファーの数、バッファーへのポインター、およびオフセットの配列を取得します (データの書き込みを開始する位置を示すオフセットが各バッファーに 1 つずつ格納されます)。データは、描画関数の呼び出し時にこれらのストリーム出力バッファーに書き込まれます。内部変数は、ストリーミング出力バッファーへのデータの書き込みを開始する位置を追跡します。この変数は、SOSetTargets が再度呼び出されて、新しいオフセット値が指定されるまで増え続けます。
ターゲット バッファーに書き出されるデータはすべて、32 ビット値です。