前言

在游戏开发、计算机图形学、机器人学、VR/AR等领域,准确、高效地表示和操作三维空间中的旋转,是构建一切动态世界的基础。然而,“旋转”这个看似简单的概念,在数学和工程上有多种截然不同的表达方式,每一种都有其独特的优势和无法回避的“天坑”。

你可能听说过“万向节死锁”,可能对四元数的“抽象”感到困惑,也可能好奇为什么AI领域偏爱一种叫rotation_6d的新奇玩意儿。本文将为你一次性梳理清楚欧拉角、轴角式、旋转矩阵、四元数、Rotation6D这五种主流旋转表示法的核心思想、优缺点以及它们各自的“最佳”应用场景。


一、欧拉角 (Euler Angles) - 最直观的“积木”

  • 是什么 (What is it?)
    这是最符合人类直觉的旋转方式。它将一个复杂的3D旋转分解为三次沿着固定坐标轴(如X, Y, Z)的简单旋转。例如,“先绕Y轴旋转45度(偏航 Yaw),再绕X轴旋转30度(俯仰 Pitch),最后绕Z轴旋转15度(翻滚 Roll)”。

    关键点: 旋转的顺序至关重要!同样的三个角度,不同的旋转顺序(如 ZYX vs XYZ)会得到完全不同的最终姿态。

  • 优缺点 (Pros and Cons)

    • 优点:
      • 极其直观: 人类最容易理解和编辑,只有3个数字。
      • 节省空间: 只需要3个浮点数存储。
    • 缺点:
      • 万向节死锁 (Gimbal Lock): 这是它的致命缺陷。在特定姿态下(例如,Pitch角为±90度时),三个旋转轴中的两个会重合,导致丢失一个旋转自由度,使得物体无法向某个方向转动。
      • 插值效果差: 在两个欧拉角姿态之间进行线性插值,物体的旋转路径会非常不自然,速度也不均匀。
      • 歧义性: 同一个空间姿态可能有多组不同的欧拉角表示。
  • 核心应用场景 (Key Use Cases)

    • 3D软件和游戏引擎的编辑器界面: 如Unity和Unreal Engine的Inspector面板中显示的Rotation。
    • 为什么用它? 因为它为美术师、设计师和开发者提供了一个可读、可编辑的接口。当你需要精确设置一个“朝上30度”的旋转时,直接输入数字远比操作四元数或矩阵方便。它作为人机交互的接口非常优秀,但在底层的计算和存储中通常会被转换为其他更稳健的格式。

二、轴角式 (Axis-Angle) - 最纯粹的“几何”

  • 是什么 (What is it?)
    它用一个旋转轴 (Axis) 和一个旋转角度 (Angle) 来定义旋转。例如,“围绕向量 (0, 1, 0) 旋转 90 度”。这完美符合罗德里格旋转公式(Rodrigues’ Rotation Formula)的描述。

  • 优缺点 (Pros and Cons)

    • 优点:
      • 几何意义清晰: 非常直观地描述了一个旋转动作的本质。
      • 无万向节死锁: 不存在轴重合的问题。
      • 紧凑: 存储一个三维向量和一个角度,共4个浮点数。
    • 缺点:
      • 难以组合: 连续进行两次轴角旋转,其结果的“新轴角”非常难以直接计算。通常需要先转成矩阵或四元数进行组合运算,再转回来。
      • 插值不直接: 简单地对轴和角分别进行线性插值,效果可能不理想。
  • 核心应用场景 (Key Use Cases)

    • 物理引擎中的约束设置: 例如,定义一个门的合页(Hinge Joint)或车轮的转轴。
    • 3D建模软件的用户指令: 当用户希望围绕某条特定的边或轴线旋转物体时。
    • 为什么用它? 因为它直接映射到物理世界或用户的几何意图。当旋转的轴心非常明确时,轴角式是最自然的描述方式。

