[JavaScript] Three.jsでPMX / VMDを読み込んでアニメーションを再生する

はじめに

glTFのフリーモデルを読み込んで使用していましたが、PMX / VMD もThree.jsで動くようなので試しに動かしてみたのでその実装メモです。

モデルデータとVMDはニコニコ立体からお借りしています。

サーバにアップロードはできないので、ローカルでの動作テストのみで使用しています。

1. この記事で扱うこと

この記事では、Three.js を使って PMX / VMD 形式のモデルとモーションを Web 上で再生する方法を整理する。

対象は以下の内容に限定する。

  • PMX(MMDモデル)の読み込み方法
  • VMD(モーションデータ)の適用手順
  • アニメーションを更新するための基本的なループ処理
  • VMD が存在しない場合の安全な扱い方
  • MMDAnimationHelper 使用時の physics 設定に関する注意点

Blender や MMD 本体でのモデリング・モーション制作手順、 Three.js の基本的なシーン構築やレンダリング設定については扱わない。

また、本記事は Three.js の公式サンプルに含まれる MMDLoader / MMDAnimationHelper を前提とし、 PMX / VMD を「実行時に組み合わせて再生する」用途に焦点を当てる。

PMX / VMD を初めて触る人だけでなく、 glTF との違いや、Web用途での実装上の注意点を整理したい人向けの内容とする。

2. 使用環境

本記事で動作確認を行った環境は以下の通り。

  • Three.js(r152 系)
  • MMDLoader
  • MMDAnimationHelper
  • Blender(VMD 作成・確認用)

Three.js は r152 系を前提とする。 これは MMDLoader / MMDAnimationHelper がこの世代では安定して動作するためで、 最新バージョンでは import 構成や依存関係の変更により、そのままでは動作しない場合がある。

Blender は必須ではないが、 VMD の作成・確認や PMX モデルの調整を行う場合に使用する。

3. PMX / VMD の基本構造

PMX / VMD は、モデルとアニメーションを明確に分離した設計になっている。

PMX(モデルデータ)

PMX には、キャラクターモデルそのものに関する情報が含まれる。

  • メッシュ(頂点・UV・法線)
  • ボーン構造
  • IK 定義
  • モーフ(表情・体型変形)
  • 物理定義(剛体・ジョイント)

PMX 自体には アニメーション情報は含まれない。 あくまで「見た目と骨格、挙動の定義」を持つモデルデータである。


VMD(モーションデータ)

VMD には、時間変化を伴うデータのみが含まれる。

  • ボーンの回転・位置アニメーション
  • モーフ(表情)の変化
  • IK の ON / OFF 情報

VMD は 特定の PMX に直接依存しない。 ボーン名が一致していれば、同じ VMD を別の PMX に適用できる。


モデルとアニメーションは別ファイル

PMX と VMD は、最初から 別ファイルとして扱うことが前提のフォーマットである。

  • PMX:キャラクターの「器」
  • VMD:キャラクターの「動き」

この分離により、

  • 同一モデルに複数のモーションを差し替え可能
  • モーション資産を再利用しやすい
  • 実行時にアニメーションを切り替えられる

といった特徴を持つ。


実行時に組み合わせる設計

Three.js で PMX / VMD を扱う場合も、この設計は変わらない。

  • PMX を先に読み込む
  • 必要に応じて VMD を読み込む
  • 実行時に両者を結合して再生する

PMX にアニメーションが「含まれていない」ことは仕様であり、 VMD を別途読み込んで適用するのが正しい使い方となる。

この点は、アニメーションがモデル内に含まれることの多い glTF とは 考え方が大きく異なる部分である。

4. 必要な import

PMX / VMD を Three.js で扱うために、以下のモジュールを使用する。

  • three.module.js
  • MMDLoader
  • MMDAnimationHelper

本記事では ES Modules(type="module")と importmap を前提とする。


importmap の設定例

<script type="importmap">
{
  "imports": {
    "three": "https://cdn.jsdelivr.net/npm/three@0.152.0/build/three.module.js",
    "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.152.0/examples/jsm/"
  }
}
</script>

Three.js のバージョンは r152 系に固定している。 これは MMDLoader / MMDAnimationHelper がこの世代では問題なく動作するためである。


JavaScript 側の import

import * as THREE from 'three';
import { MMDLoader } from 'three/addons/loaders/MMDLoader.js';
import { MMDAnimationHelper } from 'three/addons/animation/MMDAnimationHelper.js';

補足

  • three/addons/examples/jsm/ へのエイリアス
  • 最新版 Three.js(r180 以降)では MMDLoader 周りの import がそのまま動作しない場合がある
  • 本記事のコード例は ビルドツール不要・ブラウザ直読みを前提としている

この import が正しく通れば、 PMX / VMD を扱うための準備は完了している。

