3.TouchDesignerでビジュアルエフェクトを作る

ここが一番試行錯誤したところです。TouchDesignerでビジュアルエフェクトを作っていきます。サンプルはここにあります。
ちなみに全体像はこんな感じ。

スクリーンショット 2021-03-16 22.46.46

途中大変な感じになってますが、やってることはそんなに難しくないので大丈夫です。1つずつ説明していきます。

概要

概要を説明すると、まずoFから送信されたデータを受け取ります。それをTouchDesingerの座標系に則するように値を調整し、bounding boxをoFで動かした時のように、動画の位置と対応させます。(動画内で手(ペン・紙)が写ってる所に、bounding boxを表示させる)
oFから送られてきた座標と幅、高さの平面をTouchDesignerで作成し、その平面の中からランダムに座標を抜き出し、そこをパーティクルの発生源とします。ラベルの名前によってパーティクルの色を変更します。
Teachable Machineから送られてきた接触判定で、パーティクルの発生の数を変更します。

順番に説明していきます。

OSC通信でoFからのデータを受け取る

まずは、oFから送られてきたラベル、bounding boxの座標・幅・高さを受け取っていきます。
と言っても受け取るだけなら簡単です。
TouchDesignerのDATタブの中から、OSC In というパラメータを選択します。(今後「パラメータ タブ」という表記をします。このパラメータであれば「OSC In DAT」と書きます)

スクリーンショット 2021-03-16 22.54.03

Local Addressの部分を、oFで指定した通り"localhost"にします。Portも、oFで設定したポート番号と揃えます。私の場合は"12345"でした。
そうすると、こんなデータが送られてきます。

スクリーンショット 2021-03-16 22.57.03

一番左の列に取得したデータがすべて載っています。次の列からaddressやラベル番号、名前、boundingboxのx座標、y座標、bounding boxの幅、高さになります。

使う値だけを選び出していく

このままでは使いにくいので、Select DATで使いたい奴だけ選び出していきます。TouchDesignerはPythonも書けるので、Pythonで選ぶこともできると思いますが、私はやってません。Pythonめちゃめちゃ書ける人はそちらも挑戦してみるといいと思います。
とりあえず、ラベルの名前とbounding boxのx座標、y座標、幅、高さだけを選びます。

スクリーンショット 2021-03-16 23.12.58

Select DATのパネルの"Start Col Index"というのをいじると始まりの列を変更できます。
さらにまたSelect DATを使って、ラベルごとに分けていきます。今回はラベルが3つなので、3つSelect DATを用意します。

スクリーンショット 2021-03-16 23.21.40

Select DATの中の" Start Row Name"の所に"pen"、"paper"、"hand"のそれぞれを入力します。
さて、DATの状態のままだと色々使うのに不便なので、DAT to CHOPを使います。CHOPの状態にすると、数値としての利用が簡単になります。

スクリーンショット 2021-03-16 23.28.27

次に、Select CHOPを使って、x座標、y座標、幅、高さに分けていきます。このSelect DAT/CHOPを使っている所全般、もうちょっとうまいやり方がありそうな気がします。

スクリーンショット 2021-03-16 23.39.55

わかりやすいように"Rename"のところで名前をつけています。これをy座標、幅、高さでも同じようにやっていきます。"paper"、"hand"でも同様にです。
ここでポイントです。それぞれのSelect CHOPにConstant  CHOPを追加して、Math CHOPにつなぎます。Math CHOPのOPタブの中の"Combine CHOPs"を"Add"にして、値を調整します。これによって、Select CHOPとConstant CHOPの値を足し合わせてくれます。

スクリーンショット 2021-03-16 23.54.46

これは、oFからデータが送られてこなかった場合、値が無かったものとされてしまうのを防ぐためです。データが送られてこなかった場合、Select CHOPの値はnullになってしまいますが、後々こうなると面倒なので、Constant CHOPを追加して、データが送られてこなかった場合でも値がnullではなく0になるように調整しておきます。

TouchDesignerの座標系に値を調整する

oFから送られてきたこれらのデータですが、上の画像をよく見るとわかると思いますが、値がすごく小さいです。また、oFの座標系とTouchDesignerの座標系が違うので、このあと調整していきます。私はここで一番時間を使いました。

oFの座標系とTouchDesignerの座標系

oFの座標系は画面の左上を(0, 0)としています。

無題8_20210317000742

一方TouchDesignerは画面の中心を(0, 0)としています。

無題9_20210317001317

なので、oFの座標系で送られてきた値を、TouchDesignerの座標系に適応させないといけません。

webカメラで取得した動画にの上に、これらのx座標、y座標、幅、高さの値を当てはめた平面を置いていきます。要は、oFで出ていたbounding boxをTouchDesignerで再現するような感じです。
まずは、アスペクト比をwebカメラの値に揃えます。16:9か4:3が多いと思います。私が使ったwebカメラのアスペクト比は4:3(1024×768)だったので、平面の最大の大きさを幅1.6、高さ1.2にしました。この値が大きすぎると重くなるので、これくらいにしておきます。
TouchDesignerの座標系に則るように、x座標は-0.8~0.8、y座標は-0.6~0.6、幅は0~1.6、高さは0~1.2の間の値になるように調整していきます。Math CHOPを使って、値を調整していきます。

スクリーンショット 2021-03-17 16.23.06

