前言
MikuMikuDance(简称MMD)是一款动画软件,早期视为Vocaload角色制作动画的软件,现在还经常能在B站等视频网站,或一些动画网站(某I站)看到MMD作品。
我在高中也简单学过操作这款软件以及PE、水杉等软件,学会了简单k帧、套动作、调渲染、加后期、压缩等技术,这与我学习计算机专业有很大的关系(虽然学校学的和这个八竿子打不着,或许我应该学美术去),现在已经分不清很多东西了,封面静画就是杂七杂八过气MME一锅扔的成果,得益于G渲的强大,还能看出一点效果。
现在我想学一些3D的开发,包括用程序读取模型、动作等,很快我就想到之前用过的MMD。
一些3D姿势估计(3D pose estimate)或许能得到骨骼位置以及PAF(骨骼间关系),但我需要知道3D动画是如何储存动作数据的,才能想到怎样将姿势估计得到的数据转化为动作数据。
因此我找了一些资料解析MMD的动作数据VMD(Vocaload Mation Data)文件,并写下这篇记录。
我的参考文献:
MMD中的VMD文件格式详解国内博客,解释VMD格式并用Java读取
VMD file formatMMD Wiki
本文会用python解析vmd文件,并纠正上述文章的一点错误。
根据MMD的规矩,上借物表:
名称
来源
MikuMikuDanceE_v803
圝龙龍龖龘圝
八重樱
神帝宇
封面静画:
名称
类别\来源
LightBloom
背光
AutoLuminousBasic
自发光特效
HgSAO
阴影
SoftLightSB
柔化
SvSSAO
阴影
XDOF
景深
dGreenerShader
G渲
Tokyo Stage
场景
一、格式说明
首先,vmd文件本身是一个二进制文件,里面装着类型不同的数据:uint8、uint32_t、float,甚至还有不同编码的字符串,因此我们需要二进制流读入这个文件。
vmd格式很像计算机网络的协议格式,某某位是什么含义,区别是,vmd文件的长度理论上是无限的,让我们来看看。
vmd的大致格式如下:
头部
关键帧数量
关键帧
头部
最开始的就是头部(header),看到这就有十分强烈的既视感:
类型
长度
含义
byte
30
版本信息
byte
10 or 20
模型名称
其中,版本信息(VersionInformation)长度为30,是ascii编码的字符串,翻译过来有两种,一为“Vocaloid Motion Data file”,二为“Vocaloid Motion Data 0002”,长度不足30后用\0(或者说b'\x00')填充。这是由于vmd版本有两种,大概是为了解决模型名称长度不足,因此后续只影响模型名称的占用长度。
模型名称(ModelName),是动作数据保存时用的模型的模型名,通过这个我们可以获取到那个名称,我们知道,一个动作数据想要运作起来,只要套用模型的骨骼名称是标准的模板就可以,因此我想象不出这个名称有何用处,或许某些模型带有特殊骨骼,例如翅膀之类的,这样能方便回溯?模型名称的长度根据版本而决定,version1为10,version长度为20。编码原文写的是shift-JIS,是日语编码,这样想没错,然而我试验后发现并非如此,例如经常改模型的大神神帝宇的模型,他的模型名称用shift-JIS为乱码,用gb2312竟然能正常读出来;还有机动牛肉大神的模型,他的模型名称用gb2312无法解码,用shift-JIS解码竟然是正常的简体中文???怎么做到的?
骨骼关键帧(BoneKeyFrame)
骨骼关键帧,分为两部分:骨骼关键帧数、骨骼关键帧记录:
类型
长度
含义
uint32_t
4
骨骼关键帧数量 BoneKeyFrameNumber
类型
长度
含义
byte
15
骨骼名称 BoneName
uint32_t
4
关键帧时间 FrameTime
float*3
12
x,y,z空间坐标 Translation.xyz
float*4
16
旋转四元数x,y,z,w Rotation.xyzw
uint8_t * 16 or uint32 * 4
16
补间曲线x的坐标 XCurve
uint8_t * 16 or uint32 * 4
16
补间曲线y的坐标 YCurve
uint8_t * 16 or uint32 * 4
16
补间曲线z的坐标 ZCurve
uint8_t * 16 or uint32 * 4
16
补间曲线旋转的坐标 RCurve
byte
111
合计
为何要分开写呢?因为骨骼关键帧数量只需要一个就够了,而后面骨骼关键帧记录的数量会和前面的骨骼关键帧数量保持一致,最后大概是这种效果:
我们可以查一下,每个骨骼关键帧的数量为111字节。
旋转坐标
一开始还没发现,旋转坐标竟然有四个,分别为x, y, z, w,急的我去MMD里查看一下,发现和我印象中没有什么差别
都是[-180, 180]的角度值,我用程序跑的时候,这四个值完全看