「 インスタンシング 」一覧

OpenGL で インスタンシング描画


OpenGL 4.x でのインスタンシング描画を試してみました。インスタンシング描画とはCPU(C++)からの描画呼び出しは1回で、同じモデルを複数回描画することを指します。このときモデル形状のデータは使い回して、別々の場所に描画するということをやってみます。これはインスタンシングの中でもハードウェアインスタンシングと呼ばれていて、古くはDirectX 9.0c Shader Model 3 のころに導入されたものなので知っている人も多いかと思います。
 一方で OpenGL でハードウェアインスタンシングが使えるようになったのはそこから結構遅れました。こっそりと対応APIが入っていたこともありましたが、OpenGL 3.3付近でようやく標準的なものとなったようです。

インスタンシング用API

複数回の描画を行うためには、モデル形状のような回数には不変なデータと、位置指定のようなインスタンス毎で変化するデータの2種類を制御・設定する必要が出てきます。これらを設定するAPIが下記になります。

このAPIは、頂点データの増分間隔を設定するものとなります。通常の頂点データは1つの頂点データを処理するとその分だけカウンタがインクリメントされて次の頂点データを処理するようになります。このAPIで、divisor に 1 を設定した場合、index で示される場所の頂点データは描画インスタンス毎にインクリメントされるようになります。
通常の増分に戻す場合には、divisor = 0 として設定します。またここで index は頂点入力アトリビュート、もしくは頂点入力としてのGLSL入力変数のロケーション番号となります。

描画のAPIはこれらを使用します。OpenGLの拡張名としては GL_ARB_draw_instanced になります。countに頂点数やインデックス数を指定します。そして、通常の glDrawArrays,glDrawElements では存在しなかった最後の引数 primCount がインスタンス数を設定するようになっています。primCount なので、プリミティブ数かと間違えそうになりますが、インスタンス数です。注意しましょう。

サンプル

立方体のデータを1つ、それを4つほど出すためのワールド行列4つ分のバッファを用意します。どちらも頂点バッファとして準備します。準備した後、ワールド行列のバッファの方はインスタンス毎に参照データを更新したいため、glVertexAttribDivisor 関数を用いて設定を行っておきます。

ポイントは頂点データとして GLSL は mat4 の行列を受け取れるように記述できるのですが、glVertexAttribPointer では GL_FLOAT の4つのデータを1単位として合計4回のセットが必要になります。このミスマッチが気になったので、自分では GLSL の頂点入力としても vec4 の4つを宣言して使用するようにしました。

描画そのもののコードはこんな感じになります。

worldMatを更新の部分で書くインスタンス毎のワールド行列を生成し、頂点バッファとして送り込んでおきます。
実行した結果このような感じになります。立方体のデータ1つで4つ描画しています。

instancing_gl

今までの設定でどのようなことが起こっているかを図示するとこのような状況となります。

instancing_gl_2

インダイレクト描画でインスタンシングを使う

DirectX 11 ではインダイレクト描画であってもインスタンシング描画をできるようなAPIになっていました。OpenGLではどうなのかを試してみたいと思います。

DirectXの DrawInstancedIndirectの関数に対応するのは、やはり glDrawArraysIndirect, glDrawElementsIndirect になるようで、Indirectのバッファの中身はどうなのかを確認してみます。

このようになっており、第二メンバでインスタンス数を設定できるようになっています。この構造体の名前やメンバ名はここで命名したものが含まれています。公式のドキュメント等では別名がついています。なぜここだけ特殊なのかというと、公式の方がわかりにくい&歴史的事情でメンバ名が変わるなどがあったためです。
参考までに公式のほうでは現在このようになっています。

typedef struct {
      GLuint count;
      GLuint primCount;
      GLuint first;
      GLuint reservedMustBeZero;
} DrawArraysIndirectCommand;

typedef struct {
      GLuint count;
      GLuint primCount;
      GLuint firstIndex;
      GLint  baseVertex;
      GLuint reservedMustBeZero;
} DrawElementsIndirectCommand;

最後のメンバ reservedMustBeZero ですが、OpenGLの世代が進むと baseInstance を設定できるように変更になっていきます。バージョンにとらわれず使用したい場合にはゼロを指定してしまうのがよいでしょう。また、ここでprimCount となっているメンバですがこれはインスタンス数という意味です。glDrawArraysInstancedの関数と同様になっており、間違えないように注意しましょう。

この構造体に従って値をセットしてバッファからの描画をおこなえば、先ほどと同じような描画結果を得ることができました。すなわち OpenGL でも DirectX と同様に、インダイレクト描画であってもインスタンシング描画が行えるということが確認できました


インスタンシング


今日もインスタンシング

昨日に引き続き、今日はShaderInstancingをトライしていた。しかしHWインスタンシングに比べて動作は理解しやすい。単に形状データを繰り返し、頂点バッファを満たしておけばいい。あとはそれに対する変形パラメータを外部から叩き込めば終わり。必要回数、必要ポリゴン数になるまでdrawPrimitiveを繰り返す。

そんなわけで、さっくり実装できた。

HLSL

今は起動時で読み込んでコンパイルさせているけど、デフォルトのシェーダーくらいはlibに組み込んでしまいたいと思って色々調べてみた。どうやらバイナリのリソースとしてリソーススクリプトで記述すれば良さそうです。

GUIの操作としては、リソースのウィンドウで追加→ファイルから〜
その時の.rcの中身にはこんな風にして追記されることになります。

(グループ名を入れたかったのでそこだけは手作業)

<名前> <グループ名> DISCARDABLE “ファイル名.fx”

ここまでやって気付いたのだが、そのままバイナリファイルに組み込まれるみたい…。

さらに調べてみると .fxから .fxoを作ってしまうという方法もあるようだ。コンパイル済みシェーダーといったところか。

こちらを組み込みにすれば、先ほどよりはマシだろう。読まれたくないならば、
fxoをさらに暗号化してリソースにインポートすればいい話だし。

これには、プロジェクトに追加されている.fxファイルにコンパイルの記述を追加する。

こんな感じでコマンドライン行に追加。最初のはバージョンを決めるためのものだろう。
さらに、出力ファイルの箇所で “bin/$(InputName).fxo” と記述するのも忘れずに。

デバッグ版とリリース版で同じでも問題ないと思うけど、オプションを変更することも可能です。