5. MMDAnimationHelper の初期化

PMX / VMD を Three.js 上で再生する場合、 MMDAnimationHelperシーン全体で 1 つだけ生成して共有するのが基本となる。

helper を共有する理由

MMDAnimationHelper は以下を内部で管理する。

  • 登録された PMX メッシュ
  • 対応するアニメーション(VMD)
  • IK 更新
  • 物理演算(有効時)

複数の helper を作成すると、

  • 更新処理が分散する
  • 物理演算の管理が複雑になる
  • 意図しない挙動やパフォーマンス低下が起きやすい

そのため、1 シーンにつき 1 helper を用意し、 すべての PMX モデルをそこに登録する構成が推奨される。


初期化例

const mmdHelper = new MMDAnimationHelper({
  afterglow: 2.0,
  physics: false,
});

physics の ON / OFF について

physics オプションは、 MMD の物理演算(髪・スカート等の揺れ)を有効にするかどうかを指定する。

physics: false(推奨)

  • Ammo.js 不要
  • 処理が軽い
  • Web / VR 向け
  • ボーンアニメーションは通常通り動作する

Three.js で PMX / VMD を扱う場合、 まずは OFF にするのが安全


physics: true

  • Ammo.js の読み込みが必須
  • CPU 負荷が高い
  • 初期化コストが大きい
  • VR やモバイル環境では負荷が問題になりやすい
<script src="https://cdn.jsdelivr.net/npm/ammo.js"></script>

physics: true を指定した状態で Ammo.js が読み込まれていない場合、 実行時にエラーが発生する。


実運用での考え方

  • キャラクターの動作確認・制御が目的 → physics: false
  • 揺れ物表現まで含めたい → physics: true(負荷を理解した上で)

PMX / VMD は 物理演算を必須としない設計のため、 用途に応じて physics を切り替えられる点が大きな利点となる。

6. PMX + VMD を読み込む関数

PMX / VMD を扱う場合、 必ず PMX を先に読み込み、VMD は後から適用する

VMD は常に存在するとは限らないため、 VMD の有無で処理を分岐させるのが安全な実装になる。


基本方針

  • PMX を MMDLoader.load() で読み込む
  • VMD がある場合のみ loadAnimation() を呼ぶ
  • helper.add(mesh)必ず実行する
  • animation は オプション扱い

実装例

function loadMMDModel(pmxPath, vmdPath = null) {
  return new Promise((resolve, reject) => {
    const loader = new MMDLoader();

    // PMX を先に読み込む
    loader.load(
      pmxPath,
      (mesh) => {

        // VMD が無い場合
        if (!vmdPath) {
          mmdHelper.add(mesh, {
            physics: false,
          });

          resolve({ mesh, helper: mmdHelper });
          return;
        }

        // VMD がある場合のみ loadAnimation
        loader.loadAnimation(
          vmdPath,
          mesh,
          (vmdAnimation) => {
            mmdHelper.add(mesh, {
              animation: vmdAnimation,
              physics: false,
            });

            resolve({ mesh, helper: mmdHelper });
          },
          undefined,
          reject
        );
      },
      undefined,
      reject
    );
  });
}

ポイント解説

PMX を先に読み込む

loadAnimation() は、 対象となる PMX メッシュが存在していないと呼び出せない

そのため、

  1. PMX を読み込む
  2. メッシュを取得する
  3. そのメッシュに対して VMD を適用する

という順序が必須になる。


helper.add(mesh) は必須

MMDAnimationHelper は、

  • add() されたメッシュのみ管理する
  • update() は登録済みメッシュにしか作用しない

そのため、VMD が無い場合でも必ず helper.add(mesh) を呼ぶ必要がある


animation はオプション

mmdHelper.add(mesh, {
  animation: vmdAnimation,
});

animation は省略可能。

  • 指定した場合:VMD が再生される
  • 指定しない場合:Tポーズ(待機状態)で保持される

これにより、

  • 静的キャラ
  • 待機キャラ
  • 後からモーションを差し替えるキャラ

を同じ仕組みで扱える。


設計上の利点

この構成にしておくと、

  • VMD が無いモデルも安全に扱える
  • 実行時にモーションを差し替えられる
  • PMX / VMD の構造に忠実な実装になる

PMX / VMD は 「実行時に組み合わせる」設計であるため、 この分岐構造が最も破綻しにくい実装となる。

7. VMD が無い場合の扱い

PMX / VMD の設計上、 VMD が存在しない状態は異常ではない

VMD を指定しない PMX は、 待機状態のキャラクター、もしくは静的モデルとして扱うことができる。


待機キャラとしての利用

VMD を指定せずに PMX を MMDAnimationHelper に登録すると、

  • ボーンは初期姿勢(Tポーズなど)を維持
  • IK は有効
  • 表情や姿勢は固定されたまま保持される

