VMDファイルの形式と中身|構造をバイト単位で解説(ヘッダ・キーフレーム・補間曲線)

VMDは「30バイトのヘッダ + 20バイトのモデル名 + 6つのブロック」という素直なバイナリです。ただし補間曲線の並びとカメラの扱いに落とし穴があり、そこを外すと「読めているのに再生が合わない」という形で表面化します。自前のパーサとライタを書いて実物で往復させたときに分かったことをまとめます。

最終更新: 2026-09-18 · kphpリポジトリ同梱の実VMD 31本で「読み→書き→読み」が一致することを確認。本文の件数はその31本を数えた実測値

手元のVMDの種類・長さ・ボーン名を確認したいだけなら、PMX・PMD・VMDを調べるへドロップするとすぐ確認できます。 左右反転・切り出し・結合・速度変更など実際に加工したい場合はVMDの道具箱から目的を選べます。どちらもアップロード・ログイン不要です。

前提

  • 数値はすべてリトルエンディアン。 整数はuint32、実数はfloat32(IEEE754単精度)。
  • 文字列はShift_JISの固定長。 長さフィールドは無く、余りは0埋めです。UTF-8ではありません。
  • 時間の単位はフレーム番号で、30fps固定。 番号 ÷ 30 が秒です。ファイル内にfpsは書かれていません。
  • 座標系はMMDのもの(左手系・Yが上)。three.jsなど右手系へ持っていくときは変換が要ります(後述)。

全体のレイアウト

先頭からこの順に並びます。各ブロックは「件数(uint32) + 件数ぶんのレコード」で、レコード長は種類ごとに固定です(IK/表示だけ可変)。

位置内容レコード長
0ヘッダ Vocaloid Motion Data 0002 + 0埋め30バイト
30モデル名(Shift_JIS)20バイト
50ボーンのキーフレーム数(uint32)+ 本体111バイト
…表情(モーフ)数 + 本体23バイト
…カメラ数 + 本体61バイト
…照明数 + 本体28バイト
…セルフ影数 + 本体9バイト
…IK/表示切替数 + 本体可変(9 + 21 × IK数)

後ろのブロックは存在しないことがあります。 実測した31本のうち、照明とセルフ影のレコードを1件でも持つファイルは0本でした。件数フィールドすら書かれていないファイルがあるので、「残りが4バイト未満なら打ち切る」という読み方にしないと落ちます。

ヘッダの文字列は古い版だと Vocaloid Motion Data file(バージョン0001)です。判定は先頭一致(Vocaloid Motion Data で始まるか)で見るのが安全です。

ボーンのキーフレーム(111バイト)

オフセット長さ型内容
015Shift_JISボーン名(0埋めの固定長)
154uint32フレーム番号
1912float32 × 3位置 (x, y, z)。初期姿勢からの差分
3116float32 × 4回転(クォータニオン x, y, z, w)
4764uint8 × 64補間曲線(後述)

ボーン名は15バイトに収まらないと書けません。 Shift_JISの日本語は1文字2バイトなので、日本語だけなら7文字までです。左親指0のような標準ボーンは収まりますが、モデル固有の長い補助ボーン名は入らないことがあります。書き出す側は、エンコードして15バイト以内に戻せるかを必ず確かめてください。

位置は絶対座標ではなく、モデルの初期姿勢からの移動量です。センターやIKボーン以外はほぼ0のまま、回転だけが入っているのが普通です。

補間曲線64バイトの並び

ボーンの補間曲線は、X移動・Y移動・Z移動・回転の4項目それぞれに3次ベジェの制御点2つ(x1, y1, x2, y2)を持ちます。値は0〜127で、127が1.0にあたります。ところがVMDはこの16個の値を素直に並べておらず、同じ16バイトの並びを1バイトずつずらしながら4回書くという独特の形をしています。

