在OpenGL图形编程中,四元数相乘(Quaternion Multiplication) 是实现旋转组合与累积的核心数学运算。一个旋转四元数可以表示一次旋转,而连续多次旋转(例如,先绕Y轴旋转90度,再绕自身X轴旋转30度)则需要通过四元数的乘法来合并成一个等效的最终旋转。这与矩阵乘法的效果类似,但计算效率更高,且能避免万向节死锁。
| 四元数相乘 | 组合两个或多个旋转,得到一个新的等效旋转。 | 在四维超复数空间中的哈密顿积(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₂
关键特性:
二、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)。
解决方案步骤(基于四元数乘法):
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; // 组合水平和垂直旋转
// 新的朝向 = 本帧的增量旋转 * 上一帧的朝向
glm::quat newOrientation = deltaQuat * currentOrientation;
// 重要:对新的四元数进行归一化,防止浮点数误差累积导致其不再是单位四元数
newOrientation = glm::normalize(newOrientation);
currentOrientation = newOrientation; // 更新状态
为什么这样做有效? 因为四元数乘法天然地表示了旋转的复合。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—基于四元数的摄像机系统




![第5章,[Win32 章节] :边框绘制函数(六)-171主机测评](https://www.171host.com/wp-content/uploads/2026/08/20260822144238-6a89b55eb002b-220x150.png)