欢迎光临
我们一直在努力

四元数相乘实现高效旋转组合

在OpenGL图形编程中,四元数相乘(Quaternion Multiplication) 是实现旋转组合与累积的核心数学运算。一个旋转四元数可以表示一次旋转,而连续多次旋转(例如,先绕Y轴旋转90度,再绕自身X轴旋转30度)则需要通过四元数的乘法来合并成一个等效的最终旋转。这与矩阵乘法的效果类似,但计算效率更高,且能避免万向节死锁。

运算类型作用数学本质OpenGL中的对应操作
四元数相乘 组合两个或多个旋转,得到一个新的等效旋转。 在四维超复数空间中的哈密顿积(Hamilton Product),不满足交换律(q1 * q2 ≠ q2 * q1),这与3D旋转的顺序性一致。 用于增量更新物体朝向,例如根据输入每帧更新相机旋转。
四元数转换矩阵 将四元数旋转应用到顶点数据。 从4维复数到3×3(或4×4)旋转矩阵的映射,用于着色器计算。 在CPU端计算好旋转矩阵,通过uniform传递到着色器。
四元数插值(SLERP) 在两个旋转之间平滑过渡。 在四维单位球面上沿大圆弧进行插值。 用于关键帧动画,实现平滑的旋转过渡。

一、四元数乘法的数学原理

给定两个四元数 q₁ = [w₁, (x₁, y₁, z₁)] 和 q₂ = [w₂, (x₂, y₂, z₂)],它们的乘积 q = q₁ * q₂ 可以通过哈密顿规则计算。其结果 q = [w, (x, y, z)] 的各个分量如下:

w = w₁*w₂ – x₁*x₂ – y₁*y₂ – z₁*z₂
x = w₁*x₂ + x₁*w₂ + y₁*z₂ – z₁*y₂
y = w₁*y₂ – x₁*z₂ + y₁*w₂ + z₁*x₂
z = w₁*z₂ + x₁*y₂ – y₁*x₂ + z₁*w₂

