3.TouchDesignerでビジュアルエフェクトを作る
ここが一番試行錯誤したところです。TouchDesignerでビジュアルエフェクトを作っていきます。サンプルはここにあります。
ちなみに全体像はこんな感じ。
途中大変な感じになってますが、やってることはそんなに難しくないので大丈夫です。1つずつ説明していきます。
概要
概要を説明すると、まずoFから送信されたデータを受け取ります。それをTouchDesingerの座標系に則するように値を調整し、bounding boxをoFで動かした時のように、動画の位置と対応させます。(動画内で手(ペン・紙)が写ってる所に、bounding boxを表示させる)
oFから送られてきた座標と幅、高さの平面をTouchDesignerで作成し、その平面の中からランダムに座標を抜き出し、そこをパーティクルの発生源とします。ラベルの名前によってパーティクルの色を変更します。
Teachable Machineから送られてきた接触判定で、パーティクルの発生の数を変更します。
順番に説明していきます。
OSC通信でoFからのデータを受け取る
まずは、oFから送られてきたラベル、bounding boxの座標・幅・高さを受け取っていきます。
と言っても受け取るだけなら簡単です。
TouchDesignerのDATタブの中から、OSC In というパラメータを選択します。(今後「パラメータ タブ」という表記をします。このパラメータであれば「OSC In DAT」と書きます)
Local Addressの部分を、oFで指定した通り"localhost"にします。Portも、oFで設定したポート番号と揃えます。私の場合は"12345"でした。
そうすると、こんなデータが送られてきます。
一番左の列に取得したデータがすべて載っています。次の列からaddressやラベル番号、名前、boundingboxのx座標、y座標、bounding boxの幅、高さになります。
使う値だけを選び出していく
このままでは使いにくいので、Select DATで使いたい奴だけ選び出していきます。TouchDesignerはPythonも書けるので、Pythonで選ぶこともできると思いますが、私はやってません。Pythonめちゃめちゃ書ける人はそちらも挑戦してみるといいと思います。
とりあえず、ラベルの名前とbounding boxのx座標、y座標、幅、高さだけを選びます。
Select DATのパネルの"Start Col Index"というのをいじると始まりの列を変更できます。
さらにまたSelect DATを使って、ラベルごとに分けていきます。今回はラベルが3つなので、3つSelect DATを用意します。
Select DATの中の" Start Row Name"の所に"pen"、"paper"、"hand"のそれぞれを入力します。
さて、DATの状態のままだと色々使うのに不便なので、DAT to CHOPを使います。CHOPの状態にすると、数値としての利用が簡単になります。
次に、Select CHOPを使って、x座標、y座標、幅、高さに分けていきます。このSelect DAT/CHOPを使っている所全般、もうちょっとうまいやり方がありそうな気がします。
わかりやすいように"Rename"のところで名前をつけています。これをy座標、幅、高さでも同じようにやっていきます。"paper"、"hand"でも同様にです。
ここでポイントです。それぞれのSelect CHOPにConstant CHOPを追加して、Math CHOPにつなぎます。Math CHOPのOPタブの中の"Combine CHOPs"を"Add"にして、値を調整します。これによって、Select CHOPとConstant CHOPの値を足し合わせてくれます。
これは、oFからデータが送られてこなかった場合、値が無かったものとされてしまうのを防ぐためです。データが送られてこなかった場合、Select CHOPの値はnullになってしまいますが、後々こうなると面倒なので、Constant CHOPを追加して、データが送られてこなかった場合でも値がnullではなく0になるように調整しておきます。
TouchDesignerの座標系に値を調整する
oFから送られてきたこれらのデータですが、上の画像をよく見るとわかると思いますが、値がすごく小さいです。また、oFの座標系とTouchDesignerの座標系が違うので、このあと調整していきます。私はここで一番時間を使いました。
oFの座標系とTouchDesignerの座標系
oFの座標系は画面の左上を(0, 0)としています。
一方TouchDesignerは画面の中心を(0, 0)としています。
なので、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を使って、値を調整していきます。
Math CHOPの"Range"を調整します。この画像では、xの値を調整しています。OFから送られてきたデータは0~1までの値で送られてくるので、それを"To Range"のところで-0.8~0.8の値に変更しています。xが3つあるのは、"pen"と"paper"と"hand"のそれぞれのx座標をまとめて突っ込んでいるからです。
y座標では"To Range"を-0.6から0.6に。
幅は0~1.6に。
高さは0~1.2に調節しています。
値を平面に当てはめていく
データの値がTouchDesignerの座標系に適応したところで、それを利用した平面を作っていきます。
この作業は、ここのサイトを参考にしています。
https://note.com/twistcube/n/n26109089a5a0
まずは、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を使用するだけです。
Networks Addressを"localhost"にし、Network Portを指定していた"13253"にします。Activeを"On"にすると通信が始まります。
こんな感じに文字列が送られてきます。
接触判定の値によってパーティクルの挙動を変える
文字列のままでは都合が悪いので、数値にしていきます。
Constant CHOPを作ります。
opcaityの値がありますが、作っているうちに使わなくなったので無視して大丈夫です。大事なのは2つ目の"particle_birth"です。
文字列によって"particle_birth"の値を変更するために、Pythonで簡単なコードを書いていきます。
WebSocket DATを作成すると、同時にwebsocket_callbacksというテキストエディタが作成されます。その中にPython書いていきます。
こんな感じですが、コード書いている部分は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"の値を実際にパーティクルの生まれる量に適応していきます。
Particle SOPのBirthのexpressionを書いていきます。
op('constant2')['particle_birth']
でConstant2 CHOPの"particle_birth"の値を参照できます。
微調整
パーティクルで尾を引く感じにしたかったのでRender TOPにFeedback TOPとLevel TOPを追加します。
Level TOPの"opacity"の値をいじっていきます。
これで尾を引く感じになります。
一通りの技術的な説明は以上になります。
長々とお付き合いいただきありがとうございました。初めてのnoteでの執筆のうえ、かなり内輪向けかつopenFrameworksやJavascript、TouchDesignerを扱ったことがある人向けの説明で、不親切な説明であったと思います。すみません。
実際の作品はプロジェクターで投影したので、その設置の仕方など工夫した点はありますが、その辺は元気があれば書こうと思います。とりあえず一旦これで終わりにします。ありがとうございました。
この記事が気に入ったらサポートをしてみませんか?