はじめに
glTFのフリーモデルを読み込んで使用していましたが、PMX / VMD もThree.jsで動くようなので試しに動かしてみたのでその実装メモです。
モデルデータとVMDはニコニコ立体からお借りしています。
サーバにアップロードはできないので、ローカルでの動作テストのみで使用しています。
ニコニ立体
3D投稿サービス「ニコニ立体」
https://3d.nicovideo.jp/【MMD】全親移動モーション(歩き・走り)【配布】
【MMD】全親移動モーション(歩き・走り)【配布】 [エンターテイメント] これまで動画用に作成して使ったり使わなかったりしたモーションを配布します。以前のセンターボー...
https://www.nicovideo.jp/watch/sm425679671. この記事で扱うこと
この記事では、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.jsMMDLoaderMMDAnimationHelper
本記事では 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 メッシュが存在していないと呼び出せない。
そのため、
- PMX を読み込む
- メッシュを取得する
- そのメッシュに対して 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)
確認ポイント
vmdPathをconsole.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 は今でも選択肢の一つとして成立している。
💬 コメント