三、旋转矩阵 (Rotation Matrix) - 最底层的“苦力”

  • 是什么 (What is it?)
    一个 3x3 的正交矩阵RTR=IR^T R = IRTR=I, det⁡(R)=1\det(R)=1det(R)=1)。矩阵的三列(或三行)分别代表了原始坐标系的X, Y, Z三个基向量经过旋转后,在新的坐标系下的方向。

  • 优缺点 (Pros and Cons)

    • 优点:
      • 无万向节死锁: 绝对稳健。
      • 应用旋转最高效: 将一个向量(或大量顶点)进行旋转,只需要一次矩阵-向量乘法。这是GPU的拿手好戏。
      • 组合旋转方便: 多个旋转的叠加就是矩阵的连乘。
    • 缺点:
      • 不直观: 人类很难从9个数字中直接看出旋转的轴和角度。
      • 存储冗余: 用9个浮点数表示一个仅有3个自由度的旋转,浪费空间。
      • 插值极差: 对两个旋转矩阵的9个元素直接进行线性插值,得到的结果矩阵通常不再是正交矩阵,会导致物体在旋转过程中被拉伸或压缩。
      • 容易出错: 浮点数精度问题可能导致矩阵不再严格正交,需要定期“正交化”来修正。
  • 核心应用场景 (Key Use Cases)

    • 图形渲染管线: 作为模型-视图-投影 (MVP) 变换矩阵的一部分,最终在GPU上对顶点进行变换。
    • 物理引擎和机器人学的底层计算: 在进行大量坐标变换和动力学计算时。
    • 为什么用它? 因为它是线性代数的通用语言,是硬件(GPU)的原生计算形式。在需要对成千上万个点进行相同旋转变换时,它的计算效率无可替代。

四、四元数 (Quaternion) - 最优雅的“全能选手”

  • 是什么 (What is it?)
    一个扩展的复数,形式为 q=w+xi+yj+zkq = w + x\mathbf{i} + y\mathbf{j} + z\mathbf{k}q=w+xi+yj+zk。在3D旋转中,我们使用单位四元数w2+x2+y2+z2=1w^2 + x^2 + y^2 + z^2 = 1w2+x2+y2+z2=1),它可以看作是对轴角式的一种紧凑且计算性能更优的编码。

  • 优缺点 (Pros and Cons)

    • 优点:
      • 无万向节死锁: 完美解决了欧拉角的痛点。
      • 球面线性插值(Slerp)完美: 可以在两个姿态间提供最短、最平滑的插值路径,角速度恒定,是制作流畅动画的关键
      • 存储紧凑: 只需4个浮点数。
      • 计算高效: 组合旋转(四元数乘法)比矩阵乘法更快。
    • 缺点:
      • 极其不直观: 数学概念抽象,人类很难直接理解和编辑四元数的四个分量。
  • 核心应用场景 (Key Use Cases)

    • 游戏引擎和动画系统的核心: 几乎所有现代实时3D应用(游戏、VR等)都使用四元数在内部表示和计算旋转。
    • 航空航天、无人机姿态控制。
    • 为什么用它? 因为它在避免万向节死锁、提供高质量插值、保持计算效率和存储紧凑性之间取得了完美的平衡。对于需要大量平滑动态旋转的场景,四元数是事实上的工业标准。

