3/22にdraw(tokyo);#2というオーディオビジュアルのパーティがCIRCUS TOKYOで開催されて、自分はサブフロアのオープンでAnielさんのVJとして出演した。とにかくナイスパーティで、draw crewとスタッフ、演者、CIRCUS TOKYO、お客さんに感謝。ありがとうございました。
【イベント告知】
— draw(); (@function_draw) 2025年3月2日
draw(tokyo); #2 | #function_draw
🗓️2025/03/22 (土) 14時~
🏙️CIRCUS TOKYO
🎟️前売¥4000+1D 当日¥4500+1D
タイムテーブル公開!!
Live coding、ジェネVJ、マシンライブ、DJを楽しめるオーディオビジュアルイベントです!
▼前売チケット&詳細はこちら▼https://t.co/6O9Zfi1aN5 pic.twitter.com/xChMKDNakI
VJをやるにあたって「Rector」というアプリを作ったのでそれについて解説する。
やりたかったこと
4年くらい前から「ライブコーディングのコーディング部分をゲーム的インターフェースに置き換えられないか?」という疑問(願い?)が自分の中にあり*1、2回くらい挑戦したんだけど色々うまくいかなくて挫折したという経験がある。3月に仕事をやめるというのもあり、もう一度やってみようということで去年の11月くらいからRectorというプロジェクトを始めた。RectorはReconstructorの略で、VJは音を映像に再構築する行為だと思っているし、ライブコーディングを別のインターフェースで再構築したいという気持ちも込められている。Twitterに進捗を投稿していたらdraw(tokyo);でVJしませんかと誘ってもらい、しかしプロジェクト的には不確実性が高いのでかなり悩んだ。が、大元の問題が解決できなかったとしても、3ヶ月あればUnityでルックを作って60分間VJをやることはどうにかできるだろう、という打算でお誘いを受けることにした。
(悟りについてはここではないところで別途何か書くかも)
「ライブコーディングのコーディング部分をゲーム的インターフェースに置き換える」ということについてもう少し書く。自分は、シェーダーライブコーディングというのは
という構成で、演者が決められたルールの元でデバイスを通してUIを操作し、それによって絵が出る様子を聴衆が眺める、みたいなように認識している。このうちルールとUIの部分をコードが書けない人でも実行できるようなインターフェースにしたい。ミニマルな例だと
- デバイス:マウス
- UI:画面に赤いボタンと青いボタンが表示されている
- ルール:ボタンを押すと画面に表示されたキューブの色がボタンの色に変わる
とかだが、これだと面白くないので、もっと奥深いルールを作る必要がある。4年前、自分は仕事でゲーム開発をしていたので、ゲーム的なインターフェースに置き換えられないか?ということを考えいた。例えば、キャラクターを操作してオブジェクトにインタラクトするとパーティクルが出る、の延長のイメージ。
聴衆目線でのライブコーディングの面白さは何かについて考えると
- すべてがコードから作られていてすごい
- シェーダー固有のかっこよさ
- その場で操作していてすごい
- ライブ性
- コーディング・キーボードの身体性
- 操作しているのが見えて面白い
- UIが見えることの面白さ
- コードがかっこいい
- エディタやプログラムのルックがかっこいい
- 絵が徐々に変わっていって面白い
- "ビルド"的要素
- sphere→repetition→cube→grid traversal→animation→shading、みたいな
- 独立したものをバラバラに出すのではなく積み上げていく奥深さがあるということ
- こちらの予想を超えてきてすごい
- 予測可能性とそれを超えられる奥深さ
- 単純に絵がかっこいい
- システムの表現力・演者の腕
などがあると思っていて、このうちシェーダー固有のかっこよさは諦めることにして、それ以外の部分について考える必要がでてくる。ゲームの世界ではこういうのはRTA配信に似た部分があると思っていて、自由度が高く奥の深い、グラフィックとUIがかっこいいゲームがあり、それを高い練度で音楽に合わせてプレイする画面をみんなで観る、みたいなイメージ。そんなものが作れるのか?というと今でもよくわかってない(ので不確実性が高いと書いた)。
作ったもの
#function_draw
— Saina(さいな) (@SainaKey) 2025年3月22日
. @shiva_duke28 さんの自作ジェネVJソフトカッコ良すぎる pic.twitter.com/qeylwKRwnS
当日のVJのいい感じの部分の切り抜き
- デバイス:ゲームパッド(DUALSHOCK4)
- UI:ノードベースのGUI
- ルール:ノードベースのGUIで定義したイベントフローに従って、ユーザー入力・音・時間などの入力がパーティクル・カメラ・ポスプロなどに流れて絵を出る
全然ゲーム的インタフェースじゃないのでは?というのは全くもってその通りです。
UIからVFX切り替えられるようにした pic.twitter.com/5L4w2smLLI
— shivaduke (@shiva_duke28) 2024年10月21日
これは開発初期の動画で、最初はキャラクターを操作してオブジェクトにインタラクトしてUIを開き、リストからVFXを選んでパラメータを変更してということをやっていたが、まずカメラをガンガン動かすとキャラクターの操作が困難になる。また演者目線でも聴衆目線でも入力(= 音)と出力(パーティクルなど)のバインド状況を一覧できる方が良いというのもあり、結局ノードベースになった。
たとえば、「編集モード」と「パフォーマンスモード」を用意して、編集モードでは3人称視点でキャラクターを操作して背景・カメラ・パーティクルなどを編集し、パフォーマンスモードではVJシステムとしてカメラが動く、みたいな説もあったけど、編集モード中の絵がイマイチそう・開発工数が膨れる・不確実性が高い・絵を出すのに時間がかかる、というので今回は早々に諦めた。山にパーティクルを採取しにいったり、カメラドローンを買うためにバイトしたり、ストロボを炊くために電源を買ったりしたい気持ちは今でも結構ある。
最終的に残った「聴衆が演者と同じ画面を観る」と「PS4コントローラだけで操作が完結する」については、やりたかったことの一部が達成されたようには思う。
Unityプロジェクトとしては構成はかなりシンプル
- Unity6
- URP
- VFX Graph
- UIToolkit
- Cinemachine
- Cysharp/UniTask
- Cysharp/R3
- keijiro/Lasp
ノードベースGUIの仕様
グラフはノードとエッジから構成される。DAGに限定してループは許容しない(ソートや無限ループの判定が面倒臭い)。各ノードはインプットスロットとアウトプットスロットを持ち、エッジはノードのアウトプットスロットを別のノードのインプットスロットに接続する。各スロットには名前と値の型があり、たとえば音声入力の音量を返すLevelノードは以下の4つのスロットを持つ。
- インプット
- Active: bool
- アウトプット
- Low: float
- Mid: float
- High: float
ノード一覧
- VFX: 18個(VFX Graphに対応する)
- Camera: 13個(Cinemachine VirtualCameraに対応する) + 設定制御ノード 1個
- Event:
- Operator
- Switch 2
- Switch 4
- Switch 16
- Switch 4x4
- And
- Or
- Gate
- Negate
- With
- Math
- MAD:
(x,a,b) → ax + b
- Float
- Vector3
- Sin
- Cos
- Min
- Max
- Mod
- Step
- Circle:
(x,r)→(r * cos(x), r * sin(x))
- MAD:
- Scene
- 背景シーンに紐づいたノード 6個(ポスプロとか部屋のスケールを変えるやつとか)
- System
- HUD Style
ノードパラメータ
ゲームパッドのR1ボタンを押している間、画面右側にノードのインプットスロットの値を表示するUIが表示される。現状は、int、float、bool、event型をサポートしていて、Vector3やTransformは仕様が定まらないので放置している。役割としてはShader Graphのエッジが繋がってないスロットに表示されてるやつ(↓の 5 の部分)に相当する。これがあるだけでノードの数をかなり減らせる。
UIとしては Touch DeisgnerのParameter Dialog を参考にした。ゲームパッドでfloat値を変更する都合上、値の刻み方を1, 0.1、0.01などで変更したくなるので、□ボタンを押すことで刻み方を変えられるようにもした。masawadaさんのリプライきっかけでかなりUXがよくなったので大感謝。
ノードアクション
各ノードには"アクション"を定義することができる。ゲームパッドの□ボタンを押すとフォーカス中のノードに対してアクションを呼び出す。未定義の場合は何もしない空のアクションになる。ノードパラメータの中でも特に頻繁に呼ぶものをワンボタンで呼べるようにする、みたいなやつ。
アクションの例
- VFX:Active値(= 表示・非表示)をトグルする
- Level:Active値(= アウトプットするかどうか)をトグルする
- Camera: そのカメラにスイッチする
- Switch 4: シーケンスを次のステップに進める
ノードのミュート
ゲームパッドのL1ボタンを押すことでフォーカス中のノードの"ミュート"値をトグルできる。ミュート中はインプットとアウトプットを全て無視する。アクションでトグルしていたActive値と役割が重複する部分もあるけど、これは任意のノードで使える。イメージとしてはコーディングにおけるコメントアウトに近い。ドロップやブレイクなどの曲の展開時に急いでグラフを編集する余裕はないのでこれとアクションで対応する。ノードの名前に取り消し線を引くのがわかりやすくてお気に入り。こういうわかりやすさは演者にも客にも大切。
細かい話を書くと、ノードパラメータのUIからの変更はミュートを無視するようになっている(便利なので)。
プレイ動画
簡単なやつ
組み合わせ
動画用にできるだけ急いで組んだけど、慣れるとこれくらいは考えながらサクサク作れる。「絵が徐々に変わっていって面白い」が少しはできたかなと思う。
落とし穴
同じグラフでもエッジを繋いだ順番によって挙動が変わるケースがある。
たとえば上の画像で、Switch 2の片方にカメラノードが2つ繋がっている。カメラノードはActiveスロットにtrueが流れてくると「自身のPriorityを1、他のカメラのPriorityを0」にする。これは後勝ちなので後から繋いだエッジが優先される。内部的にはRxを使っていて、アウトプットをObservable、インプットをObserverとして、エッジを作るのがSubscribeに相当する。なので、購読順に依存するのはそれはそうという感じだが、グラフ構造によって全てが決定されるわけではない、というのはちゃんと書いておきたかった。
というかそもそも、各ノードがめちゃくちゃステートフルで、上からtrueが降ってきているのにノードアクションでfalseに上書きしたりミュートして無視したりとぐちゃぐちゃにできる。グラフはノード間のデータをどう流すかを決める道のようなもので、道の上にどういうデータが流れたかはグラフの外部から自由に書き換えられるようになっている。
ゲームパッド vs ノードベース GUI
ノードベースのGUIはマウスで操作するのが一般的だと思うが、スロットをドラッグ&ドロップしてエッジを繋ぐためには、ドラッグ先の面積を大きくしないと操作速度が落ちる(フィッツの法則)。一方で、RectorではGUIの描画面積を小さくしたいという要件があり、これらが強く逆行してしまう。加えて、自分はマウスが苦手で、家でも得意じゃないのに慣れない箱で高精度で高速にマウスを操作できる自信がなかった。ということでゲームパッドで操作するようにした。
ゲームパッドで操作するためには、システム側でノードの位置をいい感じに整理して常に見やすい配置にする必要がある。色々調べるとDAGをいい感じに描画する手法として階層グラフ描画(杉山フレームワーク)というものがある、ということがわかったのでそれを実装した。
読んだのはこの辺:
- https://orsj.org/wp-content/corsj/or63-1/or63_1_20.pdf
- Fast and Simple Horizontal Coordinate Assignment | SpringerLink
- [2008.01252] Erratum: Fast and Simple Horizontal Coordinate Assignment
1つ目の日本語解説が読みやすく、メインは2本目のアルゴリズム部分だが、それにはミスがあって3つめがそのErratum(Erratumの方が読みやすい)。
keep
- いい感じに動いてくれて助かる
- グラフ自体にジェネラティブ感があってよい
- アニメーションするのがよい
problem
- 「カメラの制御は左側」みたいな土地勘が全く使えないのが結構クリティカルで脳の負担が増える
- ソートのタイミングをマニュアルにしてもいいかも
- フォーカス中のノードが画面外に消えるのがストレス
コントローラアサインと画面のステート
コントロラーはDUALSHOCK4。長いこと使ってるが全然壊れないのですごい。
- 十字キー:GUIナビゲーション(ノードのフォーカスの移動、スロットのフォーカスの移動)、ノードパラメータの変更(スライダー操作)
- ×:決定
- ○:キャンセル・戻る
- □:ノードアクション
- △:ノード生成メニューを開く
- L1:ミュート トグル
- R1:押している間だけノードパラメータを開く
- L2:グラフのズームアウト
- R2:グラフのズームイン
- 左スティック:キャラクター操作(使ってない)
- 右スティック:グラフ全体の移動
- スタートボタン:シーン選択画面
- セレクトボタン:設定画面(オーディオデバイス選択、解像度変更、アプリ終了)
ノードベースエディタのステートマシン
keep
problem
- L2/R2やアナログスティックをもっと効率的に絵作りに反映させる方法があると思う
- たとえばカメラのFOV変更はL2/R2で行うのが自然(カメラ編集モード?)
- ステートマシンの状態がぱっと見でわかりにくく不要な遷移が頻発する
- エッジ生成するまでにボタンを何度も押さないといけなくてコストが高い(アクションやミュートでカバーした)
- もう少し便利な機能が生やせるように思う(スロット選択中にノード生成すると接続先を指定できて、生成後に自動で接続する、とか)
絵作り
UIにしろ3D映像にしろ制作はど素人なのでPinterest、VJフッテージ集、解説動画系(動画、写真、モーショングラフィックス)、demoなどを色々見た。システムの開発に時間がかかりすぎてしまい、終盤にネタをあまり増やせなかったのが反省点。
Rector用に使っていたPinterest Pinterest
UIのデザイン
UIは聴衆にも見せる必要があるのでデザインには結構時間をかけた。
フォントはJetBrains Mono。等幅でコードっぽさが欲しかったので普段からIDEで見ているものにした。
グラフのデザインはPure Dataを参考にしたというかパクった。Unity畑の自分にとってはノードベースエディタというとShader GraphやVFX Graphがまず思いつくんだけど、ノードの面積が大きくて後ろの絵が見えにくくなってしまう。Pure Dataはhover中のスロットだけ名前を表示するようになっていて、見た瞬間に膝を叩いた。Rectorでは後ろの絵の見やすさとUIの見やすさがトレードオフになっていて、できるだけ前者を優先しつつ厳しいところを後者に渡す形で調整していった。
グラフ以外のHUD部分には色々パラメータとかログとかを表示した。こういうのはジェネ系の常套手段だと思うが、Rectorのこだわりとしてはプレイヤーにも意味のある情報を表示するようにしている。Application.logMessageReceived
を購読しているので例外がでるとエラーログも表示されて便利。
HUDの枠の不透明度を下げることでシネマスコープにできるのを本番前日に思いついて、慌てて実装したのだけど、評判が良くて嬉しい。このためにstylesheetをいじってちゃんと比率を2.35:1にした。
VFX
VFX Graphを使って作った。あまり凝ったものは作ってなくて、シェーダーもほとんど書いてない。URPでもLitシェーダーに影をキャストさせてモーションブラーさせるだけでかなり豊かになる。パーティクルの動きを考えるときにモーショングラフィックスのハウツー動画を見たりした。ネタが思いつかないときはPinterestを眺めてかっこいいなと思ったのをUnityで再現したりしていた。
カメラワーク
実装的には難しいことは全くやってなくてCinemachineを使ってカメラワークを複数作ってPriorityを変えてスイッチしてるだけ。指定したTransformの方を向く機能やスイッチ時にfrustumの補間する機能があるので、そういう部分をノードに露出した。VJ素材の動画を見たりしてカメラを動かすとよさそうに思ったので円、直線(水平)、直線(垂直)の3つのSplineを用意して、CinemachineSplineDollyで移動させるようにした。同じ円を使う場合でもオフセットを変えることでローアングルにしたり超ハイアングルで空から眺めたりとか色々バリエーションを出した。移動速度とオフセットをノードに露出することで、めちゃくちゃ高速に回転させたり、オフセットをアニメーションさせて変化を出せるようにした。
一応Cinemachineの拡張も作っていて、「カメラの速度に合わせてFOVを変える」やつと「一定秒ごとにTracking Targetの近くにランダムな点を取って、そこに向けて移動する」を組み合わせて移動するパーティクルを追尾させてFOVをギュンギュンするカメラが作れるようにした。FOVとかをノードに露出しても良さそう。
結構いいかもしれんが3D酔いしそう pic.twitter.com/U6ZeXgCkrV
— shivaduke (@shiva_duke28) 2025年2月20日
ポスプロ
ポスプロは(実装が追いついかなかったのもあるけど)URP公式のものだけ使った。
- Bloom
- DoF
- FilmGrain
- Motion Blur
- Chromatic Aberration
- カラグレ系
- Tonemapping
- White Balance
- Color Adjustment
- Shadows Midtone Highlights
カラグレは3つのシーンごとに個別に調整した。箱の機材が明るすぎた時に眩しいと困るのでExposure、Contrast、Saturationをグラフからも操作できるようにしたのだけど、副産物としてExposureをオーディオリアクティブにしてパチパチさせる、みたいなのができるようになって嬉しい。カラグレも全然わからんかったので↓の動画を見たらかなり参考になった。
Terrain
アンビエントと聞いていたのでTerrainで草原を作った。Unityの公式の動画と公式アセットがとてもよいのでおすすめ。
VJの練習
本番でノードを1から組み上げるつもりだったので、実質RTA的なものであり、練度を上げる必要があった。 Spotifyを流しながら実機ビルドでプレイするのはかなり初期から頻繁にやっていた。そもそもVJらしいVJをした経験がほぼなかったので、どういう音をどういう絵や動きで取るのかを掴む必要があったし、操作精度の向上やUXの改善にためにもとにかく触りまくる必要があった。
当日の機材はこんな感じ
今日のVJブースです pic.twitter.com/VhMSnD7s3t
— shivaduke (@shiva_duke28) 2025年3月22日
keep
- ゲームパッドはやはり身体性が高く、立ってコントローラを握れば家でも箱でも感覚があんまり変わらなかった
- 踊りながら触りやすくて最高
problem
- 普段は4Kモニタでプレイしていたが当日はiPadを見ながらやったので画面が狭くて戸惑った
- 常に本番に近い環境でやるか、あるいはプロジェクターの投影先を見るべきだった。
- MIDIコンのアサインもできるといいなとは思うが、本番でその場でアサインするのは大変そう
- グローバルな輝度調整とかがあるといいのかもしれない
- Mac Mini + iPad、機材が増えるしやはり不安定な部分があるのでポータブルモニター買った方が良い
所感
「ライブコーディングのコーディング部分をゲーム的インターフェースに置き換える」という問題が解決できたとは思わないけど、ちゃんと取り組んだ上で納得のいく形で着地させられたとは思う。開発についてはクラスターで3年働いてプロジェクト進行やソフトウェアエンジニアリングについて多くを学ばせてもらったことが良い形で出力できた。プロジェクト進行的な話はあまり書かなかったが、よくわからん中でリスクを減らしつつ勧められたと思う。Rectorの実装はcsファイルが144個で合計で22,000行くらいになった。そこそこ書いたと思うが、オーバーエンジニアリングにならなくてよかった。
映像制作については初心者マインドで色々触れてよかったが、もうちょっとネタを増やしたり細部を詰められるとよかったかな〜と思う。ポスプロも作りたい。
VJについては、タイムテーブルを勘違いしていて14:00 ~ 15:00だと思っていたら15:00直前に14:30 ~ 15:30ということが発覚して大慌てした。90分なんとかやりきれたけど、序盤のグラフを構築するフェーズをお客さんに見せられなかったのがとても残念。VJ行為自体はめっちゃ楽しかったのでまたやりたい。Anielさん最高でした、Big Love...
4ヶ月くらい開発していて、普通に大変だった。特に3月はフルコミットしていたので過集中状態が続いてしんどかった。MOROHAの革命とルックバックのサントラでどうにか保っていた。が、終わってみると大変清々しい気持ちでやってよかったな〜と思う。またやりたい。素晴らしい機会をくれた draw crew たちに改めて感謝です。ありがとうございました。