[JavaScript] Three.jsでPMXにVPD(ポーズ)を適用する方法

はじめに

今日は、UIの実装か、バトルモードの作成を予定していましたが、「js\loadModel.js」のコード内に、建造物やキャラクターのモデルロード、 更に、animation分岐、MMD / GLTF モデルのロード、衝突判定の作成と、カオスに近い状態だったので、それを重い腰を上げてリファクタ。

その結果、なにも実装するゆとりはなく、力尽きました…。

ただ、そのお陰で責務分けと、今後の拡張のゆとりはできましたが…。

このままでは、記事ネタがないので、毎日続けてきた記事更新も止まるかと思いしたが、VMDのアニメーション実装以外に、VPDのポーズも作れる事を思い出したので、モデルロードのリファクタを終わらせたのもあり、そこに新たなコードを追加して実装してみたので、その内容を記事にしてみます。

Three.jsでPMX / VMDを扱う方法については、過去記事で紹介してるのでそちらの方を参照ください。

PMXやVMDには、製作者の著作権があるので、気軽にサーバにアップロードしたりはできませんのでご注意ください。

アニメキャラの場合は、2次創作になるので、モデルの制作自体もややグレーです。

日本は、コミケを中心とした二次創作文化が許容されている?ので、見逃されてる所はありますが、本来はグレーですね…。

うちのサイトでは、ローカル内で実験と勉強を兼ねて配布されているモデルデータで遊んでいますが、もしサーバに公開する場合は、 自作するか、購入するか、著作権フリー、商用可能なフリーモデルに全て差し替える予定です。

VPD配布元(制作者:鬼芽さん)

モデル配布元(制作者:Moggさん)

1. 前提:前回記事との関係

前回の記事では、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も同じ延長線上にある。

実際に動かしてみると、 思っていたほど遠い話ではなかったはずだ。