蝶の大群とGPUその2

English version is ready: 英語

どうも吉村です。

前回の記事は実験中ということで、紹介にとどまっていた蝶の大群の操作&レンダリングについて、
もう少し踏み込んでいきます。

また、今回は一段グレードアップしまして、蝶が8種類登場します。

ColorfulButterfly from wowdev on Vimeo.

ここでは今回のポイントである、
ジオメトリインスタンシグと、OpenGL同期オブジェクトについて説明します。

ジオメトリインスタンシグは、同じものを大量に描画する際の高速化テクニックです。
glDrawArrays等のコールを減らしGPUとCPUの関係性を疎にするものです。

通常、3Dモデルを表示させたい場合、
頂点シェーダーに以下のように記述することがあります。

mainでは、a_positionをu_location分ずらし、アフィン変換行列をかけ算し、gl_Positionに出力します。

しかしながら、このアプローチの場合、3Dモデルが1000体いた場合、
glUniform系の命令とglDrawArrays系をループで大量にコールしなければなりません。

それをなんとかするのがジオメトリインスタンシグです。
ジオメトリインスタンシグを使用する場合、先ほどの宣言は以下のように変更することが可能です。

・・・しかしながらこれは普通に見ると微妙に見えます。
なぜなら、もし表示したい3Dモデルが1000ポリゴンだったとしたら、
1000ポリゴン分ai_locationにデータを流し込まなければならないからです。
データとして

こういう状態では非常にやりにくいです。
できれば次のようなデータ構造でいきたいわけです。

この願いを叶えてくれるのが、
glVertexAttribDivisor(ARB)です。
第一引数には、glGetAttributeLocationで取得したLocationを
第二引数には、
0を入れる -> 通常状態。
1を入れる -> インスタンス1つ描画するごとにバッファを1つ前に進める。大抵はこれ
nを入れる -> インスタンスn描画するごとにバッファを1つ前に進める。

というものです。
そしてインスタンスの個数は
glDrawArraysInstancedARBで指定します。
最終引数以外はglDrawArraysと同じで、
最後の引数でインスタンス数を指定します。
これで晴れて1回の描画コールで1000でも100000でもオブジェクトを大量に描画することが可能になります。

今ひとつイメージがわかないと思われるので、GPUが行うことを疑似コードに起こしてみましょう。

このように理解すれば問題ないでしょう。(glVertexAttribDivisorでの第二引数が1の場合)
※もちろん実際にはこの処理は並列化して実行されるため、このコードのように逐次は実行されません。

上の例ではインスタンスの変数はpositionだけですが、
ちなみに今回の蝶では以下のようなデータセットになっています。

positionは位置
directionは方向
offsetはアニメーションの進捗オフセット
textureIndexが見に行くテクスチャのインデックス

次にOpenGL同期オブジェクトについて説明します。
今までopenclのコードを非同期でディスパッチする際に、glFinishで描画の完了を待っていたのですが、
これは効率的でないことが分かりました。
代わりに以下のように同期オブジェクトを使用することで、
より無駄無く同期することが可能です。

使える環境であれば、積極的に使っていきたいところです。

※iOS6以降でも拡張機能としてglFenceSyncAPPLEがあるようです。

これら効率的な描画と、OpenCLを用いることで、
25万匹のカラフルな蝶をリアルタイムで制御&レンダリングが可能となります。(前回は単色のみでした)

個々の技術はとても汎用的な技術なので、
これからがんがん活用していきたい所です。

今回のフルソースコードはこちらです。
画像は公開用に別のものを使用していますが、
本質的なコードはすべて含まれています。
https://github.com/wowdevjp/butterfly-GPU