嵐のバーチャル大合唱っぽいものをやってみたい！

# ごあいさつ
港区にある放送局でCGやビジュアル開発の仕事をしています。今年も TouchDesigner アドカレやらせていただきます。よろしくお願いします。
ちなみに去年の記事はこちらです：[いまさらだけど TouchDesigner でプロジェクションマッピング
](https://qiita.com/aokey/items/d6852c11fea25888c206)

# はじめに
嵐もあと15日で活動休止ですね。なんだか寂しい気もしますが、解散ではなく嵐としての活動を休止するだけで、それ以外は変わらずみたいのようです。

今年の始め頃から拡大してきたコロナウィルス感染の影響で、集客イベントが減り無観客やVRのイベントが増えてきましたが、観客を Zoom でつないでLEDウォールやVR空間に登場させるという演出をしばしば目にするようになりました。
その中で目を惹いたのが、嵐が登場するソフトバンクのCM「5Gバーチャル大合唱」。5Gという高速無線回線を使って大勢の人とつながって嵐と一緒に合唱しようぜ！というコンセプトのCMなのですが、初めて目にした時は、オォォォォスゲージャンコレ！と一人でテンション上がってしまいました。
そのCMがこちら。
https://www.youtube.com/watch?v=h81UWMj5fmU

CM なので収録したものをCGや合成で作ったのだと思いますが、これ、生でやれたちょっとおもしろいかもなぁと思い、実装してみたら何人くらいまでできるのか？を試してみようと思ったのが今回のはじまりです。

[サンプルはこちら](https://drive.google.com/file/d/1Bv-7i618AFdU6arcgsgVfVsARtVlW2qZ/view?usp=sharing)

#おことわり
今回は大きめのテクスチャを使うので　Commercial 以上のライセンスが必要です。ゴメンナサイ。

# 下準備
Zoom や Microsoft Teams は1画面で最大49人を表示できるようです。
![Zoom49-Sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308950/d5251a87-9fcd-2353-bb8c-2eb5c573ce17.png)

この4枚の画像をPCから出力された映像と仮定して作業しますので、Zoom や Microsoft Teams の 7 x 7 人の画像をネットで適当に4枚拾ってきてください。（サンプルには入れてあります）

### 画面の人数について
上のような画面キャプチャーを分割器を使って4面マルチにすると 49 x 4 = 196 人を HD の画面内に収めることができます。この 196 人分の映像を 4 ライン組み合わせて 4K を作ると 196 x 4 = 784 人、さらに 4K を 4 ライン組みみ合わせて 8K にすると 3,136 人分のキャプチャー画面ができあがります。

このキャプチャー画面は、コンバーターや画面分割器などを使ってマルチ画面を構築していくのが一般的なやり方なのですが、機材を集めるのが大変なので、ここでは TouchDesigner 上でマルチ画面を作って検証することにします。

ちなみに、Blackmagic DeckLink 8K Pro は 12G が 4 本あるので、デュアルリンクで使えば 8K30p を2系統入力できるので、1枚のキャプチャーボードで 6,272 人分の映像を取り込むことができます。
![DeckLink8KPro.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308950/b61f765c-b129-75af-e4fb-de2eafaa6b44.png)
https://www.blackmagicdesign.com/jp/products/decklink/techspecs/W-DLK-34

このボードを2枚使えば理論上は12,544人、3枚使えば18,816人扱えることになるのですが、3枚というのは空きスロット数の制限もあるのでやや厳しいかも知れません。
そこで今回は、キャプチャーボードを2枚使った場合を想定し、ダミーのテクスチャーを作って実装してみたいと思います。

# 実践
### 12,544人画面の作成
まずは49人の画像4枚を使ってHDの画面に敷き詰めます。その画像を4つずつタイリングしてき、4K → 8K → 16K と増やしていきます。では、実際にやっていきます。

1. Movie File In TOP を4つ用意して4枚の画像を読み込む
2. Crop TOP で不要な部分をカットし Fit TOP でHDの型にはめる
3. Layout TOP でグリッド状にレイアウト。Layout -> Align は "Grid Rows" か "Grid Columns" を選択

これでひとまず 49 x 4 = 196 人分の画像が完成
![HD_196.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308950/3b292df1-80eb-0376-6def-70ef0f98e3ba.png)
4. HD のソースを4つ組み合わせて4Kに、さらに4つずつ組み合わせて8K、16Kと作っていきます。ここでも Layout -> Align は "Grid Rows" か "Grid Columns" を選択
![8Kx4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308950/02e06b4c-c7b4-6339-9546-a121eaeaabad.png)

これで16Kの画像ができました。つづいてこれを3Dオブジェクトに貼って動かしていきます。

### Grid SOP で画面を作る
いままでの作業で 112 x 112 = 12,544 人分のテクスチャができあがったので、次はそれぞれの顔に当てはめる板を作っていきます。

1. Grid SOPを用意し、Rows, Columns をそれぞれ 112 （人物画面の数）に設定。MMB(マウスの中ボタン）でPoints, Vertices が 12,544 になっているのを確認
![GridSOP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308950/dfdf06ad-2e9c-d3f0-c192-791413d2a713.png)

2. Grid SOP の後ろに Null SOP をつなぐ
3. Zoom 1人分の画面用として Rectangle SOP を1つ作り、後ろに Null SOP をつなぐ
4. Null SOP の出口を右クリックして Geometry COMP を作る

ここまででこんな感じになるんじゃないでしょうか？

![Geometry.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308950/f95398c5-aae3-fd01-8cef-1ddfbfbec68e.png)

ではここから調整をしていきます。
5. Rectangle SOP ( rectangle1 ) の Size を画面アスペクトに合わせ ( 0.16, 0.09 ) に設定
6. Geometry COMP　の Instance -> Instancing を ON
7. Translate OP に null2 を設定し、Translate X,Y,Z を P(0), P(1), P(2)にする

これで、rectangle1 が 12,544 個複製されたはずですが、重なって表示されてるのでよくわかりません。そこで、 Grid SOP (grid1)のサイズを大きくしてみます。Size を ( 20, 20 ) に設定してみましょう。どうでしょうか？バラけて見えるのではないでしょうか？
![Instancing.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308950/90f44504-9d79-c288-3bc8-8250b71c788a.png)

### テクスチャ
1. 先ほど作った layout_8Kx4_12544 の後ろに Null TOP をつないで、名前を 16K に変更
2. Constant MAT を作り、Color Map に 16K (Null TOP) を設定
3. geo1 ( Geometry COMP ) の Render -> Material に今作った Constant MAT (constant1) を設定
4. 3D表示の定番である Render TOP と Camera COMP を作り適当に配置
![Render.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308950/9cfaf952-aaf1-e4a1-b2aa-e828df488f1b.png)

これで、Zoom 画面を Grid SOP を使ってインスタンシングした Rectangle SOP に張り付けることができました！でもよく見ると、各画面 ( Rectangle SOP ) に12,544人分の画像が貼り付けられちゃってますよね？なので、これを調整していきます。

### テクスチャの調整
いまは 1つの Rectangle SOP に 12,544 人が描かれています。これはどういうことかというと、それぞれの Rectangle SOP に対して ( 0, 0, 0 ) から ( 1, 1, 1 )　分のエリアのテクスチャを貼りつける。という設定になっていたからです。Offset で指定されたところが描画の原点、Scale が切り取る領域の大きさを表します。

テクスチャの UV は 0 -> 1 までの値で指定します。4K でも 8K, 16K でも 0 -> 1 で指定することになります。今回は、1画面に縦横 112 x 112 画面あるので、切り取る領域 (Scale) は縦横共に 1/112　ずつとなります。これを Offset で 1/112 ずつ移動していけばバラバラに貼り付けることができるようになります。
ではさっそく。

1. rectangle1  と null3 の間に Texture SOP を入れ、Texture -> Scale を ( 1/112, 1/112, 1 ) と設定。そうすると左端に居る人がわっさり現れてきますね！
![Texture.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308950/7529c097-d3b9-01ce-09e8-4f7a9e9b91b0.png)
2. 次にオフセット。オフセットさせる方法はいくつかありますがここでは分かり易い Patter CHOP を使います。Pattern -> Type は "Ramp" に設定
3. U 方向に 112 人いるので Pattern -> Length を 112 と設定
4. U のオフセットは 0 から始まり、1 - 1/112 で終わるので、Pattern -> To Range を (0, 1 - 1 / 112 ) にします。これで1行分のUオフセットは完了
5. これを縦方向分 112 繰り返すので、Pattern -> Length を "112 * 112" に変更し、Number of Cycles を 112 に変更
![PatternU.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308950/db1b2b71-2b3f-7af1-f14c-326f4d4145cf.png)
6. 次はVのオフセット。Patter CHOP を1つ作ります
7. 今度は112個単位で値が変化していくので、Pattern -> Type を "Step" に設定
8. Length は U の時と同様に "112 * 112" を設定してサンプル数を合わせます
9. オフセットの値を 1/112 ずつ変化させていきたいので、Step per Cycle は 1 / 112。こうすることで、0 から 1 / 112 ずつ変化する 112 段の階段が作れました。
![PatternV.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308950/702fa71a-551c-5fd0-9328-7afbd33a4a1c.png)
10. 今作った pettern1 （ U のオフセット）、patter2 （ V のオフセット）を Merge CHOP でマージし、インスタンスで使う時に分かりやすいよう Rename CHOP で offsetu, offsetv と名前を変えておきます
11. 最後に Null CHOP をつなげて、この Null CHOP を geo1 (Geometry COMP)の Instance 2 -> Texture Coord OP に設定
12. Texture Mode は Offset に設定し、U に offsetu, V に offsetv を選んであげると、1つ1つ Rectangle に別々の顔がアサインされているのがわかると思います。（サンプルではリピートしているので同じ顔がいっぱい出てきます）
![UVMap.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308950/5e267eb6-a140-918e-0229-2e8c2ddfb734.png)

さて、ここまで出来たらあとは動かすだけですね。

### Rectangle を動かす
嵐にのバーチャル大合唱のようにしたいので、なんとなくフワフワ漂っているような動きを作ってみます。
1. Grid SOP の各 Point を Particle に変換したいので grid1 と null2 の間に Convert SOP を追加し、Conert to で "Particles Per Point" を選択
2. この Particle にフィールド的な動きを付けたいので、Spring SOP を作り Convert SOP -> Sprint SOP 1つ目（操作対象）につなぎます
3. 操作した結果を位置情報として反映させたいので Sprint SOP -> null2 につなぎ変え
4. フィールドの元となる Metaball SOP、Force SOP を作り、Metaball SOP -> Force SOP のようにつなぎかえ、Force SOP の出力を Spring SOP の3つ目（Force)に入れます
5. 操作したパーティクルが外側に逃げて行かないように Box SOP を作り、Size = (　22, 22, 22 ) くらいに設定して Box SOP -> Spring SOP 2つ目（ Collision ) につなぎます
6. Spring SOP の Force で Turbulence, Turb Period を適当に設定すると動き出すと思います。サンプルでは Turbulence = ( 2.5, 2.5, 1.5 )、Tub Period = 0.07 くらいにしています
![SpringSOP2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308950/7be3564d-e7c7-0eed-98cf-7b09ad5a7a41.png)

結果はこんな感じになります。
![Result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308950/1c4b06f6-9941-8a25-9052-3fba40d56023.png)

私の環境（Core i7、GeForce GTX 1080 搭載）では 60fps で動いてましたが、もしかしたら他の環境では 60fps でないかもしれませんのでご了承下さい。

# 課題
## GPU 負荷
今回の作り方では、Layer TOP のところが Cook されていないのですが、途中に Level TOP などの適当な処理を挟んで強制的に Cook させると Layer TOP の処理負荷が異常にあがります。これは 16K という強大なテクスチャを毎回構成しているためです。
Texture Instancing (Texture Index OP, Texture Index) を使えば負荷を減らすことができそうです。この辺については後日別記事にて書きたいと思います。
→ [TouchDesigner: Instance Textures 基礎編](https://qiita.com/aokey/items/8b498021769a1ebe7e0f)
→ [TouchDesigner: Instance Textures ちょっと応用編](https://qiita.com/aokey/items/91fe875583907b775fd2)

## パーティクルの動き
今回は SOP ベースでパーティクルを操作しましたが、より大量に、より高速に動かすには GPU パーティクルでの操作がおススメです。
そのあたりは、森岡先生の "[TouchDesignerでGPGPU Particle Systemを作る Vol.1 基本編](https://qiita.com/ToyoshiMorioka/items/91c4e1427c2b4296c2a4)" や
kazuhide さんの"[【TouchDesigner】GPGPU Particle Systemを使ったポイントクラウド描写の練習](https://note.com/kazuhide_note/n/n3e1fd7f44839)"を参考にされると良いかと思います。

# おわりに
今回はパーティクルベースでアニメーションを付けてみましたが、もう少しバーチャル大合唱に寄せるのであれば Tube SOP を使って円形に囲み、 Noise SOP で軽く動きを付ける方が演出的には近いかも知れません。

なんだか中途半端な感じになってしまいましたが、何かのヒントになれれば幸いです。
あと、担当日ギリギリの投稿になってしまスイマセンデス！