はじめに
こんにちは、oishihiroaki(irishoak)です。
昨今、TouchDesignerのユーザーの拡がり目覚ましいですが、その陰でTouchDesignerと正しく表記がなされなかったために、若者がTouchDesigner警察に注意を受ける事案がありました。
案外知られていないことですが、公式では、“Touch Designer”と単語の間にスペースを挟まず、“TouchDesigner”と表記するのが適切なようです。(大文字小文字についてはこだわりはないんだろうか…)
世間にはそもそもTouchやDesignerの正しい綴りを覚えられない幼稚園児もいますが、
まずは正しい表記を覚えて、健全なTouchDesigner開発を行うことができるようになりたいものです。今回はそのために、TouchDesignerの正しい表記を印象的に脳に刻み込むことができるようなコンテンツを作成いたしました。この記事では、その作成過程について解説したいと思います。
コンテンツ概要
音声ファイルを再生して、音の強さを取得し、その強さによって、TouchDesignerのテキストが動きます。TouchDesignerの文字の間のスペースがなくなれば、背景のポリスメンの絵が、OKジェスチャに変化します。
動画 TouchDesignerPoliceAudioReactive
制作プロセス
このサンプルコンテンツのプロジェクトデータはこちらのGithubリポジトリにアップしています。
TDPoliceAudioReactive Github
プロジェクトフォルダの作成
TouchDesignerを起動し、File > Create Project Folder からプロジェクトフォルダを作成します。
Project Folder に TDPoliceAudioReactive(任意) と入力し、
Rename File、Media Folders の Audio と Images の チェックボックスをチェックし、Createボタンを押します。
そうすると、.toeファイルと、Audio、Imageというフォルダが同階層に作られます。
音声ファイルの再生と解析
デフォルトで存在しているオペレータオブジェクトを消去し、
OP Create Dialog から
CHOP > Audio File In、CHOP > Audio Device Out オペレータを作成し、以下のように接続します。
サンプルの音声ファイルが再生されるようになりました。
CHOP > Analyze オペレータを作成し、ノードを接続。
LRの2つのチャンネルのオーディオデータが得られます。
CHOP > Math オペレータを作成し、ノードを接続。
Combine Channels を Average
Channel Post OP を Positive に変更します。
これにより、LRの2つのチャンネルが一つにまとめられ、正負の値をとっていたものが、絶対値をとり正の値に修正されます。
CHOP > Filter オペレータを作成
これにより、値の変化が滑らかになります。
このままだと、現在の値を計算するためにサンプルする幅が広く、音の特徴が消えた値になってしまいます。そこで、Filter Width を 0.15 ぐらいに調整します。サンプリングする幅が変化し、音の特徴をある程度保った値が得られます。
CHOP > Null オペレータを作成し、接続しておきましょう。
TouchDesignerテキストオブジェクトの作成
COMP > Geometry オペレータを作成し、ダブルクリックして中に入り、すでに存在するTorus SOPを削除します。
SOP > Fontオペレータを2つ作成します。
2つあるFontオペレータのTextプロパティにTouch
もう一つに、Designerと入力します。(※つづりに注意してください!)
Level of Detail の 値を調整することにより、フォントのメッシュの分割具合が調節できます。
SOP > Extrude オペレータを2つ作成し、それぞれ接続します。
これにより、テキストが押し出されました。
Depth Scaleによって押し出しの奥行きのスケールが調整できます。 0.25 ぐらいにしましょう。
この状態であると、厚みを変化させた時に、原点とオブジェクトの中心がずれてしまいます。そこで、
Depth Translate のExpressionに me.par.depthscale*-0.5
と入力します。
Expressionを記述する際、まず、Depth Scaleの入力ボックス上で、右クリックをし、ポップアップからCopy Parameter をクリックします。それから、Depth TranslateのExpression入力ボックス上で、右クリックをし、Paste Referencesをクリックします。そうすると、Depth Scaleの参照である me.par.depthscale
がペーストされます。この辺りは、Houdiniと共通ですね。
SOP > Merge を作成し、ノードを接続します。
これにより、2つのSOPが一つのSOPにまとめられました。
SOP > Null を作成し、ノードを接続し、右下の紫と青の●にチェックを入れ、RenderフラグとDisplayフラグをONにします。
uキーを押して上の階層に戻ります。
背景の作成
絵文字(Apple製)画像データの取得
絵文字の画像データを手に入れます。
Emojipedia Apple Emoji List
ここより、Apple製の Police Officer と Gesturing OK の絵文字の画像を(手動で…)保存します。
画像データはプロジェクトフォルダのImageディレクトリの下に、PoliceOfficer、GesturingOKというフォルダを作り、それぞれ分けて保存します。
2D Texture Array に絵文字画像データを読み込む
TOP > Texture 3D オペレータを作成します。
このオペレータではボリュームデータや、2D Texture Array として複数のテクスチャを1つのデータにまとめて扱うことができます。
2つ作成したら、プロパティを、
Type を 2D Texture Array
Pre Fill を ON
Replace Single を ON
Cache Size を 12
に変更します。
TOP > Movie File In オペレータを2つ作成します。
Viewerフラグ (左上の○)を OFF にし、
FileのExpression に './Image/PoliceOfficer'
と入力、
もう一つの Movie File In オペレータでは、
'./Image/GesturingOK'
と入力、
Play Mode を Specify Index
Index のExpressionに me.time.frame-1
と入力、
単位をIndex に変更します。
Texture 3Dオペレータに接続すると、画像がまとめて読み込まれると思います。
(うまく読み込まれない場合、Texture 3D の Resetプロパティを一度ONにしてからOFFにしてみてください)
GLSLによる背景エフェクトの作成
TOP > GLSL オペレータを作成します。
Uniform変数の設定
Vector 1 タブ を開き、
Uniform Name に time
それぞれ、
value0x : me.time.frame
value0y : me.time.seconds
value0z : absTime.frame
value0w : absTime.seconds
と入力します。
これで、シェーダ内で時間のデータがとることができるようになります。
Python Tips #Time
absTime Class
**修正点(Uniform floats should specified on 'Vectors' pages)**
以下、**Arrays1** で **audioVolume** を定義していますが、Windows のIntel GPUでは、**uniform float audioVolume** がうまく機能しないようで、**Vector1**ページに記述するべきです。 [Github issues #4 (Uniform floats should specified on 'Vectors' pages)](https://github.com/hiroakioishi/TDPoliceAudioReactive/issues/4)Arrays1 タブを開き、
一番上の CHOP のテキストボックスに、null1オペレータをドラッグ&ドロップします。
Uniform Name に audioVolume と入力します
これで、シェーダ内で音の強さのデータを取得できるようになります。
解像度の設定
解像度を設定します。
Common タブを開き、
Output Resolution を Custom Resolution に
Resolution を 1280 720 と設定します。
GLSLにTexture 3Dを接続
作成したGLSLオペレータの第1入力に、PoliceOfficerを読み込んだTexture 3Dオペレータ、第2入力に、GesturingOKを読み込んだTexture 3Dオペレータを接続します。
コードの記述
glslと紐付いたglsl1_pixelのEditボタンを押し、GLSLのコードを記述します。
**修正点(macOS GLSL compiler stricter than Windows)**
[2D Texture Array](https://www.derivative.ca/wiki088/index.php?title=2D_Texture_Array) こちらには、冒頭で `#extension GL_EXT_texture_array : enable` を記述してね、とあったのですが、これを記述するとMacOSでコンパイルエラーを起こします。コメントアウトしましょう。 [Github issues #3(macOS GLSL compiler stricter than Windows)](https://github.com/hiroakioishi/TDPoliceAudioReactive/issues/3)/**
* TDPoliceAudioReactive EmojiTile Background Effect
*/
// 2D Texture Array を 有効に (I's not necessary)
// #extension GL_EXT_texture_array : enable
// 2D Texture Array の画像数
#define TEX_ARRAY_NUM 12
// 時間(frame, seconds, absTime.frame, absTime.seconds)
uniform vec4 time;
// 音の強さ
uniform float audioVolume;
// 出力するフラグメントのカラー
out vec4 fragColor;
void main()
{
// アスペクト比 (1280, 720)
vec2 aspectRatio = vec2(1.7777777, 1.0);
// 縦のタイル(タイル一つに絵文字が一つ表示される)の数 --- 時間によって変動させる
float tileNumV = 12.5 + 10 * sin(time.w * 0.5);
// UV座標値 --- 中央 半タイル分ずれた位置でスケーリングされるようにオフセットをかける
vec2 halfTileSize = vec2(1.0/(tileNumV * aspectRatio.x) * 0.5, 1.0/tileNumV * 0.5);
vec2 uv = vUV.xy - vec2(0.5, 0.5) + halfTileSize;
// 縦横のそれぞれのタイル数
vec2 tileNum = vec2(tileNumV * aspectRatio.x, tileNumV);
// タイルのUV(画像を表示するときに使用)
vec2 tileUv = mod(uv.xy, 1.0/tileNum) * tileNum;
// タイルのインデックス(タイルごとに異なる画像を表示するために使用)
vec2 tileIndex = floor(uv.xy * tileNum.xy) / tileNum.xy;
// タイルインデックスごとに異なるシンプレックスノイズパターン(0.0~1.0)を取得
// (ポリス絵文字をノイズ模様で出しわけするために使用)
float snoisePattern = 0.5 + 0.5 * TDSimplexNoise(vec3(tileIndex.xy * aspectRatio, time.w * 0.5));
// タイルインデックスごとに異なる中央から広がるパターン(0.0~1.0)を取得
vec2 diffCenterToEachTile = tileIndex * aspectRatio;
float circleWavePattern = mod(2.0 * dot(diffCenterToEachTile, diffCenterToEachTile) + time.w * -0.75, 1.0);
// ポリスの画像カラーを取得
vec4 policeTexCol = texture(sTD2DArrayInputs[0], vec3(tileUv.xy, snoisePattern * TEX_ARRAY_NUM));
// OKジェスチャの画像カラーを取得
vec4 okGestureTexCol = texture(sTD2DArrayInputs[1], vec3(tileUv.xy, circleWavePattern * TEX_ARRAY_NUM));
// ポリス絵文字タイルの背景の色 --- サイレンのように赤と青に交互に切り替わるように
vec3 policeTexColBgCol = 0.6 * mix(vec3(1.0, 0.0, 0.0), vec3(0.0, 0.0, 1.0), 0.5 + 0.5 * sin(time.w * 20.0));
// 背景の色とブレンド
policeTexCol.rgb = mix(policeTexColBgCol, policeTexCol.rgb, policeTexCol.a);
// OKジェスチャ絵文字タイルの背景の色
vec3 okGestureBgCol = vec3(0.0);
// 背景の色とブレンド
okGestureTexCol.rgb = mix(okGestureBgCol, okGestureTexCol.rgb, okGestureTexCol.a);
// 音の大きさによって、警察とOKジェスチャの画像を入れ替える
vec3 color = mix(policeTexCol.rgb, okGestureTexCol.rgb, smoothstep(0.5, 0.8, audioVolume));
// 出力するフラグメントのカラーをセット
fragColor = TDOutputSwizzle(vec4(color, 1.0));
}
タイルのUVとインデックスを算出
絵文字をタイル状に敷き詰めるために、タイルのUVとインデックスをそれぞれ、mod関数とfloor関数を使って求めます。
modは剰余、floorは小数点以下の値を切り捨てた値を返します。
UVとインデックスを画像として表示するとこのようになります。
※ 除算は演算コストが高く、シェーダプログラミングではできるだけ使用は避けるべきです。1.0/tileNum
など行っていますが、すべてのピクセルで共通の値は、事前にCPU上で計算し、uniform変数としてGLSLに渡した方が本来は良いです。
ノイズパターンを取得
ポリスの絵文字がノイズ模様になるように描画するポリスの画像の種類が変わるようにします。そのために、タイルのインデックスによって異なるノイズの値を算出します。
TouchDesignerには、TDSimplexNoise(vec3 v)というシンプレックスノイズを返す関数が用意されています。うれしいですね。引数には、xy成分に、タイルのインデックス、z成分に時間を渡し、時間で変化する2Dのノイズパターンを得ます。
2D Texture Array から 画像のカラーデータを取得
2D Texture Arrayのデータを取得するためには、sTD2DArrayInputs 変数を使用します。この変数は配列構造になっており、[0]にはGLSL Topの第1入力に接続したTexture 3Dオペレータのデータ、[1]には第2入力に接続したデータが渡されます。
実際に、カラーデータを取得するには、texture(sTD2DArrayInputs[0], vUV.stp)関数を使用します。第1引数には2D Texture Array を第2引数にはuvデータを渡します。uvデータの各成分は、.stが画像のUV座標、.p成分は、2D Texture Arrayの配列番号に対応します。p成分の配列番号は整数で指定するようになっており、0や0.5を指定した場合は、0番目の画像が、2.0や2.9と指定した場合は2番目の画像が取得されます。
音の強さによってポリスとOKジェスチャの画像の切り替えを行う
音による画像の切り替えは以下のコードで実現しています。
vec3 color = mix(policeTexCol.rgb, okGestureTexCol.rgb, smoothstep(0.1, 0.9, audioVolume));
mix関数は第1引数と第2引数に指定した値を、第3引数に指定した値(0.0~1.0)によってブレンドします。
smoothstepは、第3引数に指定した値が、第1引数と第2引数で指定した値の範囲で変化するときに、滑らかに0.0~1.0の範囲で変化するような値を返します。
つまり、音の強さの値が0.1から0.9で変化するときに0.0から1.0の値が返され、画像が(比較的)滑らかに切り替わるようになります。
GLSL Topを記述する際は、ここの情報を参照されてください。
TouchDesigner Write a GLSL TOP
TouchDesignerテキストを音の強さで動かす
音の強さによって先ほど作成したテキストが動くようにします。
テキストの Geometory TOPをダブルクリックして中に入ります。
**修正点 : SOP cooking order optimization**
以下、サンプルでは、Font SOPで、移動のためのExpressionを記述していますが、これだと、処理コストが高い **Font** SOP と **Extrude** SOP が毎フレーム実行されてしまい、パフォーマンスが落ちてしまいます。。 そのため、それぞれの文字の処理の後に、**Transform** SOP を配置し、そこで移動のためのExpressionを記述することで、**Font** SOP、 **Extrude** SOP での不要な実行を防ぐことができ、高速化を図ることができます。 [Github issues #2(SOP cooking order optimization)](https://github.com/hiroakioishi/TDPoliceAudioReactive/issues/2) ![add Transform SOP](https://qiita-image-store.s3.amazonaws.com/0/35267/51be1a3f-ae68-69ce-b826-26cfa889da0e.jpeg)「Touch」の方のFontオペレータの
Center Text Vertically を Off に、
Translateプロパティのtyに -0.25 と入力、
txのExpressionに min(-1.525, -1.525-1.0+op('../null1')['chan1'])
と入力します。
Extrude SOP のあとに、Transform SOP を作成し、その Translate tx プロパティのExpressionに min(-1.525, -1.525-1.0+op('../null1')['chan1'])
と入力します。これによって、音が大きければ真ん中にスライドするようになりました。
「Designer」の方のFontオペレータも、
Center Text Vertically を Off、
Translateプロパティtyに -0.25、
txのExpressionは max(2.175, 2.175+1.0-op('../null1')['chan1'])
と入力します。
Extrude SOP のあとに、Transform SOP を作成し、その Translate tx プロパティのExpressionに max(2.175, 2.175+1.0-op('../null1')['chan1'])
と入力します。
テキストを回転させる
uキーを押し、上の階層に移動します。
geo1 オペレータの Rotate の Expression にそれぞれ以下のように入力します。
rx : absTime.seconds * 30.0
ry : absTime.seconds * 40.0
rz : absTime.seconds * 10.0
これで、時間によって、テキストがぐるぐると回転するようになりました。
テキストのレンダリングと外側光彩エフェクト
TOP > Render オペレータを作成します。
また、COMP > Camera、COMP > Light も作成します。テキストが描画されるようになりました。
外側光彩のためのブラーエフェクトの作成
TOP > Blur を作成し、
Pre-Shrink を 3、
Filter Size を 10、
に設定します。
TOP > Monochrome を作成し、
RGB を Alpha、
Alpha を Alpha、
に設定します。
TOP > Constant を作成し、カラーの設定をします。
TOP > Multiply を作成し、
Fixed Layer を Input1 に設定。
第1入力に、mono1を、
第2入力に、constant1を接続します。
そうすると、Constantで設定した色の、テキストのシルエットがぼけた画像が得られます。
※ Blur と Multiply の間に、Level TOP を挟んで、Gamma の値を高く設定すると、光彩の強さを上げることができます。このように、簡単にポストエフェクトを使うことができるのはTouchDesignerの強みですね。
TOP > Over を作成し、
第1入力に、render1、
第2入力に、multiply1 を接続します。
これで、テキストの後ろの外側光彩エフェクトがかかった画像が得られました。
合成
TOP > Over を作成し、
第1入力に、over1
第2入力に、glsl1 を接続します。
TOP > Out を作成し、over2 を接続します。
これで完成です。
まとめ
いかがでしたでしょうか。これでもう、TouchとDesignerの間にスペースを入れることはなくなることと思います。
私自身、TouchDesignerに関しては初心者であり、誤りや非効率的な実装を行っている箇所があるかと思います。そのような点を見つけられましたら、ご指摘いただけると幸いです。TouchDesignerは、他のツールと比較して動画再生周りとても強く、何度か案件で命を救われました。それに対しての感謝を込めて少しでもコミュニティに寄与できればと思います。
最後に、このような下らないものを作ってしまい本当に申し訳ありませんでした。