チュートリアル 2:三角形のレンダリング

Bb172486.d3d10_Tutorial02(ja-jp,VS.85).jpg

まとめ

前のチュートリアルでは、ウィンドウに単一色を出力する必要最小限の Direct3D 10 アプリケーションを作成しました。ここでは、画面に単一の三角形をレンダリングするように、そのアプリケーションを拡張します。三角形に関連するデータ構造の設定手順を 1 つずつ説明します。

このチュートリアルでは、結果として、ウィンドウの中央に三角形がレンダリングされます。

Source

(SDK ルート)\Samples\C++\Direct3D10\Tutorials\Tutorial02

  • 三角形の要素
  • 入力レイアウト
  • 三角形のレンダリング

三角形の要素

三角形は、頂点とも呼ばれる 3 つの点によって定義されます。一意な位置の 3 つの頂点のセットで、一意な三角形を定義します。GPU で三角形をレンダリングするには、3 つの頂点の位置を指示する必要があります。2D の例として、図 1 のような三角形をレンダリングする場合は、位置 (0, 0) (0, 1) および (1, 0) の 3 つの頂点を GPU に渡すことで、目的の三角形をレンダリングするために必要な情報を GPU に提供します。

図形 1.  3 つの頂点によって定義した 2D の三角形

Bb172486.d3d10_Tutorial02_Figure1_Triangle(ja-jp,VS.85).png

ここまでで、三角形をレンダリングするには GPU に 3 つの位置を渡す必要があることがわかりました。この情報を GPU に渡す方法を次に示します。Direct3D 10 では、位置などの頂点情報は、バッファー リソースに格納されます。頂点情報の格納に使用されるバッファーは、頂点バッファーと呼ばれます。3 つの頂点のために十分な大きさの頂点バッファーを作成し、それに頂点の位置を格納する必要があります。Direct3D 10 では、アプリケーションにおいて、バッファー リソースを作成するときにバッファー サイズをバイト単位で指定する必要があります。3 つの頂点に対してバッファーが十分な大きさでなければならないことはわかっていますが、各頂点に必要なバイト数がわかりません。これを知るには、頂点レイアウトについて理解する必要があります。

入力レイアウト

1 つの頂点には、1 つの位置があります。多くの場合、法線、1 つ以上の色、テクスチャー座標 (テクスチャー マッピングに使用される) など、その他の属性もあります。頂点レイアウトは、各属性で使用されるデータ型、各属性のサイズ、メモリー内での属性の順序など、これらの属性がメモリー内でどのように存在するかを定義するものです。通常、属性には C の構造体のフィールドのようにさまざまな型があるため、頂点は構造体で表されます。頂点のサイズは、構造体のサイズから適宜取得されます。

このチュートリアルでは、頂点の位置のみを操作します。そのため、頂点の構造体を、型 D3DXVECTOR3 の単一フィールドを使用して定義します。この型は、3 つの浮動小数点成分のベクトルであり、一般的に 3D での位置に使用されるデータ型です。

struct SimpleVertex
{
    D3DXVECTOR3 Pos;  // Position
};

これで、頂点を表す構造体が用意されました。これは、アプリケーションにおいてシステム メモリーへの頂点情報の格納を処理します。ただし、頂点を含む頂点バッファーを GPU に送るときは、単にそれに大量のメモリーを送っています。また、GPU は、バッファーから正しい属性を抽出するために、頂点レイアウトについて知っている必要があります。これを実現するには、入力レイアウトを使用する必要があります。

Direct3D 10 では、入力レイアウトは、GPU に理解可能な方法で頂点の構造体を表す Direct3D オブジェクトです。頂点の各属性は、D3D10_INPUT_ELEMENT_DESC 構造体で表せます。アプリケーションで、1 つ以上の D3D10_INPUT_ELEMENT_DESC を定義してから、配列を使用して頂点全体を表す入力レイアウト オブジェクトを作成します。次は、D3D10_INPUT_ELEMENT_DESC のフィールドを詳細に見てみましょう。

