☰
四元数旋转原理与工程避坑:从复数乘法到姿态表示
2026/10/29 4:24:51 网站建设 项目流程

简介:一份围绕四元数与三维旋转关系的技术文档,面向计算机图形学、机器人学与计算机视觉方向的学习者,也适合需要补齐姿态表示基础的开发者。正文从四元数定义、虚单位关系与乘法规则讲起,借助复数与二维旋转的类比,逐步推导四元数到三维旋转矩阵的对应关系,并强调用基础几何和线性代数理解旋转表示,而非只做抽象代数推导,适合作为课程复习、论文查阅或工程实现前的概念梳理材料。压缩包为单个PDF文件,约478KB,篇幅集中、便于检索,正文还给出MATLAB/Octave演示代码与GIF动画的获取线索。已有182人学习,读者可掌握四元数基本运算、旋转矩阵映射及姿态表示思路,为后续处理三维旋转、插值和相关工程问题打基础。

1. 四个数存一个姿态:四元数真正解决的问题不是省内存

把物体绕任意轴转 37.5 度,角度存成三个欧拉角,再和另一个旋转复合,结果经常和预期差一个方向;换成四个数,同样的复合只是十几次乘加,怎么叠都不会拧。这个反直觉的差距来自四元数(Quaternion)的本质:它不是“更省空间的旋转矩阵”,而是复数乘法在三维空间的推广。姿态表示真正难的两件事——复合时不撞奇异点、插值时不走远路——四元数用四个数加一次乘法同时处理了。搞懂它,等于搞懂了 3D 旋转为什么长这样。

2. 复数乘法拆成矩阵:2D 旋转的缩放与旋转为什么能分开

四元数的很多性质是复数的直接类比,绕开复数直接看四元数,会觉得那套 i、j、k 规则是从天上掉下来的。这份资料选择从 2D 旋转切入,是有道理的:2D 里能用手画出来的几何直觉,到了 3D 只需要多处理一个“垂直分量”,剩下的代数结构几乎照搬。先把复数乘法为什么等于“缩放 + 旋转”这件事拆开看,后面四元数的每个公式都能找到对应物。

2.1 复数乘法的矩阵形式:一个矩阵藏着两个变换

任意复数可以写成 z = a + bi,也可以写成向量 [a, b]ᵀ。两个复数相乘,按分配律展开,利用 i² = -1 化简,结果是 (ac - bd) + (ad + bc)i。这个结果恰好等于矩阵 [[a, -b], [b, a]] 乘以向量 [c, d]ᵀ。换句话说,z1 乘 z2 这件事,等价于用 z1 对应的矩阵去变换 z2 对应的向量。矩阵的四个元素只由 z1 的实部和虚部决定,z2 只是被变换的对象。

import math def complex_mul(z1, z2): """(a+bi)(c+di),按分配律展开后合并实部虚部""" a, b = z1 c, d = z2 return (a * c - b * d, a * d + b * c) def complex_matrix(z): """复数 z = a+bi 对应的 2x2 实矩阵 第一列是 z 乘 1 的结果,第二列是 z 乘 i 的结果""" a, b = z return ((a, -b), (b, a)) def mat_vec_mul(m, v): return (m[0][0] * v[0] + m[0][1] * v[1], m[1][0] * v[0] + m[1][1] * v[1]) z1, z2 = (2.0, 3.0), (-1.0, 0.5) direct = complex_mul(z1, z2) via_matrix = mat_vec_mul(complex_matrix(z1), z2) print(direct, via_matrix) # 两个结果一致

上面这段代码验证了一件事:复数乘法不需要单独的规则,它就是矩阵乘向量。complex_matrix的两个列向量分别是 z 乘 1 和 z 乘 i 的结果,这正好是线性变换在基向量上的作用,也是把“复数乘法”翻译成“矩阵”的标准做法。参数上没有任何特殊约定,实部和虚部直接对应矩阵的四个位置。

真正的关键在下一步:把这个矩阵按模长提取出来。设 ∥z∥ = √(a² + b²),把矩阵的每个元素都除以 ∥z∥,再把 ∥z∥ 提到矩阵外面,剩下的矩阵里,a/∥z∥ 就是 cos θ,b/∥z∥ 就是 sin θ,其中 θ = atan2(b, a)。于是 [[a, -b], [b, a]] 被拆成两个矩阵的乘积:左边是 [[∥z∥, 0], [0, ∥z∥]] 这个纯缩放矩阵,右边是 [[cos θ, -sin θ], [sin θ, cos θ]] 这个纯旋转矩阵。

