Jacques Heunis

Rotors: A practical introduction for 3D graphics

2023 年 4 月 17 日

在屏幕上呈现 3D 图形时,我们需要一种表达渲染几何体旋转的方式。为了避免使用轴和角度存储旋转带来的问题,我们可以使用 quaternions。然而,quaternions 要求我们在 4 个不同的空间维度中思考,而人类在这方面非常不擅长。幸运的是,还有一种替代方案,有些人认为它更优雅且更容易理解:Rotors。

Rotors 源于一个名为几何代数的数学领域。在过去的几年里,我看到越来越多的人声称我们应该完全抛弃 3D 图形中的 quaternions,并用 rotors 替代它们。我对这两种方法都一无所知,所以我决定尝试一下 rotors。但我很难在网上找到能与我的思考方式产生共鸣的教育材料,因此本文是我自己对 rotors 及相关数学概念的解释。它的目的是为 3D 图形实现旋转,并且部分用作教育文本,部分用作参考页面。

本文分为两个部分:前半部分纯粹是理论性的,我们将了解 rotors 的“来源”、研究它们的行为,并了解如何使用它们来执行旋转。后半部分将介绍实际应用,并包含针对你在 3D 图形中可能遇到的用例的示例代码。

A word on notation

\(\global\def\v#1{\mathbf{#1}}\) 在本文中,我们将用粗体小写字母书写向量、双向量和三向量(例如 \(\v{v}\) 是一个向量)。Rotor 将用粗体大写字母书写(例如 \(\v{R}\) 是一个 rotor)。

我们 3D 空间的基本元素表示为 \(\v{e_1, e_2, e_3}\),因此例如 \(\v{v} = v_1\v{e_1} + v_2\v{e_2} + v_3\v{e_3}\)。

在给出乘法表的情况下,第一个参数是表最左侧列中的条目,第二个参数是表顶行中的条目。

由于本文主要关注 3D 图形和模拟,我们将示例限制为 3 个空间维度。Rotors(与 quaternions 不同)可以轻松扩展到更高的维度,但这留给读者作为练习。

Theory: Adding rotors to our mathematical toolbox

Introducing: The wedge product

我们首先定义一种组合两个向量的新方法:所谓的“wedge product”,写为 \(\v{a \wedge b}\)。我们将两个向量的 wedge product 定义为一种结合律乘积,该乘积分布在加法之上,并且当两个参数相同时为零: \[\begin{equation} \v{v \wedge v} = 0 \tag{ 1 } \end{equation} \] 由此我们可以证明 wedge product 也是反对称的: \(\v{(a \wedge b) = -(b \wedge a)}\) 给定向量 \(\v{a}\) 和 \(\v{b}\): \[ \begin{aligned} (\v{a + b}) \wedge (\v{a + b}) &= 0 \\ (\v{a \wedge a}) + (\v{a \wedge b}) + (\v{b \wedge a}) + (\v{b \wedge b}) &= 0 \\ 0 + (\v{a \wedge b}) + (\v{b \wedge a}) + 0 &= 0 \\ (\v{a \wedge b}) &= -(\v{b \wedge a}) \end{aligned} \] 但是,我们还没有指定如何实际“计算”wedge product。我们知道当两个参数等效时它产生零,但如果它们不等效呢?在这种情况下,我们通过用它的基元素表达参数并相乘来“计算”wedge product。

当涉及到一对基向量时,我们只是保持它们不变。因此,例如,我们不会进一步简化 \(\v{e_1} \wedge \v{e_2}\)。这是因为 \(\v{e_1} \wedge \v{e_2}\) 不是一个向量。它是一种名为 bivector 的新实体。如果你将普通向量视为一个点(与原点偏移),那么通过将 wedge product 应用于这两个向量而产生的 bivector 可以被可视化为包含原点和这两个点的无限平面。等效地,你可以将 bivector 视为垂直于我们 wedged 在一起的两个向量形成的平面的方向。bivector \(\v{e_1 \wedge e_2}\) 在某种意义上是与向量 \(\v{e_3}\) 同方向的法线。

与我们有基向量 (\(\v{e_1}, \v{e_2}, \v{e_3})\) 的方式相同,我们也有基 bivector:\(\v{e_{12}}, \v{e_{23}}, \v{e_{31}}\)。方便的是,这些 bivector 基元素是向量基元素的简单 wedge product:\[ \v{e_{12}} = \v{e_1} \wedge \v{e_2} \\ \v{e_{23}} = \v{e_2} \wedge \v{e_3} \\ \v{e_{31}} = \v{e_3} \wedge \v{e_1} \] 请注意,(与向量一样)我们不限于一组特定的基 bivector。有些文本更喜欢使用 \(\v{e_{12}}, \v{e_{13}}, \v{e_{23}}\)。计算结果略有不同,但逻辑相同。对于这篇文章,我们将始终使用 \(\v{e_{12}}, \v{e_{23}}, \v{e_{31}}\) 。需要注意的重要一点是,3 维的情况在这里有点误导。很容易将向量与 bivector 混淆,因为它们具有相同数量的基元素。这在更高的维度中是不正确的。例如,在 4 维空间中,有 4 个基向量,但有 6 个基 bivector,因此我们应始终明确声明我们在计算中使用的基元素。

最后一个认识是,在 3D 中,我们可以更进一步。除了向量(表示线)和 bivector(表示平面)之外,我们还有 trivector,它们表示体积。Trivector 在 3D 中是我们能达到的最远距离,因为空间本身是 3 维的,没有空间容纳更多的维度!3D 中的 trivector 有时被称为“pseudoscalars”,因为它们只有 1 个基元素:\(\v{e_{123}}\)。3D 中的 trivector 是有方向的(从 trivector 基元素的系数可以为负这一意义上说),否则不包含位置信息。

下面是我们的 3D 基向量的 wedge product 的乘法表: \[\begin{array}{c|c:c:c} \wedge & \v{e_1} & \v{e_2} & \v{e_3} \\ \hline \v{e_1} & 0 & \v{e_{12}} & -\v{e_{31}} \\ \v{e_2} & -\v{e_{12}} & 0 & \v{e_{23}} \\ \v{e_3} & \v{e_{31}} & -\v{e_{23}} & 0 \\ \end{array}\]

Wedge product of non-basis vectors

让我们看看如果我们以上述方式将两个任意 3D 向量 wedged 在一起会发生什么: \(\v{v \wedge u} = (v_1u_2 - v_2u_1)\v{e_{12}} + (v_2u_3 - v_3u_2)\v{e_{23}} + (v_3u_1 - v_1u_3)\v{e_{31}}\) 给定向量 \(\v{v} = v_1\v{e_1} + v_2\v{e_2} + v_3\v{e_3}\) 和 \(\v{u} = u_1\v{e_1} + u_2\v{e_2} + u_3\v{e_3}\): \[ \begin{align*} \v{v \wedge u} &= (v_1\v{e_1} + v_2\v{e_2} + v_3\v{e_3}) \wedge (u_1\v{e_1} + u_2\v{e_2} + u_3\v{e_3}) \\ \v{v \wedge u} &= (v_1\v{e_1} \wedge u_1\v{e_1}) + (v_1\v{e_1} \wedge u_2\v{e_2}) + (v_1\v{e_1} \wedge u_3\v{e_3}) \tag{distribute over +}\\ &+ (v_2\v{e_2} \wedge u_1\v{e_1}) + (v_2\v{e_2} \wedge u_2\v{e_2}) + (v_2\v{e_2} \wedge u_3\v{e_3}) \\ &+ (v_3\v{e_3} \wedge u_1\v{e_1}) + (v_3\v{e_3} \wedge u_2\v{e_2}) + (v_3\v{e_3} \wedge u_3\v{e_3}) \\ \v{v \wedge u} &= v_1u_1(\v{e_1 \wedge e_1}) + v_1u_2(\v{e_1 \wedge e_2}) + v_1u_3(\v{e_1 \wedge e_3}) \tag{pull out coefficients}\\ &+ v_2u_1(\v{e_2 \wedge e_1}) + v_2u_2(\v{e_2 \wedge e_2}) + v_2u_3(\v{e_2 \wedge e_3}) \\ &+ v_3u_1(\v{e_3 \wedge e_1}) + v_3u_2(\v{e_3 \wedge e_2}) + v_3u_3(\v{e_3 \wedge e_3}) \\ \v{v \wedge u} &= 0 + v_1u_2\v{e_{12}} - v_1u_3\v{e_{31}} \\ &- v_2u_1\v{e_{12}} + 0 + v_2u_3\v{e_{23}} \\ &+ v_3u_1\v{e_{31}} - v_3u_2\v{e_{23}} + 0 \\ \v{v \wedge u} &= (v_1u_2 - v_2u_1)\v{e_{12}} + (v_2u_3 - v_3u_2)\v{e_{23}} + (v_3u_1 - v_1u_3)\v{e_{31}} \\ \end{align*} \] 现在,这些系数看起来非常熟悉,不是吗?它们正是通常的 3D 叉积的系数。1 这与我们之前声称 bivector 充当法线的说法一致:如果你查看哪些系数与哪些 bivector 基元素相关联,你会发现 \(\v{e_{23}}\) 的系数与通常的 3D 叉积中 \(\v{x}\) 的系数相同。

通过“共享”3D 向量叉积的方程,我们可以得出结论:bivector \(\v{v \wedge u}\) 的大小等于 \(\v{v}\) 和 \(\v{u}\) 形成的平行四边形的面积。一个整洁的几何证明(带有图表)可以在 mathematics Stack Exchange 上找到。面积的符号表示平行四边形的绕组顺序,但是哪个方向是正的,哪个方向是负的将取决于你的坐标系的惯用性。

与向量一样,bivector 可以写成一些基元素的总和,每个基元素都乘以一些标量。因此,毫不奇怪,与向量一样,将两个 bivector 加在一起只是添加每个组成部分的问题。

因此,我们有向量加法和 bivector 加法。我们可以将向量添加到 bivector 吗?可以,但是我们将它们保留为单独的项。就像我们不尝试“简化”\(\v{e_1 + e_2}\) 一样,我们也不尝试简化 \(\v{e_1 + e_{12}}\)。我们只是将它们保留为这两个不同实体的总和。结果对象既不是向量也不是 bivector,而是一个更一般的对象,称为“multivector”。multivector 只是标量、向量、bivector、trivector 等的总和。所有标量、向量、bivector 等也是 multivector,只不过它们只有一种“类型”的基元素具有非零系数。因此,例如,你可以将向量 \(\v{e_1}\) 写为 multivector \(\v{e_1} + 0\v{e_{12}}\)。

Multivector 与我们第二个新运算以及本文的主角的讨论特别相关:

Geometric product

Geometric product 是为任意 multivector 定义的,它具有结合律,并且分布在加法之上。有点恼人的是(在涉及多种类型的乘积的环境中),它没有符号,只是 \(\v{ab}\)。如果我们有两个向量 \(\v{a}\) 和 \(\v{b}\),我们可以计算它们的 geometric product 如下: \[\begin{equation} \v{ab = (a \cdot b) + (a \wedge b)} \tag{ 2 } \end{equation} \] 其中 \(\cdot\) 是我们从传统线性代数中了解到的通常的点积。请注意,如果两个输入相同,那么通过公式 1,我们得到: \[\begin{equation} \v{aa} = \v{a \cdot a} \tag{ 3 } \end{equation} \] 这一点,以及我们的基向量都是单位长度且彼此垂直这一事实,使我们得到:\(\v{e_ie_i} = \v{e_i} \cdot \v{e_i} = 1\) 且 \(\v{e_ie_j} = 0 + \v{e_i} \wedge \v{e_j} = -\v{e_je_i} ~~~\forall i \neq j\)。

特别是,这意味着 \(\v{e_1e_2} = \v{e_1 \wedge e_2} = \v{e_{12}}\)(对于其他基 bivector 也是如此)。事实上,基 bivector 是基向量的 wedge product 现在被揭示为是基向量的 geometric product 的特例。这使我们得到了 trivector 的类似定义:\(\v{e_{123}} = \v{e_1e_2e_3}\)。

在这一点上,我们可以计算 3D 中基元素的 geometric product 的完整乘法表: \[\begin{array}{c|c:c:c:c:c:c:c:c} \cdot\wedge & \v{e_1} & \v{e_2} & \v{e_3} & \v{e_{12}} & \v{e_{31}} & \v{e_{23}} & \v{e_{123}} \\ \hline \v{e_1} & 1 & \v{e_{12}} & -\v{e_{31}} & \v{e_2} & -\v{e_3} & \v{e_{123}} & \v{e_{23}} \\ \v{e_2} & -\v{e_{12}} & 1 & \v{e_{23}} & -\v{e_1} & \v{e_{123}} & \v{e_3} & \v{e_{31}} \\ \v{e_3} & \v{e_{31}} & -\v{e_{23}} & 1 & \v{e_{123}} & \v{e_1} & -\v{e_2} & \v{e_{12}} \\ \v{e_{12}} & -\v{e_2} & \v{e_1} & \v{e_{123}} & -1 & \v{e_{23}} & -\v{e_{31}} & -\v{e_3} \\ \v{e_{31}} & \v{e_3} & \v{e_{123}} & -\v{e_1} & -\v{e_{23}} & -1 & \v{e_{12}} & -\v{e_2} \\ \v{e_{23}} & \v{e_{123}} & -\v{e_3} & \v{e_2} & \v{e_{31}} & -\v{e_{12}} & -1 & -\v{e_1} \\ \v{e_{123}} & \v{e_{23}} & \v{e_{31}} & \v{e_{12}} & -\v{e_3} & -\v{e_2} & -\v{e_1} & -1 \\ \end{array}\] 一些推导的乘法表条目。万一不清楚我们如何得出上表中某些值,这里有一些工作示例:\[ \v{e_1e_3} = \v{e_1 \wedge e_3} = -(\v{e_3 \wedge e_1}) = -\v{e_{31}} \\ \v{e_1e_{12}} = \v{e_1(e_1e_2)} = \v{(e_1e_1)e_2} = 1\v{e_2} = \v{e_2} \\ \v{e_3e_{12}} = \v{e_3e_1e_2} = -\v{e_1e_3e_2} = \v{e_1e_2e_3} = \v{e_{123}} \\ \v{e_{12}e_{12}} = (\v{e_1e_2})(\v{e_1e_2}) = (-\v{e_2e_1})(\v{e_1e_2}) = -\v{e_2}(\v{e_1e_1})\v{e_2} = -\v{e_2e_2} = -1 \\ \v{e_{123}e_{2}} = \v{(e_1e_2e_3)e_2} = -(\v{e_1e_3e_2})\v{e_2} = -\v{e_1e_3}(\v{e_2e_2}) = -\v{e_1e_3} = \v{e_3e_1} = \v{e_{31}}\\ \]

为了计算两个任意 multivector 的 geometric product,我们可以将参数分解为它们的组成基元素,并且只操作那些基元素(使用上面的乘法表)。我们需要这样做,因为上面的公式 23 仅适用于 vectors,而不适用于 bivector(或 trivector 等)。

Inverses under the geometric product

在 geometric product 下,所有非零向量 \(\v{v}\) 都具有逆: \[\begin{equation} \v{v}^{-1} = \frac{\v{v}}{|\v{v}|^2} \tag{ 4 } \end{equation} \] 证明:\(\v{v}^{-1} = \frac{\v{v}}{|\v{v}|^2}\) 给定一个向量 \(\v{v} \neq 0\),让我们取 \(\v{v^\prime} = \frac{\v{v}}{|\v{v}|^2}\),然后:\[\begin{aligned} \v{vv^\prime} &= \v{v \cdot v^\prime + v \wedge v^\prime} \\ \v{vv^\prime} &= \frac{1}{|\v{v}|^2}(\v{v \cdot v}) + \frac{1}{|\v{v}|^2}(\v{v \wedge v}) \\ \v{vv^\prime} &= \frac{1}{|\v{v}|^2}|\v{v}|^2 + \frac{1}{|\v{v}|^2}0 \\ \v{vv^\prime} &= \frac{|\v{v}|^2}{|\v{v}|^2} \\ \v{vv^\prime} &= 1 \\ \end{aligned}\] 同样,如果我们从左侧相乘:\[\begin{aligned} \v{v^\prime v} &= \v{v^\prime \cdot v + v^\prime \wedge v} \\ \v{v^\prime v} &= \frac{1}{|\v{v}|^2}(\v{v \cdot v}) + \frac{1}{|\v{v}|^2}(\v{v \wedge v}) \\ \v{v^\prime v} &= \frac{1}{|\v{v}|^2}|\v{v}|^2 + \frac{1}{|\v{v}|^2}0 \\ \v{v^\prime v} &= \frac{|\v{v}|^2}{|\v{v}|^2} \\ \v{v^\prime v} &= 1 \\ \end{aligned}\] 所以 \(\v{v^\prime} = \frac{\v{v}}{|\v{v}|^2} = \v{v^{-1}}\),即 \(\v{v}\) 的逆。

类似地,两个向量 \(\v{a}\) 和 \(\v{b}\) 的 geometric product 也具有逆: \[\begin{equation} (\v{ab})^{-1} = \v{b}^{-1}\v{a}^{-1} \tag{ 5 } \end{equation} \] 证明:\((\v{ab})^{-1} = \v{b}^{-1}\v{a}^{-1}\) 给定任意两个向量 \(\v{a}\) 和 \(\v{b}\),那么我们可以从右侧相乘:\[ (\v{ab})(\v{b}^{-1}\v{a}^{-1}) = \v{a}(\v{bb}^{-1})\v{a}^{-1} = \v{a}(1)\v{a}^{-1} = \v{aa}^{-1} = 1 \] 从左侧相乘:\[ (\v{b}^{-1}\v{a}^{-1})(\v{ab}) = \v{b}^{-1}(\v{a}^{-1}\v{a})\v{b} = \v{b}^{-1}(1)\v{b} = \v{b}^{-1}\v{b} = 1 \] 并得出结论 \(\v{b}^{-1}\v{a}^{-1} = (\v{ab})^{-1}\),即 \(\v{ab}\) 的(左侧和右侧)逆。

由于每个向量都有一个逆,因此对于任意两个向量 \(\v{a}\) 和 \(\v{b}\),我们可以写成:\[\begin{aligned} \v{a} &= \v{a} \\ \v{a} &= \v{abb}^{-1} \\ \v{a} &= \frac{1}{|\v{b}|^2} \v{(ab)b} \\ \v{a} &= \frac{1}{|\v{b}|^2} \v{(a \cdot b + a \wedge b) b} \\ \v{a} &= \frac{\v{a \cdot b}}{|\v{b}|^2} \v{b} + \frac{\v{a \wedge b}}{|\v{b}|^2} \v{b} \\ \end{aligned}\]

由此,我们得出结论:对于两个任意(非零)向量 \(\v{a}\) 和 \(\v{b}\),我们可以用平行于另一个向量和垂直于另一个向量的分量来写一个向量: \[\begin{equation} \v{a} = \v{a}{\parallel b} + \v{a}{\perp b} \tag{ 6 } \end{equation} \] 其中 \(\v{a_{\parallel b}}\) 是 \(\v{a}\) 平行于 \(\v{b}\) 的分量(\(\v{a}\) 在 \(\v{b}\) 上的 projection),\(\v{a_{\perp b}}\) 是 \(\v{a}\) 垂直于 \(\v{b}\) 的分量(从 \(\v{b}\) 得到的 \(\v{a}\) 的 rejection)。我们从线性代数中知道 \[\begin{equation} \v{a_{\parallel b}} = \frac{\v{a \cdot b}}{|\v{b}|^2}\v{b} \tag{ 7 } \end{equation} \] 代入上面的计算中,我们得到 \(\v{a} = \v{a_{\parallel b}} + \frac{\v{a \wedge b}}{|\v{b}|^2} \v{b} \),由此我们得出结论 \[\begin{equation} \v{a_{\perp b}} = \frac{\v{a \wedge b}}{|\v{b}|^2}\v{b} \tag{ 8 } \end{equation} \]

Reflections with the geometric product

回想一下线性代数,给定两个非零向量 \(\v{a}\) 和 \(\v{v}\),我们可以将 \(\v{a}\) 关于 \(\v{v}\) 的反射写成: \[ \v{a^\prime} = \v{a} - 2\v{a_{\perp v}} = \v{a}{\parallel v} - \v{a}{\perp v} \] 如果我们将公式 78 代入,我们得到: \[\begin{aligned} \v{a^\prime} &= \v{a}{\parallel v} - \v{a}{\perp v} \\ &= \frac{\v{v \cdot a}}{|\v{a}|^2} \v{a} - \frac{\v{v \wedge a}}{|\v{a}|^2} \v{a} \\ &= (\v{v \cdot a})\frac{\v{a}}{|\v{a}|^2} - (\v{v \wedge a})\frac{\v{a}}{|\v{a}|^2} \\ &= \v{(v \cdot a)a}^{-1} - (\v{v \wedge a})\v{a}^{-1} \\ &= \v{(a \cdot v)a}^{-1} + (\v{a \wedge v})\v{a}^{-1} \\ &= (\v{a \cdot v + a \wedge v})\v{a}^{-1} \\ &= \v{(av)a}^{-1} \\ \v{a^\prime} &= \v{ava}^{-1} \\ \end{aligned}\] 因此,我们只能使用 geometric product 反射向量。\(\v{ava}^{-1}\) 是我们将经常看到的一种形式,有时被称为“sandwich product”。

上面的计算的第一行和最后一行共同展示了一个重要的属性:由于 \(\v{ava}^{-1} = \v{a}{\parallel v} - \v{a}{\perp v}\),我们知道 \(\v{ava}^{-1}\) 只是一个向量,不包含标量、bivector(或 trivector 等)分量。这意味着我们可以将这种 sandwich product 的输出用作另一种 sandwich product 的输入,我们很快就会这样做。

为了我们自己的方便,我们还可以为这种 sandwich product 的输出生成一个方程: 3D sandwich product 的方程 \[\begin{align*} & \v{ava}^{-1} \\ =~& (\v{av})\v{a}^{-1} \\ =~& |a|^{-2} (\v{av})\v{a} \\ =~& |a|^{-2} (\v{(a \cdot v) + (a \wedge v)})\v{a} \\ =~& |a|^{-2} \lbrack \\ & (a_1v_1 + a_2v_2 + a_3v_3) \\ & + (a_1v_2 - a_2v_1)\v{e_{12}} \\ & + (a_2v_3 - a_3v_2)\v{e_{23}} \\ & + (a_3v_1 - a_1v_3)\v{e_{31}} \\ & \rbrack (a_1 \v{e_1} + a_2 \v{e_2} + a_3 \v{e_3}) \\ =~& |a|^{-2} \lbrack \\ & (a_1v_1 + a_2v_2 + a_3v_