ストリーム出力ステージの使用開始

このセクションでは、ストリーム出力ステージでジオメトリ シェーダーを使用する方法について説明します。

ジオメトリ シェーダーをコンパイルする

このジオメトリ シェーダー (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();
}

そのコードを念頭に置いて、ジオメトリ シェーダーは頂点シェーダーまたはピクセル シェーダーとよく似ていますが、次の例外を除いて、関数によって返される型、入力パラメーター宣言、および組み込み関数を考慮してください。

Item 説明
関数の戻り値の型
関数の戻り値の型は 1 つの処理を行い、シェーダーによって出力できる頂点の最大数を宣言します。 この場合、
maxvertexcount(12)

は、最大 12 個の頂点に出力を定義します。

入力パラメーター宣言

この関数は、次の 2 つの入力パラメーターを受け取ります。

triangle GSPS_INPUT input[3] , inout TriangleStream<GSPS_INPUT> TriStream

最初のパラメーターは、GSPS_INPUT 構造体 (位置、法線、テクスチャ座標として頂点ごとのデータを定義する) によって定義される頂点の配列 (この場合は 3) です。 最初のパラメーターでは、三角形のキーワードも使用されます。つまり、入力アセンブラー ステージは、三角形のプリミティブ型 (三角形リストまたは三角形ストリップ) の 1 つとしてジオメトリ シェーダーにデータを出力する必要があります。

2 番目のパラメーターは、TriangleStream<GSPS_INPUT> 型で定義された三角形ストリームです。 つまり、パラメーターは三角形の配列であり、それぞれが 3 つの頂点 (GSPS_INPUT のメンバーからのデータを含む) で構成されます。

三角形と三角形のストリームのキーワードを使用して、GS 内の個々の三角形または三角形のストリームを識別します。

組み込み関数

シェーダー関数のコード行では、Append と RestartStrip を呼び出す最後の 2 行を除き、共通シェーダー コア HLSL 組み込み関数が使用されます。 これらの関数は、ジオメトリ シェーダーでのみ使用できます。 Append は、現在のストリップに出力を追加するようにジオメトリ シェーダーに通知します。RestartStrip では、新しいプリミティブ ストリップが作成されます。 GS ステージを呼び出すたびに、新しいストリップが暗黙的に作成されます。

残りのシェーダーは、頂点シェーダーまたはピクセル シェーダーによく似ています。 ジオメトリ シェーダーは、構造体を使用して入力パラメーターを宣言し、位置メンバーを SV_POSITION セマンティックでマークして、これが位置データであることをハードウェアに伝えます。 入力構造体は、他の 2 つの入力パラメーターをテクスチャ座標として識別します (そのうちの 1 つに面法線が含まれている場合でも)。 必要に応じて、面法線に独自のカスタム セマンティックを使用できます。

ジオメトリ シェーダーを設計した後、次のコード例に示すように D3DCompile を呼び出してコンパイルします。

DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;
ID3DBlob** ppShader;

D3DCompile( pSrcData, sizeof( pSrcData ), 
  "Tutorial13.fx", NULL, NULL, "GS", "gs_4_0", 
  dwShaderFlags, 0, &ppShader, NULL );

頂点シェーダーやピクセル シェーダーと同様に、シェーダーをコンパイルする方法 (デバッグ用、速度用に最適化など)、エントリ ポイント関数、および検証対象のシェーダー モデルをコンパイラに伝えるシェーダー フラグが必要です。 この例では、GS 関数を使用して Tutorial13.fx ファイルから構築されたジオメトリ シェーダーを作成します。 シェーダーはシェーダー モデル 4.0 用にコンパイルされます。

ストリーム出力を使用してジオメトリ シェーダー オブジェクトを作成する

ジオメトリからデータをストリーミングし、シェーダーを正常にコンパイルしたら、次の手順は ID3D11Device::CreateGeometryShaderWithStreamOutput を呼び出してジオメトリ シェーダー オブジェクトを作成することです。

ただし、まず、ストリーム出力 (SO) ステージの入力シグネチャを宣言する必要があります。 このシグネチャは、オブジェクト作成時に GS 出力と SO 入力と一致または検証します。 次のコードは、SO 宣言の例です。