この状態は、

  • NPC の待機状態
  • モーション切り替え前の初期状態

として自然に利用できる。


静的モデルとして表示

VMD を適用しない場合でも、

  • メッシュは通常の THREE.Object3D として扱える
  • シーンへの追加や transform 操作は可能

そのため、PMX を

  • 見た目だけ表示したいモデル
  • インタラクション用の配置オブジェクト

として使うこともできる。


後から VMD を差し替える

PMX を先に helper に登録しておけば、 後から VMD を読み込んで適用することが可能

loader.loadAnimation(vmdPath, mesh, (vmdAnimation) => {
  mmdHelper.remove(mesh);
  mmdHelper.add(mesh, {
    animation: vmdAnimation,
    physics: false,
  });
});

このように、実行時にモーションを切り替えることで、

  • 待機 → 歩行
  • 歩行 → 走行
  • 状態遷移によるモーション変更

といった制御が行える。


まとめとしての注意点

  • VMD が無いことはエラーではない
  • PMX は必ず helper に登録する
  • モーションは後から差し替え可能

PMX / VMD は 「最初からアニメーションが無くても成立する」 構造になっている点が、大きな特徴である。

8. animate ループでの更新処理

PMX / VMD のアニメーションを再生するためには、 毎フレーム MMDAnimationHelper を更新する必要がある。


基本的な animate ループ

const clock = new THREE.Clock();

function animate() {
  requestAnimationFrame(animate);

  const delta = clock.getDelta();

  mmdHelper.update(delta);

  renderer.render(scene, camera);
}

animate();

clock.getDelta()

getDelta() は、 **前フレームからの経過時間(秒)**を返す。

  • フレームレートに依存しない
  • 実行環境差が出にくい
  • VMD の再生速度が安定する

MMDAnimationHelper.update() には、 この delta を必ず渡す。


mmdHelper.update(delta)

update() は以下をまとめて処理する。

  • ボーンアニメーションの更新
  • IK の計算
  • (有効時)物理演算の更新
  • モーフの反映

PMX / VMD の再生は、 この 1 行に集約されている


helper を止めないこと

mmdHelper.update(delta) を呼ばない場合、

  • アニメーションが進まない
  • IK が更新されない
  • 表情や姿勢が固定される

VMD を再生していない場合でも、 helper に登録された PMX が存在する限り、 update は継続して呼び続ける必要がある。


注意点

  • PMX ごとに update() を呼ぶ必要はない
  • helper は 1 つだけ更新すればよい
  • アニメーションの有無で update を分岐しない

animate ループでは、 常に mmdHelper.update(delta) を呼ぶ という構成にしておくのが最も安全である。

9. よくあるエラーと対処

PMX / VMD を Three.js で扱う際、 発生しやすいエラーとその原因、対処方法を整理する。


VMD が読み込めない

主な原因

  • VMD ファイルのパスが間違っている
  • 拡張子の大文字・小文字違い
  • サーバー配信されていない(404)

確認ポイント

  • vmdPathconsole.log で確認
  • ブラウザの Network タブで VMD が取得できているか確認

対処

  • 正しい相対パスを指定する
  • VMD ファイルが実際に配置されているか確認する

animation が undefined になる

loader.loadAnimation(vmdPath, mesh, (vmdAnimation) => {
  // vmdAnimation が undefined
});

主な原因

  • VMD にキーフレームが存在しない
  • カメラ用 VMD を読み込んでいる
  • Blender 書き出し時に「Model Motion」が有効になっていない

対処

  • Blender で アーマチュアを選択した状態で VMD を書き出す
  • Camera / Light モーションを含めない
  • 少なくとも 1 フレーム以上のキーフレームを登録する

THREE.MMDPhysics: Import ammo.js エラー

THREE.MMDPhysics: Import ammo.js

原因

physics: true を指定しているが、 Ammo.js が読み込まれていない。

対処①(推奨)

物理演算を使用しない場合は physics: false にする。

mmdHelper.add(mesh, {
  animation: vmdAnimation,
  physics: false,
});

対処②(物理を使う場合)

<script src="https://cdn.jsdelivr.net/npm/ammo.js"></script>
  • <script type="module"> より前に読み込む
  • Web / VR では負荷に注意する

Three.js のバージョン差異

症状

  • import が通らない
  • MMDLoader が見つからない
  • 実行時に例外が発生する

原因

MMDLoader / MMDAnimationHelper は Three.js の最新バージョンでは公式サポート対象外になっている。

対処

  • Three.js を r152 系に固定する
  • importmap を使用してバージョンを明示する
"three": "https://cdn.jsdelivr.net/npm/three@0.152.0/build/three.module.js"