五、Rotation 6D - 最新潮的“AI宠儿”

  • 是什么 (What is it?)
    一种主要用于深度学习领域的旋转表示法。它通过存储旋转矩阵的前两列来表示一个旋转。由于旋转矩阵是正交的,前两列向量互相垂直,第三列可以通过前两列的叉乘计算得出。因此,用6个数字(两个三维向量)就可以无损地代表一个旋转。

  • 优缺点 (Pros and Cons)

    • 优点:
      • 对神经网络友好: 它是连续的,且无约束。神经网络可以直接输出6个任意的浮点数,然后通过格拉姆-施密特正交化总能恢复出一个有效的旋转矩阵。这比让网络去学习四元数的“单位长度”约束或矩阵的“正交”约束要简单得多。
      • 无万向节死锁。
    • 缺点:
      • 存储冗余: 比四元数多用2个浮点数。
      • 不直观: 和矩阵一样,人类难以理解。
      • 应用领域窄: 目前几乎只在AI相关的姿态估计、动作生成等领域使用。
  • 核心应用场景 (Key Use Cases)

    • AI动作生成/姿态预测模型的输出层: 作为神经网络预测角色骨骼旋转的直接输出。
    • 为什么用它? 因为它极大地降低了神经网络学习旋转的难度。它将一个有约束的优化问题(例如,输出一个单位四元数)转变为一个无约束的优化问题,使得模型的训练更加稳定和高效。

总结与对比

表达形式 核心概念 (What is it?) 重要性质 (Key Properties) 优点 (Pros) 缺点 (Cons) 核心应用场景
欧拉角 (Euler Angles) 将复杂旋转分解为沿三个固定坐标轴的依次旋转。 不可交换性:改变旋转顺序结果完全不同;周期性:存在多组角度对应同一姿态(如加减360度)。 极其直观,人类易理解和编辑;存储极省空间(3个参数)。 万向节死锁(丢失自由度);插值效果差,路径不自然;存在多组解的歧义性。 3D软件和游戏引擎编辑器(UI人机交互接口)。
轴角式 (Axis-Angle) 围绕空间中一个特定的“轴”旋转一个指定的“角度”。 零度奇异性:当旋转角为0时,旋转轴方向无意义(未定义);幅度直观:角度直接反映了旋转的绝对幅度。 几何意义清晰,贴合物理直觉;无万向节死锁;存储紧凑(4个参数)。 连续旋转的组合计算困难;插值不直接。 物理引擎约束设置(如关节、车轮);3D建模工具的用户指令。
旋转矩阵 (Rotation Matrix) 3x3正交矩阵,描述坐标系基向量旋转后的新方向。 正交性:矩阵的转置等于它的逆(RT=R−1R^T=R^{-1}RT=R1);行列式为1(保证物体不被缩放或镜像);乘法具有结合律但不可交换。 无万向节死锁,绝对稳健;应用变换最高效(GPU原生);组合多个旋转极方便(矩阵连乘)。 极其不直观;存储冗余(9个参数);插值极差,浮点运算易致精度丢失(需定期正交化修正)。 图形渲染管线(底层顶点变换);物理引擎与机器人学的大量坐标变换。
四元数 (Quaternion) 扩展复数,使用单位四元数对轴角进行计算性能更优的紧凑编码。 单位长度约束:模长必须严格为1;双覆盖性qqq−q-qq 代表同一个物理旋转(相差360度);乘法不可交换。 无万向节死锁;球面线性插值(Slerp)完美,路径平滑且角速度恒定;存储紧凑(4个参数);组合计算效率高于矩阵。 数学概念抽象,极其不直观,人类难以直接理解或编辑。 游戏引擎和动画系统的核心(内部计算标准);航空航天、无人机姿态控制。
Rotation 6D 存储旋转矩阵的前两列向量,第三列由前两列叉乘得出。 拓扑连续性:在三维旋转空间(SO(3))中是完全连续的映射,无任何奇异点或断层;基于格拉姆-施密特正交化还原。 对神经网络极度友好(连续且无约束,极大地降低模型学习难度);无万向节死锁。 存储冗余(6个参数);极不直观;目前应用领域较窄。 深度学习/AI领域的姿态估计;AI动作生成模型的输出层(骨骼旋转预测)。
表示法 存储 (float) 人类可读性 插值质量 万向节死锁 AI友好度 核心优势
欧拉角 3 ⭐⭐⭐⭐⭐ ❌ 有 ⭐⭐ 直观易编辑
轴角式 4 ⭐⭐⭐⭐ ⭐⭐ ✅ 无 ⭐⭐ 几何意义清晰
旋转矩阵 9 ✅ 无 硬件计算高效
四元数 4 ⭐⭐⭐⭐⭐ ✅ 无 ⭐⭐⭐ 插值平滑、全能
Rotation 6D 6 ⭐⭐⭐ ✅ 无 ⭐⭐⭐⭐⭐ 神经网络无约束输出