SemanticName セマンティック名は、この要素の性質または目的 (またはセマンティクス) を表す単語を含む文字列です。この単語は、C の識別子に適用可能な任意の形式の、任意のものにすることができます。たとえば、頂点の位置に適したセマンティック名は POSITION となります。セマンティック名の大文字/小文字は区別されません。
SemanticIndex セマンティック インデックスは、セマンティック名を補足するものです。頂点には、同じ性質の複数の属性がある場合があります。たとえば、テクスチャー座標のセットが 2 つある場合や、色のセットが 2 つある場合があります。"COLOR0" や "COLOR1" など、数字が追加されたセマンティック名を使用する代わりに、2 つの要素で、異なるセマンティック インデックス 0 と 1 を持つ単一のセマンティック名 "COLOR" を共有できます。
Format フォーマットは、この要素に使用するデータ型を定義するものです。たとえば、DXGI_FORMAT_R32G32B32_FLOAT の形式には 3 つの 32 ビット浮動小数点数 があり、要素を 12 バイト長にします。DXGI_FORMAT_R16G16B16A16_UINT の形式には 4 つの 16 ビット符号なし整数があり、要素を 8 バイト長にします。
InputSlot 既に説明したように、Direct3D 10 アプリケーションでは、頂点バッファーの使用によって頂点データが GPU に渡されます。Direct3D 10 では、複数の頂点バッファーを同時に GPU に送ることができます (正確には 16)。各頂点バッファーは、0 ~ 15 の入力スロット番号にバインドされます。InputSlot フィールドは、どの頂点バッファーをこの要素のためにフェッチする必要があるのかを GPU に示します。
AlignedByteOffset 頂点は頂点バッファーに格納されます。頂点バッファーは、単なる大量のメモリーです。AlignedByteOffset フィールドは、この要素のためのデータのフェッチを開始するメモリー位置を GPU に示します。
InputSlotClass このフィールドには、通常は値 D3D10_INPUT_PER_VERTEX_DATA があります。アプリケーションでインスタンス化を使用する場合は、入力レイアウトの InputSlotClass を D3D10_INPUT_PER_INSTANCE_DATA に設定して、インスタンス データを含む頂点バッファーを操作できます。インスタンス化は、高度な Direct3D トピックのため、ここでは説明しません。このチュートリアルでは、D3D10_INPUT_PER_VERTEX_DATA のみを使用します。
InstanceDataStepRate このフィールドは、インスタンス化のために使用されます。ここではインスタンス化は使用していないため、このフィールドは使用しません。そのため、0 に設定する必要があります。

これで、D3D10_INPUT_ELEMENT_DESC 配列を定義して、入力レイアウトを作成できます。