// MMDが新しいキーに入れる既定値(すべて直線)。47バイト目から64バイト
20, 20,  0,  0, 20, 20, 20, 20, 107,107,107,107,107,107,107,107,
20, 20, 20, 20, 20, 20, 20,107, 107,107,107,107,107,107,107,  0,
20, 20, 20, 20, 20, 20,107,107, 107,107,107,107,107,107,  0,  0,
20, 20, 20, 20, 20,107,107,107, 107,107,107,107,107,  0,  0,  0

1行目は [i] が x1、[i+4] が y1、[i+8] が x2、[i+12] が y2 です(i = 0〜3 が X移動・Y移動・Z移動・回転)。ただし1行目の [2]・3は実物では全部0で、本当の値は2行目の写しの [17]・[18] にあります。 先頭16バイトだけ読むと、回転の緩急が変わります(補間曲線64バイトの読み方)。

制御点が対角線上、つまり (20, 20) と (107, 107) にあると直線(等速)になります。MMDが新しいキーフレームに入れる既定値もこれです。実測した31本・255,408件のボーンキーフレームのうち、この既定値のままだったのは71,122件(27.8%)でした。残り7割は作者が曲線を触っているので、補間を無視して線形に繋ぐと動きの緩急が消えます。

補間曲線は「後ろのキーフレーム」のものを使います。 「このキーフレームへどう入ってくるか」を表すためです。前のキーフレームのものを引くと、加速と減速が入れ替わります。

補間は「同じボーンのキーフレームどうし」の間にしか効かない

ここが手でモーションを作るときにいちばん引っかかる点です。30フレーム目でだけ腕を上げたつもりでも、そのボーンに他のキーフレームが無ければ、0フレーム目から最後まで上がったままになります。「動かしていない時間」は、動かしていないことを明示的に書かないと表現できません。

そのため、キーフレームを並べてVMDを作るときは登録した全キーフレームのボーン名の和集合を取り、書かれていないところへ初期姿勢(回転なし・移動なし)を書き込む必要があります。このサイトのモーション作成機能はそうしています。

表情(モーフ)のキーフレーム(23バイト)

オフセット長さ型内容
015Shift_JISモーフ名(0埋めの固定長)
154uint32フレーム番号
194float32ウェイト(0.0〜1.0)

補間曲線を持ちません。 表情はキーフレーム間を線形に繋ぐのが仕様です。

カメラのキーフレーム(61バイト)

オフセット長さ型内容
04uint32フレーム番号
44float32注視点からの距離。負の値が「後ろに引く」
812float32 × 3注視点 (x, y, z)
2012float32 × 3回転(オイラー角・ラジアン。クォータニオンではない)
3224uint8 × 24補間曲線(6項目 × 4バイト)
564uint32視野角(度)
601uint8パースの有無(0 = パースあり)

カメラの補間曲線24バイトは、6項目 ×(x1, x2, y1, y2)の素直な並びです(ボーンのようなずらし書きはありません)。項目の順は 0〜2 = 注視点のX/Y/Z、3 = 回転、4 = 距離、5 = 視野角。回転はX/Y/Zで分かれておらず、3項目ぶんまとめて1本の曲線です。

カメラ位置の式に注意。 正しくは 位置 = 注視点 + R ・ (0, 0, -distance) です。R ・ (注視点 + (0,0,-distance)) と組み立てると、注視点が原点から離れているほどずれます。three.js r171 の MMDLoader.buildCameraAnimation() は後者になっており、実物のカメラVMD(129キーフレーム)で測ると作者の指定した距離 16.40 が実効 15.29、24.60 が 21.95 になっていました。位置のずれは平均3.4・最大19.0(モデルの全高が約20)です。向きは注視点を見るので補正されてしまい、「寄りの絵が引きになる」という形でしか気づけません。

キーフレームが1フレームしか離れていない区間は補間しません。 カメラの「カット」(瞬間的な切り替え)はこの形で書かれています。実測したcamera.vmdは129キーフレーム中32箇所がこれでした(フレーム番号で並べ直してから数えた値。ファイル内の並び順のまま数えると30になり、少なく出ます)。素直に補間すると、カットのたびにカメラが滑って飛びます。

