DirectXの話 第132回

Draw Indirect

今回はDirectX11から追加されたDraw Indirectを使ったオクルージョンカリングをやってみました。

上の映像は1024台の戦車を描画していますが、オクルージョンカリングのあり/なしでかなり速度差が出ました。

家の環境(RADEON HD 5870)では40ms前後の差が出ています。

オクルージョンカリングの手法は以前やってみたやつと同じです。

背景(サンプルでは床と円柱)を描画した後、深度の値を縮小バッファに縮小コピーしています。

今回は幅と高さをそれぞれ1/8にしています。

これに対してCompute Shaderを用いてAABBのオクルージョン判定を行います。

AABBは8頂点をスクリーンに変換し、そこからスクリーンスペースのAABBを求めています。

もっと正確に求めることは出来ると思いますが、ここでは高速化のために簡単な方法を用いました。

これ自体は以前にもやっているのでこれ以上の解説は行いません。

今回の本命はDraw Indirect命令にあります。

ここからはこの説明に移ります。

Draw Indirect命令はDirectX11から追加されています。

DirectX10ではDrawAuto()という命令が存在しましたが、これをより汎用的にした命令、という感じのようです。

Draw Indirect命令は以下の2つが存在しています。

DrawInstancedIndirect()

DrawIndexedInstancedIndirect()

どちらもインスタンス描画用の命令です。

インスタンス描画とは同じモデルを複数描画する際に使用する描画命令です。

同じモデルを大量に描画したい場合というのは結構あります。

木や草などはコピペで大量に配置するのが基本ですが、モデルが分けられている場合は1つずつ描画キックしてやらなければいけません。

描画キックはそれ自体が結構重いため、高速化のためには描画キック自体を減らす必要があります。

インスタンス描画をする場合、Vertex ShaderにはSV_InstanceIDという属性でインスタンス番号を取得することが出来ます。番号は0~の連番が割り振られます。

この番号からワールド行列を定数バッファかStructuredBufferから取得すれば別々の場所に同じモデルを描画することが出来ます。

物理パラメータを取得すると樹木の揺れ方なども計算してデフォーミングを行うことも出来ますし、テクスチャを番号に応じて変更することで別の物を描画しているように見せることも出来ます。

例えば無双シリーズのようなゲームを作成する場合には使えるかもしれませんね。

使い方はどうするのでしょうか?参考に、DrawInstancedIndirect()の引数を見てみましょう。

ID3D11Buffer* pBufferForArgs

UINT AlignedByteOffsetForArgs

この2つです。なお、DrawIndexedInstancedIndirect()も同じ引数です。

pBufferForArgsはByteAddressBufferを指定します。

ByteAddressBufferは作成時にMiscFlagsに対してD3D11_RESOURCE_MISC_DRAWINDIRECT_ARGSを立ててやる必要があります。

このフラグを立てることによるペナルティはないっぽいので、RWByteAddressBufferを作成する際は常に立てても問題ないと思います。

このバッファには何を入れればいいのかというと、元になった関数であるDrawInstanced()かDrawIndexedInstanced()の引数を突っ込む必要があります。

どちらも4バイト整数のみしか使用していないので、DrawInstancedIndirect()の場合は4*4バイト、DrawIndexedInstancedIndirect()の場合は5*4バイトが必要になります。

AlignedByteOffsetForArgsはバッファの先頭から何バイト目からを引数として使用するかを指定します。

今回の戦車モデルは2つのメッシュが存在するため、このオフセットを利用して2回の描画キックを行っています。

今回はDrawIndexedInstancedIndirect()を使用しているため、描画キック1回に付き20バイトのバッファが必要になります。

もちろんバッファを複数用意してもかまわないのですが、モデルによってメッシュの数はまちまちなので、それを複数のバッファで対処するのは大変です。

なので、1つのバッファに複数描画キック分のサイズを指定します。今回は20*2で40バイトです。

最初の描画キックではこの引数を0として、バッファの先頭から20バイトを利用します。2回目のキックは引数を20にすることで20バイト目から20バイトを利用することになります。

メッシュの数に対するスケーラブルな対応についてはcompute_occlusion.hlslの98行目辺りからを参考にしてください。

なお、Indirect系命令としてはDispatchIndirect()という命令もあります。

わかるとは思いますが、こちらはCompute Shaderを実行するDispatch()命令のIndirect処理です。

こちらも使い方は同じですね。

あまり長々解説するのもあれなので、サンプルは以下からDLしてください。

Download:Sample123.zip

デフォルトではオクルージョンカリングはOFFになっています。

1024台の戦車がフルに描画されているので、環境次第では相当重くなるかと思います。

ONにしてもらえれば少しは軽くなるはずです。

と言っても、家の環境ですら30FPS出ないわけですが。

なお、戦車モデルは1台5000~6000ポリゴンくらいのはずです。

最大で500万~600万ポリゴン。まあ、そりゃ重いよね。

ほぼ完全にVertex Shaderネックになっているので、Pixel Shaderを軽くしたとしてもほとんど効果はありません。

あまりにも重いので、今回のサンプルでは戦車をシャドウキャスタにはしていません。

次回の予定は今のところありません。

SVO生成方法が自分の中でまとまればいいのですが、未だによく理解できずに悩んでいるわけで。

以前やってみたマーチングキューブをGPU生成するってのはどうかな…

なんか面白そうなネタがあったら教えてください。