はじめに
今日は、UIの実装か、バトルモードの作成を予定していましたが、「js\loadModel.js」のコード内に、建造物やキャラクターのモデルロード、 更に、animation分岐、MMD / GLTF モデルのロード、衝突判定の作成と、カオスに近い状態だったので、それを重い腰を上げてリファクタ。
その結果、なにも実装するゆとりはなく、力尽きました…。
ただ、そのお陰で責務分けと、今後の拡張のゆとりはできましたが…。
このままでは、記事ネタがないので、毎日続けてきた記事更新も止まるかと思いしたが、VMDのアニメーション実装以外に、VPDのポーズも作れる事を思い出したので、モデルロードのリファクタを終わらせたのもあり、そこに新たなコードを追加して実装してみたので、その内容を記事にしてみます。
Three.jsでPMX / VMDを扱う方法については、過去記事で紹介してるのでそちらの方を参照ください。
[JavaScript] Three.jsでPMX / VMDを読み込んでアニメーションを再生する
Three.jsでPMX / VMDを扱う方法を整理。MMDLoaderによるモデル読み込み、VMDモーションの適用、MMDAnimationHelperでの更新処理、VMDなしの場合の扱い、physics設定の注意点を解説する。
https://humanxai.info/posts/javascript-threejs-pmx-vmd-mmdloader-vmd-animation/PMXやVMDには、製作者の著作権があるので、気軽にサーバにアップロードしたりはできませんのでご注意ください。
アニメキャラの場合は、2次創作になるので、モデルの制作自体もややグレーです。
日本は、コミケを中心とした二次創作文化が許容されている?ので、見逃されてる所はありますが、本来はグレーですね…。
うちのサイトでは、ローカル内で実験と勉強を兼ねて配布されているモデルデータで遊んでいますが、もしサーバに公開する場合は、 自作するか、購入するか、著作権フリー、商用可能なフリーモデルに全て差し替える予定です。