这个分解不是数学游戏,它直接说明了复数乘法的几何语义:缩放 ∥z∥ 倍,再逆时针转 θ 度。而且两个因子互不干扰,缩放只改长度,旋转只改方向。四元数后面要做的事情,本质上就是把“提取模长、分离旋转”这套动作在 4 维空间里重做一遍。

2.2 单位复数与指数形式:旋转角的三种等价写法

当 ∥z∥ = 1 时,缩放因子退化为 1,矩阵只剩旋转部分。此时 a = cos θ、b = sin θ,复数写成 cos θ + i sin θ。这个形式就是单位圆上的一个点,它乘任何复数,效果都是把对方逆时针旋转 θ 度、长度不变。把它代回矩阵,得到的就是教科书里的 2D 旋转矩阵。

再往前走一步,cos θ + i sin θ 可以写成 e^{iθ},这是复指数展开的直接结果。于是 2D 旋转有了三种等价写法:矩阵型 v' = R(θ)v、复数积型 v' = zv、指数型 v' = e^{iθ}v。三种写法算出来的结果完全一样,差别只在于你手上有什么数据。有矩阵就直接乘,有角度就构造 e^{iθ},有现成的单位复数就直接用。

这份资料在这里埋了一个伏笔:三种形式在 2D 里可以随意切换,是因为 2D 旋转只有一个参数 θ。到了 3D,旋转多了一个“轴”,指数形式里的 i 就变成了一个三维单位向量,θ 也要拆成两半。四元数的 q = cos(θ/2) + sin(θ/2)u,长得几乎和 e^{iθ} 一样,那个 1/2 就是从这来的。

注意:θ 用 atan2(b, a) 而不是 atan(b/a),是因为 atan 只能返回 (-π/2, π/2),遇到第二、三象限的复数会给出错误的角度。这个坑在四元数转欧拉角时会再出现一次。

2.3 旋转复合与交换律:2D 的好处到了 3D 就没有了

两个单位复数 z1 = e^{iθ}、z2 = e^{iφ},依次作用在向量 v 上,结果是 z2(z1v) = (z2z1)v。把 z2z1 乘开,用三角恒等式化简,得到 cos(θ+φ) + i sin(θ+φ)。也就是说,两个旋转复合之后还是旋转,新角度就是两个角度之和,模长保持为 1。

更值得注意的是 z2z1 = z1z2。复数乘法满足交换律,所以先转 θ 再转 φ,和先转 φ 再转 θ,结果一模一样。这在 2D 里是显然的:所有旋转都绕着同一个点、在同一个平面内,先后顺序不影响最终朝向。

3D 里这件事立刻失效。拿一本书,先绕 x 轴转 90 度,再绕 y 轴转 90 度,记下书的朝向;复位,改成先绕 y 轴再绕 x 轴,两次的最终朝向不一样。这不是操作问题,是几何事实:3D 旋转群是非交换群。四元数乘法不满足交换律,正是因为这个几何事实,而不是代数上的某种缺陷。理解这一点,后面看到 q1q2 ≠ q2q1 就不会觉得别扭,反而会觉得“本来就该这样”。

3. 轴角旋转:一个单位向量怎么把 3D 旋转压回平面问题

3D 旋转最直观的描述是轴角:给定一根过原点的轴 u,把向量 v 绕着它转 θ 度。这个描述有四个变量(u 的三个坐标加 θ),但 3D 旋转实际上只有三个自由度。多出来的那一个,来自“轴的长度不影响方向”这个事实。把轴约束成单位向量,四个变量就退化成三个有效自由度,后面的推导也全部依赖这个约束。

3.1 为什么旋转轴必须单位化:自由度与方向的争论

定义一个方向只需要两个量,就像地球上的位置只需要经度和纬度。轴角表示里的 (x, y, z) 三个坐标,实际上只有两个在描述方向,第三个描述的是长度。绕 (0, 0, 1) 转和绕 (0, 0, 3) 转,指的是同一根轴。如果放任长度自由变化,同一个旋转会对应无穷多个四元数,复合、插值、比较全部失去意义。

