オーディオリアクティブなメッシュをモーフィングする

#はじめに
岐阜県にある[IAMAS](https://www.iamas.ac.jp/)という学校に通っている [@minoge1001](https://twitter.com/minoge1001) と申します。

先日、VJとして参加させて頂いた[Interim Report Edition4](http://interim-report.org/edition4)というイベントで作成したエフェクトについて紹介いたします。オーディオリアクティブなメッシュをモーフィングで変化させるもので、以下のようなイメージです。

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Audio reactive mesh morphing 01 <a href="https://twitter.com/hashtag/TouchDesigner?src=hash&amp;ref_src=twsrc%5Etfw">#TouchDesigner</a> <a href="https://twitter.com/hashtag/GLSL?src=hash&amp;ref_src=twsrc%5Etfw">#GLSL</a> <a href="https://twitter.com/hashtag/creativecoding?src=hash&amp;ref_src=twsrc%5Etfw">#creativecoding</a> <a href="https://t.co/sg6pGMDRdP">pic.twitter.com/sg6pGMDRdP</a></p>&mdash; Yugo Minomo (@minoge1001) <a href="https://twitter.com/minoge1001/status/1193901395167965184?ref_src=twsrc%5Etfw">November 11, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Audio reactive mesh morphing 02 <a href="https://twitter.com/hashtag/TouchDesigner?src=hash&amp;ref_src=twsrc%5Etfw">#TouchDesigner</a> <a href="https://twitter.com/hashtag/GLSL?src=hash&amp;ref_src=twsrc%5Etfw">#GLSL</a> <a href="https://twitter.com/hashtag/creativecoding?src=hash&amp;ref_src=twsrc%5Etfw">#creativecoding</a> <a href="https://t.co/6P9hAVUvNA">pic.twitter.com/6P9hAVUvNA</a></p>&mdash; Yugo Minomo (@minoge1001) <a href="https://twitter.com/minoge1001/status/1194228367702183936?ref_src=twsrc%5Etfw">November 12, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

#Agenda
サンプルファイルは以下からダウンロードできます。
[AudioreactiveMeshMorphing.toe](https://drive.google.com/file/d/1lKX_gvKvtuR09M9xLl_24b9m35ZxoZ5j/view?usp=sharing)

大まかな流れは次の通りです。

1. `Grid SOP`を準備し、**ポイント位置と法線方向をTOPに変換**する
1. **オーディオリアクティブなTOPを作成し、法線方向への変化量**とする
1. 1.と2.で作成した３つのTOPを用い、`GLSL MAT`で**変形するgridを作成**する
1. 1.の`Grid SOP`を`Twist SOP`などで変形させ、**複数のパターンを準備**する
1. **ポイント位置のTOPと法線方向のTOP**を`Switch TOP`で**徐々に変化**させる

**TOPでモーフィングを行うことで、頂点数が多くなってもスムーズな動作が可能**となります（GTX1060で、100万頂点でも60fpsをキープ）。また、一度仕組みを作ってしまえば、**SOPレベルでメッシュを変形させるだけで様々なバージョンを手軽に作成**することができます。

途中、GLSLが出てきますが、GLSLを書いた事がない方にもなるべくわかるように書くつもりです。もし分からなければ、ソースをコピペして読み飛ばしてもらって大丈夫です。

#ポイント位置と法線方向のテクスチャ化

![gridSOP.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/b181cc4e-9f10-d986-3374-f76d12055014.jpeg)
`Constant CHOP(constant1)`で画像の縦横のサイズを設定します。とりあえず`変数名=gridSize`/`値=100`としておきます。
`Grid SOP`を作り、`Orientation=ZX Plane`/`Compute Normals=ON`にします。`Rows`と`Columns`は先に作った`gridSize`を参照させておきます。
`Grid SOP`の後ろには`Null SOP`をつなげておきます。

![texturize01.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/02cb38b8-63df-0aaf-5104-87f46b34dd55.jpeg)
`Null SOP(null1)`を`SOP to CHOP`で位置データ(Position XYZ)に変換したのち、`Shuffle CHOP`の`N Value`を`gridSize`とすることで100サンプルずつに分割します。これによって、**10000サンプル×3チャンネルのデータが100サンプル×300チャンネルのデータに変換**されます。但し、このままでは`tx0→…→tx99→ty0→…→ty99→tz0→…→tz99`というチャンネルの順序になっていますので、`Reorder CHOP`で`Channel Reorder Method=Numeric Suffix Sort`とすることで、`tx0→ty0→tz0→tx1→…→tx99→ty99→tz99`と並び変えます。この結果を`CHOP to TOP`（`Data Format=RGB`）に入れることで、`Grid SOP`の**100×100の各ポイントの位置データ（XYZ）が、100×100の画像の各画素（RGB）に格納**されます。

![texturize02.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/c86d4028-789b-9453-0f0d-ea1e167dcc81.jpeg)
上記の`SOP to CHOP`～`CHOP to TOP`をコピペし、`sopto2`の変換データを`Normal`として法線データのTOPも作成します。最後に、2つの`CHOP to TOP`の後段に`Null TOP`を繋げ、それぞれ名前を`position`と`normal`としておきます。

#オーディオリアクティブなTOPの作成

![audioVisualize02.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/c5f914bf-7810-dc69-d38d-6fe6e3413062.jpeg)
`Audio File In CHOP(Mono=ON)`、`Analyze CHOP(Function=RMS Power)`、`Null CHOP(null2)`と繋げます。音源のファイルはデフォルトのままで構いません。また、音をモニタリングするために`Audio Device Out CHOP`を`Audio File In CHOP`の後ろに接続します。`Noise TOP`を開き、パラメータを以下のようにします。

|タブ|パラメータ|値|
|:---:|:---:|:---:|
|Noise|Harmonics|4|
|Noise|Amplitude|op('null2')['chan1']|
|Noise|Offset|0|
|Transform|Translate z|absTime.seconds|
|Common|Resolution w|op('constant1')['gridSize']|
|Common|Resolution h|op('constant1')['gridSize']|

`Noise TOP`の後ろに`Null TOP`を繋ぎ、`height`と名前を付けておきます。

#Vertex Shaderによるメッシュの変形

![GLSL01.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/84f091fb-d13d-e72f-b7a4-311635f8ce96.jpeg)
`Null SOP`のOUTを中クリック（もしくは右クリック）して後段に`Geomrtry COMP`を繋ぎ、`Camera COMP`と`Render TOP`を出しておきます。また、`GLSL MAT`を`Geometry COMP`の`Material`に割り付けておきます。

![GLSL02.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/1c83a3f9-65e0-43e9-59e3-d05c90c6b6e2.jpeg)
`GLSL MAT`の`Sampler 1`タブに作成した3つのTOPを割り当てます。

|Sampler Name|TOP|
|:---:|:---:|
|uPosMap|position|
|uNormMap|normal|
|uHeightMap|height|

また、`Vectors 1`タブに新たに2つの変数を設定します。

|Uniform Name|Value|役割|
|:---:|:---:|:---:|
|uPosScale|10|ベースとなるグリッドの大きさを調整する係数|
|uHeightScale|4|法線方向の変位量を調整する係数|

さらに、`Common`タブの`Wire Frame`を`Topology Wire Frame`にしておきます。

ここまで準備ができたら、Vertex Shaderを書いていきます。

```glsl:glsl1_vertex

uniform sampler2D uPosMap;
uniform sampler2D uNormMap;
uniform sampler2D uHeightMap;
uniform float uPosScale;
uniform float uHeightScale;

void main() 
{
	int id = gl_VertexID;
	vec2 res = textureSize(uPosMap, 0);
	vec2 uv = vec2(float(id % int(res.x)), float(floor(id / int(res.x)))) / res;
	vec2 texCoord0 = uv + (1.0 / res) * 0.5;

	vec3 pos = texture(uPosMap, texCoord0).rgb;
	vec3 norm = texture(uNormMap, texCoord0).rgb;
	float height = texture(uHeightMap, texCoord0).r;
	
	vec3 newPos = pos * uPosScale + norm * height * uHeightScale;

	vec4 worldSpacePos = TDDeform(newPos);
	gl_Position = TDWorldToProj(worldSpacePos);
}
```

全体は上記のようになります。Vertex Shaderはジオメトリ（この場合は`Grid SOP`）の頂点データを制御するプログラムです。Vertex ShaderはGPU上で頂点ごとに並列動作するため、CPUで計算するSOPの世界と比較して非常に高速です。**100×100=10000個の頂点のそれぞれに対して上記のmain関数が同時に走っている**イメージです。

```glsl
uniform sampler2D uPosMap;
uniform sampler2D uNormMap;
uniform sampler2D uHeightMap;
uniform float uPosScale;
uniform float uHeightScale;
```

先ほど設定したuniform変数を読み込みます。このように書くことで、GLSLのコードの外にあるデータを取り込むことができます。以降のコードではこの名前でアクセスします。

```glsl
int id = gl_VertexID;
vec2 res = textureSize(uPosMap, 0);
vec2 uv = vec2(float(id % int(res.x)), float(floor(id / int(res.x)))) / res;
vec2 texCoord0 = uv + (1.0 / res) * 0.5;
```

[森岡さんの記事](https://qiita.com/ToyoshiMorioka/items/91c4e1427c2b4296c2a4)のコードを借用しました。ざっくり言うと**Grid SOPのあるポイントが、100×100の画像のどの画素に対応しているかを計算し、その画素の座標を算出**しています。結果として`texCoord0`には、計算すべき画素の位置の座標が入っています。この時の座標は**x方向、y方向ともに0～1に正規化された値**となっています。

```glsl
vec3 pos = texture(uPosMap, texCoord0).rgb;
vec3 norm = texture(uNormMap, texCoord0).rgb;
float height = texture(uHeightMap, texCoord0).r;
```

上で算出した`texCoord0`を基に、位置情報を持つテクスチャ（`uPosMap`）/法線情報を持つテクスチャ（`uNormMap`）/変化量の情報を持つテクスチャ（`uHeightMap`）の当該画素の情報を抽出します。各関数の後ろについている`.rgb`や`.r`は各テクスチャのどのチャンネル（Red/Green/Blue/Alpha）のデータを取り出すかを示しています。位置データと法線データはXYZのデータがそれぞれRGBに格納されているので`.rgb`、変化量は1次元の値なので`.r`となります。

```glsl
vec3 newPos = pos * uPosScale + norm * height * uHeightScale;
```

位置情報（`pos`）を`10(uPosScale)`倍し、大きさが`10`のgridにします。さらに法線方向（`norm`）にオーディオリアクティブな変化量（`height`）×`4(uHeigntScale)`だけ動かします。`uPosScale`や`uHeightScale`の値は`GLSL MAT`の`Vectors 1`タブで自由に変更可能です。

```glsl
vec4 worldSpacePos = TDDeform(newPos);
gl_Position = TDWorldToProj(worldSpacePos);
```

`TDDeform関数`の引数を、デフォルトの`P`から先ほど計算した`newPos`に変更します。この部分は座標変換と組み込み変数（`gl_Position`）への代入を行っており、今回の実装では気にすることはありません。


![camera.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/13c882ce-e957-983d-348e-c6688e053665.jpeg)
前の段階まで設定すると音に反応してグリッドが変形していると思います。但し、カメラが真横から捉えているので、いい感じの位置に移動させます。まず`Null COMP`を作成し、`Camera COMP(cam1)`の`Look At`に設定します。こうすることで、カメラがどの位置にあっても常に`Null COMP`（初期値で`(0, 0, 0)`）の方を向くようになります。その後、`cam1`の`Translate=(10, 10, 10)`とすることで、斜め上から俯瞰で見たような映像となります。

![baseSys.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/2ae2e82e-cd2c-dea0-071f-7500f1c15787.jpeg)
この段階での全体のネットワークと`Render TOP`の表示結果は上記の通りです。

#モジュール化

`Grid SOP`の各種データを画像データに変換する部分を**モジュール化**し、使い回しし易くします。

![collapse_before.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/664e9b37-2542-d28c-c147-c643c2abc769.jpeg)
`grid1`から`geo1`に直接接続すると、`null1`が分岐されます。その後、上図で赤く記した9つのノードを選択し、ネットワーク上のノードの無い部分で`Collapse Selected`を選択します。その結果、選択部分が一つの`Base COMP`にまとまります。

![collapse_after02.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/a51376cc-75d4-60e9-7bc4-f4a0c176bc4c.jpeg)
内部でエラーが起きているので、修正していきます。

![texturize_base01.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/79945de6-5dac-8c4c-a80c-4ab4f8d339f3.jpeg)
エラーの原因は`Shuffle CHOP`の参照先のパスが変わってしまったことなので、`N Value`の値を`op('../constant1')['gridSize']`に修正します（opのカッコ内に`../`を追記）。また、`base1`の2つのTOP出力が交差するのを修正するために、`out1`の`Connect Order`を`1`にします。

#複数パターンの生成と切り替え

![twistsop_parameter.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/671f4126-ab53-a3fc-c646-9b16899ae666.jpeg)
上の階層に戻って`base1`を複製します。生成された`base2`と`grid1`の間に`Twist SOP(twist1)`を挿入します。もう一つ`base1`を複製し、生成された`base3`と`grid1`の間に2つの`Twist SOP(twist2、twist3)`を挿入します。`Twist SOP`の各パラメータは画像を参照してください。

![switchTOP.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/6b5f6335-8970-dc3b-5d54-de053cef8882.jpeg)
`base1`と`position`の間、`base1`と`normal`の間に`Switch TOP`を挿入し、それぞれ`posSwitch`、`normSwitch`と名前を変更します。また、`base2`と`base3`の2つのOUTも、それぞれの`Switch TOP`に接続します。ここで、2つの`Switch TOP`の`Blend between Inputs`を`ON`としておきます。こうすることで、3つ以上の入力に対しても`Cross TOP`のような**フェードの切り替わり**ができるようになります。

![button.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/94f857fc-285b-bbbe-3e04-381db3946c6c.jpeg)
次に、切り替えのためのボタンを作ります、`Button COMP`、`Count CHOP`、`Limit CHOP`、`Filter CHOP`、`Null CHOP`を順に接続します。`Button COMP`の`Button Type`は`Momentary`にしておきます。`Limit CHOP`の`Type`は`Zigzag`にし、`Maximum`を`2`（**接続するbaseの数-1**）に設定します。こうすることで、ボタンを押すたびに値が`0→1→2→1→0→1→…`と変化していきます。`Filter CHOP`の`Filter Width`で、モーフィングに要する時間を調整します（今回は`8`とします）。最後に、`Null CHOP`の値を`posSwitch`と`normSwitch`の`index`から参照することで、ボタンを押すたびにモーフィングができるようになります。

![final.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/036a5de8-dc5d-170a-bf95-8e44734656c6.jpeg)
**最終的なネットワークは上図のようになります。**[このような動作](https://youtu.be/xeaHx0PUxZ0)になります。

#おまけ
ここまででかなり長文になってしまいましたので、あとは少しだけ補足情報を書きます。

##Paletteの活用

基となる`Grid SOP`を変形させることで無数のバリエーションが創り出せますが、個性的な形状をイチから作成するのは大変です。そのような時には**Palleteを活用**するのがおすすめです。例えば、[このようなモーフィング](https://youtu.be/1imZS_gZ4uI)が簡単に作れます。

###mesh

![Pallete_mesh.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/745ea03c-de24-5f46-0faa-dc4b42c9abe4.jpeg)
`Pallete`の`Generators`の中に、その名も[mesh](https://docs.derivative.ca/Palette:mesh)というコンポーネントがあります。これは、様々なパラメトリック方程式でメッシュを変形させたもので、`Rows`と`Columns`に`gridSize`を指定することで、簡単に入力の一つとして使うことが出来ます。`Shape`を変更することで種類を選択し、`Extra *`を変更することで形状を調整できます。

###superFormula

![Pallete_superFormula.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/9fb93f54-d5c5-91a5-8ff9-e52e6cfa5b7f.jpeg)
同じ`Generators`の中に[superFormula](https://docs.derivative.ca/Palette:superFormula)というコンポーネントがあり、こちらでも複雑な形状を作成することができます。使用する際には、`Resolution`の値を`gridSize`をします。ご興味のある方は[wikipediaの記事](https://en.wikipedia.org/wiki/Superformula)をご覧ください。

##波形が流れていくような動き
記事の最初で引用したTwitterでの作例では、音の波形が流れています。これは、`Feedback TOP`を使った手法で作成しており、詳細は[Matthew Raganさんの記事](https://matthewragan.com/2015/05/08/thp-494-598-audio-part-6-topography-1-touchdesigner/)をご参照ください。

##色情報の付加
Twitterの作例では、メッシュ自体に色がついており、かつ波形の高さで色が変わるようになっています。本記事では詳細は省略しますが、ざっくり紹介すると
- `Ramp TOP`でカラーマップを用意する
- `Lookup TOP`を使い、`height`の値（0に近いほど低く、1に近いほど高い）と`Ramp TOP`の色を対応させる
- `GLSL MAT`にカラー情報をテクスチャとして渡し、色情報を付加する
というプロセスを踏んでいます。余力のある方はぜひ試してみてください。