Three中的MMD动画解析

Three中库的常用操作:将向量,四元数等结构独立的定义出来,重复使用以优化性能

Physics

物理演算真正最繁琐的工作在MMD模型的Loader那里,这里拿到的都是解析好的参数

物理世界里面主要有两种东西,都是对Bullet结构的封装:

  • RigidBody:刚体,用于模拟衣服布料,头发等结构,和衣服,头发上的骨骼挂钩,还有对应到模型的人体结构,也和骨骼模型相关
  • Constraint:约束,顾名思义吧

Ammo中使用到的刚体形状:

  • 球体
  • 长方体
  • 胶囊

模型的动画更新步骤:

  1. 调用helper._animateMesh:
    1. 首先进行是Three基本的动作更新
    2. 计算IK骨骼
    3. 计算付与(MMD模型专有技术,英文翻译作Grant)
    4. 更新物理世界
      1. _updateRigidBodies:把图形世界的骨骼状态更新到物理世界,传给RigidBody类内部进行计算
        1. RigidBody调用updateFromBone
        2. 内部遍历调用_setTransformFromBone,在这个函数内部获取图形世界的变换数据并且赋值过来
      2. _stepSimulation:让物理世界进行一定时间的物理模拟
      3. 将物理模拟对模型的位移缩放旋转全部清零
      4. _updateBones:将结构状态返回赋给图形世界,传给RigidBody类内部进行计算
        1. RigidBody调用updateBone,内部为图形世界的骨骼分别更新三大位移和旋转

注意:

  • 物理世界中的刚体并不一定和骨骼完全重合,其中有一个可以配置的偏移参数
  • 整个物理模拟目测是在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

关于这里腿部骨架的层级结构,有两个,搞得我有点蒙:

  • 左足

    • 左ひざ
      • 左足首
        • 左つま先
  • 左足D

    • 左ひざD
      • 左足首D
        • 左足先EX

其中左足首应该是和左足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但是这个东西只可以设定为正数,负数的时候他的算法是失效的

如果动作停止播放,音频根本就不会停止播放

这里面暂停音频播放只会在时间线结束的时候执行,我想要实现的中途暂停播放,定位播放根本无法实现

Contributors: TSKI433