念一叨

面向游戏开发的四元数教程

本来和浊心师傅说好了十一放假要教他用四元数,结果因为各种各样的不凑巧最后也没找到合适的时间,故作此篇。

通常,要深入理解一个数理概念,最好的方法是在脑海里建立合适的图景(image,或直觉) ——例如,在讨论「温度」时,可以想象很多小球(分子或原子)在空间中飞行(流体)或震动(固体); 在讨论「定积分」时,可以想象函数曲线下一块可变限的曲边梯形 ——然而这需要长时间的练习、在未建立合适图景时的盲目且痛苦的练习,才能够在某个随机的时刻突然开悟(the Eureka moment)。

幸运的是,游戏开发不是数学研究,不需要深入理解一个概念才能够对其各种性质进行探讨; 游戏开发往往注重应用——不管你理不理解这个概念,或者怎么理解都无所谓,只要能用对了、能出活就行了。 本篇文章将尝试用尽可能少的数学细节给读者介绍四元数的应用方法。

第零节:结论

我们先把整篇文章的结论放在这里:

单位四元数是三维旋转的表示,其乘法等同于旋转的复合。

读者可以在每一节读完后回过头来看这个结论,看看是否有新的理解。 Even better yet,可以尝试直接不看后面,从这个结论出发独立思考。

在后面每节中,我都会像这样,先再开头列出该节的结论。 如果你觉得这结论很显然的话,大可以直接跳过该节不看。

第一节:朝向与旋转

朝向与旋转是两种概念。朝向是状态,旋转是过程。

众所周知, 四元数可以用来表示和计算三维旋转。 在解释四元数前,我们首先得搞清楚什么是三维旋转。 读者可能觉得这很显然:

三维旋转就是一种空间变换嘛! 我有一个三维刚体,保持它的一点不动而其余部分随意运动,这个过程就是三维旋转。

这种说法的话,也不能说不对,但是完全不能给我们带来任何关于「旋转到底是什么」的提示。 可能的话,我们希望找到一个更“可付诸实践”的描述或定义,能够按照字面转写成数学结构。

考虑下面的描述:

旋转是只改变点相对于旋转中心的朝向,而不改变距离的变换。

这是一个很显然的事,不过它足够“可付诸实践”吗? Let's find out.

给定球面上一点 $A$,它经过中心在球心 $O$ 的旋转变换后到达球面上的另一点 $A^\prime$:

此处有插图。

这符合刚才描述的性质,球面上的点距离球心的距离是恒定的。 但是为了确保“可付诸实践”,应该能够从给定的信息完全描述一个旋转。 目前,给定的信息是一点在旋转前后的位置与旋转中心的位置。 很遗憾地,仅仅知道这些并不足以完全描述一个旋转:

考虑 $A$ 附近的另一点 $B$,试问它在经过同样的旋转变换后会到达哪一点?

如果你考虑得足够全面,你会发现 $B^\prime$ 可以是球面上距离 $A^\prime$ 与 $|AB|$ 等距的圆环上的任意一点!

此处有插图。

这是因为旋转不光涉及到旋转角度,同时还涉及转轴的方向。 能够将 $A$ 变换到 $A^\prime$ 的旋转,其转轴可以是从旋转中心指向球面上距离两者等距的任意一点的射线。 如果你检查所有这些可能的转轴的话,你会发现它们与球面相交的部分正好是垂直平分两点的大圆great circle)。 这实际上是在说:单一朝向变化不能完全确定一个三维旋转

注:这不很显然。 作为对比,一个点在平移前后的位置可以完全决定一个平移。

事实上,如果给定至少两个点(其朝向相对于旋转中心不平行)在旋转前后的朝向的话,就可以唯一确定一个三维旋转了。 (思考题:为什么是两个?) 每个点确定一个转轴可能在的大圆,两个大圆相交与球面上的一对对拓点,唯一确定一条转轴,进而旋转角也确定了。

