You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何四元数整体Slerp与分轴Slerp后相乘结果不一致?

分轴四元数Slerp插值与完整四元数Slerp结果偏差问题

问题背景

我需要将完整四元数拆分为X、Y、Z三个单轴旋转四元数,分别执行Slerp插值后再相乘合并,但结果与直接对完整四元数执行Slerp的结果存在显著偏差。测试验证过拆分后的单轴四元数可以正确重构原完整四元数,但插值后的结果差异明显。由于动画约束必须采用分轴插值方式,特此询问该方案是否可行。

测试代码及数据

var quat_A_full = quaternion.EulerXYZ(ax_rad, ay_rad, az_rad);//-0.3161251f, 0.7749172f, 0.0828281f, 0.5410246f
var quat_B_full = quaternion.EulerXYZ(bx_rad, by_rad, bz_rad);//-0.5004139f, 0.5069054f, 0.1151911f, 0.6923611f

var quat_A_x = quaternion.RotateX(ax_rad);//0.2424405f, 0f, 0f, 0.9701663f
var quat_A_y = quaternion.RotateY(ay_rad);//0f, 0.8527206f, 0f, 0.5223673f
var quat_A_z = quaternion.RotateZ(az_rad);//0f, 0f, 0.5134861f, 0.8580979f

var quat_B_x = quaternion.RotateX(bx_rad);//-0.7160885f, 0f, 0f, 0.6980096f
var quat_B_y = quaternion.RotateY(by_rad);//0f, 0.4602496f, 0f, 0.8877895f
var quat_B_z = quaternion.RotateZ(bz_rad);//0f, 0f, -0.3183014f, 0.9479896f

var quat_A_full_reconstructed = math.mul(quat_A_z, math.mul(quat_A_y, quat_A_x);
// quat_A_full == quat_A_full_reconstructed <-- 结果为TRUE

var quat_B_full_reconstructed = math.mul(quat_B_z, math.mul(quat_B_y, quat_B_x);
// quat_B_full == quat_B_full_reconstructed <-- 结果也为TRUE

// 但插值后结果偏差明显
var result_full = math.slerp(quat_A_full, quat_B_full, animation);

var result_parts = math.mul(
    math.slerp(quat_A_z, quat_B_z, animation), 
    math.mul(math.slerp(quat_A_y, quat_B_y, animation), 
             math.slerp(quat_A_x, quat_B_x, animation))
);

// 两者结果差异显著:
// result_full:
// (-0.4088441f, 0.6605082f, 0.09956253f, 0.621822f)
// 欧拉角(度): [ x: -107.9114542, y: 47.7336353, z: 80.7913939 ]
// vs
// result_parts:
// (-0.2624312f, 0.6446596f, 0.2598185f, 0.6693567f)
// 欧拉角(度): [ x: -87.4064322, y: 46.6058942, z: 87.1785867 ]

使用的Unity风格Slerp函数

public static quaternion slerp(quaternion q1, quaternion q2, float t)
{
    float dt = dot(q1, q2);
    if (dt < 0.0f)
    {
        dt = -dt;
        q2.value = -q2.value;
    }

    if (dt < 0.9995f)
    {
        float angle = acos(dt);
        float s = rsqrt(1.0f - dt * dt);    // 等价于1.0f / sin(angle)
        float w1 = sin(angle * (1.0f - t)) * s;
        float w2 = sin(angle * t) * s;
        return quaternion(q1.value * w1 + q2.value * w2);
    }
    else
    {
        // 角度较小时使用线性插值
        return nlerp(q1, q2, t);
    }
}

解答

这个方案不可行,核心原因是四元数乘法不满足线性性,分轴插值再合并的逻辑和完整四元数球面插值的本质完全不同:

  • 四元数Slerp是在四元数的球面空间上插值两个旋转之间的最短路径,而分轴插值相当于对每个欧拉分量独立插值后再复合旋转,本质是欧拉角空间的插值,天生存在路径不唯一、万向锁等问题,和球面插值的路径完全不一致。
  • 旋转的复合顺序会影响最终结果,即使单轴插值的顺序和原四元数构建顺序一致,独立插值后的单轴旋转已经脱离了原旋转的耦合关系,无法还原完整四元数Slerp的中间状态。

如果必须满足分轴动画约束,建议采用两种替代方案:

  1. 将四元数转换为欧拉角,对每个欧拉分量进行插值(需处理角度周期跨越±180度的情况),再转换回四元数。但要注意万向锁问题可能带来的异常。
  2. 先对完整四元数执行Slerp,再对结果进行目标轴的旋转约束修正,这种方式既保留了球面插值的平滑性,又能满足约束要求。

内容的提问来源于stack exchange,提问作者Spectraljump

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 04:55:08