// Define the input layout
D3D10_INPUT_ELEMENT_DESC layout[] =
{
    { L"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },  
};
UINT numElements = sizeof(layout)/sizeof(layout[0]);

頂点レイアウト

次のチュートリアルでは、テクニック オブジェクトおよび関連するシェーダーについて説明します。ここでは、テクニックのために Direct3D 10 頂点レイアウトを作成することのみに専念します。一方で、テクニックとシェーダーがこの頂点レイアウトと密接に関連しているということを説明します。これは、頂点レイアウト オブジェクトの作成には頂点シェーダーの入力署名が必要なためです。最初に、テクニックの GetPassByIndex() メソッドを呼び出して、テクニックの最初のパスを表すエフェクト パス オブジェクトを取得します。次に、パス オブジェクトの GetDesc() メソッドを呼び出して、パス記述の構造体を取得します。この構造体には、pIAInputSignature という名前のフィールドがあります。これは、このパスで使用される頂点シェーダーの入力署名を表すバイナリ データを指します。このデータを取得すると、ID3D10Device::CreateInputLayout() を呼び出して頂点レイアウト オブジェクトを作成することや、ID3D10Device::IASetInputLayout() を呼び出してそれをアクティブな頂点レイアウトとして設定することが可能になります。これらすべてを行うためのコードを次に示します。

// Create the input layout
D3D10_PASS_DESC PassDesc;
g_pTechnique->GetPassByIndex( 0 )->GetDesc( &PassDesc );
if( FAILED( g_pd3dDevice->CreateInputLayout( layout, numElements, PassDesc.pIAInputSignature, 
        PassDesc.IAInputSignatureSize, &g_pVertexLayout ) ) )
    return FALSE;
// Set the input layout
g_pd3dDevice->IASetInputLayout( g_pVertexLayout );

頂点バッファーの作成

また、初期化中に行う必要があることは、頂点データを保持する頂点バッファーの作成です。Direct3D 10 で頂点バッファーを作成するには、D3D10_BUFFER_DESC と D3D10_SUBRESOURCE_DATA という 2 つの構造体に入力してから、ID3D10Device::CreateBuffer() を呼び出します。D3D10_BUFFER_DESC は、作成する頂点バッファー オブジェクトを表します。また、D3D10_SUBRESOURCE_DATA は、作成時に頂点バッファーへコピーされる実際のデータを表します。頂点バッファーの作成と初期化は、後でバッファーを初期化する必要がないように同時に実行されます。頂点バッファーへコピーされるデータは頂点であり、3 つの SimpleVertex の配列です。頂点配列内の座標は、シェーダーのレンダリング時にアプリケーション ウィンドウの中央に三角形が表示されるように選択されます。頂点バッファーの作成後は、ID3D10Device::IASetVertexBuffers() を呼び出してデバイスをそれにバインドできます。完全なコードを次に示します。

// Create vertex buffer
SimpleVertex vertices[] =
{
    D3DXVECTOR3( 0.0f, 0.5f, 0.5f ),
    D3DXVECTOR3( 0.5f, -0.5f, 0.5f ),
    D3DXVECTOR3( -0.5f, -0.5f, 0.5f ),
};
D3D10_BUFFER_DESC bd;
bd.Usage = D3D10_USAGE_DEFAULT;
bd.ByteWidth = sizeof( SimpleVertex ) * 3;
bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = 0;
bd.MiscFlags = 0;
D3D10_SUBRESOURCE_DATA InitData;
InitData.pSysMem = vertices;
if( FAILED( g_pd3dDevice->CreateBuffer( &bd, &InitData, &g_pVertexBuffer ) ) )
    return FALSE;

// Set vertex buffer
UINT stride = sizeof( SimpleVertex );
UINT offset = 0;
g_pd3dDevice->IASetVertexBuffers( 0, 1, &g_pVertexBuffer, &stride, &offset );

プリミティブ トポロジー

プリミティブ トポロジーは、GPU での、三角形のレンダリングに必要な 3 つの頂点の取得方法を示します。単一の三角形をレンダリングするにはアプリケーションで GPU に 3 つの頂点を送る必要があることは、既に説明しました。したがって、頂点バッファーの中には 3 つの頂点があります。2 つの三角形をレンダリングする場合はどうでしょうか。方法の 1 つとしては、6 つの頂点を GPU に送ります。最初の 3 つの頂点では 1 つ目の三角形を、次の 3 つの頂点では 2 つ目の三角形を定義します。このトポロジーは、トライアングル リストと呼ばれます。トライアングル リストには、理解が容易であるという利点がありますが、特定のケースではとても非効率になります。このようなケースは、連続してレンダリングされた三角形が複数の頂点を共有している場合に起こります。たとえば、左側の図 3a は、A B C と C B D という 2 つの三角形で構成された正方形を示しています (規則により、三角形は、一般的にそれらの頂点を時計回りの順序でリストすることによって定義されます)。トライアングル リストを使用してこれらの 2 つの三角形を GPU に送る場合は、頂点バッファーは次のようになります。

A B C C B D

B と C は両方の三角形で共有されるため、それらが頂点バッファーで 2 回現れている点に注目してください。

Bb172486.d3d10_Tutorial02_Figure3_MultiTriangles(ja-jp,VS.85).png

図 3a) は 2 つの三角形で構成された正方形を、図 3b は 3 つの三角形で構成された五角形を示しています。

