蝶の大群とGPUその2
English version is ready: 英語
どうも吉村です。
前回の記事は実験中ということで、紹介にとどまっていた蝶の大群の操作&レンダリングについて、
もう少し踏み込んでいきます。
また、今回は一段グレードアップしまして、蝶が8種類登場します。
ColorfulButterfly from wowdev on Vimeo.
ここでは今回のポイントである、
ジオメトリインスタンシグと、OpenGL同期オブジェクトについて説明します。
ジオメトリインスタンシグは、同じものを大量に描画する際の高速化テクニックです。
glDrawArrays等のコールを減らしGPUとCPUの関係性を疎にするものです。
通常、3Dモデルを表示させたい場合、
頂点シェーダーに以下のように記述することがあります。
1 2 |
uniform vec3 u_location; attribute vec3 a_position; |
mainでは、a_positionをu_location分ずらし、アフィン変換行列をかけ算し、gl_Positionに出力します。
しかしながら、このアプローチの場合、3Dモデルが1000体いた場合、
glUniform系の命令とglDrawArrays系をループで大量にコールしなければなりません。
それをなんとかするのがジオメトリインスタンシグです。
ジオメトリインスタンシグを使用する場合、先ほどの宣言は以下のように変更することが可能です。
1 2 |
attribute vec3 ai_location; attribute vec3 a_position; |
・・・しかしながらこれは普通に見ると微妙に見えます。
なぜなら、もし表示したい3Dモデルが1000ポリゴンだったとしたら、
1000ポリゴン分ai_locationにデータを流し込まなければならないからです。
データとして
1 2 3 4 |
struct Vertex{ vec3 position; vec3 location; }; |
こういう状態では非常にやりにくいです。
できれば次のようなデータ構造でいきたいわけです。
1 2 3 4 5 6 |
struct Vertex{ vec3 position; }; struct Instance{ vec3 location; }; |
この願いを叶えてくれるのが、
glVertexAttribDivisor(ARB)です。
第一引数には、glGetAttributeLocationで取得したLocationを
第二引数には、
0を入れる -> 通常状態。
1を入れる -> インスタンス1つ描画するごとにバッファを1つ前に進める。大抵はこれ
nを入れる -> インスタンスn描画するごとにバッファを1つ前に進める。
というものです。
そしてインスタンスの個数は
glDrawArraysInstancedARBで指定します。
最終引数以外はglDrawArraysと同じで、
最後の引数でインスタンス数を指定します。
これで晴れて1回の描画コールで1000でも100000でもオブジェクトを大量に描画することが可能になります。
今ひとつイメージがわかないと思われるので、GPUが行うことを疑似コードに起こしてみましょう。
1 2 3 4 5 6 7 8 9 10 |
for(int iInstance = 0 ; iInstance < INSTANCE_NUM ; ++iInstance) { ai_location = locations[iInstance]; //インスタンス座標を更新する for(int iVertex = 0 ; iVertex < VERTEX_NUM ; ++iVertex) { a_position = vertices[iVertex] //頂点ごとにデータを入れる VertexShader(); //頂点シェーダーを呼び出す } } |
このように理解すれば問題ないでしょう。(glVertexAttribDivisorでの第二引数が1の場合)
※もちろん実際にはこの処理は並列化して実行されるため、このコードのように逐次は実行されません。
上の例ではインスタンスの変数はpositionだけですが、
ちなみに今回の蝶では以下のようなデータセットになっています。
1 2 3 4 5 6 7 |
struct ButterflyInstance { float3 position; float3 direction; float offset; float textureIndex; }; |
positionは位置
directionは方向
offsetはアニメーションの進捗オフセット
textureIndexが見に行くテクスチャのインデックス
次にOpenGL同期オブジェクトについて説明します。
今までopenclのコードを非同期でディスパッチする際に、glFinishで描画の完了を待っていたのですが、
これは効率的でないことが分かりました。
代わりに以下のように同期オブジェクトを使用することで、
より無駄無く同期することが可能です。
1 2 3 |
GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, 1000 * 1000); glDeleteSync(sync); |
使える環境であれば、積極的に使っていきたいところです。
※iOS6以降でも拡張機能としてglFenceSyncAPPLEがあるようです。
これら効率的な描画と、OpenCLを用いることで、
25万匹のカラフルな蝶をリアルタイムで制御&レンダリングが可能となります。(前回は単色のみでした)
個々の技術はとても汎用的な技術なので、
これからがんがん活用していきたい所です。
今回のフルソースコードはこちらです。
画像は公開用に別のものを使用していますが、
本質的なコードはすべて含まれています。
https://github.com/wowdevjp/butterfly-GPU