Instancing10 サンプル

このサンプルでは、インスタンス化およびテクスチャー配列を使用して、複雑なシーンのレンダリングに必要な描画呼び出しの回数を抑える方法を示します。さらに、アルファトゥカバレッジを使用して、半透明のプリミティブの並べ替えを避ける方法も示します。

Ee416415.d3d10_sample_instancing(ja-jp,VS.85).jpg

パス

ソース SDK ルート\Samples\C++\Direct3D10\Instancing10
実行可能ファイル SDK ルート\Samples\C++\Direct3D10\Bin\x86 または x64\Instancing10.exe

サンプルが動作するしくみ

どのフレームでも、実行する描画呼び出しの回数を減らすことは、3D アプリケーションにとってグラフィックのパフォーマンスを高めるための手段となります。シーンで何度も描画呼び出しを行う必要があるのは、さまざまな部分でさまざまなステートが要求されるからです。ここでいうステートとは、一般に、行列やマテリアル プロパティなどです。このような課題に対応する方法の 1 つに、インスタンス化とテクスチャー配列を使用する方法があります。このサンプルでは、インスタンス化によって、CPU でオブジェクトごとにワールド行列を更新しなくても、同じオブジェクトをさまざまな場所に何度も描画できるようにします。また、テクスチャー配列を使用することで、複数のテクスチャーを同じリソースに読み込んだり、複数のテクスチャーに別のテクスチャー座標を使ってインデックスを付けられるようにします。これにより、新しいオブジェクトを描画するときにテクスチャーのリソースを変更する必要がなくなります。

サンプルでは、3 回の描画呼び出しを使用して、多数の葉が茂る何本かの木と、根本に生えている草を描きます。これには、シーン全体で何度もインスタンス化されてから DrawIndexedInstanced を使って描画された、木、葉、および草のメッシュをそれぞれ 1 つずつ使用します。葉や草の表現にバリエーションを付けるために、テクスチャー配列を使用して、葉と草の両方のインスタンスに作成したさまざまなテクスチャーを格納します。また、アルファトゥカバレッジを使用して、CPU の負荷を軽減しながら葉と草を順不同で描けるようにします。残りの環境は、6 回の描画呼び出しを使用して描きます。

島と木のインスタンス化

島や木を複製するために、サンプルには 2 つの情報が必要になります。1 つはメッシュ情報です。この例では、木のメッシュは tree.sdkmesh から読み込まれ、島のメッシュは island.sdkmesh から読み込まれます。もう 1 つの情報は、すべての木のインスタンスの位置を示す行列リストが格納されたバッファーです。この場合、4x4 D3DMATRIX 構造体の配列は、サンプルの CreateRandomTreeMatrices() 関数でランダムな位置およびスケーリング データを使用して定義および変換されます。

サンプルでは、次のように IASetVertexBuffers を使用して、メッシュ情報を頂点ストリーム 0 に、行列をストリーム 1 にバインドします。

ID3D10Buffer* pVB[2];pVB[0] = g_MeshTree.GetVB10( 0, 0 );pVB[1] = g_pTreeInstanceData;Strides[0] = ( UINT )g_MeshTree.GetVertexStride( 0, 0 );Strides[1] = sizeof( D3DXMATRIX );pd3dDevice->IASetVertexBuffers( 0, 2, pVB, Strides, Offsets );

この情報をシェーダーに正しく取り込むには、次の InputLayout を使用します。

const D3D10_INPUT_ELEMENT_DESC instlayout[] ={    { L"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },    { L"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0 },    { L"TEXTURE0", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D10_INPUT_PER_VERTEX_DATA, 0 },    { L"mTransform", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 0, D3D10_INPUT_PER_INSTANCE_DATA, 1 },    { L"mTransform", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 16, D3D10_INPUT_PER_INSTANCE_DATA, 1 },    { L"mTransform", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 32, D3D10_INPUT_PER_INSTANCE_DATA, 1 },    { L"mTransform", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 48, D3D10_INPUT_PER_INSTANCE_DATA, 1 },};

頂点シェーダーが呼び出される回数は、(メッシュ内の頂点の数)*(インスタンスの行列の数) です。行列はシェーダーの入力となるため、シェーダーはその時点で処理しているインスタンスに応じて、頂点を正しい位置に配置できます。

葉のインスタンス化

1 枚の葉は 1 本の木全体でインスタンス化し、1 本の木はサンプル全体を通して何度もインスタンス化するため、葉は、木や草のメッシュとは異なる方法で処理する必要があります。木の行列は定数バッファーに読み込まれます。InputLayout は、シェーダーが m_iNumTreeInstances で指定された回数だけ葉のメッシュ データを確認してから次の葉の行列に進むように設定されます。最後の要素 fOcc は、葉に陰影を付けるために使用される、焼き込み後のオクルージョン項です。