2 つ目の三角形をレンダリングするときに、頂点バッファーから 3 つの頂点すべてをフェッチするのではなく、前の三角形の 2 つの頂点を使用して頂点バッファーからは 1 つの頂点のみをフェッチするように GPU に 指示できる場合は、頂点バッファーをより小さくできます。結論から言うと、これは Direct3D によってサポートされ、トポロジーはトライアングル ストリップと呼ばれます。トライアングル ストリップをレンダリングする場合は、最初の三角形は、頂点バッファーの最初の 3 つの頂点によって定義されます。次の三角形は、前の三角形の最後の 2 つの頂点と、頂点バッファーの次の頂点によって定義されます。例として、トライアングル ストリップを使用した図 3a の正方形では、頂点バッファーは次のようになります。

A B C D

最初の 3 つの頂点 A B C は、最初の三角形を定義しています。2 つ目の三角形は、B と C (1 つ目の三角形の最後の 2 つの頂点) および D で定義されています。したがって、トライアングル ストリップ トポロジーを使用することにより、頂点バッファー内の頂点の数が 6 つから 4 つになりました。同様に、図 3b のような 3 つの三角形でトライアングル リストを使用するには、次のような頂点バッファーが必要となります。

A B C C B D C D E

トライアングル ストリップを使用すると、頂点バッファーのサイズが大幅に小さくなります。

A B C D E

トライアングル ストリップの例では、2 つ目の三角形が B C D と定義されていることに気付いたかもしれません。これらの 3 つの頂点は、時計回りの順序になっていません。これは、トライアングル ストリップを使用する際の自然な現象です。これに対処するために、GPU では、前の三角形からもたらされる 2 つの頂点の順序が自動で交換されます。これは、2 つ目、4 つ目、6 つ目、8 つ目、といった順番の三角形に対してのみ実行されます。これにより、各三角形が、正しいワインディング順序 (この場合は時計回り) の頂点で定義されるようになります。トライアングル リストとトライアングル ストリップのほかにも、Direct3D 10 では、さまざまな種類のプリミティブ トポロジーがサポートされています。このチュートリアルではそれらについては説明しません。

コード内にある三角形は 1 つのため、何を指定するかは重要ではありません。しかし、何かを指定する必要があるため、トライアングル リストを選択しました。

// Set primitive topology
g_pd3dDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST );

三角形のレンダリング

不足している最後のアイテムは、実際に三角形のレンダリングを行うコードです。既に説明したように、このチュートリアルでは、エフェクト システムを使用します。テクニックを表す D3D10FX_TECHNIQUE_DESC 構造体を受け取るために、前に取得したテクニック オブジェクト上で ID3D10EffectTechnique::GetDesc() を呼び出すことにから始めます。D3D10FX_TECHNIQUE_DESC のメンバーの 1 つである Passes は、テクニックに含まれるパスの数を示します。このテクニックを使用して正しくレンダリングするには、アプリケーションにおいて、そこにあるパスと同じ回数のループを行う必要があります。ループ内で、最初にテクニックの GetPassByIndex() メソッドを呼び出してパス オブジェクトを取得してから、その Apply() メソッドを呼び出して、エフェクト システムを関連するシェーダーにバインドし、グラフィック パイプラインにステートをレンダリングする必要があります。次に行うことは、ID3D10Device::Draw() の呼び出しです。これは、現在のバッファー、頂点レイアウト、およびプリミティブ トポロジーを使用してレンダリングするように GPU に命令します。Draw() への最初のパラメーターは、GPU に送る頂点の数です。2 つ目のパラメーターは、送信を開始する最初の頂点のインデックスです。ここでは 1 つの三角形をレンダリングしており、頂点バッファーの最初からレンダリングしているため、2 つのパラメーターにそれぞれ 3 と 0 を使用します。三角形レンダリング コード全体は、次のようになります。

// Render a triangle
D3D10_TECHNIQUE_DESC techDesc;
g_pTechnique->GetDesc( &techDesc );
for( UINT p = 0; p < techDesc.Passes; ++p )
{
    g_pTechnique->GetPassByIndex( p )->Apply(0);
    g_pd3dDevice->Draw( 3, 0 );
}