现在,我们可以把前面发现的关于旋转与朝向的性质总结归纳成条:

  • 三维旋转是一种变换(映射),将三维空间里的一个点映射到另一个点。

    今后我们默认旋转中心总在坐标原点,这样「点」与「向量」就是同义词了。

  • 旋转不改变向量的长度,只改变朝向。

    因此我们可以不涉及长度地讨论旋转,而将其视作朝向到朝向的变换。

  • 仅凭一个向量在旋转前后的朝向变化,不足以确定一个旋转。
  • 两组不平行的朝向变化对足以确定一个旋转。
  • 一个旋转又可以由转轴与角度唯一确定。
  • 这样定义的「旋转」没有中间状态,只是直接把一个朝向变成另一个朝向。

    至于如何插值,后面会说。

第二节:表象

全体三维旋转构成一个性质良好的结构,任意两个旋转的复合还是一个旋转,并且存在(唯一)一个效果为零的旋转。

在前一节中,我们用了不少诸如「一个旋转」这种表述。 为了有效地在数学语境里讨论三维旋转,我们引入以下形式化记号:

记全体三维旋转所构成的群为 $\mathrm{Spin}(3)$。 任意三维旋转 $\mathcal R\in\mathrm{Spin}(3)$ 施于向量 $\mathbf v$ 记为 $\mathcal R(\mathbf v)$, 或简记为 $\mathcal R\mathbf v$。 特别地,要指定旋转的转轴与角度,可以记作 $\mathcal R_{\mathbf a,\theta}$。 例如,在 Unity 左手系中,将 x 轴单位向量绕 y 轴转 1/4 圈得到 z 轴单位向量,记作: $$\mathcal R_{\mathbf y,\tau/4}(\mathbf x)=\mathbf z.$$ 这种记号并非通用记号,只是为了方便本文进行的临时约定。

注意,这里我们只说 $\mathcal R$ 是三维旋转,而并没有说它实际上是什么。 能够表示三维旋转的数学结构有很多,欧拉角、旋转矩阵、四元数…… 这里的 $\mathcal R$ 可以是任意一种结构,但重点在于我们不关心,只需要知道它是三维旋转并且可作用于三维向量上即可。

像这样不关心实质而只关心功能的,数学上称之表象;与之对应的实质则称为表示。 用表象思考、演算可以让我们不拘泥于底层的运算细节,看到更本质的结构。

注:这里可以与 OOP 里的多态接口、线性代数里的 Dirac 括号平行对比理解。

三维旋转有一个很重要的性质是它们可以复合。 这是说,先执行一次任意旋转,然后再执行任意一次旋转,这两步的效果之总和仍然是一个旋转。 我们不妨用符号 $\ast$ 来表示旋转的复合: $$(\mathcal R_1\ast\mathcal R_2)(\mathbf v)=\mathcal R_1(\mathcal R_2(\mathbf v)),$$ $$ \forall\mathcal R_1,\mathcal R_2,\ \exists\mathcal R^\prime,\ \mathcal R^\prime=\mathcal R_1\ast\mathcal R_2. $$ 并且这复合是可结合的,即对于任意三个旋转,有: $$ (\mathcal R_1\ast\mathcal R_2)\ast\mathcal R_3= \mathcal R_1\ast(\mathcal R_2\ast\mathcal R_3). $$

另一个性质是任意旋转都有一个旋转作为自己的。 这是说,当我对某个物体执行了一次旋转后,可以再执行一个反向的旋转把它给转回原样。 顺便一提,「什么都不做」的变换也可以看作是一个旋转,只不过旋转的角度为 0。 这在数学上等同于恒等变换,即 $\mathrm{id}$;当作旋转时不妨记作 $\mathcal R_0$。 $$ \forall\mathcal R,\ \exists\mathcal R^{-1},\ \mathcal R\ast\mathcal R^{-1}=\mathcal R_0:=\mathrm{id}. $$

熟悉群论的读者可能已经看出来了,这四条性质正是的定义。 因此 $\mathrm{Spin}(3)$ 的学名叫做三维旋转群。 读者可以参考前面的链接对比群的性质逐条参考。 凡是群所满足的推论,三维旋转群也都满足。

尽管上面这几条性质都很直观,甚至听起来像废话,但是三维旋转却不具备一个我们很习以为常的性质:可交换性。 即,三维旋转的复合在一般情况下是不允许改变次序的。 例如,将 x 轴上的单位向量先绕 y 轴旋转 1/4 圈,再绕 x 轴旋转 1/4 圈,得到与 z 轴平行的向量; 但是先绕 x 再绕 y 旋转,得到的是与 y 轴平行的向量。