最终建议

没有“最好”的旋转表示法,只有“最适合”的场景。

  • 当你需要给用户一个编辑接口时,请使用欧拉角
  • 当你需要将旋转作为最终变换应用到大量顶点时,请在底层使用旋转矩阵
  • 当你需要在动画中进行平滑插值或在程序中频繁组合旋转时,四元数是你的不二之вершен。
  • 当你训练一个深度学习模型来生成动作时,请认真考虑使用Rotation 6D作为你的输出表示。

理解它们各自的战场,才能在你的项目中游刃有余地驾驭3D旋转。


模拟面试:旋转表示法的转换

面试官: 好的,我们刚才聊了各种旋转表示法的优缺点。现在我们来谈谈它们之间的转换。我们先从一个常见的开始:如何将一个旋转矩阵转换为四元数?这个过程中有什么需要特别注意的地方吗?

回答:

将旋转矩阵转换为四元数,本质上是从矩阵的元素中反向求解出四元数 q = w + xi + yj + zk 的四个分量。这个过程有几种实现方法,但核心思想是利用四元数到旋转矩阵公式的反向关系。

转换公式可以由旋转矩阵 RRR 的对角线元素的和(即矩阵的迹, Trace)来分情况讨论:

  1. 主要路径: 首先计算矩阵的迹 tr(R) = R11 + R22 + R33。根据公式,我们知道 tr(R) = 4w^2 - 1。因此可以求出 w:
    w=12tr(R)+1w = \frac{1}{2} \sqrt{\text{tr}(R) + 1}w=21tr(R)+1
    一旦 w 被求出,x, y, z 可以通过矩阵的非对角线元素计算得出:
    x=R32−R234wx = \frac{R_{32} - R_{23}}{4w}x=4wR32R23
    y=R13−R314wy = \frac{R_{13} - R_{31}}{4w}y=4wR13R31
    z=R21−R124wz = \frac{R_{21} - R_{12}}{4w}z=4wR21R12

  2. 需要注意的特殊情况:
    这个方法最需要注意的地方是当 w 非常接近于0时(即旋转角度接近180度),tr(R) + 1 会很小,此时用它作为分母会导致数值不稳定,产生巨大的精度误差。

    为了解决这个问题,我们需要检查哪个分量(w, x, y, z)是最大的,并以此为基础来计算其他分量,从而避免除以一个很小的数。

    • 如果 w 是最大的(即 tr(R) > 0),则使用上述公式。
    • 如果 x 是最大的(即 R11 是对角线元素中最大的),则先计算 x,再计算 w, y, z
    • 同理,如果 yz 是最大的,也有对应的计算路径。

    例如,如果 x 最大,则:
    x=121+R11−R22−R33x = \frac{1}{2} \sqrt{1 + R_{11} - R_{22} - R_{33}}x=211+R11R22R33
    w=R32−R234x,y=R12+R214x,z=R13+R314xw = \frac{R_{32} - R_{23}}{4x}, \quad y = \frac{R_{12} + R_{21}}{4x}, \quad z = \frac{R_{13} + R_{31}}{4x}w=4xR32R23,y=4xR12+R21,z=4xR13+R31

总结一下,这个转换过程的关键注意事项是:

  • 数值稳定性: 必须通过分情况讨论来避免除以接近零的数,保证在任何旋转角度下转换的精度。
  • 符号模糊性: 四元数 q-q 代表同一个旋转。因此,转换结果可能是 q-q,这通常不影响最终的旋转效果,但在需要连续姿态的动画插值中,需要保证符号的一致性(例如,通过点乘检查保证走最短路径)。

