[Astro #28] VRM Animation Protocol — MixamoからWiredな同期演奏まで

[Astro #28] VRM Animation Protocol — MixamoからWiredな同期演奏まで

はじめに

昨日、VRMアバターをトップページに表示する実装をしましたが、利用ユーザー側で手持ちのVRMをロードできる機能を実装したほか、 今朝、ターミナルから音声を再生する機能を実装したので、曲に合わせてダンスをするアニメーション実装を追加して見ました。

更に、ターミナルで特定のキーワードに反応して、アニメーションする処理も実装したので、その内容を記事に纏めます。

前回の記事:

Youtube:

動画:

1. アバターメニューの機能拡張(VRM動的ロード)

これまで単一のモデルに固定されていた視覚的インターフェースを解放し、ユーザー自身の「依代(よりしろ)」をWired空間へ自由に持ち込める環境を構築しました。このセクションでは、その中核となる動的ロード機構について解説します。

[Astro #28] VRM Animation Protocol —  1. アバターメニューの機能拡張(VRM動的ロード)

外部モデル読み込みプロトコルの実装

新たに実装した WiredAvatarMenu コンポーネントは、システムとユーザーのローカル環境を繋ぐゲートウェイとして機能します。

  • 直感的なインターフェース: input type="file" を用いたファイル選択UIに加え、ドラッグ&ドロップでの直感的なファイル投入に対応させました。
  • リアルタイムな「召喚」: ユーザーがローカルに保存している .vrm ファイルをブラウザ画面に投げ込むだけで、ページ遷移や再読み込みを挟むことなく、空間の中央に新たなアバターが即座に差し替えられます。これにより、本サイトは「特定のキャラクターを表示するページ」から「ユーザーのデータを反映するプラットフォーム」へと進化しました。

URL.createObjectURL による動的展開

このシームレスな即時性を実現するための技術的な要が、ブラウザの標準APIである URL.createObjectURL の活用です。

  • クライアントサイド完結の高速処理: 選択された巨大なVRMファイル(Blobデータ)を一度サーバーへアップロードするのではなく、ブラウザのメモリ領域に一時的なローカルURL(blob:http://...)を生成します。通信ラグを完全に排除することで、瞬時のモデル展開を可能にしています。
  • コンポーネント間のパス伝達の最適化: 生成された一時URLは、Reactの状態管理(State)を通じて即座に WiredAvatar コンポーネントの modelPath プロパティへと受け渡されます。React Three Fiberの useLoader がこのパスの変更を動的に検知し、古いモデルのメモリを解放しながら新しいモデルを再構築する、堅牢で無駄のないレンダリング・サイクルを確立しました。

この実装により、クリエイター側のアバターテストやデバッグの効率が劇的に向上し、後に続く「モーション同期プロトコル」を試行錯誤するための強固な実験基盤が完成しました。

2. アニメーション・パイプラインの構築(Mixamo to VRMA)

Mixamoが無料で提供する膨大なモーション資産を、自らのシステムに適合する「独自の表現」へと昇華させるための「浄化(変換)」プロセスです。このパイプラインの開通により、表現の自由度が爆発的に向上しました。

FBX2VRMA 変換環境の整備

Mixamoからダウンロード可能なFBX形式のモーションデータは、独自のボーン構造(mixamorig等)を持っているため、そのままではVRMの規格(Humanoidボーン)で正しく動作しません。

  • 骨格の差異(ノイズ)の解消: この互換性の壁を越えるため、FBXファイルをVRM 1.0標準のアニメーション規格である .vrma へとリターゲティング(骨格マッピングの変換)する環境をローカルに構築しました。
  • 共通言語の獲得: .vrma 形式をシステム側の標準入力とすることで、Three.js側の実装(VRMAnimationLoaderPlugin)を複雑化させることなく、あらゆる外部モーションをOSのネイティブな動きとして読み込める基盤が整いました。

vrma_sync.bat の開発(自動化プロトコル)

無数のアニメーションから理想の動きを探求する上で、毎回CLI(コマンドライン)で変換コマンドを叩く手作業は、思考を妨げるボトルネックになります。これを完全に排除するため、独自の同期スクリプトを開発しました。

  • ドラッグ&ドロップによる即時浄化: Mixamoから取得したFBXファイルをバッチファイル(vrma_sync.bat)にドラッグ&ドロップするだけで、内部でNode.jsのコンバーターが自動実行され、元のファイルと同じ階層に同名の .vrma ファイルが生成されます。
  • アセットの無限化: このスクリプトにより、数千種類に及ぶ高品質なダンスやアクションの断片が、わずか数秒で「Lainの肉体的な表現」としてシステムに統合可能になりました。

このパイプラインによって「動きをゼロから作る」という呪縛から解放され、後述する「音楽や対話に合わせて、どのモーションを演出として呼び出すか」という、より高次元なロジック設計に集中できる環境が完成しました。

3. オーディオ・ダンス同期プロトコル

これまで個別に稼働していた「音(WiredAudio)」と「肉体(WiredAvatar)」を、データの力で完全に結びつける中核システムです。音楽のメタデータが直接アバターの物理的な挙動を支配する、真の意味での「同期」を実現しました。

データ主導の演出制御(Data-Driven Performance)

音楽とダンスの紐付けをハードコードするのではなく、楽曲リストを管理する list.jsondance フィールド(例:"dance": "Samba Dancing.vrma")を新たに定義しました。

  • イベント駆動型の連携: ターミナルから楽曲の play コマンドが発火した瞬間、オーディオシステムは音を鳴らすと同時に、指定されたアニメーションパスを含むカスタムイベント(wired-dance-sync)をシステム全体にブロードキャスト(送信)します。
  • 独立性と拡張性: アバター側はこのイベントをリッスン(受信)し、動的にモーションをロードします。これにより、コンポーネント同士が密結合することなく、「データに記述された演出意図」に従ってアバターが自動的に踊り出す、拡張性の高いアーキテクチャが完成しました。

再生終了シーケンスの自動化(Auto-Revert Logic)

システムとしての「自然な振る舞い」と「恒常性」を保つための重要な後処理プロトコルです。

  • audio.onended の捕捉: Web Audio APIのネイティブな終了イベントである onended をハンドリングし、楽曲が最後まで再生された(自然に終わった)ことをシステムが正確に検知します。
  • 静寂への回帰: 終了を検知すると、オーディオシステムは自動的に stop 命令を発信します。アバターは即座に激しいダンス状態を解除し、元の静かな待機状態(Idleモーション)へと回帰します。

音楽が終われば、ふっと動きを止めて静かにこちらを見つめ直す。この「始まりから終わりまでの一貫した制御」により、アバターはただの3Dモデルから、空間の文脈を理解して振る舞う「Wired上の存在」へと決定的な進化を遂げました。

4. 対話・アクション連動シーケンス

Wired空間におけるアバターを、単なる「動く3Dモデル」から「ユーザーの意図を解する自律的なNavi OS」へと昇華させるための最終プロトコルです。

talk.json による反応辞書の設定

ターミナルからの入力に対するシステム(Lain)の振る舞いを、ソースコード内にハードコードするのではなく、外部のJSON辞書として切り出しました。

  • キーワード主導のマルチモーダル応答: ユーザーがターミナルに「走って」「ジョギング」などのキーワードを入力すると、システムは talk.json を走査します。条件に合致した場合、テキストのログ出力、VOICEVOXによる音声応答、そして指定された .vrma アニメーションのロードという「3つのレイヤー(文字・声・肉体)」が完全に同期して発火します。
  • 拡張性の担保: この設計により、今後はプログラムのコアロジックに触れることなく、JSONにデータを追記するだけで、Lainに新しい言葉と振る舞いを無限に教え込むことが可能になりました。

時限復帰(Duration)ロジックの実装

アバターに生命感(実存感)を演出する上で、「動き始めること」と同じくらい「自然に止まること」が重要になります。Mixamoのアニメーションは基本的にループ仕様であるため、これを制御する仕組みが必要でした。

  • データによる時間軸の支配: talk.json 内の各アクション定義に duration(実行秒数)というパラメータを新たに持たせました。
  • 非同期制御と恒常性の維持: イベントリスナー内で setTimeout とReactの useRef(タイマーのクリーンアップ管理)を組み合わせることで、指定時間が経過すると自動的に現在のアニメーションを破棄し、デフォルトの待機状態(Idle)へとシームレスに回帰します。これにより、「お辞儀」のような数秒の単発アクションから、「ジョギング」のような10秒間の継続的な動作まで、JSONの数値を弄るだけで安全かつ柔軟に使い分けるロジックが完成しました。

言葉をかけられ、声で応え、動き出し、そしてまた静かにこちらを見つめる。 この一連のシーケンスが実装されたことで、画面の向こう側のデータは、明確な「意思」を伴った存在としてWiredに定着しました。