照明のキーフレーム(28バイト)

オフセット長さ型内容
04uint32フレーム番号
412float32 × 3色 (r, g, b)。0.0〜1.0
1612float32 × 3位置 (x, y, z)。実際は光の向きベクトル

補間曲線はありません。線形補間で仕様どおりです。 また、ここに書かれている「位置」は座標ではなく方向で、長さに意味はありません。平行光源の向きとして使い、光源をどこに置くかは描画側で決めます(モデルの内側に入ると影が欠けるので、一定の距離に正規化するのが無難です)。

three.jsが内部で使うmmdparserは照明トラックを読みません。 照明VMDを扱いたいなら、そこだけは自前でパースする必要があります。

セルフ影(9バイト)とIK/表示切替(可変長)

ブロックオフセット内容
セルフ影0 / 4 / 5フレーム番号(uint32) / 種別(uint8) / 距離(float32)
IK・表示0 / 4 / 5フレーム番号(uint32) / 表示(uint8) / IK数(uint32)
9以降[IKボーン名 20バイト + 有効フラグ 1バイト] × IK数

IK/表示切替だけはレコード長が可変(9 + 21 × IK数)なので、件数だけを見て一気に読み飛ばすことができません。1件ずつIK数を読んで進める必要があります。ここでのボーン名は15バイトではなく20バイトである点にも注意してください。

種類(モーション・表情・カメラ・照明)の見分け方

VMDのファイル自体に「これはカメラです」という印はありません。 どのブロックに何件入っているかで判断します。このサイトは次の順で判定しています(現行のmmdbox.netと同じ閾値)。

判定条件
モーションボーンのキーフレームが10件を超える
表情表情が10件を超え、ボーンが10件以下
カメラカメラが10件を超え、ボーンが10件以下
照明照明が1件以上あり、ボーンとカメラが10件以下
不明上のどれにも当てはまらない

照明だけ閾値が0なのは、光の色を数フレームだけ変えるファイルが実際に多いためです。10件を境にすると、そういうファイルが「不明」に落ちます。実測した31本ではモーション24本・カメラ1本・表情1本・不明5本になりました(不明はキーフレームがごく少ない断片です)。

MMD座標系から右手系(three.js)へ

  • 位置は Z を反転する。
  • クォータニオンは X と Y を反転する((x, y, z, w) → (-x, -y, z, w))。
  • カメラのオイラー角は Z だけ反転して XYZ 順で組む。注視点も Z を反転する。
  • 左右反転(ミラー)は別物。位置は X を反転、回転は (x, y, z, w) → (x, -y, -z, w)(回転軸は擬ベクトルなので、鏡の面に平行な成分だけ符号が変わる)。ボーン名の「左」と「右」も入れ替える必要があります。

書き出すときに気をつけること

  • レコードは生バイト列のまま持ち回るほうが安全。 補間曲線64バイトのような触る必要のないデータを構造体へ展開して書き戻すと、往復で取りこぼしが起きます。トリミングや結合で必要なのは「どのレコードを残すか」と「フレーム番号を書き換えること」だけです。フレーム番号の位置はボーン・表情が15バイト目、それ以外は0バイト目です。
  • 2つのVMDを繋ぐときは、後ろのVMDをずらしてから連結する。 どちらも0フレーム目から始まるので、そのまま結合すると同じ(ボーン, フレーム)が重複して壊れます。
  • 速度を上げると、丸めで同じ(ボーン, フレーム)が重なる。 落とした件数は利用者に見せるべきです(黙って捨てると、なぜ短くなったのか分かりません)。
  • モデル名の20バイトは、途中でマルチバイト文字が切れていることがある。 そのままデコードすると末尾に置換文字(U+FFFD)が出ます。表示に使うなら落としてください。