第 8 章 三维旋转 (Rotation in Three Dimensions)

      +

      核心结论

      • 方向 vs 朝向:方向 = 向量(2 个数,§7.3.2 球坐标);朝向 = 含 twist 的完整姿态(最少 3 个数)。"飞行器朝东"是朝向;"向东的箭头"是方向。

      • 矩阵形式:3×3 旋转矩阵的行 = 物体空间基向量在 upright 空间的坐标(行向量约定下);矩阵是正交的,逆 = 转置。

      • 欧拉角 (heading / pitch / bank):3 个角度描述朝向;直观但 有万向锁(pitch = ±90° 时 heading 与 bank 退化)。

      • 轴-角 / 指数映射:(轴 n̂, 角度 θ) 4 个数;指数映射压缩为 e = θ n̂ 3 个数;无万向锁但有 2π 周期性歧义。

      • 四元数 (quaternion):4 个数 (w, x, y, z);本质是 4D 超球面上的旋转;slerp 插值平滑无万向锁;游戏 / 图形事实标准。

      • 表示间转换:5 种表示各有取舍——矩阵用于 GPU 着色 / 复合;欧拉角用于 UI / 人类输入;轴角 / 指数映射用于物理引擎;四元数用于插值 / 动画。

      本章主旨

      本章是 3D 数学的"旋转枢纽"——5 种旋转表示 (matrix / Euler / axis-angle / exponential map / quaternion) 看似冗余,实则各有工程优势。理解每种表示的"什么场景用什么"是关键。第 5 章已讲过旋转矩阵的几何,本章把它放到"朝向表示"的大框架里,并补上欧拉角(人类友好)、四元数(插值友好)、指数映射(物理友好)。第 9 章起几何图元(线 / 球 / 平面)会用到本节的叉积与朝向概念。

      一、核心概念

      本章围绕 6 个核心概念展开:从朝向的精确定义出发,介绍 4 种主流旋转表示 (matrix / Euler / axis-angle / quaternion),最后给表示间转换的工具与各表示的优劣对比。

      概念 定义 + 重要性 实现提示

      方向 vs 朝向

      方向 = 向量(2 个数);朝向 = 含 twist 的完整姿态(≥3 个数)。矩阵的行 = 物体空间基向量在 upright 空间的坐标——这给出了完整的"姿态 + 朝向"信息。

      §8.1-§8.2;§8.2.1 解释"用哪种矩阵"——物体 → upright 与 upright → 物体互为转置(正交矩阵性质)。

      欧拉角 (Euler angles)

      3 个角度:heading / pitch / bank(绕 y / 右臂轴 / 前进轴顺序旋转);人类友好(飞行员 / 相机)。

      §8.3;缺点:万向锁(pitch = ±90° 退化)。UI / 用户输入。

      轴-角 / 指数映射

      (n̂, θ) 4 个数;指数映射 e = θ n̂ 压缩为 3 个数;Euler 旋转定理保证任何旋转 = 一次绕某轴。

      §8.4;物理引擎(角速度)首选;动画数据存储的可选方案。

      四元数 (quaternion)

      [w, (x, y, z)]:w 是标量分量;v = (x, y, z) 是 3D 向量分量;单位四元数 (`

      q

      = 1`) 表示旋转。

      §8.5;slerp 球面线性插值——插值平滑;游戏 / 动画 / IK 事实标准。库:glm::quat, Eigen Quaternion

      slerp (球面线性插值)

      在 4D 超球面弧上插值;slerp(q0, q1, t) = (sin((1-t)ω)/sin ω) q0 + (sin(tω)/sin ω) q1ω = acos(q0 · q1)

      §8.5.12;关键细节:取 dot 积非负以选最短弧;ω 接近 0 时退化为 lerp。

      表示间转换

      二、详细笔记

      2.1 方向 vs 朝向 (Direction vs Orientation)

      What:方向描述一个向量(无 twist);朝向描述一个物体的完整姿态(含 twist / bank)。

      Why:飞行器 / 角色 / 相机的姿态需要 3 个自由度才能完全描述;分清"方向"与"朝向"是后续所有旋转表示的前提。

      How

      参数个数对比:

      • 方向:2D 用 1 个数(极坐标的角度 θ);3D 用 2 个数(heading / pitch 或 lat / long)。

      • 朝向:3D 至少 3 个数(heading / pitch / bank 或一个 3×3 旋转矩阵的 9 个数,其中正交约束减到 3 自由度)。

      "方向"和"朝向"的混淆代价

      向量绕自身轴的"扭转"对向量本身没影响——向量无方向以外的几何结构。但对物体(如飞行器)扭转会改变"哪个方向是上"——这是朝向的本质附加信息。

      When:写朝向控制代码前先问"我需要的是方向还是朝向";光的方向只需 2 个数,飞行器的朝向需要 3 个数。

      Example:导弹的方向 = 飞行方向向量(2 个数);飞行器的朝向 = 飞行方向 + 滚转角(3 个数)。

      2.2 矩阵形式 (Matrix Form)

      What:3×3 旋转矩阵表达朝向;矩阵的行(行向量约定下)= 物体空间基向量在 upright 空间的坐标。

      Why:GPU / 着色器内部都用矩阵;矩阵也是 5 种表示中唯一能直接复合(§5.6)并用于变换链的。

      How

      构造(§8.2):

      \[\mathbf{R}_{\text{obj} \to \text{upright}} = \begin{bmatrix} -\hat{x}_{\text{obj in upright}}- \\ -\hat{y}_{\text{obj in upright}}- \\ -\hat{z}_{\text{obj in upright}}- \end{bmatrix}\]

      "用哪种矩阵"——物体 → upright 还是 upright → 物体?两者互为转置(正交矩阵)。

      "物体 → upright"约定的工程优势

      行向量约定下,矩阵的行 = 物体基向量在 upright 中的坐标——读起来直观:"物体朝向是哪些向量"。 upright → 物体(转置版本)则读起来绕——"在 upright 空间下的物体投影"。代码侧固定前者,可读性大幅提升。

      When:GPU 着色器;变换链 M = T · R · S;任何需要做"复合变换"的场景。

      Example:飞行器飞行方向 = +z(upright 中),上方向 = +y(upright 中),右方向 = +x(upright 中)。则矩阵行 = [(1,0,0), (0,1,0), (0,0,1)] = I——这意味着物体坐标系与 upright 完全重合。

      2.3 欧拉角 (Euler Angles)

      What:3 个角度 heading / pitch / bank 顺序旋转得到朝向;直观、与人类语言对应。

      Why:UI 输入(鼠标 / 手柄)、飞行员 / 航海描述、相机控制等场景天然用 3 个角度。

      How

      heading / pitch / bank 定义(本书):

      • h (heading):绕 +y 轴(垂直轴);0° 朝 +z("北")。

      • p (pitch):绕右臂轴("右"向量);0° 水平,正值向下(左手系)。

      • b (bank):绕前进轴("前"向量);0° 平飞,正值顺时针。

      旋转顺序:行向量约定下 R = R_h · R_p · R_b(先 bank、再 pitch、再 heading)——"最后旋转的写在最左"(行向量约定的"先发生写在右"之反,因为这里的"顺序"是数学复合顺序)。

      万向锁(§8.3.4):pitch = ±90° 时 heading 与 bank 退化(同一方向),自由度从 3 降到 2。

      欧拉角的最大坑 = 万向锁
      • 现象:相机"卡住转不到某个方向",角色"翻转后突然反向"。

      • 根因:pitch = ±90° 时基向量的某些线性组合坍缩。

      • 缓解:限制 pitch ∈ (-89°, +89°);内部用四元数,只在 UI 显示时转欧拉角。

      When:UI 输入;人类可读的姿态描述;磁盘存储(必要时配合限制范围)。

      Example:FPS 相机初始 (h=0°, p=0°, b=0°):朝 +z、平视、不滚转;鼠标向上 = p 增加(俯视)。

      2.4 轴-角与指数映射 (Axis-Angle & Exponential Map)

      What(n̂, θ) 4 个数描述"绕 n̂ 旋转 θ";Euler 旋转定理保证任何 3D 旋转 = 一次绕某轴。指数映射 e = θ n̂ 压缩为 3 个数。

      Why:Euler 旋转定理的理论保证;指数映射微分性质好(适合物理引擎的角速度);轴-角适合动画数据存储(仅记录起点 / 终点 / 时间)。

      How

      轴-角形式:(n̂, θ),4 个数(n̂ 是单位向量)。

      指数映射:e = θ n̂ —— 3 个数;θ = |e|n̂ = e / |e|

      转换为矩阵(Rodrigues 公式,§5.1.3):

      \[\mathbf{R}(\hat{\mathbf{n}}, \theta) = \mathbf{I} + \sin\theta \, [\hat{\mathbf{n}}]_\times + (1 - \cos\theta) \, [\hat{\mathbf{n}}]_\times^2\]

      其中 [n̂]_× 是 n̂ 的 反对称矩阵(叉积的矩阵形式)。

      指数映射 vs 欧拉角
      • 欧拉角:直观、有万向锁。

      • 指数映射:无万向锁(轴可任意)、微分性质好(适合物理引擎)、可表示"任意倍数"(θ 可以是 720° / 秒以表达角速度)。

      • 缺点:θ = 0 时轴无定义(但 e 自动归零,规避了此问题);θ 需模 2π 化简。

      When:物理引擎(角速度用指数映射);动画数据(关键帧插值);表示"绕任意轴的旋转"。

      Example:绕 (1, 1, 0) / √2 旋转 90° = 指数映射 e = 90° · (1, 1, 0) / √2 ≈ (1.11, 1.11, 0) rad(注意 θ 用弧度)。

      2.5 四元数 (Quaternions)

      What:4 个数 [w, (x, y, z)];单位四元数 |q| = 1 表示旋转;几何上是 4D 超球面上的旋转。

      Why:slerp 球面线性插值(动画友好);无万向锁;4 个数表达 3 自由度(多余 1 个用于规避奇异);游戏 / 图形事实标准。

      How

      基本结构:

      \[\mathbf{q} = [w, \mathbf{v}] = [w, (x, y, z)],\quad |\mathbf{q}| = \sqrt{w^2 + x^2 + y^2 + z^2} = 1\]

      核心运算(§8.5.3-§8.5.11):

      • 共轭 q* = [w, -v];逆 = 共轭(单位四元数)。

      • 乘法:复合旋转,先右后左(与矩阵乘法约定一致)。q1 q2 ≠ q2 q1

      • 点积 q0 · q1 = w0 w1 + x0 x1 + y0 y1 + z0 z1 = cosω,其中 ω = 两旋转夹角。

      • 轴角转换(n̂, θ) → q = [cos(θ/2), sin(θ/2) · n̂]

      四元数的"双重覆盖"

      q-q 表示同一旋转。原因是 4D 超球面上的对径点对应 3D 同一旋转(绕 n̂ 转 θ 等价于绕 -n̂ 转 -θ)。这导致"选最短弧"的 slerp 实现需要先 dot(q0, q1) < 0 时翻转其中一个。

      When:角色 / 相机动画(slerp);IK 求解;3D 引擎内部朝向表示(Unity / Unreal 默认四元数)。

      Example:绕 y 轴 90° 旋转 = 四元数 [cos45°, (0, sin45°, 0)] = [0.707, (0, 0.707, 0)]

      2.6 slerp 球面线性插值 (Spherical Linear Interpolation)

      What:在 4D 超球面弧上线性插值;slerp(q0, q1, t) 给 t ∈ [0, 1] 的中间朝向。

      Why:动画过渡(相机摇移、角色转身)需要平滑插值;欧拉角 lerp 会经过万向锁区,slerp 不会。

      How

      公式(§8.5.12):

      \[\text{slerp}(\mathbf{q}_0, \mathbf{q}_1, t) = \frac{\sin((1-t)\omega)}{\sin\omega} \mathbf{q}_0 + \frac{\sin(t\omega)}{\sin\omega} \mathbf{q}_1\]

      其中 ω = acos(q0 · q1)

      实现细节(列表 8.3):

      • q0 · q1 < 0 时翻转 q1(选最短弧)。

      • ω 接近 0 时退化为 lerp(避免除零)。

      • ω 接近 π 时基本线性(极端情况下也无问题)。

      slerp vs lerp vs nlerp
      • lerp:直接线性插值 q0 + t(q1 - q0),结果不一定是单位四元数(需后续 normalize)。

      • nlerp:先 lerp 再 normalize;速度快但非匀速。

      • slerp:球面插值,结果始终是单位四元数,且 匀角速度 过渡;动画首选。

      When:骨骼动画关键帧插值;相机平滑过渡;粒子朝向插值;任何"从姿态 A 平滑过渡到姿态 B"的场景。

      Exampleq0 = [1, 0, 0, 0](无旋转)、q1 = [0.707, (0, 0.707, 0)](绕 y 90°)。ω = acos(1·0.707) = 45°。slerp at t=0.5 = [cos22.5°, (0, sin22.5°, 0)][0.924, (0, 0.383, 0)]——绕 y 45°。

      2.7 表示间转换 (Conversions between Representations)

      What:5 种旋转表示互转;每种转换都有"惯例差异"(手性、角度顺序、轴向约定)。

      Why:实际工程需要跨表示——从 UI 接收欧拉角,内部用四元数,提交 GPU 矩阵。

      How

      常见转换表(§8.7):

      • 欧拉角 → 矩阵:3 次矩阵乘法(按顺序)。

      • 矩阵 → 欧拉角:从矩阵元素反解(多种约定,结果可能不同)。

      • 轴角 → 四元数q = [cos(θ/2), sin(θ/2) · n̂]

      • 四元数 → 矩阵:用 v 与 w 构造 3×3 矩阵(§8.5.7)。

      • 四元数 → 欧拉角:从四元数分量反解(多种约定)。

      转换函数的"约定传染"
      • 欧拉角 → 矩阵需要约定"旋转顺序"(heading / pitch / bank vs yaw / pitch / roll vs Tait-Bryan)。

      • 矩阵 → 欧拉角在不同约定下结果可能不同(甚至出现"branch cut",需用 atan2 处理)。

      • 库函数(GLM / Eigen / DirectXMath)的转换函数各自有内部约定;跨库搬运时务必先验证。

      When:所有跨表示的场景——UI 输入 → 内部存储 → GPU 提交;序列化(动画 / 物理)。

      Example:游戏代码典型流程——Quaternion q = Quat::fromEuler(pitch, yaw, roll); Mat4 m = Mat4::fromQuat(q); shader.setUniform("model", m);

      三、关键图表

      视觉图表

      图 8-1
      Figure 1. 图 8-1:扭转向量无变化
      图 8-2
      Figure 2. 图 8-2:扭转物体改变朝向
      图 8-3
      Figure 3. 图 8-3:用矩阵定义朝向
      图 8-4
      Figure 4. 图 8-4:heading / pitch / bank 约定
      图 8-5
      Figure 5. 图 8-5:万向锁示意
      图 8-6
      Figure 6. 图 8-6:四元数的几何解释
      图 8-7
      Figure 7. 图 8-7:slerp 几何(4D 超球面上的弧)

      非可视化条目

      非可视化条目(表 / 算法)
      编号 内容摘要

      式 8-1

      heading / pitch / bank 的旋转矩阵(按顺序相乘)。

      表 8.1

      5 种旋转表示的优劣对比(自由度 / 存储 / 奇异 / 插值 / 复合)。

      式 8-2 至 8-12

      四元数核心公式:共轭、乘法、点积、轴角转换、矩阵转换、slerp 公式。

      列表 8.1, 8.2, 8.3

      C 代码:欧拉角 → 矩阵、矩阵 → 欧拉角、四元数 slerp。

      表 8.2

      heading / pitch / bank 的旋转矩阵(含 9 个元素的具体表达)。

      核心公式对照表

      核心公式对照表
      表示 公式

      矩阵(行 = 物体基向量在 upright 中)

      \(\mathbf{R} = \begin{bmatrix} -\hat{x}_{\text{obj}}- \\ -\hat{y}_{\text{obj}}- \\ -\hat{z}_{\text{obj}}- \end{bmatrix}\)

      欧拉角(h, p, b)

      \(\mathbf{R} = \mathbf{R}_h \cdot \mathbf{R}_p \cdot \mathbf{R}_b\)

      轴-角 → 矩阵(Rodrigues)

      \(\mathbf{R}(\hat{\mathbf{n}}, \theta) = \mathbf{I} + \sin\theta \, [\hat{\mathbf{n}}\)\times + (1 - \cos\theta) \, [\hat{\mathbf{n}}]\times^2]

      轴-角 → 四元数

      \(\mathbf{q} = [\cos(\theta/2),\ \sin(\theta/2) \cdot \hat{\mathbf{n}}\)]

      四元数乘法

      \(\mathbf{q}_1 \mathbf{q}_2 = [w_1 w_2 - \mathbf{v}_1 \cdot \mathbf{v}_2,\ w_1 \mathbf{v}_2 + w_2 \mathbf{v}_1 + \mathbf{v}_1 \times \mathbf{v}_2\)]

      四元数 slerp

      \(\text{slerp}(\mathbf{q}_0, \mathbf{q}_1, t) = \frac{\sin((1-t)\omega)}{\sin\omega} \mathbf{q}_0 + \frac{\sin(t\omega)}{\sin\omega} \mathbf{q}_1\)

      四、思维导图

      mindmap
        root((第 8 章 三维旋转))
          方向vs朝向
            向量2个数
            朝向含twist
            至少3个数
          矩阵形式
            行是物体基向量
            正交可逆等于转置
          欧拉角
            headingpitchbank
            人类友好
            万向锁坑
          轴角指数映射
            nhat与theta
            Euler旋转定理
            物理引擎首选
          四元数
            四维超球面
            w与v
            slerp平滑
          表示间转换
            约定差异
            库函数封装
            跨表示流程

      五、重点与易错点

      1. 方向 ≠ 朝向:方向 2 个数,朝向 ≥3 个数;飞行器姿态必须用朝向。

      2. 矩阵行 = 物体基向量在 upright 中的坐标(行向量约定下);"物体 → upright" 还是 "upright → 物体"?两者互为转置,代码侧固定前者(更直观)。

      3. 欧拉角的最大坑 = 万向锁:pitch = ±90° 时 heading / bank 退化;缓解:限制 pitch ∈ (-89°, +89°),内部用四元数。

      4. 轴-角 / 指数映射的 n̂ 必须单位化;θ 用弧度;Rodrigues 公式的 [n̂]_× 是反对称矩阵(叉积的矩阵形式)。

      5. 四元数的双重覆盖:q 与 -q 表示同一旋转;slerp 实现要先 dot(q0, q1) < 0 时翻转以选最短弧。

      6. slerp ≠ lerp:lerp 需后续 normalize(非匀速);nlerp = lerp + normalize(接近匀速但仍有偏差);slerp 严格匀速(首选)。

      7. 四元数乘法不交换q1 q2 ≠ q2 q1;复合旋转先右后左。

      8. 旋转顺序约定:heading / pitch / bank 的"复合顺序"在不同书 / 库中可能不同;UI 输入到内部存储时务必对齐。

      9. 库函数约定传染glm::eulerAnglesEigen::eulerAngles 默认顺序不同;跨库搬运时先验证。

      10. 物理引擎中角速度优先用指数映射:可表示任意角速度(720° / 秒区分于 360° / 秒);四元数则把这两者视为相同。

      11. 跨章衔接:第 9 章几何图元(线 / 球 / 平面 / 三角形)会用到本节的叉积;第 11 章力学用指数映射表示角速度;第 13 章曲线动画会用 slerp 插值。