今回は頂点バッファとインデックスバッファを使って矩形の描画をしていきます。
頂点バッファとは?
頂点バッファとは頂点情報の塊のことです。
例えば前回のコードでいえばここです。
const SimpleVertex vertices[4] = { { 10.0f, 10.0f, 0.0f, 1.0f, 0xffffffff }, { 50.0f, 10.0f, 0.0f, 1.0f, 0xffffffff }, { 10.0f, 50.0f, 0.0f, 1.0f, 0xffffffff }, { 50.0f, 50.0f, 0.0f, 1.0f, 0xffffffff }, };
ただ実際に描画するにはこの情報をGPU側に転送する必要があります。
前回使用したDirectX9のDrawPrimitiveUP関数はその部分を簡略化してくれる関数です。
関数内で渡した頂点バッファの情報をGPU側から参照できるメモリにコピーして描画してくれます。
今回は何をするの?というと予めGPU側から参照できるバッファを用意して描画しようということです。
では何故コピーする方法ではダメなのかというと、一番大きな要因はコピーでは処理的に負荷が掛かってしまう事がです。
2Dの単純な矩形であれば特に問題はないですが、3Dモデルの様な頂点数が数千~数万以上あるものになると必須です。
頂点バッファの生成
DirectXにはGPUから参照するためのオブジェクトが用意されています。
先ずはそのオブジェクトを作成して先ほどの頂点バッファを書き込む関数を作っていきます。
// 頂点バッファの生成 IDirect3DVertexBuffer9* CreateVertexBuffer(const void* pVertices, size_t size) { IDirect3DVertexBuffer9* pVertexBuffer; IDirect3DDevice9* pDevice = GetDirect3DDevice(); // 指定したサイズの頂点バッファを作成 if( FAILED(pDevice->CreateVertexBuffer(size, 0, 0, D3DPOOL_MANAGED, &pVertexBuffer, NULL)) ) { return NULL; } void* pData; // バッファをロックしてデータを書き込む if( SUCCEEDED(pVertexBuffer->Lock(0, size, &pData, 0)) ) { memcpy(pData, pVertices, size); pVertexBuffer->Unlock(); } return pVertexBuffer; }
前回もそうでしたがDirectXで何かをする際は主にDirect3Dデバイスを経由して行うことになります。
今回は頂点バッファを生成する処理をデバイスから呼び出しています。
pDevice->CreateVertexBuffer(size, 0, 0, D3DPOOL_MANAGED, &pVertexBuffer, NULL);
CreateVertexBufferはそのまま頂点バッファを生成する処理です。
第一引数のsizeof(Vertex)はバッファサイズを指定しています。
第二引数の使用方法を設定しています。
CPUからの参照を許可するか?等を設定できます。
第三引数は頂点フォーマットを指定できます。
描画時にも設定できるので基本は必要ありません。
第四引数のD3DPOOL_MANAGEDはどのメモリに配置するか指定しています。
状況に応じて変えた方が実行時の効率がいいのですが、D3DPOOL_MANAGEDにしておけば管理は楽になります。
第五引数の&pVertexBufferは生成した頂点バッファの格納先を指定します。
第六引数のNULLは現在使用されていない値です。必ずNULLを指定します。
これで頂点バッファを作成する準備が整いました。
では実際に生成して描画をしてみましょう。
IDirect3DVertexBuffer9* g_pVertexBuffer; // リソースの初期化 bool InitializeResource(void) { const SimpleVertex vertices[8] = { { 10.0f, 10.0f, 0.0f, 1.0f, 0xffff7f7f }, { 50.0f, 10.0f, 0.0f, 1.0f, 0xff7fff7f }, { 10.0f, 50.0f, 0.0f, 1.0f, 0xff7f7fff }, { 50.0f, 50.0f, 0.0f, 1.0f, 0xffffffff }, }; // 頂点バッファの生成 g_pVertexBuffer = CreateVertexBuffer(vertices, sizeof(vertices)); if( g_pVertexBuffer == NULL ){ return false; } return true; } // リソースの解放 void CleanupResource(void) { SAFE_RELEASE(g_pVertexBuffer); }
これでリソースの初期化が出来ました。
前回と全く同じでは面白くないので今回は頂点カラーを少しカラフルに変えています。
合わせて解放処理も忘れないようにしましょう。
次に描画処理を前回の物から少し変えていきます。
// 使用する頂点バッファを設定 pDevice->SetStreamSource(0, g_pVertexBuffer, 0, sizeof(SimpleVertex)); // 描画 pDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
前回、DrawPrimitiveUPを呼び出していた箇所を頂点バッファの設定とDrawPrimitiveに変更します。
描画の処理の変更はこれだけです。
それでは実行結果を見てみましょう。
無事にカラフルな矩形が表示されたはずです。
最初のうちは理解しにくい部分が多いかもしれませんが、頂点バッファの描画は以外と簡単なんです。
次はインデックスバッファオブジェクトを生成する処理を作ってインデックスバッファを使った描画の方法を書いていきます。
インデックスバッファとは?
インデックスバッファとは頂点バッファのデータを効率良く使うために使用される情報です。
無くても対応はできますが、無駄なデータを減らして最適な描画を行う事が出来ます。
例えば四角形を作る際に4つの頂点で描画をしていました。
これがもし2つの四角形を一度に描画しようと思うとどうすればいいでしょうか?
1つは2回描画処理を呼び出すという方法があります。
ただ一般的にDrawコールと呼ばれるDrawPrimitiveなどの関数には大きな処理負荷が掛かってきます。
そのDrawコールの回数を減らすには頂点を増やして一度で2つ書いていきます。
単純に頂点バッファのみで2つ描画するには下記のような書き方になります。
const SimpleVertex vertices[12] = { // 1つ目の左上側 { 10.0f, 10.0f, 0.0f, 1.0f, 0xffff7f7f }, { 50.0f, 10.0f, 0.0f, 1.0f, 0xff7fff7f }, { 10.0f, 50.0f, 0.0f, 1.0f, 0xff7f7fff }, // 1つ目の右下側 { 10.0f, 50.0f, 0.0f, 1.0f, 0xff7f7fff }, { 50.0f, 10.0f, 0.0f, 1.0f, 0xff7fff7f }, { 50.0f, 50.0f, 0.0f, 1.0f, 0xffffffff }, // 2つ目の左上側 { 110.0f, 110.0f, 0.0f, 1.0f, 0xffff7f7f }, { 150.0f, 110.0f, 0.0f, 1.0f, 0xff7fff7f }, { 110.0f, 150.0f, 0.0f, 1.0f, 0xff7f7fff }, // 2つ目の右下側 { 110.0f, 150.0f, 0.0f, 1.0f, 0xff7f7fff }, { 150.0f, 110.0f, 0.0f, 1.0f, 0xff7fff7f }, { 150.0f, 150.0f, 0.0f, 1.0f, 0xffffffff }, };
バッファのサイズが一気に大きくなりました。
次に描画処理
pDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 4);
D3DPT_TRIANGLESTRIP→D3DPT_TRIANGLELISTに変わって描画するポリゴン数も2→4になりました。
何故、四角形が二つで先ほどの倍の8個ではないのか?と疑問に思った方もいると思います。
もしD3DPT_TRIANGLESTRIPのまま頂点も8個で描画してしまうとポリゴンがつながってしまうのでこんな感じになってしまいます。
これじゃ思っていたのと違いますよね?
なので複数の矩形を描画するときはD3DPT_TRIANGLELISTを使って重複する頂点バッファをいくつも持たなければ出来ないので無駄なデータが多くなります。
インデックスバッファはこの無駄なデータを最小限の情報で補ってしまおうというものです。
インデックスバッファの生成
インデックスバッファもオブジェクトを生成する関数を作成していきます。
// インデックスバッファの生成 IDirect3DIndexBuffer9* CreateIndexBuffer(const UINT16* pIndeces, size_t size) { IDirect3DIndexBuffer9* pIndexBuffer; IDirect3DDevice9* pDevice = GetDirect3DDevice(); // 16byte型のインデックスバッファを作成 if( FAILED(pDevice->CreateIndexBuffer(size, 0, D3DFMT_INDEX16, D3DPOOL_MANAGED, &pIndexBuffer, NULL)) ) { return NULL; } void* pData; // バッファをロックしてデータを書き込む if( SUCCEEDED(pIndexBuffer->Lock(0, size, &pData, 0)) ) { memcpy(pData, pIndeces, size); pIndexBuffer->Unlock(); } return pIndexBuffer; }
頂点バッファの生成とかなり似ているのが分るかと思います。
基本的にDirectXのバッファは似た振る舞いで作成できるようになっているので流れは基本的に似通っています。
pDevice->CreateIndexBuffer(size, 0, D3DFMT_INDEX16, D3DPOOL_MANAGED, &pIndexBuffer, NULL)
引数の内容も大体頂点バッファと同じようなものです。
異なる箇所としては第三引数のD3DFMT_INDEX16の部分です。
D3DFMT_INDEX16はインデックスバッファのフォーマットを指定しています。
D3DFMT_INDEX16を指定した場合はインデックスバッファは16bit(2byte)になります。
16bitで足りない場合はD3DFMT_INDEX32を指定することで32bit(4byte)まで利用できます。
16bitは65535個までの数値を扱えるのでそれなりのクオリティのモデルであれば問題なく作れるかと思います。
次に先ほどの生成処理をインデックスバッファを含めた形に修正します。
IDirect3DVertexBuffer9* g_pVertexBuffer; IDirect3DIndexBuffer9* g_pIndexBuffer; // リソースの初期化 bool InitializeResource(void) { const SimpleVertex vertices[8] = { // 1つ目の矩形 { 10.0f, 10.0f, 0.0f, 1.0f, 0xffff7f7f }, { 50.0f, 10.0f, 0.0f, 1.0f, 0xff7fff7f }, { 10.0f, 50.0f, 0.0f, 1.0f, 0xff7f7fff }, { 50.0f, 50.0f, 0.0f, 1.0f, 0xffffffff }, // 2つ目の矩形 { 110.0f, 110.0f, 0.0f, 1.0f, 0xffff7f7f }, { 150.0f, 110.0f, 0.0f, 1.0f, 0xff7fff7f }, { 110.0f, 150.0f, 0.0f, 1.0f, 0xff7f7fff }, { 150.0f, 150.0f, 0.0f, 1.0f, 0xffffffff }, }; // 頂点バッファの生成 g_pVertexBuffer = CreateVertexBuffer(vertices, sizeof(vertices)); if( g_pVertexBuffer == NULL ){ return false; } const UINT16 indices[12] = { 0, 1, 2, 2, 1, 3, // 1つ目の矩形 4, 5, 6, 6, 5, 7, // 2つ目の矩形 }; // インデックスバッファの生成 g_pIndexBuffer = CreateIndexBuffer(indices, sizeof(indices)); if( g_pIndexBuffer == NULL ){ return false; } return true; } // リソースの解放 void CleanupResource(void) { SAFE_RELEASE(g_pVertexBuffer); SAFE_RELEASE(g_pIndexBuffer); }
頂点バッファは12個から8個に減りました。
その代わりにインデックスバッファが12個分用意してあります。
これを見てインデックスバッファがどういった役割か分かった方もいるかと思います。
インデックスバッファとは重複した頂点バッファを作らないように使用する頂点バッファを番号(インデックス)で管理するための物です。
頂点バッファには様々な情報が入っているのでデータサイズが大きくなることがあります。
なのでインデックスバッファを使用して削減した方が全体のデータ量が少なくて済むのです。
// インデックスバッファの設定 pDevice->SetIndices(g_pIndexBuffer); // 頂点バッファの設定 pDevice->SetStreamSource(0, g_pVertexBuffer, 0, sizeof(SimpleVertex)); // 描画 pDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 8, 0, 4);
今度はDrawPrimitiveをDrawIndexedPrimitiveに変更します。
そしてDrawコールを呼び出す前に使用するインデックスバッファの設定を追加しています。
インデックスバッファを使った描画も変更はこれだけです。
実際に実行してみるとこうなります。
矩形が思ったところに2つ出ました。
これが出来れば複雑なモデルだとしても基本の流れはこの処理なので描画することができるようになります。
ただこのままでは指定した色の矩形が出るだけで絵が出せていません。
次の記事では画面に事前に用意した絵を表示するための方法を記載していきたいと思います。