所以推导里直接规定 ∥u∥ = 1。这个约束带来两个具体好处:一是 v 在 u 上的正交投影可以简写成 (u·v)u,不用再除以 u·u;二是叉乘 u×v⊥ 的长度恰好等于 ∥v⊥∥,因为叉乘长度公式里的 sin(π/2) 等于 1,而 ∥u∥ 也等于 1。少了这两个简化,后面的公式会长出一堆分母。

工程代码里,这个约束必须显式执行。用户输入一个轴向量,不管长度是多少,进任何计算之前先归一化。归一化失败(零向量)要单独处理,否则除法会直接产生 NaN,然后这个 NaN 会顺着旋转矩阵污染整个场景图。

3.2 拆成平行与垂直分量:投影加叉乘造出平面基

把 v 拆成平行于 u 的 v∥ 和垂直于 u 的 v⊥,两部分分别旋转再相加。v∥ 的旋转最简单:它和轴重合,转多少度都不动,v'∥ = v∥。真正需要处理的是 v⊥。

v∥ 是 v 在 u 上的正交投影,公式是 (u·v)u(在 u 为单位向量的前提下)。v⊥ 就是 v - v∥。现在问题变成:在一个垂直于 u 的平面里,把 v⊥ 转 θ 度。这个平面里目前只有一个向量,不足以描述旋转,需要再找一个和 v⊥ 正交、且同样位于平面内的向量。叉乘正好干这个:w = u × v⊥,方向垂直于 u 和 v⊥,长度等于 ∥v⊥∥,所以 w 也落在以原点为圆心、∥v⊥∥ 为半径的圆上。

有了 v⊥ 和 w 这两个正交的基向量,平面内的旋转就可以直接写出来:v'⊥ = cos θ·v⊥ + sin θ·w。这一步是纯三角学,把旋转后的向量分解到两个基上,系数分别是 cos θ 和 sin θ。注意叉乘的顺序不能反,u × v⊥ 和 v⊥ × u 差一个负号,反了就等于把旋转方向搞反。

3.3 合并两项:得到轴角旋转的一般公式

把平行和垂直两部分的结果加起来,v' = v∥ + cos θ·v⊥ + sin θ·(u × v⊥)。再把 v∥ = (u·v)u、v⊥ = v - (u·v)u 代进去,利用叉乘的分配律把 u × v⊥ 化简成 u × v(因为 u 平行于 v∥,叉乘为零),整理后得到:

v' = cos θ·v + (1 - cos θ)(u·v)u + sin θ·(u × v)

这就是轴角旋转的一般形式。它只用了点乘和叉乘,没有任何矩阵,也不需要坐标系。三项各有明确含义:第一项是原向量按 cos θ 缩放,第二项补上平行分量少转的那部分,第三项是垂直分量转向 w 方向的贡献。

import math def dot(u, v): return sum(a * b for a, b in zip(u, v)) def cross(u, v): return (u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2], u[0] * v[1] - u[1] * v[0]) def normalize(u): n = math.sqrt(dot(u, u)) if n < 1e-12: raise ValueError("旋转轴不能是零向量") return tuple(x / n for x in u) def rotate_by_axis_angle(v, axis, theta): """v' = cosθ·v + (1-cosθ)(u·v)u + sinθ(u×v)""" u = normalize(axis) # 单位化是公式成立的前提 c, s = math.cos(theta), math.sin(theta) uv = dot(u, v) uxv = cross(u, v) return tuple(c * v[i] + (1 - c) * uv * u[i] + s * uxv[i] for i in range(3)) print(rotate_by_axis_angle((1, 0, 0), (0, 0, 5), math.pi / 2)) # 轴长度无关,归一化后等价于 (0,0,1),结果约为 (0, 1, 0)

这段代码把公式逐项照抄,(0, 0, 5)这个非单位轴能正常工作,靠的就是normalize那一行。theta用的是弧度,顺时针逆时针由右手定则决定:拇指指向轴的正方向,四指弯曲的方向就是正旋转方向。把theta改成负值,旋转方向反过来,这比记“顺时针是正还是负”可靠得多。

这个公式还有一个好处:它天然避免了万向锁。欧拉角需要三个轴按固定顺序依次旋转,中间某个角度到 90 度时,两个旋转轴会重合,失去一个自由度。轴角公式里根本没有“顺序”这个概念,一根轴加一个角度,不存在轴重合的问题。四元数继承的正是这个性质。

4. 四元数乘法:ijk = -1 为什么注定了旋转不能交换