切り分けの基本方針

  • PMX が表示されるか
  • VMD が Network で取得できているか
  • vmdAnimation が生成されているか
  • mmdHelper.update(delta) が毎フレーム呼ばれているか

この順に確認すれば、 ほとんどの問題は特定できる。


PMX / VMD は構造が単純な分、 エラーの原因も限定されやすい。 落ち着いて一つずつ切り分けるのが重要である。

10. PMX / VMD を使うメリット

PMX / VMD は、 キャラクターを「実行時に動かす」用途に特化した設計になっている。 Three.js 上で扱った場合も、その利点はそのまま活かせる。


モデルとモーションの分離

PMX と VMD は、最初から役割が分かれている。

  • PMX:見た目・骨格・定義
  • VMD:動きのみ

この分離により、

  • 同一モデルに複数モーションを適用できる
  • モデルを差し替えてもモーションを再利用できる
  • 実行時にモーションを切り替えやすい

という構造になっている。


モーション差し替えが容易

VMD はモデル外部のファイルとして扱われるため、

  • 待機 → 歩行 → 走行
  • 状態遷移による切り替え
  • 後からモーションを追加

といった制御を、 モデルを再読み込みすることなく行える。

これは、アニメーションがモデルに内包されがちな形式と比べて、 実装上の自由度が高い点である。


ファイルサイズが小さい

PMX にはアニメーションが含まれないため、

  • モデルデータ自体が比較的軽量
  • モーションは必要な分だけ読み込める
  • ネットワーク転送量を抑えやすい

Web 環境では、 必要なデータだけを段階的に読み込める点が大きな利点となる。


キャラクター用途に向いた設計

PMX / VMD は、

  • ボーン制御
  • IK
  • 表情モーフ
  • 実行時アニメーション切り替え

を前提に設計されている。

そのため、

  • キャラクター主体のシーン
  • NPC やアバター表示
  • Web / VR 空間での人物表現

といった用途では、 扱いやすく、破綻しにくい構造になっている。


PMX / VMD は最新のフォーマットではないが、 キャラクター制御という目的に対しては、今でも合理的な選択肢と言える。

11. 向いている用途・向かない用途

PMX / VMD は汎用フォーマットではなく、 用途を選ぶ形式である。 Three.js で使用する際も、この点を理解しておくことが重要になる。


向いている用途

キャラクター主体のシーン

  • プレイヤーキャラクター
  • NPC
  • アバター表示

PMX / VMD は、 1 体〜少数キャラクターを丁寧に動かす用途に向いている。


モーション切り替えが必要なケース

  • 待機 / 歩行 / 走行
  • 状態遷移によるアニメーション変更
  • 実行時にモーションを差し替えたい場合

VMD を外部ファイルとして扱う設計のため、 実行時のモーション管理がしやすい


Web / VR 表示

  • WebXR
  • VR 空間での人物表示
  • インタラクティブなキャラクター操作

PMX / VMD は、

  • ボーン制御
  • IK
  • 表情モーフ

を前提にしているため、 「動きのある存在」としての表現に向いている。


向かない用途

大量キャラの同時表示

  • 画面内に数十体以上のキャラクター
  • 群集表現

PMX / VMD は CPU 側の処理が多く、 大量キャラを同時に動かす用途には不向きである。


Web標準一本で完結したいケース

  • glTF だけで完結したい
  • Three.js 最新版を常に使いたい
  • フォーマット依存を避けたい

PMX / VMD は Web 標準ではなく、 Three.js の公式サポートも限定的である。

そのため、

  • Three.js のバージョン固定
  • 依存関係の管理

を受け入れられない場合には適さない。


使い分けの考え方

  • 背景・静的オブジェクト → glTF
  • キャラクター → PMX / VMD

という役割分担を行うことで、 それぞれの強みを活かした構成が可能になる。

PMX / VMD は **「キャラクターに特化した選択肢」**として捉えるのが適切である。

12. まとめ

PMX / VMD は長期間仕様が更新されていないが、 それは未完成だからではなく、必要な要素が既に揃っているためである。

  • モデルとモーションが分離されている
  • 実行時に組み合わせる前提の設計
  • キャラクター用途に必要な要素(ボーン、IK、モーフ)を内包している

これらの点は現在でも有効で、 Web や VR といった用途でも破綻しにくい。

一方で、Three.js の最新版では MMDLoader / MMDAnimationHelper の公式サポートは弱く、 そのままでは動作しないケースが増えている。

そのため PMX / VMD を利用する場合は、

  • Three.js のバージョンを固定する
  • import 構成や依存関係を明示的に管理する

といった前提が必要になる。

これらを受け入れられるのであれば、 PMX / VMD は 現在でも実用的なキャラクター表現手段として利用できる。

Web 上でキャラクターを動かす用途において、 PMX / VMD は今でも選択肢の一つとして成立している。