Three中的MMD动画解析
Three中库的常用操作:将向量,四元数等结构独立的定义出来,重复使用以优化性能
Physics
物理演算真正最繁琐的工作在MMD模型的Loader那里,这里拿到的都是解析好的参数
物理世界里面主要有两种东西,都是对Bullet结构的封装:
- RigidBody:刚体,用于模拟衣服布料,头发等结构,和衣服,头发上的骨骼挂钩,还有对应到模型的人体结构,也和骨骼模型相关
- Constraint:约束,顾名思义吧
Ammo中使用到的刚体形状:
- 球体
- 长方体
- 胶囊
模型的动画更新步骤:
- 调用helper._animateMesh:
- 首先进行是Three基本的动作更新
- 计算IK骨骼
- 计算付与(MMD模型专有技术,英文翻译作Grant)
- 更新物理世界
- _updateRigidBodies:把图形世界的骨骼状态更新到物理世界,传给RigidBody类内部进行计算
- RigidBody调用updateFromBone
- 内部遍历调用_setTransformFromBone,在这个函数内部获取图形世界的变换数据并且赋值过来
- _stepSimulation:让物理世界进行一定时间的物理模拟
- 将物理模拟对模型的位移缩放旋转全部清零
- _updateBones:将结构状态返回赋给图形世界,传给RigidBody类内部进行计算
- RigidBody调用updateBone,内部为图形世界的骨骼分别更新三大位移和旋转
- _updateRigidBodies:把图形世界的骨骼状态更新到物理世界,传给RigidBody类内部进行计算
注意:
- 物理世界中的刚体并不一定和骨骼完全重合,其中有一个可以配置的偏移参数
- 整个物理模拟目测是在mesh没有任何缩放变换的状态下进行的,即使mesh有缩放位移和旋转,也是在物理演算之后再回复回去的
- 关于warmup函数,应该是先进行短暂的物理模拟,时长为一帧,防止刚上场就突然炸毛
- 自定义的updateOne函数:这个主要是考虑到MMD有付与,要和IK同时解算,不然的话一般的非IK骨骼照理说不需要update的
图形世界中的参数结构
真正从MMD模型文件读取后的原始数据存储在mmd.mesh.geometry.userData.MMD中
要实现这些功能全靠MMDHelper,所有这些动态数据都存在helper的内部,关键结构objects,这是一个WeakMap,键值有动画,模型等等东西,对应到的数据就是跟动画相关的自建数据
UserData的结构
MMD的自有数据存储在geometry的userData里面
- bones:原始的骨骼数据
- constraints:限制,对刚体的运动范围进行限制
- grants:付与,一个MMD特有的鬼东西
- iks:现在准备干活的关键
- rigidBodies:刚体信息
IK数据结构解析:
- effector:被直接影响的骨骼index
- target:IK骨骼的index
- iteration:IK计算精度
- links:链接的骨骼
- index:链接骨骼的index
- enabled:是否启用
- rotationMax:顾名思义
- rotationMin:同上
{
"target": 118,//IK骨骼的index
"effector": 100,//被直接影响的骨骼index
"iteration": 40,//IK计算精度
"maxAngle": 2.0000007152557373,
"links": [//链接的骨骼
{
"index": 99,//链接骨骼的index
"enabled": true,//是否启用
"rotationMin": {//顾名思义,看着应该是欧拉角
"x": 0.008726646192371845,
"y": 0,
"z": 0
},
"rotationMax": {//同上
"x": 3.1415927410125732,
"y": 0,
"z": 0
}
},
{
"index": 98,
"enabled": true
}
]
}
从整个IK链条来看,link要倒着数直到effector
摘自IK源码注释:
/**
* CCD Algorithm
* - https://sites.google.com/site/auraliusproject/ccd-algorithm
*
* // ik parameter example
* //
* // target, effector, index in links are bone index in skeleton.bones.
* // the bones relation should be
* // <-- parent child -->
* // links[ n ], links[ n - 1 ], ..., links[ 0 ], effector
* iks = [ {
* target: 1,
* effector: 2,
* links: [ { index: 5, limitation: new THREE.Vector3( 1, 0, 0 ) }, { index: 4, enabled: false }, { index : 3 } ],
* iteration: 10,
* minAngle: 0.0,
* maxAngle: 1.0,
* } ];
*/
MMD中,一条腿有两个IK骨骼结构,一个是脚掌IK控制脚踝,一个是脚踝IK控制膝盖和大腿,所谓左足IK親其实不是一个IK,结构如下:
- 左足IK親
- 左足IK
- 左つま先IK
- 左足IK
其中两个IK的结构如下:
左足IK:
- 左足
- 左ひざ
- 左足IK
左つま先IK:
- 左足首
- 左つま先IK
关于这里腿部骨架的层级结构,有两个,搞得我有点蒙:
左足
- 左ひざ
- 左足首
- 左つま先
- 左足首
- 左ひざ
左足D
- 左ひざD
- 左足首D
- 左足先EX
- 左足首D
- 左ひざD
其中左足首应该是和左足IK默认处在相同位置,然后腿部骨骼还有一波奇妙的操作,IK直接控制的所有骨骼都没有关联任何顶点……腿部的顶点全都是关联到带D字母的骨骼上的,然后带D的骨骼和不带D的骨骼依照上方的关系一一对应的具有付与(一个MMD里面特别离谱的概念……)关系,所以真正IK带动腿部的链式反应举例长这样:左足IK->左ひざ->左足->左足D->关联“左足D”的顶点
关于付与的介绍:
/**
* Solver for Grant (Fuyo in Japanese. I just google translated because
* Fuyo may be MMD specific term and may not be common word in 3D CG terms.)
* Grant propagates a bone's transform to other bones transforms even if
* they are not children.
* @param {THREE.SkinnedMesh} mesh
* @param {Array<Object>} grants
*/
付与的结构解析:
{
"index": 18,//被付与骨骼
"parentIndex": 16,//主动变换的骨骼
"ratio": 0.8999999761581421,//控制权重
"isLocal": false,//不清楚
"affectRotation": true,//付与旋转
"affectPosition": false,//付与位移
"transformationClass": 0//不清楚
}
MMD骨骼结构中一些关键作用的分析
- センター:控制整个模型的位置
- 全ての親:没什么用
- 下半身:关键是下半身的旋转
Audio载入的分析
MMD播放里面使用AudioManager类来管理音频播放,但是这玩意儿其实做的挺简陋的
它其实是对THREE的audio类的进一步封装,它其实只做一件事,添加一个计时系统用来判断是否应该播放或暂停音频
它里面有记录当前播放时间,但这并不是和音频真正关联的,而是靠MMD动画播放的时间推算出来的,他根本就不知道音频真正播放了多久
有一个选项是delayTime但是这个东西只可以设定为正数,负数的时候他的算法是失效的
如果动作停止播放,音频根本就不会停止播放
这里面暂停音频播放只会在时间线结束的时候执行,我想要实现的中途暂停播放,定位播放根本无法实现