D3D11_SO_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
};

D3D11Device->CreateGeometryShaderWithStreamOut( pShaderBytecode, ShaderBytecodesize, pDecl, 
    sizeof(pDecl), NULL, 0, 0, NULL, &pStreamOutGS );

この関数は、次のようないくつかのパラメーターを受け取ります。

  • コンパイルされたジオメトリ シェーダーへのポインター (ジオメトリ シェーダーが存在せず、頂点シェーダーから直接データがストリーミングされる場合は頂点シェーダー)。 このポインターを取得する方法については、「コンパイル済みシェーダーへのポインターの取得」を参照してください。
  • ストリーム出力ステージの入力データを記述する宣言の配列へのポインター。 (D3D11_SO_DECLARATION_ENTRY を参照してください。)SO ステージから出力される要素の種類ごとに 1 つずつ、最大 64 個の宣言を指定できます。 宣言エントリの配列は、ストリーム出力にバインドするバッファーが 1 つだけであるか複数であるかに関係なく、データ レイアウトを記述します。
  • SO ステージによって書き出される要素の数。
  • 作成されるジオメトリ シェーダー オブジェクトへのポインター (ID3D11GeometryShader を参照)。

この状況では、バッファー ストライドは NULL、ラスタライザーに送信されるストリームのインデックスは 0、クラス リンケージ インターフェイスは NULL です。

ストリーム出力宣言は、データをバッファー リソースに書き込む方法を定義します。 出力宣言には、必要な数のコンポーネントを追加できます。 SO ステージを使用して、1 つのバッファー リソースまたは多数のバッファー リソースに書き込みます。 1 つのバッファーの場合、SO ステージでは、頂点ごとにさまざまな要素を書き込むことができます。 複数のバッファーの場合、SO ステージでは、頂点ごとのデータの 1 つの要素のみを各バッファーに書き込むことができます。

ジオメトリ シェーダーを使用せずに SO ステージを使用するには、ID3D11Device::CreateGeometryShaderWithStreamOutput を呼び出し、頂点シェーダーへのポインターを pShaderBytecode パラメーターに渡します。

出力ターゲットを設定する

最後の手順では、SO ステージ バッファーを設定します。 データは、後で使用するためにメモリ内の 1 つ以上のバッファーにストリーミングできます。 次のコードは、頂点データと、データをストリーミングする SO ステージに使用できる単一のバッファーを作成する方法を示しています。

ID3D11Buffer *m_pBuffer;
int m_nBufferSize = 1000000;

D3D11_BUFFER_DESC bufferDesc =
{
    m_nBufferSize,
    D3D11_USAGE_DEFAULT,
    D3D11_BIND_STREAM_OUTPUT,
    0,
    0,
    0
};
D3D11Device->CreateBuffer( &bufferDesc, NULL, &m_pBuffer );

ID3D11Device::CreateBuffer を呼び出してバッファーを作成します。 この例では、CPU によって頻繁に更新されることが予想されるバッファー リソースの一般的な既定の使用方法を示します。 バインド フラグは、リソースをバインドできるパイプライン ステージを識別します。 SO ステージで使用されるリソースも、バインド フラグ D3D10_BIND_STREAM_OUTPUT を使用して作成する必要があります。

バッファーが正常に作成されたら、ID3D11DeviceContext::SOSetTargets を呼び出して現在のデバイスに設定します。

UINT offset[1] = 0;
D3D11Device->SOSetTargets( 1, &m_pBuffer, offset );

この呼び出しは、バッファーの数、バッファーへのポインター、オフセットの配列 (データの書き込みを開始する場所を示す各バッファーへの 1 つのオフセット) を受け取ります。 描画関数が呼び出されると、これらのストリーミング出力バッファーにデータが書き込まれます。 内部変数は、ストリーミング出力バッファーへのデータの書き込みを開始する位置を追跡し、SOSetTargets が再度呼び出され、新しいオフセット値が指定されるまで、その変数は引き続きインクリメントされます。

ターゲット バッファーに書き出されるすべてのデータは、32 ビット値になります。

ストリーム出力ステージ