TouchDesigner Advent Calendar 2017の12/17の投稿となります。
ここでは、TouchDesignerでのGPGPU Particle Systemの作り方を2回に分けて書きます。今回はその2回目。
今回の完成ソースはここ。
ある程度分かっている人は完成したソースを見ながら、初心者は空のProjectから作るぐらいが良いかもしれません。
前回のコードをベースに動きをつける
前回の投稿でGPGPU Particle Systemの表示部分が完成しました。
今回は動きの部分を実装していきます。
ポイントは基本的な入力を全てTOPにすること。
こうすることで各particleに個別のパラメータを持たせることが可能になります。
初期位置と移動量を持たせる
前回制作したときは、TOPは『Particleの絶対座標を記したもの』として扱いましたが、今回は『Particleの初期位置を記したもの』と『Particleの移動量を記したもの』に分けます。
こうすることで任意の座標にparticleを配置し、そこから動かすことができます。
1. 初期位置を作る
今回は座標中央、XYZ=(0,0,0)の座標からParticleを飛ばします。
Constant CHOPで(0,0,0)の黒を指定し、16bit floatに変更します。
2. 移動量を作る
速度はNoise Topで作りましょう。
移動量なので値は小さめにします。こちらも16bit float。
3. 初期位置と速度を足す
Composite SOPのAddを使ってConstant TOPで作った初期位置にNoise TOPで作った速度を足し合わせます。
4. 移動し続けるようにする
このままだと一回しか移動しないので、移動し続けるためにFeedback TOPを使います。
初心者の頃はFeedback TOPの挙動がよく分からなくて困ったりするのですが、要するに「一発目だけ左の接続元の絵を利用し、それ以降は参照元の絵を使う」というシンプルなTOPです。
Composite TOPの後にNull TOPをつけ、Constant TOPとComposite TOPの間にFeedback TOPを設置し、Null TOPをFeeback TOPに参照させます。
うまく組めるとComposite TOPが加算されて派手な色になっていくと思います。
5. 確認する
前回作ったNoise TOPを取り外し、今回のNull TOPをnull_posとnull_colorに接続します。
この状態でFeedback TOPのResetを押すと、画面中央から放射状にParticleが飛んでいくと思います。
Particleに寿命を持たせる
上でできたParticleはひたすら進んでいくので、feedbackのresetを押して定期的に初期位置に戻す必要があります。
そこで応用性を広げるために、Particleに寿命を持たせて、寿命が来たら初期位置に再度出現するようにします。
今回は白、つまり1.0になると寿命が来た、と判定させるため、黒から段々明るくなって、白になってまた黒に戻るループを組みます。
1. 寿命の初期値を決める
上の初期位置同様、Constant TOPの黒、16bit floatで組みます。
今回は1chしか使わないのでmonoで作っていますが、GPUが圧迫されていないのであればRGBを設定しても問題ないでしょう。
2. 寿命の進行速度を決める
こちらもNoise TOPで作ります。寿命の進行速度が固定だと絵がイマイチなので absTime.seconds
を使って動的に変化させます。
3. 加算し、年齢が進行しつづけるようにする
ここは上と全く一緒です。
Composite TOPでaddを設定して加算し、Null TOPとFeedback TOPで加算しつづけられるようにする。するとComposite TOPがどんどん白くなっていくと思います。
4. ループを作る
白、つまり値が1になるとリセットして黒になるようにしたいので、ここでGLSL TOPを使います(もしかしたらTOPでうまく書けるのかもしれませんが・・・)。
out vec4 fragColor;
void main()
{
vec4 color = texture(sTD2DInputs[0], vUV.st);
fragColor = TDOutputSwizzle(fract(color));
}
記述は見ての通り簡単でピクセルの値は0から1の間で動いてるので、fract関数を使って整数部分を切り捨てて返すだけです。つまり、1.0以下だと普通に値をそのまま利用して、1.0になったら0.0として返すだけ、という関数です。
これで寿命側のComposite TOPが変な動きのループする砂嵐になってるとおもいます。
5. 3つの画像から今回利用する位置情報を生成する
今回、Particleは寿命の値が1かどうかによって、位置情報のComposite TOPを利用するか、あるいは位置情報の初期値であるConstant TOPを使うかを決めます。
つまり、Particleの移動量を足した位置情報であるComposite TOP、Particleの初期位置であるConstant TOP、Particleの現在の寿命であるComposite TOPの3つの画像を入力し、新たな「寿命による判断を加えて、Particleによっては初期位置に配置しなおした位置情報」のTOPを作ります。
これももしかしたらTOPのみで作れるかもしれませんが、今回はGLSLMulti TOPを使い、shaderを書きます。
GLSLMulti TOPに、「1. 位置情報のComposite TOP」「2. 初期位置のConstant TOP」「3. 寿命のComposite TOP」の順に繋ぎます。
そしてコードは以下。
out vec4 fragColor;
void main()
{
vec4 recentPos = texture(sTD2DInputs[0], vUV.st);
vec4 originalPos = texture(sTD2DInputs[1], vUV.st);
int life = int(texture(sTD2DInputs[2], vUV.st).r);
recentPos = mix(recentPos, originalPos, life);
fragColor = TDOutputSwizzle(recentPos);
}
これも見ればある程度分かるのではないでしょうか。
1つめの入力画像から移動後の位置を、2つめの画像から初期位置を、3つ目の画像から寿命を取得しています。
lifeは0であればまだ寿命は来てない、1だと寿命が来ている、ということになります。
そしてこのシェーダで参照後、寿命のほうで書いたシェーダによって0に戻されてまた年を重ね始めます。
CPU側で書く場合はif文で判定するのですが、Shaderでif文を書くと遅い、という話なのでmix関数を利用しています。
mix関数はこちらにある通り、線形補完の関数ですが、今回0か1かしか入れてないのでrecentPosを採用するか、originalPosを採用するかの2択的な処理となっています。
できあがったノードのレイアウトはこんな感じ。
まとめ
いかがでしたでしょうか。
openFrameworksをもりもりやってた頃はGPGPU Particleって敷居が高いイメージがあって、やらなかった口なのですが、TouchDesignerだと勘所抑えると意外と書くところ少なく実装できてしまうし、全体も把握しやすいように思います。
また、TOPが殆どGPU最適化しているため、リアルタイムにバリバリ使える、というのもShaderを書く箇所を少なくできている理由だと思います。
読んでも『わからんかった...』という人のために
知り合いのモーションデザイナ、CGアーティストにGPGPU Particleでデザインをしてもらうためコンテナ群を製作してます。
そのうち公開すると思います。