Edited at

VMD(Vocaloid Morion Data)ファイルのフォーマットがわからなかったからバイナリ見てみた

これは「VTuber Tech #1 Advent Calendar 2018」の18日目の記事ですが、

微妙に遅れて12/21に投稿しています。


紹介

インキュベーションセンターの「タピオカ」さんが

「VMD(Vocaloid Morion Data)ファイルのバイナリ解析」をしていたので紹介します。


VMDファイルのバイナリ解析をすることになった背景

最近、業務でUnityを扱っており、

3Dキャラクターのモーションをいじることが多くなりました。

キャラクタモーションを1体1体作ったりいじったりする分には、

既存のGUIベースのツールを利用すればよいのですが、

複数キャラクターのモーション処理を始めると、

「プログラムでキャラクターのモーションを直接いじりたい」

という場面が出てきます。

そういうわけで、

VMDファイルをプログラムから編集できるソフトを探したのですが、

これがなかなか見つからない。

ならば、VMDファイルを直接いじろうと、いろいろ検索したのですが、

VMDファイルに関する公式ドキュメントが見つからない。

数少ない情報として見つかったのが、2つのホームページ。

で、このホームページ見たときに思ったんです。

「バイナリ見れば動かせるようになるんじゃね!?」


結論

VMDファイルは以下のように構成されている

VMDファイル

┣ ヘッダ

┣ ボーン

┃  ┣ボーンのキーポイント数

┃  ┣1個目のキーポイントの情報

┃  ┣2個目のキーポイントの情報

┃   ...

┃ 

┣ スキン

┃  ┣スキンのキーポイント数

┃  ┣1個目のキーポイントの情報

┃  ┣2個目のキーポイントの情報

┃   ...

┃ 

┣ カメラ 

┃  ┣カメラのキーポイント数

┃  ┣1個目のキーポイントの情報

┃  ┣2個目のキーポイントの情報

┃   ...

┃ 

┣ 照明 

┃  ┣照明のキーポイント数

┃  ┣1個目のキーポイントの情報

┃  ┣2個目のキーポイントの情報

┃   ...

┃ 

┣ シャドウ 

┃  ┣シャドウのキーポイント数

┃  ┣1個目のキーポイントの情報

┃  ┣2個目のキーポイントの情報

┃   ...

┃ 

┗ I K

   ┣IKの数

   ┣1個目のIKの情報

   ┣2個目のIKの情報

    ...


VMDファイルの中身の詳細


  • バイナリエディタStirlingを用いて解析

  • 各数値はリトルエンディアンで格納されている
    ※カメラ、照明、シャドウ、IKは未調査


ヘッダ


  • VMDファイルの概要を書き込む部分、50バイト



char VmdHeader[30]


  • バージョン情報を表示する、char 30バイト



char VmdModelName[20]


  • 3Dモデルの名前を表示する、char 20バイト



ボーン


  • ボーンの情報を書き込む部分

  • まず、ヘッダの直後にボーンのキーポイント数を記載する、unsigned int 4バイト



キーポイントの情報


  • 各キーポイントの情報を書き込む部分、111バイト

  • 今回は上半身のボーンを例として用いる



char BoneName[15]


  • ボーン名、char 15バイト



DWORD FrameNo


  • フレーム番号、unsigned long 4バイト



float Location[3]


  • ボーンの位置(x, y, z)、float 4*3バイト



float Rotation[3]


  • ボーンの回転(x, y, z, w)、float 4*4バイト



BYTE Interpolation[64]


  • ボーンの補完情報、64バイト



スキン


  • スキン(表情)の情報を書き込む部分

  • まず、ボーン情報の直後にスキンのキーポイント数を記載する、unsigned int 4バイト



キーポイントの情報


  • 各キーポイントの情報を書き込む部分、23バイト

  • 今回はウィンク右のスキンを例として用いる



char SkinName[15]


  • スキン名、char 15バイト



DWORD FrameNo


  • フレーム番号、unsigned long 4バイト



float Weight


  • MMDにおける表情スライダーの値、float 4バイト



pythonでのVMDファイルの出力方法

江良野様のVMD-Liftingを基に作成

def write_vmd_file(filename, bone_keypoints, skin_keypoints):

"""Write VMD data to a file"""![vmd16.png](https://qiita-image-store.s3.amazonaws.com/0/267235/a8a1c61d-5059-c1dd-b710-895ee5591322.png)

fout = open(filename, "wb")
# header
fout.write(b'Vocaloid Motion Data 0002\x00\x00\x00\x00\x00')
fout.write(b'Dummy Model Name ')

# bone keypoints
fout.write(struct.pack('<L', len(bone_keypoints))) # ボーンのキーポイント数
for bk in bone_keypoints: # 各キーポイントの情報を格納
fout.write(bk.name) # ボーン名
fout.write(bytearray([0 for i in range(len(bk.name), 15)])) # ボーン名15Byteの残りを\0で埋める
fout.write(struct.pack('<L', bk.frame)) # フレーム番号
fout.write(struct.pack('<f', bk.posx())) # x座標
fout.write(struct.pack('<f', bk.posy())) # y座標
fout.write(struct.pack('<f', bk.posz())) # z座標
fout.write(struct.pack('<f', bk.rotx())) # x回転
fout.write(struct.pack('<f', bk.roty())) # y回転
fout.write(struct.pack('<f', bk.rotz())) # z回転
fout.write(struct.pack('<f', bk.rotw())) # w回転
fout.write(bytearray([0 for i in range(0, 64)])) # 補間パラメータ(64Byte)

# skin keypoints
fout.write(struct.pack('<L', len(skin_keypoints))) # スキンのキーポイント数
for sk in skin_keypoints: # 各キーポイントの情報を格納
fout.write(sk.name) # スキン名
fout.write(bytearray([0 for i in range(len(sk.name), 15)])) # スキン名15Byteの残りを\0で埋める
fout.write(struct.pack('<L', sk.frame)) # スキン番号
fout.write(struct.pack('<f', sk.weight)) # スキンのパラメータ(0~1)

fout.write(struct.pack('<L', 0)) # カメラキーフレーム数
fout.write(struct.pack('<L', 0)) # 照明キーフレーム数
fout.write(struct.pack('<L', 0)) # セルフ影キーフレーム数
fout.write(struct.pack('<L', 0)) # モデル表示・IK on/offキーフレーム数

fout.close()

※ボーン名やスキン名は日本語であるため、bk.name = b'\x89\x45\x98\x72' # '右腕'のようにバイナリコードを直接入力する必要がある


結果

pythonコードからvmdファイルを直接生成できた

また、MMDに読み込ませても問題なく動作させることができた