TouchDesignerでGPGPU Particle Systemを作る Vol.2 応用編

TouchDesigner Advent Calendar 2017の12/17の投稿となります。
ここでは、TouchDesignerでのGPGPU Particle Systemの作り方を2回に分けて書きます。今回はその2回目。
[今回の完成ソースはここ。](https://github.com/ToyoshiMorioka/TouchDesignerStudy/tree/master/gpgpuTutorial02)
ある程度分かっている人は完成したソースを見ながら、初心者は空のProjectから作るぐらいが良いかもしれません。

# 前回のコードをベースに動きをつける
前回の投稿でGPGPU Particle Systemの表示部分が完成しました。
今回は動きの部分を実装していきます。
ポイントは基本的な入力を全てTOPにすること。
こうすることで各particleに個別のパラメータを持たせることが可能になります。

# 初期位置と移動量を持たせる
前回制作したときは、TOPは『Particleの絶対座標を記したもの』として扱いましたが、今回は『Particleの初期位置を記したもの』と『Particleの移動量を記したもの』に分けます。
こうすることで任意の座標にparticleを配置し、そこから動かすことができます。

### 1. 初期位置を作る
今回は座標中央、XYZ=(0,0,0)の座標からParticleを飛ばします。
Constant CHOPで(0,0,0)の黒を指定し、16bit floatに変更します。
![01_constant01.PNG](https://qiita-image-store.s3.amazonaws.com/0/152718/fe1ac9bc-5ce4-dc43-20a5-c87416f9a4dc.png)
![02_constant02.PNG](https://qiita-image-store.s3.amazonaws.com/0/152718/0adde8d4-404c-a795-dac9-6c7c760ea63a.png)


### 2. 移動量を作る
速度はNoise Topで作りましょう。
移動量なので値は小さめにします。こちらも16bit float。
![03_Noise01.PNG](https://qiita-image-store.s3.amazonaws.com/0/152718/8e20bc75-9a33-cfe2-f4ef-d7b802925c6a.png)
![04_Noise02.PNG](https://qiita-image-store.s3.amazonaws.com/0/152718/b793ec38-a693-8283-09a6-deba75ee62ab.png)


### 3. 初期位置と速度を足す
Composite SOPのAddを使ってConstant TOPで作った初期位置にNoise TOPで作った速度を足し合わせます。

![05_Comp.PNG](https://qiita-image-store.s3.amazonaws.com/0/152718/acbf6f55-5a11-3a97-490e-3ca10e51156d.png)


### 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が飛んでいくと思います。
![05_settings.PNG](https://qiita-image-store.s3.amazonaws.com/0/152718/ee3a8c2f-d418-201f-b73e-cee493d85ebc.png)



# Particleに寿命を持たせる
上でできたParticleはひたすら進んでいくので、feedbackのresetを押して定期的に初期位置に戻す必要があります。
そこで応用性を広げるために、Particleに寿命を持たせて、寿命が来たら初期位置に再度出現するようにします。
今回は白、つまり1.0になると寿命が来た、と判定させるため、黒から段々明るくなって、白になってまた黒に戻るループを組みます。

### 1. 寿命の初期値を決める
上の初期位置同様、Constant TOPの黒、16bit floatで組みます。
今回は1chしか使わないのでmonoで作っていますが、GPUが圧迫されていないのであればRGBを設定しても問題ないでしょう。

### 2. 寿命の進行速度を決める
こちらもNoise TOPで作ります。寿命の進行速度が固定だと絵がイマイチなので `absTime.seconds` を使って動的に変化させます。
![08_Noise01s.PNG](https://qiita-image-store.s3.amazonaws.com/0/152718/6795d112-d135-6779-4cda-95dd04b6cea4.png)
![09_Noise02.PNG](https://qiita-image-store.s3.amazonaws.com/0/152718/db3f6b23-2ac6-e4ac-7ab8-c3033ba23c29.png)

### 3. 加算し、年齢が進行しつづけるようにする
ここは上と全く一緒です。
Composite TOPでaddを設定して加算し、Null TOPとFeedback TOPで加算しつづけられるようにする。するとComposite TOPがどんどん白くなっていくと思います。

### 4. ループを作る
白、つまり値が1になるとリセットして黒になるようにしたいので、ここでGLSL TOPを使います(もしかしたらTOPでうまく書けるのかもしれませんが・・・)。

```glsl:glsl2_pixel.dt
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が変な動きのループする砂嵐になってるとおもいます。
![06_settings.PNG](https://qiita-image-store.s3.amazonaws.com/0/152718/c30c118c-3d7a-85f1-5155-dbad17433002.png)

### 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」の順に繋ぎます。
そしてコードは以下。

```glsl:glslmulti1_pixel.dat
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関数は[こちら](https://qiita.com/edo_m18/items/71f6064f3355be7e4f45)にある通り、線形補完の関数ですが、今回0か1かしか入れてないのでrecentPosを採用するか、originalPosを採用するかの2択的な処理となっています。
できあがったノードのレイアウトはこんな感じ。

![07_settings.PNG](https://qiita-image-store.s3.amazonaws.com/0/152718/e14eddd5-49ee-73f0-b683-e7ba796a16a4.png)

# まとめ
いかがでしたでしょうか。
openFrameworksをもりもりやってた頃はGPGPU Particleって敷居が高いイメージがあって、やらなかった口なのですが、TouchDesignerだと勘所抑えると意外と書くところ少なく実装できてしまうし、全体も把握しやすいように思います。
また、TOPが殆どGPU最適化しているため、リアルタイムにバリバリ使える、というのもShaderを書く箇所を少なくできている理由だと思います。

# 読んでも『わからんかった...』という人のために
知り合いのモーションデザイナ、CGアーティストにGPGPU Particleでデザインをしてもらうためコンテナ群を製作してます。
そのうち公開すると思います。