はじめに
昨日、Unity用にVROID STUIOで作成したオリジナルアバターを、R3Fでも使おうと実装を試みたのですが、表示はできても、アニメーション処理がうまくいかず、思いのほかハマったので、その備忘録メモです。
ネット上にも殆ど実装例がないようで、実装前の段階でも嫌な予感はしていて、Nextjsとreactの旧バージョンにダウンロードして安全な実装を試みたにもかかわらず、うまくいかなかったです。(表示するだけなら簡単)
厳密には、アニメーションファイルとVRMのボーンデータの名前の一致?が問題だったようで、その辺を修正するコードも書いたのですがうまくいかず、またバージョンの差異で仕様が変わるなど、中々厄介な事も分かりました。
前回の記事:
[Next.js #07] R3FでglTF/VRMを扱うための“橋渡し”基礎まとめ
バニラThree.jsではglTFもVRMも実装済みの開発者向けに、React Three Fiberで同じ資産を扱うための“橋渡し部分”だけをまとめる。useGLTF・Suspense・再レンダー・VRMの内部構造がReactと衝突する問題点など、R3F特有 …
https://humanxai.info/posts/nextjs-07-r3f-gltf-vrm-bridge/プロローグ
この記事では、Next.js(App Router)+ React Three Fiber(R3F)で VRM / BVH / PMX を扱う際に必ず踏む互換性問題を整理する。
昨日の実装で発生した現象は、すべて “仕様とバージョンの不一致” に起因していた。
発生した問題の一覧:
three-vrmの 0.x / 1.x / 2.x が混在し、どの VRM がどの three.js に対応しているか不明瞭- R3F の
useLoaderが VRM 0.x / 1.0 を正しく処理できず、読み込み段階で破綻 BVHLoaderが three.js のバージョン依存で正常に動作しない- PMX(MMDモデル)が VRM / glTF とは根本的に異なる体系で統合できない
- その結果、正しい入口(正規ルート)を踏まずに実装を進めた影響で、全工程が破壊
- 1日かけて構築した VRM / BVH の実装が完全ロスト
本記事ではこれらの原因を分解し、 Next.js + R3F でキャラクターモデルを扱う際の正しい実装ルート を明確にする。
1. VRM / BVH / PMX を R3F に入れるなら「正しい入口」が必要
Next.js(App Router)+ React Three Fiber(R3F)で VRM / BVH / PMX を扱う場合、最初に選ぶライブラリと three.js バージョンが実装成否を左右する。
以下は、昨日の実装で明らかになった互換性の差分である。
-
VRM 0.x / 1.x / 2.x は互換が異なる モーション、スケルトン、GLTF Extension 仕様が大きく違う。
-
three-vrm の対応バージョンは three.js の特定バージョンで固定 例:three-vrm 1.0 は three.js v0.149 での動作を前提としている。
-
R3F の
useLoaderは VRM 0.x を想定していない glTF Extension が古く、パイプラインで正しく処理されない。 -
PMX(MMD)は VRM/glTF とは別体系であり統合不可 ボーン構造・剛体・ジョイント・物理処理が完全に異なる。
-
BVH は R3F が自動処理しないため、AnimationMixer を手動で構築する必要がある ローダー結果 Skeleton Mixer useFrame の更新を自前で行う。
総括: 実装が破綻した理由は、 「互換性の異なるモデル形式を同一レイヤーで扱おうとしたこと」。 本記事では、その“正しい入口”を明確にする。
2. VRM の地獄:three-vrm の 0.x / 1.x / 2.x の違い
VRM は 0.x / 1.x / 2.x で仕様が大きく異なる。 three-vrm も three.js も、それぞれのバージョンに固定した互換関係を持つため、 R3F で扱う場合は「どの VRM を使うか」を最初に決定する必要がある。
以下は各バージョンの挙動と、昨日の検証で分かったポイント。
VRM 0.x 系
- three.js の 古いバージョン に依存
- R3F の
useLoaderが想定していない形式 - glTF Extension が旧仕様のまま
- 互換パッチなしではロード時にエラーが発生する
rotateVRM()が壊れる主原因(骨格構造が現行VRMと異なる)
総評: R3F でそのまま扱うのは非推奨。 Next.js + R3F 環境では実質 “対象外”。
VRM 1.x 系(昨日使用したバージョン)
- three.js v0.149 が想定動作バージョン
- glTF Extension が 0.x より整理されて一定の安定性
- ただし “移行期” の仕様で、関数・API が揺れている
VRMUtils.rotateVRM周りの挙動がまだ不安定- R3F の
useLoaderと組み合わせても動作は可能(条件付き)
総評: 現時点では最も扱いやすいが、 “three.js v0.149 とセットで使用する” 前提が必須。
VRM 2.x 系
- glTF Extension が大幅に刷新されている
- ボーン情報・BlendShape が整理され、将来性は最も高い
- ただし three-vrm 2.x はまだ開発中要素が多い
- R3F は 2.x にまだ正式対応していない
- API 変更が急で、開発者向けの情報が少ない
総評: 将来的には最適解だが、 Next.js + R3F の本番運用では様子見が必要。
結論:この記事で “どれを使うべきか” を明示する
- 安定運用:VRM 1.x + three.js v0.149
- 旧VRMを使う案件:0.x は R3F 非推奨(Vanilla Three.js で運用)
- 実験用途:VRM 2.x(R3Fとの併用は非推奨)
R3F で扱う際の“正しい入り口”は VRM 1.x 前提で環境を整えること。
3. BVHLoader が動かない理由:three.js のバージョン依存
BVH(Biovision Hierarchy)は、古いモーションデータ形式であり、 three.js のバージョンによって内部処理が大きく変化する。 そのため、Next.js + R3F 環境では“そのままロードして動く”ことはほぼない。
以下は、検証によって判明した要点である。
three.js r145〜r152 の間で BVHLoader の挙動が変わる
- skeleton 構造の生成処理がバージョンごとに異なる
- TransformHierarchy 周りの実装差分が破綻の原因
AnimationClipの扱いも旧仕様から移行期にある
このため、 “特定バージョン以外では動かない BVH が存在する” という状況が発生する。
AnimationMixer が glTF と BVH を統合できない
glTF(特にVRM)と BVH のスケルトン構造は互換性がない。
- glTF humanoid に最適化された構造
- BVH 任意階層の骨ツリー
よって、 VRM に BVH を直適用することは不可能。
BVH を使う場合は、 BVH のスケルトン専用の AnimationMixer を別に作成する 必要がある。
R3F はアニメーション制御を自動化しない
R3F は three.js の描画ループを React に統合するだけであり、 AnimationMixer の update() は自動実行されない。
そのため、BVH を R3F で動かすには次の工程が必須になる。
- Loader で読み込む
- Skeleton / Bone 構造を取り出す
- AnimationMixer を生成
mixer.update(delta)を useFrame 内で手動更新
Vanilla three.js で処理してから R3F に渡すのが正解
以下は BVHLoader を単体で動かす際の基本構造。
const loader = new BVHLoader();
const result = loader.parse(bvhText);
const clip = result.clip;
const skeletonHelper = new SkeletonHelper(result.skeleton.bones[0]);
scene.add(skeletonHelper);
const mixer = new AnimationMixer(result.skeleton.bones[0]);
mixer.clipAction(clip).play();
R3F では この構造を useFrame に取り込む必要がある。
結論:R3F に BVH をそのまま入れるのは非現実的
- R3F の Loader 任せでは動かない
- AnimationMixer を手動で構築・更新する必要がある
- glTF(VRM) と BVH は構造が違いすぎて直接結合不可
- three.js のバージョン差分を把握しておくことが必須
4. PMX(MMD) の衝撃:VRM/gltf と完全に別文化
PMX(MikuMikuDance モデル)は、VRM/glTF の標準仕様とは全く異なる設計思想で構築されている。 そのため、Next.js + R3F のワークフローに直接統合することは現実的ではない。
以下は、PMX が他の 3D モデル形式と“根本的に違う”理由である。
1. 物理処理(剛体・ジョイント)が完全独自仕様
PMX は以下のような独自の物理体系を採用しており、glTF/VRM とは互換がない。
- ボーン IK(逆運動学)とは別に PMX固有の仕組み
- 剛体(Rigid Body)設定
- ジョイント(Joint)設定
- MMD 専用の物理エンジンパラメータ
three.js の MMDAnimationHelper がこれを内部で処理しているが、 構造全体が VRM/gltf の HumanBone とは全く異なる。
2. MMDAnimationHelper は R3F のライフサイクルと非互換
- 自前の時間管理
- 自前の update loop
- R3F の
useFrameと統合されていない - State オブジェクトへのアタッチ前提で作られていない
つまり、 MMDAnimationHelper は three.js 単体で使うために設計されており、 React の状態管理に乗らない。
R3F に入れ込むと挙動が崩れる原因はこれ。
3. モデルの座標系・比率・ボーン構造が glTF/VRM と共通化されていない
glTF / VRM は以下のポリシーで統一されている:
- Y-up
- HumanBone(標準化された人型ボーン)
- glTF Extension による一元管理
- Scale / Rotation の標準的扱い
一方 PMX は:
- 軸向きが違う
- 回転の基準が違う
- ボーンの階層構造や数が非標準
- モデルごとに仕様がバラつく
- 単位もファイルによって異なる
→ そのため PMX と VRM を同じ Skeleton 系統で扱うことは不可。
4. 結論:VRM と PMX を同一レイヤーで扱うのは設計上不可能
よくある要求:
「VRM の隣に PMX を置いて、モデルだけ切り替えたい」
これは原則不可能で、
- パイプラインが違う
- 物理エンジンが違う
- ボーン体系が違う
- 座標系が違う
- 更新処理の仕組みも違う
→ 完全に“別文化”として扱う必要がある。
正しい扱い方
- PMX three.js + MMDAnimationHelper 専用パイプライン(R3Fに寄せない)
- VRM three-vrm + glTF pipeline(R3F ベースで構築)
この2つは統合せず、 別 Scene / 別 Canvas / 別パイプライン として管理するのが正しい。
5. 「正しい入口」— 結局どう構築するのが正解か
VRM / BVH / PMX は、それぞれ内部仕様・ボーン体系・ローダーの仕組みが異なるため、 Next.js(App Router)+ R3F で同一のパイプラインに統合することはできない。
実装を安定させるためには、以下の “正しい入口” を選択する必要がある。
VRM(1.x を前提に構築)
-
VRM 1.x + three.js v0.149 が最も安定 three-vrm の想定バージョンと合致する。
-
R3F 内では useFrame で AnimationMixer を手動管理 VRM のアニメーション制御は自動化されないため、
mixer.update(delta)を R3F の render loop に統合する。 -
WebXR(VRモード)は別 Canvas で運用する R3F の XR サポートは VRM の挙動と混線しやすいため、 “通常 Canvas” と “XR Canvas” を分けるのが安全。
BVH(汎用モーションデータ)
-
Raw loader(BVHLoader)で読み込み、AnimationMixer を自前で構築 glTF/VRM 用のシステムとは互換性がないため、
result.skeletonを使って専用の Mixer を作成する。 -
R3F に処理を任せるのではなく、自前 update が必須
useFrame(() => mixer.update(delta))の形で明示的に更新する。
BVH は three.js のバージョン差分の影響が大きいため、 Vanilla three.js 的な扱いが前提 となる。
PMX(MMD モデル)
-
three.js の MMDAnimationHelper をそのまま使用する 物理処理・剛体計算・IK 制御が Helper 内に完結しているため、 R3F の構造に合わせると挙動が破綻する。
-
R3F に寄せない(コンポーネント化しない) R3F の state や useFrame のライフサイクルとは非互換。
-
three.js の“別シーン”として扱うのが現実解 Canvas を分ける、あるいは別の Scene として並行管理する方式が安定。
まとめ
- VRM three-vrm 1.x + R3F(AnimationMixer は手動)
- BVH Vanilla three.js 的に手動管理(R3Fへ自作統合)
- PMX three.js 専用パイプライン(R3F には載せない)
3つの形式を“同じ入り口から扱う”のではなく、 “形式ごとに最適化された入口を使い分ける” のが正しい構築方法。
6. 昨日の “1日消えたログ” を構造化して書く
この章では、実際の検証過程で発生した問題を時系列ではなく 「技術的な原因ごとに分類」して整理する。 読者が同じ誤りを避けるための記録としてまとめる。
起きた問題(分類整理)
1. VRM ロード関連の問題
- VRM 1.0 が R3F + useLoader で正常にロードされない three-vrm 1.x と R3F のローダーの設計思想が一致していない。
VRMUtils.rotateVRMの挙動が壊れる VRM 1.x / 0.x の仕様差分が原因。 骨格構造の差異が想定外の回転を発生させる。
2. アニメーション関連の問題(BVH)
- BVH が読み込めない/動かない three.js のバージョン依存により Loader が非互換。
- AnimationMixer が glTF(VRM)と BVH を統合できない スケルトン構造が根本的に違い、共有不可。
3. three.js のバージョン差分による障害
- three.js のバージョンだけでローダーやアニメーションが破綻 r145〜r152 の間で BVHLoader/Skeleton の内部実装が変化。 three-vrm の対応バージョンと一致していないと動作不可。
4. R3F に任せると破綻する領域
- R3F の
useLoaderに VRM 1.x / BVH を任せると事故が発生 VRM/BVH はロジック層が重いため、R3F の抽象化に乗らない。 アニメーション制御は手動(AnimationMixer)で行う必要がある。
5. モデル仕様の根本差異(PMX)
- PMX は VRM/glTF と文化が違いすぎて統合不能 物理・IK・剛体・ボーン・座標系すべて別体系。 three.js の MMDAnimationHelper 専用パイプラインが必要。
最後に気づいた本質
「正しい入口じゃない方向から実装を始めたため、互換性の壁に同時に衝突した。」
異なるモデル形式(VRM / BVH / PMX)を “同じ R3F の入口から扱おうとした誤り” が破綻の原因であり、 形式ごとに最適なパイプラインを選択する必要がある。
この記事の中心となるメッセージはここにある。
7. まとめ:R3Fで “キャラ資産” を扱うには道順がある
Next.js + React Three Fiber で VRM / BVH / PMX といったキャラクターモデル資産を扱う場合、 すべてを同じパイプラインで処理することはできない。
形式ごとに異なる仕様と制約が存在するため、 最初に「正しい入口」を選ぶことが、実装の安定性を大きく左右する。
VRM — three-vrm のバージョンを固定して扱う
- 推奨:VRM 1.x + three.js v0.149
- R3F の
useFrameで AnimationMixer を手動管理 - WebXR は別 Canvas に分離すると安定
BVH — Vanilla three.js + AnimationMixer が基本
- BVHLoader は三.js のバージョン差分の影響が大きい
- glTF(VRM) との統合は不可
- Mixer の
update()は R3F のuseFrameに手動で統合する
PMX — three.js 専用パイプラインとして独立運用
- MMDAnimationHelper が R3F とライフサイクル非互換
- 物理処理・IK・座標系が VRM/gltf と完全に異なる
- 専用 Scene / 専用 Canvas として扱うのが最も安定
結論
React Three Fiber は three.js の UI ラッパーであり、 全形式のキャラクターモデルを一元管理するための統合レイヤーではない。
- VRM
- BVH
- PMX
それぞれのモデル形式は別々の文化・仕様・制約を持つため、 形式ごとに最適な入口(パイプライン)を選択することが R3F でのキャラ実装の最重要ポイントである。
💬 コメント