第 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 / 用户输入。 |
轴-角 / 指数映射 |
|
§8.4;物理引擎(角速度)首选;动画数据存储的可选方案。 |
四元数 (quaternion) |
|
q |
= 1`) 表示旋转。 |
§8.5;slerp 球面线性插值——插值平滑;游戏 / 动画 / IK 事实标准。库: |
slerp (球面线性插值) |
在 4D 超球面弧上插值;slerp(q0, q1, t) = |
§8.5.12;关键细节:取 dot 积非负以选最短弧; |
表示间转换 |
二、详细笔记
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):
"用哪种矩阵"——物体 → 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。
|
欧拉角的最大坑 = 万向锁
|
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):
其中 [n̂]_× 是 n̂ 的 反对称矩阵(叉积的矩阵形式)。
|
指数映射 vs 欧拉角
|
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:
基本结构:
核心运算(§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̂]。
|
四元数的"双重覆盖"
|
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):
其中 ω = acos(q0 · q1)。
实现细节(列表 8.3):
-
q0 · q1 < 0时翻转q1(选最短弧)。 -
ω接近 0 时退化为 lerp(避免除零)。 -
ω接近 π 时基本线性(极端情况下也无问题)。
|
slerp vs lerp vs nlerp
|
When:骨骼动画关键帧插值;相机平滑过渡;粒子朝向插值;任何"从姿态 A 平滑过渡到姿态 B"的场景。
Example:q0 = [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)。
-
四元数 → 欧拉角:从四元数分量反解(多种约定)。
|
转换函数的"约定传染"
|
When:所有跨表示的场景——UI 输入 → 内部存储 → GPU 提交;序列化(动画 / 物理)。
Example:游戏代码典型流程——Quaternion q = Quat::fromEuler(pitch, yaw, roll); Mat4 m = Mat4::fromQuat(q); shader.setUniform("model", m);。
三、关键图表
视觉图表
非可视化条目
|
非可视化条目(表 / 算法)
|
核心公式对照表
|
核心公式对照表
|
四、思维导图
mindmap
root((第 8 章 三维旋转))
方向vs朝向
向量2个数
朝向含twist
至少3个数
矩阵形式
行是物体基向量
正交可逆等于转置
欧拉角
headingpitchbank
人类友好
万向锁坑
轴角指数映射
nhat与theta
Euler旋转定理
物理引擎首选
四元数
四维超球面
w与v
slerp平滑
表示间转换
约定差异
库函数封装
跨表示流程
五、重点与易错点
-
方向 ≠ 朝向:方向 2 个数,朝向 ≥3 个数;飞行器姿态必须用朝向。
-
矩阵行 = 物体基向量在 upright 中的坐标(行向量约定下);"物体 → upright" 还是 "upright → 物体"?两者互为转置,代码侧固定前者(更直观)。
-
欧拉角的最大坑 = 万向锁:pitch = ±90° 时 heading / bank 退化;缓解:限制 pitch ∈ (-89°, +89°),内部用四元数。
-
轴-角 / 指数映射的 n̂ 必须单位化;θ 用弧度;Rodrigues 公式的
[n̂]_×是反对称矩阵(叉积的矩阵形式)。 -
四元数的双重覆盖:q 与 -q 表示同一旋转;slerp 实现要先
dot(q0, q1) < 0时翻转以选最短弧。 -
slerp ≠ lerp:lerp 需后续 normalize(非匀速);nlerp = lerp + normalize(接近匀速但仍有偏差);slerp 严格匀速(首选)。
-
四元数乘法不交换:
q1 q2 ≠ q2 q1;复合旋转先右后左。 -
旋转顺序约定:heading / pitch / bank 的"复合顺序"在不同书 / 库中可能不同;UI 输入到内部存储时务必对齐。
-
库函数约定传染:
glm::eulerAngles与Eigen::eulerAngles默认顺序不同;跨库搬运时先验证。 -
物理引擎中角速度优先用指数映射:可表示任意角速度(720° / 秒区分于 360° / 秒);四元数则把这两者视为相同。
-
跨章衔接:第 9 章几何图元(线 / 球 / 平面 / 三角形)会用到本节的叉积;第 11 章力学用指数映射表示角速度;第 13 章曲线动画会用 slerp 插值。