面试官: 理解非常深刻。那么下一个问题,为什么我们有时需要将欧拉角转换为四元数?这个转换过程的本质是什么?

回答:

我们通常在以下场景需要将欧拉角转为四元数:

  1. 数据输入: 美术师或设计师在编辑器中通过欧拉角设定好一个姿态,这个姿态数据需要被转换成四元数,才能进入引擎的动画或物理系统进行后续的高效、稳健计算。
  2. 动画插值: 如果动画关键帧是以欧拉角形式存储的,直接对它们进行线性插值会导致路径不自然和万向节死锁问题。正确的做法是先将这些欧拉角关键帧转换为四元数,然后使用Slerp在四元数之间进行插值,最后再将插值结果转回欧拉角(如果需要显示的话)。

这个转换过程的本质是“旋转的组合”。

一个欧拉角旋转(例如,ZYX顺序)代表了三次连续的、围绕单个轴的旋转。而每一次单轴旋转都可以非常简单地表示为一个四元数:

  • 绕X轴旋转 θ\thetaθ 角: qx=(cos⁡(θ2),sin⁡(θ2),0,0)q_x = (\cos(\frac{\theta}{2}), \sin(\frac{\theta}{2}), 0, 0)qx=(cos(2θ),sin(2θ),0,0)
  • 绕Y轴旋转 ϕ\phiϕ 角: qy=(cos⁡(ϕ2),0,sin⁡(ϕ2),0)q_y = (\cos(\frac{\phi}{2}), 0, \sin(\frac{\phi}{2}), 0)qy=(cos(2ϕ),0,sin(2ϕ),0)
  • 绕Z轴旋转 ψ\psiψ 角: qz=(cos⁡(ψ2),0,0,sin⁡(ψ2))q_z = (\cos(\frac{\psi}{2}), 0, 0, \sin(\frac{\psi}{2}))qz=(cos(2ψ),0,0,sin(2ψ))

因此,将一个欧拉角(假设顺序为ZYX)转换为四元数,其本质就是将这三个分别代表单轴旋转的四元数按照指定的顺序乘起来

qfinal=qz⋅qy⋅qxq_{\text{final}} = q_z \cdot q_y \cdot q_xqfinal=qzqyqx

需要强调的是,四元数的乘法不满足交换律,所以这里的顺序必须严格遵守欧拉角的旋转顺序,这也是为什么在转换API中通常会有一个参数来指定RotationOrder


面试官: 非常好。我们来讨论一个实际问题。假设你正在开发一个动画系统,美术师制作的动画关键帧是欧拉角。你将它们转换为四元数并进行Slerp插值,但在预览时发现,角色在两个关键帧之间有时会发生一次“不必要的快速甩动”或“绕远路”。你认为最可能的原因是什么,如何解决?

回答:

这个问题非常经典,最可能的原因是四元数的“双倍覆盖”(Double-Cover)特性没有被正确处理。

  • 问题根源: 在三维旋转空间中,一个特定的姿态只对应一个旋转。但在四元数表示中,单位四元数 q 和它的负 -q 代表的是完全相同的最终旋转姿态。然而,它们在4D超球面上位于相对的两端。

  • 如何导致“绕远路”: 当我们要在两个四元数 q1q_1q1q2q_2q2 之间进行Slerp插值时,Slerp算法默认会沿着它们在超球面上的弧线进行。如果 q1q_1q1q2q_2q2 的夹角大于90度,Slerp会插值一条“长弧”,这在3D空间中就表现为一次大幅度的旋转,即“绕远路”。但实际上, q1q_1q1−q2-q_2q2 之间的夹角是小于90度的,它们之间的“短弧”才是我们想要的平滑过渡。

  • 解决方案:
    在执行Slerp之前,我们需要检查两个四元数的点积 (Dot Product)

    1. 计算 d=dot(q1,q2)d = \text{dot}(q_1, q_2)d=dot(q1,q2)
    2. 如果 d<0d < 0d<0,这意味着两个四元数之间的夹角大于90度,它们正指向超球面上相反的方向。
    3. 此时,我们应该将其中一个四元数反向(例如,使用 −q2-q_2q2 代替 q2q_2q2)来进行插值。这样可以确保插值路径总是沿着最短的弧线进行。
    4. 所以,插值代码应该是 Slerp(q1, dot(q1, q2) < 0 ? -q2 : q2, t)

