openFrameworksでGPUParticleを実装してみた。
openFrameworksを普段からゆるりといじっていますが、CPU計算だけだと大量の処理を同時に行うことが難しいので、最近GPU計算に興味を持ち始め、シェーダを少しかけるようになったので、GPUParticleを実装してみました。まだ拙いところはあると思いますが、参考にしていだければ幸いです。また、修正したほうがいい箇所などありましたら、指摘していただけると嬉しいです。
アルゴリズムについては、Nature of CodeのAttractorを参考にしていて、引力の中心であるAttractorの座標は音楽によって制御するようにしています。
パーティクルの数は512 * 512で約25万個くらいで、60fps出ました。強い。。。
GPUParticleとは
大量の粒子(Particle)の頂点計算をする際に、CPUで計算するのではなく、大量の並列計算が得意なGPUで計算することで、処理速度を早くし、滑らかな表現を実現する手法です。
この記事の内容はシェーダを少しかじっているくらいの人を対象に書いているので、完全な初学者の方には少し難しい部分があるかもしれません。その際にはbook of shaderなどで勉強することをお勧めします。
実行環境
- macOS High Sierra 10.13.4
- openFrameworks 0.10.0
addon
- ofxGui
実装
- ofxGui
実装
GPUParticleでは、1フレーム前の頂点、速度情報などをテクスチャに保存し、GPU側(シェーダ)で読み込み計算することで、パーティクルの更新をしています。そのためにPingPongBufferを用意し、FBOを切り替えながら、テクスチャに情報を書き込んでいきます。
また、PingPongBufferについてはYasuhiro Hoshinoさんの例を参考にしていて、初期設定や、テクスチャに書き込んだりする部分は、加速度を保存する用のテクスチャを足した以外、ほとんど変わらないので割愛します。
以下では主にシェーダ(GLSL)について説明していきます。GLSLと聞くと難しく感じるかもしれませんが、今回の実装方法は簡素な作りとなっているので、あまり慣れていない人でも比較的わかりやすいかと思います。
1. 位置を更新するためのフラグメントシェーダ(posUpdate.frag)
#version 150
precision mediump float;
uniform sampler2DRect posData;
uniform sampler2DRect velData;
uniform sampler2DRect accData;
uniform vec3 attractor;
uniform bool isAttract;
uniform vec2 mouse;
uniform float strength;
uniform float time;
in vec2 vTexCoord;
out vec4 vFragColor0;
out vec4 vFragColor1;
out vec4 vFragColor2;
// ----- 3D -----
vec3 checkEdges(vec3 p, vec3 v) {
if(p.x < 0.0 || p.x > 1.0) {
v.x *= -1;
}
if(p.y < 0.0 || p.y > 1.0) {
v.y *= -1;
}
if(p.z < 0.0 || p.z > 1.0) {
v.z *= -1;
}
return v;
}
void main() {
vec3 pos = texture(posData, vTexCoord).xyz;
vec3 vel = texture(velData, vTexCoord).xyz;
vec3 acc = texture(accData, vTexCoord).xyz;
// vec2 m = mouse / vec2(1024.0, 768.0); // mouse position
vec3 m = attractor / vec3(1024.0, 768.0, 1024.0);
vec3 dir = m - pos; // caluculate direction
float dist = length(m - pos) * 2.0; // distance from mouse to pos
float limit = 0.005;
if(dist < limit) dist = limit;
float st = strength / (dist * dist); // attractive calc
dir *= st;
if(isAttract) acc += dir; // attract
else acc -= dir * 0.1; // repulsion
vel += acc;
vel *= 0.01;
if(vel.x > 2.0) vel.x = 2.0;
if(vel.y > 2.0) vel.y = 2.0;
if(vel.z > 2.0) vel.z = 2.0;
vec3 nextPos = pos + vel;
vel = checkEdges(nextPos, vel);
// Update the Position
pos += vel;
vFragColor0 = vec4(pos, 1.0);
vFragColor1 = vec4(vel, 1.0);
vFragColor2 = vec4(acc, 1.0);
}
このシェーダでは、送られてきた位置、速度、加速度のテクスチャを読み込み、値を更新してvFragColor
で出力しています。値の更新に使ったAttractのアルゴリズムは以下の通り。
- 粒からAttractorへの向き
dir
を求める。 - 粒とAttractorの距離
dist
を求める。 - 二点間の力の強さ
st
を求める。
二点間にかかる力の強さ\hspace{5mm}
G\frac{m1*m2}{r^2}
ここでの$G$は万有引力定数、$m1,m2$は二物体の質量を表します。今回は、質量などは考えないことにしているので、分子を丸々uniform変数strength
で置き換えて計算しています。
4. 加速度acc
にdir*st
を代入する。
5. 速度vel
に加速度acc
を足す。
6. 位置pos
に速度vel
を足す。
以上のアルゴリズムを実装すると上記のようになります。こうやって書き起こしてみると今回のコードが簡素であることがわかるかと思います。checkEdgesに関しては、粒が遠くに行き過ぎないように位置に制限をかけています。
2. レンダリング用のシェーダ(render.vert)
#version 150
precision mediump float;
uniform mat4 modelViewProjectionMatrix;
uniform float time;
in vec2 texcoord;
in vec4 color;
uniform sampler2DRect posTex;
uniform vec2 screen;
out vec4 vColor;
out vec2 vTexCoord;
void main() {
vec4 pixPos = texture(posTex, texcoord);
vColor = vec4(pixPos.xy, 1.0 - pixPos.y, 1.0);
pixPos.x = pixPos.x * screen.x - screen.x / 2;
pixPos.y = pixPos.y * screen.y - screen.y / 2;
pixPos.z = pixPos.z * screen.x - screen.x / 2;
vTexCoord = texcoord;
gl_Position = modelViewProjectionMatrix * pixPos;
}
ここでは、先ほどのposUpdateで更新した位置情報をもとに、レンダリングする際の頂点の位置を決定しています。GLSLでは基本的に値が0~1の間に正規化されていることが多く、pixPosも例外ではないので、この値に拡大したい値screen
をかけることによってスクリーン上で綺麗にみることができます。描画用のフラグメントシェーダrender.frag
では、ここで送った色を出力しているだけなので割愛します。
まとめ
テクスチャを切り替えたりということがわかりづらかったりしますが、実際に作ってみるとそんなに難しいことはしていないようです。GLSLまだまだ全然理解できてないから、レイマーチングとかもやっていきたい。。。
リンク
- ソースコード https://github.com/sketchbooks99/GPUParticle
- Yasuhiro Hoshinoさんの例 http://yasuhirohoshino.com/archives/30
- The Book of Shader https://thebookofshaders.com/?lan=jp
Author And Source
この問題について(openFrameworksでGPUParticleを実装してみた。), 我々は、より多くの情報をここで見つけました https://qiita.com/sketchbooks99/items/9d6676227f49a6ddd852著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .