WebGL MMD Physics (Ammo.js) 终极调优黑皮书
症状
- [拉扯炸模] 拖拽/位移模型时,衣服和头发滞后拉长,被扯成面条或尖刺。
- [旋转撕裂] 快速旋转模型(滚筒洗衣机效用)时,衣服因极大离心力瞬间炸裂错位。
- [投石机效应] 加载 VMD 极速舞蹈动作时,衣服像被抛石机甩出一样瞬间崩坏乱飞。
- [平躺坍塌] 角色平躺或倒立时,受限于原版向外的单向关节约束,衣服向内发生挤死震荡。
- [卡死冻结] 衣服/头发较多(高密度骨骼)时,浏览器瞬间卡死长达数秒,帧率狂跌。
根本原因与解决方案
1. Mode 2 空间锚点撕裂与归位失效
- 问题: 原先为了防止 Mode 2 被
setWorldTransform暴力对齐拉扯起刺,曾将其强制降级为 Mode 1。但 Mode 2 对应的刚体(如头发根部)本身没有弹簧连接身体!降级后其失去了固定锚点,只会被重力拉扯落下,动作结束后永远无法归位。 - 解决方案: 撤销降级,正确实现 Mode 2 运动学同步。不仅不能降级,反而应依靠 Ammo.js 最标准的
setCenterOfMassTransform并同步 Motion State,来执行无撕裂的中心重置。并附加body.setActivationState(4)彻底关闭休眠,防止动作静止时刚体被算法冻住回不去。
2. 人体碰撞防撞层的“幽灵穿透”
- 问题: 虽然通过
scale保留了最大的身躯护盾,但在高速舞蹈下,衣服依然会无伤穿插人体。原因是负责同步骨骼躯干 (Mode 0) 的_setTransformFromBone错误使用了setWorldTransform(),这在物理引擎中等同于“每一帧都在瞬移重置”,使得躯干刚体由于无连续运动状态而在 Bullet 内部毫无撞击动量反馈。 - 解决方案: 运动学状态同步 (MotionState Sync)。删掉纯粹的
setWorldTransform,改为专职喂食body.getMotionState().setWorldTransform()。Bullet 会在底层的stepSimulation中根据上一帧对比自动求导算出极其精确的高速内向动能并激活弹道击退反馈!彻底打破幽灵穿模。
3. VMD 瞬移跳变引发的高速投石机效应
- 问题: VMD 高速动作甚至瞬间闪避时,骨骼间发生远距离位移。此时弹簧力无限蓄积,并在下一次步进中以超音速抛出与其绑定的布料/头发顶点。
- 解决方案: 安全限速器 (Velocity Limiter)。在
update(delta)的收尾阶段遍历动态刚体,如果发现LinearVelocity(>50.0) 或AngularVelocity(>30.0) 超出正常物理空间速度上限,则强行按比例归一化缩减向量。这样既能够不改变动能轨道方向,又像降落伞一样掐死了“超音速”乱飞的势头。
4. 非直立角度带来的引力死角挤压
- 问题: MMD 全局重力永远垂直向下,当人平躺时,重力等于把裙子直直往胸口方向压。而这触犯了原版极窄的内向关节防穿模余裕,导致不可解的碰撞死锁。
- 解决方案: 局部相对重力与局部地板 (Local Gravity & Floor)。将全局垂直重力通过
quaternion反算为贴合身体的“局部向脚”重力;同时摒弃水平静止的虚空大地,让承托地板的旋转矩阵直接绑定接驳mesh.quaternion。人物平躺时,大地瞬间化为贴着背部的立墙。
5. 高密度刚体的自发重叠与穿刺
- 问题: 物理迭代次数调低后,引擎不再有算力剥离互相镶嵌的刚体阵列,开局它们就会由于物理重叠排斥力而自我引爆。
- 解决方案: 差异化护盾与高密度特化。
- 弹簧合金化:将 ERP (纠偏复原) 拉至 0.8,Stiffness 增强 2.5 倍,提供毁灭性的原形复原能力。
- 动态泄压阀:将 CFM 调至 0.015,给原本绝对刚性的关节留下微小形变余地,卸载极端扭矩。
- 差异化身躯护盾:身躯刚体 (Mode 0) 永远保持
scale = 1.0且margin = 0.05的厚重防撞护盾;衣服/头发刚体缩水为0.9体积且超薄margin = 0.01。这创造了绝对的“屏障与游丝”互补结构,杜绝互相镶嵌。
6. Bullet / WebAssembly 底层工程暗坑
- 问题 1 (WASM 内存泄漏): 循环中不能直接将
new Ammo.btVector3()喂给涉及重力的接口,GC 回收不到底层 C++ 堆空间会导致秒级 OOM 卡死。解法:类内部挂载单例向量并重复setValue()。 - 问题 2 (静态平面失效):
btStaticPlaneShape不支持实时的中心变换系统(不支持翻转旋转),充当地板会导致断层穿刺。解法:采用极其巨大的薄btBoxShape(100x1x100) 并将 Origin Y轴下移 1 单位作为替代品。
关键经验
不要硬刚物理引擎的迭代算力!面对 Ammo.js (Bullet) 多达几百个带有关节约束的高密度模型,直接调高 numIterations 到 100 会彻底废掉浏览器的 60FPS 单线程。最高效的降维打击一定是将物理转化为纯粹的代码空间钳制(锁死、降级、同移、限速)!