Math CHOPの"Range"を調整します。この画像では、xの値を調整しています。OFから送られてきたデータは0~1までの値で送られてくるので、それを"To Range"のところで-0.8~0.8の値に変更しています。xが3つあるのは、"pen"と"paper"と"hand"のそれぞれのx座標をまとめて突っ込んでいるからです。

スクリーンショット 2021-03-17 16.29.55

y座標では"To Range"を-0.6から0.6に。

スクリーンショット 2021-03-17 16.31.14

幅は0~1.6に。

スクリーンショット 2021-03-17 16.32.28

高さは0~1.2に調節しています。

値を平面に当てはめていく

データの値がTouchDesignerの座標系に適応したところで、それを利用した平面を作っていきます。
この作業は、ここのサイトを参考にしています。
https://note.com/twistcube/n/n26109089a5a0

スクリーンショット 2021-03-17 16.41.10

まずは、Grid SOPを作り、expressionで、先ほどのMath CHOPの値をリファレンスします。この辺の基本操作は
https://note.com/toyoshimorioka/n/ndcc9e884bb88
このサイト辺りがわかりやすいと思います。
上の画像は"pen"のbounding boxを再現する平面です。
"Size"の所にMath CHOPで調整した幅と高さの数値を入れています。座標はややこしいですが、もう一段階調整が必要です。oFから送信されているbounding boxの座標は左上が原点になっているのですが、Grid SOPの座標は中心に設定されています。なので、"Center"の所で中心の座標を計算しています。
中心のx座標は、

x座標 + ( x座標 + 幅 ) / 2

で求められます。y座標も同様です。なので、expressionは

(op('math_x')[0] * 2 + op('math_wid')[0]) / 2
(op('math_y')[0] * 2 + op('math_hei')[0]) / 2

と書かれています。
op('math_x')[0]となっているのは、math_x CHOPの中の配列の1番目に"Pen"の値、2番目に"Paper"の値、3番目に"Hand"の値が入っているからです。ちなみに、先ほどConstant CHOPを噛ませたのがここで効いてきます。

平面からランダムな座標を抜き出す

詳しいやり方はhttps://note.com/twistcube/n/n26109089a5a0を見てください。

websocket通信でTeachable Machineから値を受け取る

Teachable Machineから送られてくる接触判定の値を受け取っていきます。Teachable Machineからは"touch"あるいは"untouch"という文字列が送られてきます。その値によって、パーティクルの量を変えていきます。
これも、値受け取るのは簡単です。WebSocket DATを使用するだけです。

スクリーンショット 2021-08-14 18.55.57

Networks Addressを"localhost"にし、Network Portを指定していた"13253"にします。Activeを"On"にすると通信が始まります。

スクリーンショット 2021-08-14 19.00.29

こんな感じに文字列が送られてきます。

接触判定の値によってパーティクルの挙動を変える

文字列のままでは都合が悪いので、数値にしていきます。
Constant CHOPを作ります。

スクリーンショット 2021-08-14 19.26.51

スクリーンショット 2021-08-14 19.26.38

opcaityの値がありますが、作っているうちに使わなくなったので無視して大丈夫です。大事なのは2つ目の"particle_birth"です。
文字列によって"particle_birth"の値を変更するために、Pythonで簡単なコードを書いていきます。
WebSocket DATを作成すると、同時にwebsocket_callbacksというテキストエディタが作成されます。その中にPython書いていきます。

スクリーンショット 2021-08-14 19.16.14

こんな感じですが、コード書いている部分は20行目からの

def onReceiveText(dat, rowIndex, message):
	op( 'text1' ).par.text = message
	opacity = 0
	particle_birth = 0
	if message == "touched":
		opacity = 1
		particle_birth = 500
	else:
		opacity = 0
		particle_birth = 0
	op( 'constant2' ).par.value0 = opacity
	op( 'constant2' ).par.value1 = particle_birth
	return

この部分です。

op('text1').par.text = message​

の部分は、遠目からでも状態が見やすいようにText TOPを出しているだけなので、あんまり関係ないです。"opacity"の部分も関係ないので、関係しているのは"particle_birth"の部分だけです。

if message == "touched":
		opacity = 1
		particle_birth = 500
	else:
		opacity = 0
        particle_birth = 0

要は受け取った値が"touched"のときには"particle_birth"が500になり、"untouched"のときは0になるという処理を書いています。

op( 'constant2' ).par.value1 = particle_birth

そして、Constant CHOPの"particle_birth"の値に代入しています。

"particle_birth"の値を実際にパーティクルの生まれる量に適応していきます。

スクリーンショット 2021-08-14 19.28.58

Particle SOPのBirthのexpressionを書いていきます。

op('constant2')['particle_birth']

でConstant2 CHOPの"particle_birth"の値を参照できます。

微調整

パーティクルで尾を引く感じにしたかったのでRender TOPにFeedback TOPとLevel TOPを追加します。

スクリーンショット 2021-08-14 19.36.28

Level TOPの"opacity"の値をいじっていきます。

スクリーンショット 2021-08-14 19.38.44

これで尾を引く感じになります。

一通りの技術的な説明は以上になります。
長々とお付き合いいただきありがとうございました。初めてのnoteでの執筆のうえ、かなり内輪向けかつopenFrameworksやJavascript、TouchDesignerを扱ったことがある人向けの説明で、不親切な説明であったと思います。すみません。

実際の作品はプロジェクターで投影したので、その設置の仕方など工夫した点はありますが、その辺は元気があれば書こうと思います。とりあえず一旦これで終わりにします。ありがとうございました。

この記事が気に入ったらサポートをしてみませんか?