四元数的定义和复数几乎一样,唯一的区别是虚部从 1 个变成 3 个:q = a + bi + cj + dk,其中 i² = j² = k² = ijk = -1。这个等式看起来简单,但它决定了四元数的一切性质,包括乘法不交换。很多人背下乘法表却不知道为什么,其实整张表都能从 ijk = -1 推出来。

4.1 从 ijk = -1 推出乘法表:四个分量怎么乘出来的

先推 ij。等式 ijk = -1 两边同时左乘 i,左边变成 i²jk = -jk(因为 i² = -1),右边变成 -i,所以 -jk = -i,即 jk = i。同理,两边同时右乘 k,得到 ij·k² = -k,即 -ij = -k,所以 ij = k。再用 ij = k 两边右乘 j,得到 ij² = kj,即 -i = kj,所以 kj = -i。继续这套操作,能得到完整的六条规则:ij = k、jk = i、ki = j、ji = -k、kj = -i、ik = -j。

对比一下就能看出问题:ij = k 但 ji = -k,交换之后差一个负号。这就是四元数乘法不满足交换律的根源,而且它不是随便规定的,是从 ijk = -1 这个约束里推出来的必然结果。把两个四元数按分配律展开成 16 项,再用上面六条规则逐项化简,合并同类项后得到四个分量:

q1q2 = (ae - bf - cg - dh) + (af + be + ch - dg)i + (ag - bh + ce + df)j + (ah + bg - cf + de)k

其中 q1 = (a, b, c, d)、q2 = (e, f, g, h)。这个公式就是四元数乘法的全部,没有别的隐藏规则。

4.2 左乘矩阵与右乘矩阵:一个 q 对应两个不同的线性变换

把上面的分量公式改写成矩阵乘向量的形式,q2 的四个分量作为列向量,系数全部塞进一个 4×4 矩阵。这个矩阵叫左乘矩阵,因为它等价于“用 q1 左乘 q2”。矩阵的每一列,分别是 q1 乘以 1、i、j、k 的结果,和复数那里完全一样的思路。

def q_mul(q1, q2): a, b, c, d = q1 e, f, g, h = q2 return (a * e - b * f - c * g - d * h, a * f + b * e + c * h - d * g, a * g - b * h + c * e + d * f, a * h + b * g - c * f + d * e) def left_matrix(q): """左乘 q 对应的 4x4 矩阵,列是 q 乘基元素的结果""" a, b, c, d = q return (( a, -b, -c, -d), ( b, a, -d, c), ( c, d, a, -b), ( d, -c, b, a)) q1, q2 = (1.0, 2.0, 3.0, 4.0), (-1.0, 0.5, 2.0, -3.0) m = left_matrix(q1) via_matrix = tuple(sum(m[i][j] * q2[j] for j in range(4)) for i in range(4)) print(q_mul(q1, q2) == via_matrix) # True

注意这个矩阵是反对称的:主对角线之外,m[0][1] = -b 而 m[1][0] = b,其余位置同样成对反号。右乘矩阵长得不一样,它不是简单地把左乘矩阵转置,而是四个分量的排列顺序整体后移一位再取反。两者对应同一组系数、不同的排列方式,这也直接说明了为什么 q1q2 和 q2q1 是两个不同的结果。

4.3 半角与 qvq*:为什么旋转要乘两次

四元数表示旋转的公式是 q = [cos(θ/2), sin(θ/2)u],其中 u 是单位轴。三个虚部分量构成一个三维向量,实部是半角的余弦。把向量 v 当成实部为 0 的四元数,旋转结果就是 qvq*,q* 是 q 的共轭(虚部取反)。

为什么是两边各乘一次,而不是像复数那样只乘一次?从代数上看,qv 只做了一次左乘,得到的结果不再是纯向量(实部不为零),必须再右乘 q* 把实部消掉,才能变回一个三维向量。从几何上看,每一次乘法各贡献 θ/2 的旋转,两次合起来正好 θ。这也解释了半角的来历:如果只乘一次,角度不用除以 2,但结果会跑出三维空间。

把 q = cos(θ/2) + sin(θ/2)u 代入 qvq*,展开后化简,得到的结果是 cos θ·v + (1 - cos θ)(u·v)u + sin θ(u×v)。和上一章的轴角公式一字不差。这条等价关系是整份资料的核心:四元数不是另一种旋转,它就是轴角旋转换了一套代数写法。

