VPD(ポーズデータ)の形式と、VMDとの変換|ブラウザで相互変換

ポーズはVPD、モーションはVMDで配られるのが定番ですが、中身はテキストとバイナリで別物です。変換で気をつけるのは文字コードと、キーとキーのあいだの姿勢を求める補間の2つだけです。

最終更新: 2026-10-02 · kphp同梱の実VMD 29本から116の姿勢を取り出し、VPD経由で往復させて確認

手元のファイルをすぐ変換するなら、VPD(ポーズ)とVMDを変換するへ入れてください。 VPDなら1フレームのVMDに、VMDならフレーム番号を指定してVPDにします。アップロードもログインも要りません。

VPDは「Shift_JISのテキスト」

VPDはメモ帳で開ける普通のテキストです。MMDが書き出すものはShift_JIS・改行はCRLFで、// 以降はコメントです。1本のボーンが Bone番号{名前 から } までの節になり、その中に位置(3つ)と回転(4つの四元数)が1行ずつ入ります。新しいMMDは表情も Morph番号{名前 の節で書きます(古いVPDには表情の節がありません)。

Vocaloid Pose Data file

初音ミク.osm;		// 親ファイル名
2;				// 総ポーズボーン数

Bone0{センター
  0.000000,1.250000,-0.500000;				// trans x,y,z
  0.000000,0.000000,0.000000,1.000000;		// Quaternion x,y,z,w
}

Morph0{まばたき
  1.000000;				// weight
}
  • 「総ポーズボーン数」の行は当てにせず、実際に並んでいる節を数えて読むほうが安全です(手で編集されたVPDでは食い違うことがあります)
  • UTF-8で保存し直されたVPDも出回っています。先頭にBOMがあればUTF-8として読み、無ければShift_JISとして読むのが無難です
  • ボーン名はVPDでは長さの制限がありませんが、VMDに入れると15バイトで切れます(ボーン名が化ける理由)

座標系はVMDと同じ。変換は「写すだけ」

VPDの位置と回転は、VMDのキーフレームと同じMMDの座標系です。位置は初期姿勢からの差分、回転はそのボーンの四元数で、符号の反転のような変換は要りません。だから「VPD → VMD」は、各ボーンの値をそのまま0フレーム目のキーにするだけです。VMDではキーが1つあれば最後までその値が効くので、1フレームのVMDはそのままポーズとして使えます。

実物で確かめました。kphp同梱の実VMD 29本からそれぞれ4か所の姿勢(合計116、ボーン17,832本分)を取り出してVPDに書き、読み直してVMDへ戻したところ、値のずれは最大 5.8×10⁻⁷ でした。VPDが小数6桁で書く丸めのぶんだけです。

VMDの途中のフレームをポーズにするには、補間が要る

VMDから「100フレーム目の姿勢」を取り出すとき、ちょうど100フレーム目にキーがあるボーンはその値を使えます。問題はキーとキーのあいだにあるボーンで、直前のキーの値を使うと、MMDで再生したときの姿勢とずれます。 MMDは、あいだの値を後ろのキーが持つ補間曲線(3次ベジェ)で求めています。

補間曲線(64バイト)の読み方位置X位置Y位置Z回転
制御点1のx[0][1][2][3]
制御点1のy[4][5][6][7]
制御点2のx[8][9][10][11]
制御点2のy[12][13][14][15]
  • 値は0〜127で、127で割ると0〜1の制御点になります(three.jsのMMDLoaderも同じ位置を読んでいます)
  • 経過の割合 t から、曲線上で x(s) = t となる s を探し、y(s) を進み具合として使います。位置はX・Y・Zそれぞれの曲線、回転は回転の曲線で球面線形補間(slerp)します
  • 最初のキーより前は最初のキー、最後のキーより後は最後のキーの値です(MMDの再生と同じ)
  • 表情のキーは補間曲線を持たないので、直線で補間します

同じ補間は、VMDを途中から切り出すときにも要ります。直前のキーの値を先頭に置くだけだと、実物のVMDで先頭の位置が最大5.98ユニット(約48cm)ずれました(切り出し・結合で壊れるところ)。

実物のVMDには、長さが1でない回転が入っている

四元数は本来長さ1ですが、実物のVMD 29本のキーを数えると、長さが1から0.001以上ずれたものが101件あり、いちばん大きいずれは0.0162でした。MMDはこれをそのまま再生しているので、変換の側で勝手に正規化すると元のファイルと値が変わります。このサイトの変換は、キーの値をそのまま写しています(補間したときだけは、計算の結果として長さ1になります)。