openFrameworksでGPUパーティクル
2015-09-08
先日、イベントでVJをさせていただく機会があり、ここ半年ほどあまりoFを触っていなかったので、リハビリも兼ねて、ミキサーからエフェクト、リアルタイムに生成する映像素材まで、一通りoFで作ってみました。
その中で、GPUパーティクルを用いた素材を作ってみたので、ごくシンプルな作例を載せておこうと思います。
パーティクルの数はとりあえず100万です。
Macbook Pro Retina Mid2015の最上位機で、1080pで実行して50~70fps程度でした。
ソースはGithubにあげましたので、もし興味があれば覗いてみてください。
https://github.com/yasuhirohoshino/GPUParticles
GPUパーティクルシステムの利点の一つは、比較的複雑な挙動をするパーティクルでも大量に扱うことができることです。
今回、Curl Noiseを使ったパーティクル表現をしたいと考えていましたが、普通にCPUで演算しVboMeshの頂点を移動させていくという実装では、理想的な数のパーティクルを動かすことができませんでした。
要となる部分については、oFに付属するサンプルのgl/gpuParticleSystemExampleのpingPongBufferを参考にしています。
ただ、元々のサンプルが位置と速度を別々のシェーダーで演算しているのに対し、同じシェーダーで位置と速度を演算できるように、一部を変更して使っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
void ofApp::setup(){ // パーティクルの座標・速度の保存用Fbo // RGBA32Fの形式で2つのColorbufferを用意 pingPong.allocate(texRes, texRes, GL_RGBA32F, 2); // パーティクルの位置と経過時間の初期設定 float * posAndAge = new float[texRes * texRes * 4]; for (int x = 0; x < texRes; x++){ for (int y = 0; y < texRes; y++){ int i = texRes * y + x; posAndAge[i*4 + 0] = ofRandom(-1.0,1.0); posAndAge[i*4 + 1] = ofRandom(-1.0,1.0); posAndAge[i*4 + 2] = ofRandom(-1.0,1.0); posAndAge[i*4 + 3] = 0; } } //pingPongBufferに初期値を書き込み pingPong.src->getTextureReference(0).loadData(posAndAge, texRes, texRes, GL_RGBA); delete [] posAndAge; // パーティクルの速度と生存期間の初期設定 float * velAndMaxAge = new float[texRes * texRes * 4]; for (int x = 0; x < texRes; x++){ for (int y = 0; y < texRes; y++){ int i = texRes * y + x; velAndMaxAge[i*4 + 0] = 0.0; velAndMaxAge[i*4 + 1] = 0.0; velAndMaxAge[i*4 + 2] = 0.0; velAndMaxAge[i*4 + 3] = ofRandom(1,150); } } //pingPongBufferに初期値を書き込み pingPong.src->getTextureReference(1).loadData(velAndMaxAge, texRes, texRes, GL_RGBA); delete [] velAndMaxAge; } void ofApp::update(){ pingPong.dst->begin(); // 複数バッファの書き出しを有効化 pingPong.dst->activateAllDrawBuffers(); ofClear(0); // fragment shader内で各パーティクルの位置や速度を計算 updatePos.begin(); // パーティクルの位置と経過時間のテクスチャ updatePos.setUniformTexture("u_posAndAgeTex", pingPong.src->getTextureReference(0), 0); // パーティクルの速度と生存期間のテクスチャ updatePos.setUniformTexture("u_velAndMaxAgeTex", pingPong.src->getTextureReference(1), 1); // 中略 pingPong.src->draw(0, 0); updatePos.end(); pingPong.dst->end(); // srcとdstを入れ替え pingPong.swap(); } |
このpingPongBuffer.srcをパーティクルのレンダリング時にVertex Shaderで読み込み、パーティクルの位置(=ofVboMeshの頂点座標)を更新します。
Curl Noiseについては以下の記事を参考にしています。
http://blog.livedoor.jp/akinow/archives/52378824.html
https://syphobia.wordpress.com/2011/04/12/curl-noise-for-particles/
またノイズ関数としてAshima ArtsさんのSimplex Noiseを拝借しました。
その他諸々、Andreas MullerさんのNoiseWorkshopのソースは大変参考になりました。
(GLSLのコードを別のGLSL内でインクルードできることを初めて知りました。)
色を変えたり、初期化の位置をいじったり、グローをかけてみたりして、以下のようなものができました。
Category : openFrameworks