关键特性:

  • 非交换律:q₁ * q₂ ≠ q₂ * q₁。这直观反映了3D空间中旋转顺序的重要性。先绕世界Y轴旋转,再绕本地X轴旋转,与先绕本地X轴旋转,再绕世界Y轴旋转,得到的结果通常是不同的。
  • 单位四元数:用于表示旋转的四元数必须是单位四元数,即其模长 sqrt(w² + x² + y² + z²) = 1。两个单位四元数相乘的结果仍然是单位四元数。
  • 逆旋转:一个旋转四元数 q 的逆(或共轭,对于单位四元数二者等价)为 q⁻¹ = [w, (-x, -y, -z)]。q * q⁻¹ = [1, (0,0,0)](单位四元数),表示没有旋转。
  • 二、OpenGL中的代码实现详解

    虽然可以手动实现上述乘法公式,但在实践中,通常使用成熟的数学库如 GLM (OpenGL Mathematics),它提供了高度优化的实现。

    1. 使用GLM库进行四元数乘法

    #include <glm/glm.hpp>
    #include <glm/gtc/quaternion.hpp>

    int main() {
    // 定义两个旋转:q1绕Y轴旋转90度,q2绕X轴旋转45度
    glm::vec3 axis1(0.0f, 1.0f, 0.0f); // Y轴
    glm::vec3 axis2(1.0f, 0.0f, 0.0f); // X轴
    float angle1 = glm::radians(90.0f);
    float angle2 = glm::radians(45.0f);

    // 创建两个旋转四元数
    glm::quat q1 = glm::angleAxis(angle1, axis1);
    glm::quat q2 = glm::angleAxis(angle2, axis2);

    // 核心:四元数相乘。组合旋转 q_total = q2 * q1。
    // 注意:在OpenGL的右手坐标系中,乘法顺序通常为 先发生的旋转 * 后发生的旋转。
    // 这里假设先执行q1(绕Y轴转),再执行q2(绕本地X轴转)。
    glm::quat q_total = q2 * q1; // GLM重载了 * 运算符,用于四元数乘法

    // 验证非交换律:不同的顺序,结果不同
    glm::quat q_total_reverse = q1 * q2; // 先X轴转,再Y轴转
    // q_total 与 q_total_reverse 在绝大多数情况下不相等

    // 将组合后的四元数转换为旋转矩阵,用于渲染
    glm::mat4 rotationMatrix = glm::mat4_cast(q_total);

    // … (将rotationMatrix传递给着色器)

    return 0;
    }

    代码注释:glm::quat 类重载了乘法运算符 *,直接使用 q2 * q1 即可完成哈密顿积的计算。正确的乘法顺序是理解旋转叠加的关键,通常约定为“从右向左”或“从旧到新”。

    2. 手动实现四元数乘法(理解原理)

    为了深入理解,这里提供其手写实现:

    struct Quaternion {
    float w, x, y, z;
    // 构造函数等…
    };

    Quaternion multiplyQuaternions(const Quaternion& q1, const Quaternion& q2) {
    Quaternion result;
    // 根据哈密顿积公式计算
    result.w = q1.w * q2.w – q1.x * q2.x – q1.y * q2.y – q1.z * q2.z;
    result.x = q1.w * q2.x + q1.x * q2.w + q1.y * q2.z – q1.z * q2.y;
    result.y = q1.w * q2.y – q1.x * q2.z + q1.y * q2.w + q1.z * q2.x;
    result.z = q1.w * q2.z + q1.x * q2.y – q1.y * q2.x + q1.z * q2.w;
    // 注意:为保证结果仍是单位四元数,输入q1和q2必须已经是单位四元数。
    // 可在乘法后进行一次归一化:float norm = sqrt(…); result.w/=norm; …
    return result;
    }

    三、核心应用场景:增量式旋转更新

    四元数相乘最经典的应用是实现平滑的、累积的旋转,例如处理鼠标或触控输入来控制3D物体或摄像机。

    问题场景:用户希望用鼠标拖拽来旋转一个3D模型。每帧获取鼠标的位移(Δx, Δy)。

    解决方案步骤(基于四元数乘法):

  • 计算增量旋转:将2D鼠标位移解释为一个微小的旋转。例如,将Δx映射为绕世界坐标系Y轴的旋转角度,将Δy映射为绕物体本地坐标系X轴(或经过当前方向变换后的右向量轴)的旋转角度。 float sensitivity = 0.01f;
    glm::quat deltaQuatY = glm::angleAxis(Δx * sensitivity, glm::vec3(0, 1, 0));
    glm::quat deltaQuatX = glm::angleAxis(Δy * sensitivity, currentRightVector);
    glm::quat deltaQuat = deltaQuatY * deltaQuatX; // 组合水平和垂直旋转
  • 累积总旋转:将本帧的增量旋转与物体上一帧的总旋转状态相乘,得到新的总旋转。注意乘法顺序:新旋转 = 增量旋转 * 旧旋转。这确保了旋转是基于物体当前朝向进行的(本地旋转)。 // currentOrientation 是物体当前朝向的四元数
    // 新的朝向 = 本帧的增量旋转 * 上一帧的朝向
    glm::quat newOrientation = deltaQuat * currentOrientation;
    // 重要:对新的四元数进行归一化,防止浮点数误差累积导致其不再是单位四元数
    newOrientation = glm::normalize(newOrientation);
    currentOrientation = newOrientation; // 更新状态
  • 应用到渲染:将更新后的 currentOrientation 转换为矩阵并传递给着色器。 glm::mat4 modelMatrix = glm::mat4_cast(currentOrientation);
  • 为什么这样做有效? 因为四元数乘法天然地表示了旋转的复合。deltaQuat * currentOrientation 这个操作,其几何意义是:先施加旋转currentOrientation,再在其结果上施加旋转deltaQuat。这与用户“基于当前状态再旋转一点”的直觉完全吻合。通过每帧累积微小的deltaQuat,就实现了流畅的交互式旋转。

    四、与其他操作的结合

    在实际的OpenGL渲染管线中,四元数相乘生成的最终旋转四元数,需要与其他变换(平移、缩放)结合,形成完整的模型矩阵。

    glm::mat4 model = glm::mat4(1.0f);
    // 1. 缩放
    model = glm::scale(model, glm::vec3(scaleFactor));
    // 2. 旋转 (由四元数转换而来)
    model = model * glm::mat4_cast(objectOrientationQuat); // 注意矩阵乘法顺序
    // 3. 平移
    model = glm::translate(model, objectPosition);

    注意矩阵乘法的顺序通常是先缩放,再旋转,最后平移(SRT顺序),这与变换在局部坐标系中的应用顺序相反。

    总结,四元数相乘是OpenGL中管理3D旋转状态的核心运算。它高效、稳定地解决了旋转的组合问题,通过与四元数-矩阵转换和SLERP插值结合,构成了现代3D图形引擎中处理旋转的完整且强大的工具链。理解其非交换性及与旋转顺序的关系,是正确应用它的关键。


    参考来源

    • OpenGL 四元数旋转
    • 四元数和三维旋转
    • OpenGL相机自由移动旋转缩放,四元数,欧拉角,LookAt
    • 旋转矩阵与四元数
    • 四元数学习之用四元数旋转向量
    • OpenGL—基于四元数的摄像机系统

     

    赞(0)
    未经允许不得转载:171主机测评 » 四元数相乘实现高效旋转组合
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址