VPD配布元(制作者:鬼芽さん)
紙芝居用ポーズその2 - BowlRoll
鬼芽の作品です。
https://bowlroll.net/file/344248モデル配布元(制作者:Moggさん)
「もぐ式ビーバー」 / Mogg さんの作品 - ニコニ立体
v1.10モーフ追加など
https://3d.nicovideo.jp/works/td296701. 前提:前回記事との関係
前回の記事では、Three.js を使って PMX + VMD を読み込み、アニメーションを再生する方法を整理した。
- PMX:モデルデータ
- VMD:モーション(時間軸を持つアニメーション)
- MMDLoader + MMDAnimationHelper による基本構成
この構成で「MMDモデルをThree.js上で動かす」こと自体は、すでに実用レベルまで到達している。
今回の記事は、その続編にあたる。
扱うのは VPD(Vocaloid Pose Data)。 VPDは、VMDのようなアニメーションではなく、1フレーム分のポーズ情報を表すデータだ。
ここで重要なのは、次の点。
- VMD:時間を持つ「動き」
- VPD:瞬間的な「姿勢」
- VPDはVMDの代替ではない
用途がまったく違う。
なぜVPDを扱うのか
VPDは次のような用途で使われることが多い。
- キャラクターの初期姿勢
- UIやイベントでの一瞬のポーズ切り替え
- VRやインタラクション用途での即時反映
- 表情や腕の角度などの微調整
一見すると、
「VMDより軽そう」 「アニメーションじゃないから簡単そう」
そう思われがちだ。
しかし、実際は逆だった
実際にThree.jsでVPDを扱おうとすると、かなりの人がここで詰まる。
- 読み込めているのに動かない
- 左手だけ動くが、全身ポーズが崩れない
- 派手なVPD(転倒・ジャンプ)が効かない
- センター移動が無視される
- IKが反映されない
つまり、
VPDは軽いどころか、罠が非常に多い。
フォーマット自体は単純に見えるが、 MMD特有のボーン階層・センター構造・IKを理解していないと、 「読めているのに動かない」状態に陥りやすい。
この記事でやること
この記事では、
- VPDが「なぜ動かないように見えるのか」
- 自前実装でどこまで対応できるのか
- three.js公式ルート(MMDLoader + MMDAnimationHelper)を使うと何が違うのか
- VMDとVPDをどう使い分けるのが現実的か
これらを、実際に詰まった実装過程をベースに整理する。
前回の記事が 「Three.jsでPMX / VMDをどう動かすか」 だとしたら、
今回は 「Three.jsでVPDをどう扱うのが正解か」 をはっきりさせるための記事になる。
VPDが動かずに悩んでいる人ほど、 この先の内容はそのまま役に立つはずだ。
2. VPDとは何か(短く・実用目線)
VPD(Vocaloid Pose Data)は、MMDで使われる 1フレーム分の姿勢データ だ。
VMDが「時間を持つモーション」なのに対して、VPDは次のような性質を持つ。
- 1フレームのみ
- 補間なし
- 即時に姿勢を切り替える用途向け
Three.jsやWebアプリケーションで使う場合、 **「今この瞬間の姿勢をどうするか」**を決めるためのデータと考えると分かりやすい。
VPDに含まれる情報
VPDには、各ボーンごとに次の2種類の情報が含まれている。
- 回転(Quaternion)
- 位置(Translation)
この「位置」が非常に重要で、ここを無視すると多くのVPDが正しく再現できない。
たとえば、
- 腕を上げるポーズ → 回転だけで成立する
- 前に倒れる、しゃがむ、ジャンプする → 位置移動が必須
「派手なVPDが動かない」原因の多くは、 回転だけを適用して、位置を捨てていることにある。
センター構造がすべてを決める
VPDを扱う上で、特に重要なのが次のボーンだ。
- 全ての親
- センター
- グルーブ
これらは、単なるボーンではなく、 モデル全体の重心・移動・姿勢の基準を担っている。
MMDでは、
全ての親
└ センター
└ グルーブ
└ 各ボーン
という階層構造を前提に、 位置移動と回転が積み上げられて最終姿勢が決まる。
この構造を考慮せずに、
- 各ボーンに position / quaternion を直接セットする
だけでは、 VPDの意図した姿勢は再現できない。
実装視点での要点
VPDを扱う際に最低限押さえるべきポイントは次の3つだけだ。
- VPDは「軽いデータ」ではない
- 回転だけでなく 位置も含めて姿勢
- センター系ボーンとIKを無視すると破綻する
フォーマットの詳細を暗記する必要はない。 実装で重要なのは、どこが破綻ポイントになるかを知っているかどうかだ。
次の章では、実際にやりがちな失敗パターンを整理する。
3. ありがちな失敗パターン(この記事の核)
ここは理論よりも、実際にハマった順番で書いたほうが伝わる。 VPDが動かないとき、ほぼ全員が同じ地雷を踏む。
fetch().text() で文字化け(Shift-JIS)
最初に当たる壁がこれ。
VPDは多くの場合 Shift-JIS(CP932) で保存されている。 それを何も考えずに、
fetch(vpdPath).then(r => r.text())
とすると、ボーン名がすべて文字化けする。
結果として、
- VPDは読み込めている
- パースも通っている
- でもボーン名が一致せず、1本も適用されない
という状態になる。
見た目は「完全に何も起きない」ので、 ロジックが間違っているように見えるが、実際は 文字コード問題。
ここで数時間溶かす人はかなり多い。
quaternion だけ反映して「動かない」
次にやりがちなのがこれ。
VPDには、
- 回転(quaternion)
- 位置(translation)
の両方が入っているのに、 回転だけを適用して満足してしまうケース。
bone.quaternion.copy(q);
これだけだと、
- 腕を上げる
- 首を傾ける
といった 軽いポーズ は動く。
しかし、
- 前に倒れる
- しゃがむ
- 重心が移動するポーズ
は、ほぼ確実に「動いていないように見える」。
原因は単純で、 位置情報を全部捨てているから。
センター移動を無視して「派手なVPDが効かない」
「前にこける.vpd」 「ジャンプ.vpd」
こういった派手なVPDを試しても、まったく変化がない場合がある。
この原因のほとんどは、
- センター
- グルーブ
- 全ての親
これらの 位置移動が正しく反映されていないこと。
MMDでは、 各ボーンの姿勢は単独では決まらず、 センター系ボーンの移動が 前提条件 になっている。
センターを無視した実装では、 どれだけ派手なVPDでも「立ったまま」に見える。
IKを考慮せず脚が成立しない
さらに一段深い罠が IK。
- 脚が地面を突き抜ける
- 片脚立ちが成立しない
- 重心移動すると姿勢が崩れる
これは、VPDの問題ではなく、 IKが再計算されていないことが原因。
自前でボーンに値を流し込んだだけでは、 MMD特有のIKチェーンは更新されない。
結果として、
- 腕は動く
- 体幹は動く
- 脚だけおかしい
という、非常に混乱しやすい状態になる。
自作パースで沼る
最後に行き着くのがこの状態。
- 正規表現でVPDをパース
- ボーン名を突き合わせ
- quaternionとpositionを反映
- それでも何かがおかしい
ここまで来ると、 「VPDって無理なんじゃないか?」 という気分になる。
実際には無理ではないが、 VPDをフル互換で自作実装するコストはかなり高い。
センター構造、座標系、IK、左右反転…… MMDLoaderが内部でやっていることを、 すべて自前で再現するのは現実的ではない。
この章で挙げた失敗は、どれも特殊な話ではない。 VPDをThree.jsで扱おうとした人が、ほぼ必ず一度は通る道だ。
次の章では、 自作実装でどこまでやるべきか、 そして どこからthree.js公式に任せるべきか を整理する。
4. 自作パースはどこまでやれるか
VPDを扱うにあたって、まず決めておくべきなのは 「どこまでを自分でやるか」 だ。
結論から言うと、 VPDをすべて自前で再現しようとするのはコスパが悪い。
自作で十分に対応できる範囲
自作パースでも、問題なく扱えるケースは確かに存在する。
- 腕を上げる / 下げる
- 首を傾ける
- 視線や表情の調整
- 軽い姿勢補正
これらは共通して、
- 回転が主
- 位置移動がほぼ不要
- IKに依存しない
という特徴を持つ。
UI操作やイベント演出で 「今この瞬間だけ姿勢を変えたい」 といった用途であれば、 ボーンに直接 quaternion を流し込む実装でも十分成立する。
破綻しやすい領域
一方で、次のようなVPDは自作実装では一気に難易度が上がる。
- 転倒ポーズ
- ジャンプ
- しゃがみ込み
- 片脚立ち
- 重心を大きく移動するポーズ
これらに共通するのは、
- センター / グルーブの位置移動が必須
- IKの再計算が前提
- ボーン階層の積み上げが重要
という点だ。
見た目は「1フレームのポーズ」でも、 内部では アニメーションに近い複雑な計算 が行われている。
ここをすべて自前で再現しようとすると、 実装量に対して得られるリターンが見合わなくなる。
現実的な判断基準
実装視点での割り切りは、次のようになる。
- 軽い補正・UI用ポーズ → 自作でOK
- 派手なポーズ・重心移動 → 公式に任せるべき
この線引きを最初に決めておくだけで、 VPD実装で無駄に時間を溶かさずに済む。
「全部自前」はやらなくていい
VPDはフォーマットが単純に見える分、 「全部自分でできそう」と錯覚しやすい。
しかし実際には、
- MMD特有の階層構造
- IK前提の姿勢計算
- センター移動の合成
これらを正しく再現する必要がある。
VPDを全部自前でやるのはコスパが悪い。
Three.jsを使っている以上、 使えるものは素直に使う、という判断が一番安定する。
次の章では、 three.js公式ルートでVPDを適用する方法を整理する。
5. 正解ルート:three.js公式に任せる
ここがこの記事の技術的な山場になる。
VPDを MMDとして正しく 扱いたい場合、 結論はシンプルで、three.js が用意している公式ルートに任せるのが最も安定する。
loader.loadVPD(vpdPath, false, (vpd) => {
mmdHelper.pose(mesh, vpd);
mesh.updateMatrixWorld(true);
});
この数行に、VPD実装で詰まりやすい要素がすべて含まれている。
CCDIKSolver を import する理由
import { CCDIKSolver } from 'three/addons/animation/CCDIKSolver.js';
これを import していないと、
mmdHelper.pose() 内部で IK の再計算が正しく行われない。
VPDは「姿勢データ」だが、
- 脚
- 足首
- 膝
といったボーンは IK前提で姿勢が成立する。
IKが無効な状態では、
- 転倒ポーズ
- 片脚立ち
- 重心移動
といったVPDが破綻しやすい。
サンプルコードに CCDIKSolver が含まれているのは、 VPDがIK込みで解釈される前提だからだ。
mmdHelper.pose() が内部でやっていること
mmdHelper.pose(mesh, vpd) は、
単にボーンに値を流し込んでいるわけではない。
内部では概ね次の処理が行われている。
- VPDのパース結果をボーン構造に対応付け
- センター / グルーブ / 全ての親の位置移動を合成
- ボーン階層を考慮した姿勢計算
- IKの再計算
- 最終姿勢の確定
これをすべて自作でやろうとすると、 VPDというより MMDの再実装 に近くなる。
pose() を使うことで、
MMDLoaderが前提としているロジックをそのまま利用できる。
resolve のタイミングが重要な理由
VPDの読み込みは非同期だ。
そのため、ファクトリ関数や初期化処理では VPD適用が完了してから次の処理に進む必要がある。
if (vpdPath) {
loader.loadVPD(vpdPath, false, (vpd) => {
mmdHelper.pose(mesh, vpd);
mesh.updateMatrixWorld(true);
resolve({ mesh, helper: mmdHelper });
});
return;
}
このように、
- VPDロード完了
- ポーズ適用完了
を確認したあとで resolve() することで、
- シーン追加
- コライダー生成
- 後続の姿勢処理
が 確定したポーズを前提に動く。
ここを雑にすると、
- 一瞬だけ動く
- 次のフレームで戻る
- 環境依存の挙動になる
といった不安定な状態になりやすい。
なぜ「公式に任せる」のが正解なのか
VPDはフォーマットこそ単純だが、 正しく扱うには MMD 固有の前提が多すぎる。
- センター構造
- IK
- 階層合成
これらをすべて理解した上で実装するよりも、 three.js がすでに用意している仕組みを使うほうが合理的だ。
次の章では、 VMDとVPDをどう使い分けるのが現実的かを整理する。
6. VMD / VPD の使い分け指針
VPDを扱えるようになると、次に迷うのが 「VMDとどう使い分けるべきか」 という点だ。
結論は明確で、 どちらか一方を選ぶものではない。両方使うのが前提 になる。
VMDが向いているケース
VMDは、時間軸を持つモーションデータだ。
- 歩く
- 走る
- 攻撃する
- ダンスする
といった 連続した動き や、 演出としてのアニメーションにはVMDが最適だ。
Three.js上でも、
- AnimationMixer
- MMDAnimationHelper
と組み合わせることで、 安定して「動き」を再生できる。
VPDが向いているケース
一方でVPDは、 その瞬間の姿勢を決めるためのデータ に向いている。
代表的な用途は次のとおり。
- キャラクターの初期ポーズ
- メニュー表示中の立ち姿
- UI操作に応じた姿勢切替
- VR環境での即時反映
- イベント発生時の一瞬のポーズ変更
VMDのように時間を持たないため、
- 即座に切り替えられる
- フレーム管理が不要
- 状態遷移がシンプル
という利点がある。
役割を分けると実装が楽になる
実装視点で考えると、役割分担はかなりはっきりする。
- VMD → 「動き」を担当
- VPD → 「状態」を担当
たとえば、
- 通常はVMDで歩行モーション
- メニューを開いた瞬間にVPDで立ち姿に切り替え
- VR操作中はVPDで姿勢を即時反映
といった使い方が自然だ。
VPDはVMDを置き換えない
ここを勘違いすると、設計が歪む。
- VPDでアニメーションを作ろうとする
- VMDを使わずにすべての姿勢をVPDで管理する
こうした設計は、 実装コストが跳ね上がる割にメリットが少ない。
VPDは 「軽いから使う」ものではなく、 「用途が違うから使う」もの だ。
VMDとVPDを役割で分けて考えることで、 Three.js上でのMMD実装は一気に扱いやすくなる。
次は最後に、 この記事全体の実装指針を整理する。
7. まとめではなく「実装指針」
ここでは話を締めるための総括ではなく、 これから実装するときの判断基準だけを残しておく。
VPDは軽量なデータではない
VPDは1フレーム分の姿勢データだが、 扱いが簡単という意味で「軽量」ではない。
- 回転と位置の両方を持つ
- センター構造が前提
- IK込みで姿勢が成立する
これらを知らずに触ると、 「動かない」「壊れている」と感じやすい。
VPDは 見た目よりずっとMMD寄りのデータだ。
three.js公式ルートは安定している
MMDLoader と MMDAnimationHelper は、 VPDを「正しくMMDとして扱う」前提で作られている。
- loadVPD による正規パース
- pose() による階層合成
- CCDIKSolver を含めたIK再計算
これらをまとめて任せられるのは大きい。
自前実装で詰まりやすい部分ほど、 公式ルートはすでに解決している。
自作エンジンと共存できる
公式に任せる、という判断は 「自作を捨てる」ことではない。
- UIやイベント用の軽い補正は自作
- 派手なポーズや重心移動は公式
- VMDとVPDを役割で切り替える
こうした分担をすれば、 自作エンジンの構造を壊さずにVPDを組み込める。
実装の判断軸を持つ
VPDを扱う上で大事なのは、 「全部自分でやるか」ではなく、
- どこまで自分でやるか
- どこから任せるか
を決めておくことだ。
それができていれば、 VPDは決して難解な存在ではない。
Three.jsでPMX / VMDを扱えているなら、 VPDも同じ延長線上にある。
実際に動かしてみると、 思っていたほど遠い話ではなかったはずだ。
💬 コメント