def q_conj(q): a, b, c, d = q return (a, -b, -c, -d) def q_from_axis_angle(axis, theta): u = normalize(axis) half = theta / 2.0 s = math.sin(half) return (math.cos(half), u[0] * s, u[1] * s, u[2] * s) def rotate_by_quat(v, q): """v' = q · v · q*,v 作为实部为 0 的四元数参与运算""" qv = (0.0, v[0], v[1], v[2]) return q_mul(q_mul(q, qv), q_conj(q))[1:] q = q_from_axis_angle((0, 0, 2), math.pi / 2) print(rotate_by_quat((1, 0, 0), q)) # 约 (0, 1, 0) print(rotate_by_axis_angle((1, 0, 0), (0, 0, 1), math.pi / 2)) # 同上

两行输出相同,说明 qvq* 和轴角公式是同一个变换的两种算法。q_from_axis_angle里theta / 2.0这一行是最容易写错的地方,漏掉它,所有旋转角度都会变成一半。rotate_by_quat里把 v 补成实部为 0 的四元数,是 qvq* 能成立的前提,少这一步,q 乘 v 量纲都不对。

提示:q 和 -q 表示同一个旋转。把 q 的四个分量全部取反,相当于在 qvq* 里多乘了 (-1)² = 1,结果不变。这个现象叫双重覆盖,它的工程后果是:比较两个四元数是否表示同一姿态,不能直接比较分量,要看点积的绝对值。

4.4 转矩阵与复合:q2q1 和 R2R1 的对应关系

四元数可以直接转成 3×3 旋转矩阵,公式是:

矩阵位置表达式
R[0][0]1 - 2(c² + d²)
R[0][1]2(bc - ad)
R[0][2]2(bd + ac)
R[1][0]2(bc + ad)
R[1][1]1 - 2(b² + d²)
R[1][2]2(cd - ab)
R[2][0]2(bd - ac)
R[2][1]2(cd + ab)
R[2][2]1 - 2(b² + c²)

其中 q = (a, b, c, d) 是单位四元数。这个公式和摘要里给的那一组等价,差别只在用 1 - 2(...) 还是 a² + b² - c² - d²,单位四元数下两者数值相同,前者少两次乘法,也更容易看出对角线元素的对称性。

复合旋转的规则是 q_net = q2q1,对应矩阵 R_net = R2R1。顺序必须和矩阵保持一致:作用于列向量 v 时,先执行 q1 再执行 q2,写成 q2(q1v),合并成 (q2q1)v。写成代码就是不断往左乘,而不是往右乘。这个细节在后面避坑章节会单独拎出来讲,因为它是最常见的翻车点。

从计算量看,复合两个旋转,矩阵需要 27 次乘法,四元数只需要 16 次;存储上矩阵要 9 个数,四元数只要 4 个。但这些都不是四元数被广泛采用的主要原因,真正的原因是它没有奇异点,而且插值有现成的解法。

5. 避坑排查:四元数在工程里最常见的五种翻车

四元数的公式不长,但每一条都有前置条件,条件不满足时程序不会报错,只会给出“看起来差不多但就是不对”的结果。下面这五种情况在图形学、机器人、仿真里反复出现,每一条都按现象、原因、解决三段拆开。

5.1 旋转轴没归一化:结果被悄悄放大

现象:用轴角构造四元数,旋转角度是对的,但物体明显变大或位置偏移,越远离原点越明显。

原因:推导里所有化简都建立在 ∥u∥ = 1 上。轴不归一化时,v∥ = (u·v)u 会多出一个 ∥u∥² 的因子,叉乘 u×v⊥ 的长度也不再等于 ∥v⊥∥,两项合起来的效果等价于先做了一次缩放再旋转。四元数本身不会报错,因为它只是四个数,错误要等到作用到向量上才暴露。

解决:构造四元数之前强制归一化,零向量单独报错。如果轴来自外部输入或文件,归一化之后还要检查模长是否接近 1,防止数值下溢。这条规则没有例外,任何“我确定这个轴是单位的”的假设都不值得保留。

5.2 半角漏写:所有角度都只有一半

现象:调用旋转接口传 90 度,物体只转了 45 度;传 180 度,物体转了 90 度;传 360 度,物体转了 180 度,看起来像“转不回去”。

