VPD(ポーズデータ)の形式と、VMDとの変換|ブラウザで相互変換
ポーズはVPD、モーションはVMDで配られるのが定番ですが、中身はテキストとバイナリで別物です。変換で気をつけるのは文字コードと、キーとキーのあいだの姿勢を求める補間の2つだけです。
手元のファイルをすぐ変換するなら、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になります)。