转载请注明出处,这样小编会很有成就感
作者:bilibili->墨色小风 (点个关注不迷路)
本文中的描述术语可能不专业,但更贴近中文理解。方便初学者简单清晰的理解。
*.pmx 文件是 mmd(MikuMIkuDance)的专用模型文件,其中包括 独立3D物体模型的全部信息。
可以存放3d物体与人物模型,以及模型的物理运动规则。
PMX文件的详细数据可以用 PmxEditor 软件来进行编辑查看
具体数据模块按照解析顺序有:
1.模型顶点信息
2.三角形面信息
3.贴图文件引用
4.材料数据信息
5.骨骼数据信息
6.表情数据信息
7.框架数据信息
8.刚体数据信息
9.关节数据信息
最后可能还有柔体信息,但我没用到,没研究
整个PMX模型文件在程序中的运行逻辑模型大概是
根据 1~4 模块组的数据模块来进行图形渲染,
根据 5 之后的数据信息来进行模型物理移动的计算
对于想要读取模型数据的小伙伴来说
前面几项比较重要,后面的比较复杂,
笔者没有详细使用的地方只简单讲讲,
不过数据变量名与PmxEditor软件能够对应,
具体可以学习MMD模型制作与PmxEditor软件使用相关教程。
如果日后详细用到可能会发新文补充。
如有想要详细学习的小伙伴可自行研究交流探讨,
在看过下文的数据结构的简单讲解之后自行研究应该不是什么难事。
对于数据类型的描述,我习惯以类似这样的方式描述:
byte1 : 1字节整数
byte2 : 2字节整数
byte4 : 4字节整数
byte8 : 8字节整数
byte.4 : 4字节小数(浮点数)
byte.8 : 8字节小数(浮点数)
byte4text : 4字节整数记录后续文本的字节数
下面按文件地址数据顺序一个一个进行解析
目录
首先打开一个 PMX文件
首先是文件头
{
byte4 文件标头 = "PMX "; // 通常固定
byte.4 版本号 = 2; // 通常为2
byte1 数据列字节尺寸 = 8; // 通常固定
byte1 编码方式 = ?; // 0:UTF-16LE 或 1:UTF-8
byte1 UV追加 = ?; // (1.模型顶点信息)中用到
byte1 顶点Index尺寸 = ?; // 2或4,寻址单元字节大小
byte1 纹理Index尺寸 = ?; // 2或4,寻址单元字节大小
byte1 材料Index大小 = ?; // 2或4,寻址单元字节大小
byte1 骨骼Index大小 = ?; // 2或4,寻址单元字节大小
byte1 形态Index大小 = ?; // 2或4,寻址单元字节大小
byte1 刚体Index大小 = ?; // 2或4,寻址单元字节大小
byte4text 模型名;
byte4text 模型名(英语);
byte4text 打开时提示;
byte4text 打开时提示(英语);
// 之后的文件数据 是 1~9 具体的数据模块按照解析顺序有:
byte4 模型顶点信息个数;
模型顶点信息[模型顶点信息个数];
byte4 三角形面信息个数;
三角形面信息[三角形面信息个数/3]; // 这里要除3
byte4 贴图文件个数;
贴图文件引用[贴图文件个数];
byte4 材料数据信息个数;
材料数据信息[材料数据信息个数];
byte4 骨骼数据信息个数;
骨骼数据信息[骨骼数据信息个数];
byte4 表情数据信息个数;
表情数据信息[表情数据信息个数];
byte4 框架数据信息个数;
框架数据信息[框架数据信息个数];
byte4 刚体数据信息个数;
刚体数据信息[刚体数据信息个数];
byte4 关节数据信息个数;
关节数据信息[关节数据信息个数];
byte4 柔体数据信息个数;
柔体数据信息[柔体数据信息个数];
...
}
通常情况下 PMX文件标头会以4字节 "PMX " 开头
有的版本好像略有差别
版本号=2,感觉不会变了,因为大家用习惯了,2.0版本的数据结构也较为合理,没有太多改进的必要。
数据列字节尺寸 应该必须=8
编码方式:记录着文件中所有的文本数据的编码方式,当前文件中的文本都要以这样的方式显示
=0时 按 UTF-16LE 字体编码格式存储文本
=1时 按 UTF-8 字体编码格式存储文本
其他值可以自定义,其他软件有可能不支持
UV追加 : (1.模型顶点信息)中用到
之后的6个变量记录的是对于寻址是 寻址单元的字节大小。
就是变量类型占用的字节数。
这样做是为了节省文件体积,
列如顶点个数小于65535,顶点下标可以用2字节整数表示
每个面记录3个顶点下标就能比4字节下标节省 3*(4-2)=6byte 数据
不过对于自己用源码生成的pmx文件,直接设置=4比较简洁
毕竟差几M的文件大小好像也没啥区别
模型名 : 就是模型文件在程序中显示的名称。
打开时提示 : 就是 mmd程序在导入模型文件时弹出的提示框内容,
用来提示文件信息,声明版权和注意事项用。
好像是为了兼容,pmx文件中所有的文本名称可以设置通用的英语名称。
不过mmd软件根据版本,只挑选一种进行使用、显示。
接下来是顶点信息
1.模型顶点信息
{
byte.4[3] 位置; // x,y,z
byte.4[3] 法线; // x,y,z
byte.4[3] UV; // x,y
byte.4[文件头->UV追加*4] UV追加; //
byte1 type;// 0:BDEF1 1:BDEF2 2:BDEF4 3:SDEF
{ // 根据 type
byte?[?] 骨骼ID; // 参考PmxEditor含义
byte.4[?] weight; // 参考PmxEditor含义
byte.4[?] sdef; // 参考PmxEditor含义
}
byte.4 edge_边; // 参考PmxEditor含义
}
位置 : 顶点对于模型坐标的相对位置,加载时与骨骼等位置无关。
法线 : 顶点用于渲染使用的法线
UV 记录的是当前顶点在贴图上的坐标
UV追加 好像是一个点可以在多个贴图上
type 根据类型决定 骨骼ID、weight、sdef 的数据长度和内容。
type=0 时{
byte?[1] 骨骼ID; // byte? = 骨骼Index大小
}
type=1 时{
byte?[2] 骨骼ID; // byte? = 骨骼Index大小
byte.4[1] weight;
}
type=2 时{
byte?[4] 骨骼ID; // byte? = 骨骼Index大小
byte.4[4] weight;
}
type=3 时{
byte?[2] 骨骼ID; // byte? = 骨骼Index大小
byte.4[1] weight;
byte.4[9] sdef;
}
edge_边 : 在PmxEditor中有对应数值,可以参考PmxEditor含义
2.三角形面信息
{
byte?[3] 顶点I; // byte? = 顶点Index尺寸
}
三角形面信息比较简单,3个点组成一个面
变量大小根据 头文件中记录的顶点Index尺寸。
3.贴图文件引用
{
byte4text 贴图文件路径; // 记录文件相对路径
}
贴图文件路径仅仅只是记录一下使用到到文件名,
方便之后用下标标号索引
4.材料数据信息
{
byte4text 材料名;
byte4text 材料名(英语);
byte.4[4] 扩散色; // 扩散色(r,g,b,非透视率)
byte.4[3] 反射色; // 反射色 (r,g,b)
byte.4 反射强度; // 反射强度
byte.4[3] 环境色; // 环境色(r,g,b)
byte1 比特标志;
byte.4[4] 轮廓线色彩;
byte.4 边缘尺寸;
byte? 贴图I; // byte?=纹理Index尺寸
byte? sphereIdx_球体; // byte?=纹理Index尺寸
byte1 sphere_mode_球形;
byte1 shared_toon_共享软件;
{
byte? toon_香椿;
}
byte4text 备忘录;
byte4 绘制顶点数;// 面数*3 = 点数
}
材料名就是材料在显示、使用时的名称
扩散色 : 记录着扩散色(r,g,b,非透视率)
反射色 : 记录的是反射色 (r,g,b)
反射强度、环境色 等含义可以学习下pmxeditor软件
比特标志中记载着功能使用的信息,按位记录以下是否使用
[描绘类型[1]]
[描绘类型[0]]
[顶点色]
[轮廓线_有效]
[本影]
[本影标示]
[地面阴影]
[双面绘制]
其中 描绘类型用两位表示 : 00-Tir 01-Point 10-Line 11-Point!!!
彩色边缘、边缘尺寸 参考pmxeditor软件
贴图I 就是记录之前[3.贴图文件引用]的索引下标
sphereIdx_球体、sphere_mode_球形,跳过
shared_toon_共享软件==0 时{
byte? toon_香椿; // byte? <- 纹理Index尺寸
}
shared_toon_共享软件!=0 时{
byte1 toon_香椿;
}
备忘录 跳过
绘制顶点数 : 这个参数很重要,记录着当前材质需要绘制多少顶点、绘制多少三角形面。
面数=顶点数/3
起始索引=上一个材料起始索引+上一个材料顶点数
解析到这里就能加载渲染整个模型了,
让模型动起来还需要继续解析之后的数据。
5.骨骼数据信息
{
byte4text 骨骼名;
byte4text 骨骼名(英语);
position[3]
byte? parent_idx; // byte? <- 骨骼Index大小
byte4 morph_idx;
byte2 bit_flag;
if((bit_flag & 0x1) !=0)
{
byte? connect_idx; // byte? <- 骨骼Index大小
}
else
{
byte.4 offset[3]
}
if((bit_flag & 0x0300)!=0)
{
byte? invest_parent_idx; // byte? <- 骨骼Index大小
byte.4 invest_rate;
}
if((bit_flag & 0x0400)!=0)
{
byte.4 axis_vector[3]
}
if((bit_flag & 0x0800)!=0)
{
byte.4 x_axis_vector[3]
byte.4 z_axis_vector[3]
}
if((bit_flag & 0x2000)!=0)
{
byte4 parent_key
}
if((bit_flag & 0x0020)!=0)
{
byte? ik_target_idx; // byte? <- 骨骼Index大小
byte4 ik_loop_len;
byte.4 ik_rad_limited;
byte4 ik_linkLen;
byte4 IK_链接个数;
[IK_链接]={
byte? link_idx; // byte? <- 骨骼Index大小
byte1 rad_limited;
byte.4 lower_vector[3];
byte.4 upper_vector[3];
}
}
}
其中 bit_flag 的使用标记值位标记顺序如下(从左到右,高到低)
[0][0]
[外部亲]
[物理后]
[Local轴]
[轴限制]
[移动+]
[旋转+]
[赋予见?]
[?]
[IK]
[操作]
[显示]
[移动]
[旋转]
[末端指向is骨骼]
6.表情数据信息
{
byte4text 表情名;
byte4text 表情名(英语);
byte1 panel;
byte1 type;
byte4 变形结构个数;
[变形结构]={
if(type == 0)
{
byte? morph_idx; // byte? <- 形态Index大小
byte.4 morph_rate;
}
else if(type == 1)
{
byte? vertex_idx; // byte? <- 顶点Index尺寸
byte.4 coodinate_offset[3];
}
else if(type == 2)
{
byte? bone_idx; // byte? <- 骨骼Index大小
byte.4 distance[3];
byte.4 turning[4];
}
else if(3<=type && type<=7) {
byte? vertex_idx; // byte? <- 顶点Index尺寸
byte.4 uv_offset[4];
}else if(type == 8) {
byte? material_idx; // byte? <- 材料Index大小
byte1 offset_type;
byte.4 diffuse[4];
byte.4 specular[3];
byte.4 specular_mod;
byte.4 ambient[3];
byte.4 edge_color[4];
byte.4 edge_size;
byte.4 texture_mod[4];
byte.4 sphere_mod[4];
byte.4 toon_mod[4];
}
}
}
7.框架数据信息
{
byte4text 框架名;
byte4text 框架名(英语);
byte1 flag;
byte4 框架数据个数;
[框架数据]={
byte1 type;
if(type!=0)
{
byte? idx; // byte? <- 形态Index大小
}
else
{
byte? idx; // byte? <- 骨骼Index大小
}
}
}
8.刚体数据信息
{
byte4text 刚体名;
byte4text 刚体名(英语);
byte? bone_idx_骨骼; // byte? <- 骨骼Index大小
byte1 group;
byte2 nocollision_group; // byte? <- 形态Index大小??
byte1 figure;
byte.4 size[3];
byte.4 position_位置[3];
byte.4 rad[3];
byte.4 mass;
byte.4 moving_att;
byte.4 rad_att;
byte.4 bounce_force;
byte.4 frictical_force;
byte1 mode;
}
9.关节数据信息
{
byte4text 关节名;
byte4text 关节名(英语);
byte1 type;
byte1 rigid_a_idx;
byte1 rigid_b_idx;
byte2 未知;
byte.4 position[3];
byte.4 rad[3];
byte.4 position_lower_vector[3];
byte.4 position_upper_vector[3];
byte.4 rad_lower_vector[3];
byte.4 rad_upper_vector[3];
byte.4 baunce_moving[3];
byte.4 baunce_rad[3];
}
柔体(没有)
柔体数据大多模型没有
======== ======== ======== ======== ======== ======== ======== ========
PMX文件信息大致解析完了
总结
这就是pmx文件的几个模块组
1.模型顶点信息
2.三角形面信息
3.贴图文件引用
4.材料数据信息
5.骨骼数据信息
6.表情数据信息
7.框架数据信息
8.刚体数据信息
9.关节数据信息
接下来记述一下渲染显示的逻辑。
渲染绘制一帧图像首先先从 [4.材料数据信息] 开始,
按照每一个材料数据信息中记录的绘制顶点数计算出每个材质需要渲染的面数,
三角形面数=绘制顶点数/3
然后根据[2.三角形面信息]的顺序信息,
依次按顺序读取渲染每个材质的面信息
根据三角形面信息中的三个顶点的索引获取面的3个顶点
然后根据对应参数信息渲染每一个面。
想要实现模型的物理运动,需要将模型的顶点绑定在各个骨骼、表情上,
根据框架、刚体、关节信息进行物理计算,最终实现动态效果。
关于自己实现pmx文件的生成:
PMX文件导入MMD时,骨骼及之后信息的信息个数可以为0,
但至少要有一个纹理才能加载,不然MMD软件会显示运行异常。
对应的有纹理就必须至少要准备有一张贴图链接在纹理上。
纹理材质要想显示出物体则需要准备对应的点和面。
一个pmx文件的最小结构就需要包含:
1.模型顶点信息
2.三角形面信息
3.贴图文件引用
4.材料数据信息
END