pingPongBufferとは
n-1フレームでの計算結果をnフレームでの計算に用いたいことは多くあると思います.しかしshader内では変数を保持することができません.そこでn-1フレーム目の演算結果を入力, nフレーム目での計算結果を出力とするような形でFboを2つ用意する手法をpingPongBufferといいます.
MRT(Multiple Render Targets)
1つのshaderで複数の出力を行うことをMRTと言います.MRTが主に使用される代表的な場面は遅延レンダリングなどでしょう.今回このMRTを使用する理由は、粒子などの座標を物理的な計算を元に計算する場合速度、力(抵抗、圧力)、密度など複数の情報が必要になるためです.
結果
この記事では難しい計算を置いておいてMRT, pingPongBufferにフォーカスを当てて説明するためなるべく簡易的なサンプルとして前フレームでの値を保持して状態変化(色情報)する物を上げておきます.
完成コードはここにあげてます.またなかで使用されているpingPongBuffer.hpp
はこちらを使用させていただきました.ちなみに構造としてはoFのexampleの中にあるgpuParticleとほぼ同じかと思います.
流れ
- Fboを生成
- Fbo内にColorBufferを生成
- ColorBufferへ値を登録
- MRT用のShaderで値を更新
- step4で更新されたBufferと前フレームをswap
- step4, step5を繰り返す
ColorBufferへ値の登録
Fboの中に必要な数分のColorBufferが用意できたらColorBufferの中身を初期化していきます.
res = 4;
//internal format
pingPong.allocate(res, res, GL_RGBA32F, 3);
int ch = 3;
//----ColorTexture1
float* data1;
data1 = new float [res * res * ch];
for(int y=0; y < res; y++){
for(int x = 0; x < res; x++){
auto i = (x + y * res) * ch;
data1[i] = 0.0;
data1[i+1] = 0.0;
data1[i+2] = 0.0;
}
}
//pixelFormat
pingPong.src->getTexture(0).loadData(data1, res, res, GL_RGB);
delete[] data1;
自分がハマった点なのですが、oFFboでallocateする際に渡すのはinternal format
でloadDataする時に渡すのはpixelFormat
という点です.ここで引数をミスるとなかで呼ばれるglPixelStorei
で用意したtextureのチャンネル数と配列側のデータ境界がずれてしまいます.
MRT
ここで重要なのはactivateAllDrawBuffers()
でこれをコールすることでレンダーターゲットとして全てのColorBufferが登録されます.あとはshaderへ値を送信するだけ.
void ofApp::timeStep(){
//mrt------
float t = ofGetElapsedTimef();
pingPong.dst->begin();
ofClear(0);
mrtShader.begin();
pingPong.src->activateAllDrawBuffers();
mrtShader.setUniform1f("time", t);
previewShader.setUniformTexture("tex0", pingPong.dst->getTexture(0), 0);
previewShader.setUniformTexture("tex1", pingPong.dst->getTexture(1), 1);
previewShader.setUniformTexture("tex2", pingPong.dst->getTexture(2), 2);
pingPong.src->draw(0.0, 0.0);
mrtShader.end();
pingPong.dst->end();
pingPong.swap();
//---------
}
shader側ではlayoutでロケーションを指定してあげないと出力順がおかしくなることがあります.
# version 150
# extension GL_ARB_explicit_attrib_location : enable
precision mediump float;
uniform sampler2DRect tex0;
uniform sampler2DRect tex1;
uniform sampler2DRect tex2;
uniform float time;
in vec2 vTexCoord;
layout (location = 0) out vec4 vFragColor0;
layout (location = 1) out vec4 vFragColor1;
layout (location = 2) out vec4 vFragColor2;
void main(){
vec4 c0 = texture(tex0, vTexCoord);
vec4 c1 = texture(tex1, vTexCoord);
vec4 c2 = texture(tex2, vTexCoord);
vFragColor0 = vec4(mod(c0.r + 1.0, 2.0), 0.0, 0.0, 1.0);
vFragColor1 = vec4(0.0, mod(c0.r + 1.0, 2.0), 0.0, 1.0);
vFragColor2 = vec4(0.0, 0.0, mod(c0.r + 1.0, 2.0), 1.0);
}