这个问题是实现一个稳定可靠的四元数动画插值系统的关键,忽略它会导致动画出现各种奇怪的抖动和瑕疵。


面试官: 最后一个问题,关于前沿一些的。你如何将一个 rotation_6d 的表示转换回我们更常用的四元数或旋转矩阵?这个过程的核心步骤是什么?

回答:

rotation_6d 转换回其他格式,是一个重建完整旋转信息的过程,通常是先转为旋转矩阵,再从矩阵转为四元数。

rotation_6d 存储的是旋转矩阵的前两列向量,我们称之为 c1\mathbf{c_1}c1c2\mathbf{c_2}c2

核心步骤如下:

  1. 重建一个正交基 (Orthogonal Basis):

    • 神经网络输出的 c1\mathbf{c_1}c1c2\mathbf{c_2}c2 不一定是严格正交的单位向量。所以第一步是使用格拉姆-施密特正交化 (Gram-Schmidt Orthogonalization) 来修正它们。
    • 第一基向量 b1\mathbf{b_1}b1:c1\mathbf{c_1}c1 标准化,得到 b1=c1∣∣c1∣∣\mathbf{b_1} = \frac{\mathbf{c_1}}{||\mathbf{c_1}||}b1=∣∣c1∣∣c1
    • 第二基向量 b2\mathbf{b_2}b2: 先将 c2\mathbf{c_2}c2b1\mathbf{b_1}b1 上的投影减去,得到一个与 b1\mathbf{b_1}b1 正交的向量,然后再将其标准化。即 b2=c2−(c2⋅b1)b1∣∣c2−(c2⋅b1)b1∣∣\mathbf{b_2} = \frac{\mathbf{c_2} - (\mathbf{c_2} \cdot \mathbf{b_1})\mathbf{b_1}}{||\mathbf{c_2} - (\mathbf{c_2} \cdot \mathbf{b_1})\mathbf{b_1}||}b2=∣∣c2(c2b1)b1∣∣c2(c2b1)b1
  2. 计算第三个基向量:

    • 因为旋转矩阵是右手坐标系,第三列向量 b3\mathbf{b_3}b3 可以通过前两列的叉乘 (Cross Product) 得到:
      b3=b1×b2\mathbf{b_3} = \mathbf{b_1} \times \mathbf{b_2}b3=b1×b2
  3. 构建完整的旋转矩阵:

    • 现在我们有了三个互相正交的单位基向量 b1,b2,b3\mathbf{b_1}, \mathbf{b_2}, \mathbf{b_3}b1,b2,b3,将它们作为列向量,就可以构建出完整的3x3旋转矩阵 R=(b1b2b3)R = \begin{pmatrix} \mathbf{b_1} & \mathbf{b_2} & \mathbf{b_3} \end{pmatrix}R=(b1b2b3)
  4. (可选)转换为四元数:

    • 一旦我们拥有了稳健的旋转矩阵 RRR,就可以使用前面提到的“旋转矩阵转四元数”的方法,将其转换为四元数表示。

这个过程的核心在于通过正交化和叉乘,从一个不完美的、冗余的6D表示中,重建出一个数学上严格有效的3D旋转信息。这也是rotation_6d适用于AI领域的根本原因——它允许网络“自由”输出,后续的数学步骤会确保其有效性。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