const D3D10_INPUT_ELEMENT_DESC leaflayout[] ={    { L"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },    { L"TEXTURE0", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0 },    { L"mTransform", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 0, D3D10_INPUT_PER_INSTANCE_DATA, m_iNumTreeInstances },    { L"mTransform", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 16, D3D10_INPUT_PER_INSTANCE_DATA, m_iNumTreeInstances },    { L"mTransform", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 32, D3D10_INPUT_PER_INSTANCE_DATA, m_iNumTreeInstances },    { L"mTransform", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 48, D3D10_INPUT_PER_INSTANCE_DATA, m_iNumTreeInstances },    { L"fOcc", 0, DXGI_FORMAT_R32_FLOAT, 1, 64, D3D10_INPUT_PER_INSTANCE_DATA, m_iNumTreeInstances },};

入力アセンブラーは、シェーダーに渡すことのできる InstanceID を自動的に生成します。次のシェーダー コードは、葉がどのように配置されるかを示しています。

    int iTree = input.InstanceId%g_iNumTrees;    float4 vInstancePos = mul( float4(input.pos, 1), input.mTransform  );    float4 InstancePosition = mul(vInstancePos, g_mTreeMatrices[iTree] );

シーン内に 3 本の木がある場合、葉が描画される順序は、木 1 の葉 1、木 2 の葉 1、木 3 の葉 1、木 1 の葉 2、木 2 の葉 2、というようになります。

草のインスタンス化

草のレンダリングは、木や葉とは異なる方法で処理されます。入力アセンブラーを使用して草をインスタンス化したり、行列の個別のストリームを使用したりするのではなく、草はジオメトリ シェーダーにより動的に生成されます。島の上の部分のメッシュが頂点シェーダーに渡され、そこからこの情報は GSGrassmain ジオメトリ シェーダーに直接渡されます。指定された草の密度に応じて、GSGrassmain は、現在の三角形上で草の位置に対応する擬似ランダム位置を計算します。これらの位置は、その地点に草を作成するヘルパー関数に渡されます。ランダムな浮動小数点値の 1D テクスチャーは、擬似乱数の生成に使用されます。このテクスチャーには、入力メッシュの頂点 ID によってインデックスが付けられます。これにより、フレーム間でランダム分布が変化することはなくなります。

別の方法として、クワッドの頂点を最初の頂点ストリームに配置し、各草の行列を 2 つ目の頂点ストリームに配置すると、木と同じ方法で草をレンダリングできます。2 つ目のストリームの fOcc 要素は、葉の場合と同様に、事前に計算された陰影を草に付けるために使用できますしかし、ストレージ領域を数十万もの行列のストリームに使用することは、最新のグラフィック ハードウェアであっても重要な問題となります。その点、草の生成にジオメトリ シェーダーを使用する方法は、組み込みの陰影が不十分ではあるものの、ストレージ領域を大幅に減らすことができます。

テクスチャー配列による葉の変更

テクスチャー配列とは、まさに名前が示すとおりのものです。つまり、テクスチャーの配列であり、それぞれに完全なミップマップが指定されます。texture2D 配列の場合は、z 座標によってインデックスが付けられます。InstanceID がシェーダーに渡されるため、サンプルでは InstanceID%numArrayIndices を使用して、特定の葉や草のレンダリングに使用する配列内のテクスチャーを特定します。

アルファトゥカバレッジによる透明オブジェクトの描画

サンプル内に透明な葉や草が多数あると、CPU でこれらのオブジェクトを並べ替える際に負荷がかかります。アルファトゥカバレッジを使用すると、葉や草を前後に並べ替えなくても納得のいく結果が得られるので、この問題を解決するのに役立ちます。アルファトゥカバレッジは、マルチサンプル アンチエイリアシング (MSAA) とともに使用する必要があります。MSAA は、高解像度の z バッファーで三角形のカバレッジを高周波数で評価することにより、エッジをアンチエイリアシングする手法です。アルファトゥカバレッジを使用すると、MSAA メカニズムを巧みに操作して、描画順序に依存しない透明化を擬似的に作成できます。アルファトゥカバレッジは、ピクセル シェーダーの出力アルファに基づいて、ピクセルの MSAA カバレッジ マスクを生成します。その結果は、AND によって三角形のカバレッジ マスクに結合されます。このプロセスは網戸の透明描画に似てますが、MSAA レベルで行われます。

アルファトゥカバレッジは、窓のように真に描画順序に依存しない透明化を実行する場合には適しませんが、ミップマップ化された葉のテクスチャーのようにカバレッジの表現にアルファを使用する場合には、大きな役割を果たします。