原因:四元数公式是 q = [cos(θ/2), sin(θ/2)u],构造时角度必须除以 2。如果接口内部直接用了 cos θ、sin θ,或者从矩阵转四元数时角度处理错了,就会出现整体减半。这个 bug 在角度小的时候不明显,角度一大就非常刺眼。

解决:统一约定——对外接口接收全角,内部构造四元数时除以 2;从四元数反解角度时乘以 2。写一个单元测试,输入 90 度,断言旋转后的向量和预期方向一致,这条测试能挡住绝大多数角度处理错误。

5.3 复合顺序搞反:先 A 后 B 变成了先 B 后 A

现象:代码里先调rotate(q_a)再调rotate(q_b),结果和预期差一个旋转;把两行交换一下,反而对了。

原因:四元数复合遵循矩阵乘法规则,q_net = q_b · q_a 才对应“先 a 后 b”。如果代码写成 q_a · q_b,等价于先 b 后 a。更麻烦的是,有些引擎的旋转接口是“局部坐标左乘、世界坐标右乘”,两种语义混用的时候,单看一行代码根本判断不出对错。

解决:在项目里固定一种约定并写进注释,比如“所有复合一律用 q_new = q_delta * q_old”。涉及父子节点的场景,先手算一个简单例子(绕 z 转 90 度再绕 x 转 90 度),把预期结果写成测试用例,后面所有改动都跑这个用例。

5.4 直接对四元数分量做线性插值:中间姿态绕远路

现象:两个朝向之间做动画,中间帧的姿态会“塌陷”或者绕一大圈,速度忽快忽慢,看起来像被什么东西拽了一下。

原因:四元数是单位球面上的点,对四个分量直接做线性插值,得到的中间结果不在球面上,模长小于 1,归一化之后角速度不均匀。如果两个四元数的点积为负,说明它们走的是球面上的长弧,插值会绕远路。

解决:用球面插值,或者至少用归一化线性插值。插值前先检查点积,为负就把其中一个四元数取反,保证走短弧。这一步不处理,动画会莫名其妙地翻转一下,而且很难通过看代码发现。

5.5 浮点累积漂移:转着转着就不正交了

现象:长时间运行后,旋转矩阵的行向量不再互相垂直,物体出现轻微的拉伸或倾斜;四元数的模长偏离 1,越跑越远。

原因:每一次复合都引入舍入误差,误差会随着帧数累积。四元数虽然比矩阵稳定,但模长漂移依然存在;矩阵的问题更严重,9 个元素各自漂移,最终不再满足正交约束。

解决:每帧复合之后对四元数做一次归一化,代价是几次乘除,可以忽略。用矩阵存旋转的话,需要定期做正交化,代价高得多。如果做的是长时间仿真,还要注意角度累计——四元数只能表示朝向,不能表示“转了多少圈”,需要额外记录圈数。

6. 设计取舍:矩阵、欧拉角、轴角与四元数到底该怎么选

四种表示没有绝对优劣,选型取决于你更怕哪一种失败模式。下面这张表把关键维度列出来:

表示存储复合代价插值奇异点主要问题
旋转矩阵9 个数27 次乘法需转四元数无累积漂移后失去正交性
欧拉角3 个数需转矩阵逐轴插值有万向锁依赖轴序,不可交换
轴角4 个数需转矩阵角度可插,轴难插无轴与角耦合,复合不便
四元数4 个数16 次乘法球面插值无双重覆盖,需归一化

矩阵的优势是能直接作用于向量,渲染管线末端几乎都用矩阵;缺点是存储大、复合贵、漂移后要正交化。欧拉角适合给人和 UI 看,三个角度直观,但一旦参与复合就会撞上万向锁,而且轴序不同结果不同。轴角适合参数化编辑,用户输入一根轴和一个角度很自然,但两个轴角复合需要先转成别的东西。四元数在存储、复合、插值三个维度上都不差,代价是双重覆盖和“不直观”。

我自己的习惯是:内部计算一律用四元数,对外展示转成欧拉角,送进渲染管线前转成矩阵,三者之间的转换点固定在少数几个函数里。这样做的好处是每种表示只在自己擅长的场合出现,出问题时也容易定位是哪一层转换写错了。有一次做长时间仿真,忘了在每帧末尾归一化四元数,跑了几个小时之后物体的朝向开始缓慢偏转,排查了很久才定位到那几行乘法。从那以后,我每次写完旋转相关的循环,都会强制走一遍“归一化 + 单位测试”的流程,哪怕看起来完全没必要。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询