OpenGLの transform feedback を使って GPU パーティクルっぽいものにチャレンジしてみました。以前、transform feedback を試していたときは、ストリームアウトばかり気にしていて、せっかくのGPUで演算が完了できるという点を見落としていました。
今回は VBOを2つ用意してこれをピンポンすることで頂点および情報の更新を行っています。
CPUからは初期情報だけは送り込むものの、その後の頂点の情報に関しては無関係です。 Transform feedback バッファに書き込まれた個数を取得して、描画命令の発行を行うくらいにとどまっています。
実装で大変だったこと
各1スプライト単位でカラー情報を保持させていたのですが、32bitのUINT でデータを送り込んでいました。しかし、feedback バッファに書き込む際には float4 になっており、2度目以降のカラー情報がおかしな状況となっていました。これに気づくまで結構かかりました。transform feedbackを使ってデータを書き込む際には1要素はfloatである、ということにしておいて、統一をとっておいた方がよさそうです。
結局、位置(vec3)、カラー(vec4)、速度(vec3) という情報を書き込むことにして、なんとなく実現できました。
そのほかの注意点としては、シェーダーのリンク前に glTransformFeedbackVaryings で出力変数を指定しておくことでしょうか。
const char* outVariables[] = { "Position", "Color", "Velocity" }; int outVariablesCount = _countof(outVariables); glTransformFeedbackVaryings( shaderProgram, outVariablesCount, outVariables, GL_INTERLEAVED_ATTRIBS );
更新&描画時の処理としてはこんな感じです. 更新フェーズではピクセルシェーダーで書き込みを行わないので無効化したり、頂点数を知るためにクエリを発行していたりします。
// 更新 glEnable( GL_RASTERIZER_DISCARD ); glUseProgram( shaderProgramUpdate ); glBindBufferBase( GL_TRANSFORM_FEEDBACK_BUFFER, 0, vbo[drawIndex] ); glBeginTransformFeedback( GL_POINTS ); glBeginQuery( GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, queryPrimWritten ); glDrawArrays(GL_POINTS, 0, vertexCount ); glEndTransformFeedback(); glEndQuery( GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ); GLuint outCount = 0; glGetQueryObjectuiv( queryPrimWritten, GL_QUERY_RESULT, &outCount ); // 描画 glDisable( GL_RASTERIZER_DISCARD ); glUseProgram( shaderProgramDraw ); glDrawArrays( GL_POINTS, 0, outCount );
やっていることは、ストリームアウトした物を描画指示しているだけです。
今までのブログの中では常にストリームアウトしたものを描画しているだけで、今回はこのストリームアウト先が描画フレーム毎に入れ替わるのが新しい点になります。だからこそシェーダーだけで位置更新できるのですが。
補足
どうやらこのようなTransfeedback の使い方は、レガシーな部類なんだとか。最近はもっと使いやすいようにGL拡張が追加されているとのことなので、これもちょっと調べてみようと思います。
古くさいやり方だからなのかもしれませんが、意外とCPUの占有率が高かったことが気になりました。しかもカーネル時間のほうだったので、書き込み数取得のところが負荷となっているのかも?と思っている次第です。
今までのOpenGL ストリームアウト記事
OpenGLでのストリームアウト その1
OpenGLでのストリームアウト その2
OpenGLでのストリームアウト その3
OpenGLでのストリームアウト その4
OpenGLでのストリームアウト その5