Loading

Real-Time Rendering 第四章

第四章 变换 Transforms

变换(transform)是一种操作,它接受点(points),向量(vectors)或颜色(colors)之类的实体(entities),并且以某种方式转换它们。对于计算机图形从业者来说,掌握变换是非常重要的。有了它们,你可以定位(position)、重塑(reshape)和移动(animate)物体、灯光和照相机。你还可以确保所有计算都在同一坐标系中进行,并且以不同的方式将对象投影到平面上。这里只是少数可以通过变换执行的操作,但是这已经充分说明了变换(transform)在实时图形或任何类型计算机图形中的重要性。

线性变换(linear transform)是保留向量加法和标量乘法的变换。具体来说:

\[\mathbf{f}(\mathbf{x})+\mathbf{f}(\mathbf{y})=\mathbf{f}(\mathbf{x}+\mathbf{y}) \tag{4.1} \]

\[k \mathbf{f}(\mathbf{x})=\mathbf{f}(k \mathbf{x}) \tag{4.2} \]

举个例子, \(\mathrm{f}(\mathrm{x})=5 \mathrm{x}\) 是一个使向量每个元素乘以 5 的变换。为了证明这是线性的,需要满足两个条件 (公式 \(4.1\) 与公式 4.2)。第一个 条件成立,因为任何两个向量乘以 5 然后相加就等于将向量柤加然后再相乘。第二个标量乘法条件 (公式 4.2) 已经明显是满足的。此函数我们称为缩放变换 (scaling transform),因为它可以更改对象的缩放比例 (大小) 。旋转变换 (rotation transform) 是另一个线性变 换,它可使向量绕原点旋转。包括缩放和旋转变换,实际上三维向量的所有线性变换,都可以使用 \(3 \times 3\) 矩阵表示。

然而, 这个矩阵的大小通常不够大。三元素向量 \(\mathrm{x}\) 的函数, 例如 \(\mathrm{f}(\mathrm{x})=\mathrm{x}+(7,3,2)\) 不是线性的。在两个单独的向量上执行此函数会将 (7, 3, 2) 的每个值相加两次以形成结果。将固定向量添加到另一个向量会执行平移,例如,它将所有位置移动相同的量。这是一种有用的变换类型,我们希望结合各种变换,例如,将对象缩放为原来的一半,然后将其移动到不同的位置。将函数保持在迄今为止使用的简单形式中,很难轻松地将它们组合起来。

我们可以使用仿射变换(affine transform)将线性变换(linear transforms)和平移(translations)结合起来,仿射变换通常存储为 4 × 4 矩阵。仿射变换是先执行线性变换然后执行平移变换。为了表示四维向量,我们使用齐次表示法(homogeneous notation),以相同的方式表示点和方向(使用粗体小写字母)。方向向量 (direction vector) 表示为 \(\mathbf{v}=\left(v_{x} v_{y} v_{z} 0\right)^{T}\) ,点 (point) 表示为 \(\mathbf{v}=\left(v_{x} v_{y} v_{z} 1\right)^{T}\) 。在本章中,我们将广泛使用在 realtimerendering.com 上可下载的线性代数附录中解释的术语和运算。

所有平移(translation),旋转(rotation),缩放(scaling),反射(reflflection)和剪切矩阵(shearing matrices)都是仿射(affine)。仿射矩阵(affine matrix)的主要特征就是它保留了线的平行性,但不一定保留长度和角度。仿射变换(affine transform)也可以是各个仿射变换级联(concatenations)的任何序列。

本章将从最根本的基本仿射变换(basic affine transforms)开始。本部分可以看作是简单变换的“参考手册”。之后,我们会描述一些专用的矩阵,随后对四元数(quaternions)——一种强大的变换工具,进行讨论和描述。然后是顶点融合(blending)和变形(morphing),这是表达网格动画的两种简单但有效的方法。最后,描述了投影矩阵。这些变换中大多数,它们的符号,功能和特性都总结在表 4.1 中,其中正交矩阵的逆矩阵为其转置矩阵。

变换(Transforms)是用于操纵几何体(geometry)的基本工具。大多数图形应用程序编程接口允许用户设置任意矩阵,有时库可能与实现本章讨论的许多变换的矩阵运算一起使用。但是,仍然有必要了解函数调用背后的实际矩阵及其相互作用。知道调用函数后矩阵做了什么是一个开始,但是了解矩阵本身的属性将使你的理解更进一步。例如,这种理解可以使你辨别何时处理正交矩阵(正交是其转置),从而可以更快地进行矩阵求逆。这样的知识可以让你的代码更加高效。

4.1 基本变换 Basic Transforms

本节介绍最基本的变换,例如平移、旋转、缩放、剪切、变换串联、刚体变换、法线变换和逆计算。对于有经验的读者,可以作为简单变换的参考手册,对于新手,可以作为对该主题的介绍。这些材料是本章其余部分和本书其他章节的必要背景。我们从最简单的变换——“平移”开始。

简单的变换——“平移”开始。

表 4.1 本章讨论到的大多数变换的小结

符号 Notation 名称 Name 特点 Characteristics
\(\mathbf{T}(\mathbf{t})\) 平移矩阵 translation matrix 移动一个点。仿射。
\(\mathbf{R}_{x}(\rho)\) 旋转矩阵rotation matrix \(\mathrm{x}\) 轴 旋转 \(\rho\) 弧度角。 y 轴与 z轴也使用此标记。 正交且仿射。
\(\mathbf{R}\) 旋转矩阵rotation matrix 任意的旋转矩阵。正交且仿射。
\(\mathbf{S}(\mathbf{s})\) 缩放矩阵scaling matrix 根据 \(\mathrm{s}\) 值来缩放 \(\mathrm{x}, \mathrm{y}\) , Z轴。 仿射。
\(\mathbf{H}_{i j}(s)\) 剪切矩阵shear matrix 相对于分量 \(j\) ,用因子 \(s\) 剪切分量 \(i\)\(i, j \in\{x, y, z\}\) 。仿射。
\(\mathbf{E}(h, p, r)\) 欧拉变换Euler transform 根据 head(yaw),pitch,roll三个方向的欧拉角给出的方向矩阵。正交&仿射。
\(\mathbf{P}_{o}(s)\) 正交投影orthographic projection 平行投影到某个平面或某个体积上。仿射。
\(\mathbf{P}_{p}(s)\) 透视投影perspective projection 以透视图投影到平面或体积上。
\(\operatorname{slerp}(\hat{\mathbf{q}}, \hat{\mathbf{r}}, t)\) 斯勒普变换slerp transform(注:全称为球面线性插值变换Spherical Linear Interpolation Transform) 生成关于四元数 \(\hat{\mathbf{q}}\)\(\hat{\mathbf{r}}\) 以及参数 \(t\) 的插值四元数。

4.1.1 平移 Translation

从一个位置到另一个位置的变化由平移矩阵 \(\mathrm{T}\) 表示。此矩阵通过向量 去平移实体。 \(\mathrm{T}\) 由下面的公式 \(4.3\) 给出:

\[\mathbf{T}(\mathbf{t})=\mathbf{T}\left(t_{x}, t_{y}, t_{z}\right)=\left(\begin{array}{cccc} 1 & 0 & 0 & t_{x} \\ 0 & 1 & 0 & t_{y} \\ 0 & 0 & 1 & t_{z} \\ 0 & 0 & 0 & 1 \end{array}\right) \tag{4.3} \]

\(4.1\) 显示了平移变换效果的示例。容易证明,将点 \(\mathbf{p}=\left(p_{x}, p_{y}, p_{z}, 1\right)\)\(\mathbf{T}(\mathbf{t})\) 相乘会产生一个新的点 \(\mathbf{p}^{\prime}=\left(p_{x}+t_{x}, p_{y}+t_{y}, p_{z}+t_{z}, 1\right) ,\) 这显然是一个平移。请注意,向量 \(\mathbf{v}=\left(v_{x}, v_{y}, v_{z}, 0\right)\) 不受 \(\mathbf{T}\) 乘法的影响,因为方向向量无法平 移。相反,点和向量都受其余仿射变换(affine transforms)的影响。平移矩阵的逆是 \(\mathbf{T}^{-1}(\mathbf{t})=\mathbf{T}(-\mathbf{t})\) ,即向量 \(\mathbf{t}\) 取反 \((\) negated \()\)

图4.1左边的方形进行了平移变换,平移矩阵为 \(\mathbf{T}(5,2,0)\) ,方形向右移动了 5 个单位距离,向上移动了 2 个单位距离。

在这一点上,我们应该提到的是,有时在计算机图形学中看到的另一种有效的符号方案,它使用的矩阵的底标是平移向量。例如DirectX 就是使用这种形式。在该方案中,矩阵的顺序将颠倒,即矩阵应用的顺序将从左至右读取。由于向量是行向量,因此可以将这种表示形式的向量和矩阵称为行优先形式(row-major form)。在本书中,我们将使用列优先形式(column-major form)。无论使用哪种方式,这纯粹是一种符号上的差异。当矩阵存储在内存中时,十六进制的最后四个值为三个平移值加后跟的一个值。

4.1.2 旋转 Rotation

旋转变换将一个向量(位置或方向)绕经过原点的给定轴旋转指定的角度。像平移矩阵一样,它是一个刚体变换(rigid-body transform),换句话说,它保留了变换后的点之间的距离,并保留了惯用性(handedness)(即从不导致左右两侧互换)。在计算机图形学中,这两种类型的变换对于定位和定向对象显然很有用。方向矩阵(orientation matrix)是与摄像机视图(camera view)或对象相关联的旋转矩阵,它定义了其在空间中的方向,即其向上和向前的方向。

在二维中,旋转矩阵很容易得出。假设我们有一个向量 \(\mathbf{v}=\left(v_{x}, v_{y}\right)\) ,我们将其参数化为 \(\mathbf{v}=\left(v_{x}, v_{y}\right)=(r \cos \theta, r \sin \theta)\) 。如果我们将 向量旋转 \(\phi\) 弧度(逆时针),则将得到 \(\mathbf{u}=(r \cos (\theta+\phi), r \sin (\theta+\phi))\) 。这可以重写为

\[\begin{aligned} \mathbf{u} &=\left(\begin{array}{l} r \cos (\theta+\phi) \\ r \sin (\theta+\phi) \end{array}\right)=\left(\begin{array}{l} r(\cos \theta \cos \phi-\sin \theta \sin \phi) \\ r(\sin \theta \cos \phi+\cos \theta \sin \phi) \end{array}\right) \\ &=\underbrace{\left(\begin{array}{c} \cos \phi-\sin \phi \\ \sin \phi \\ \cos \phi \end{array}\right)}_{\mathbf{R}(\phi)} \underbrace{\left(\begin{array}{c} r \cos \theta \\ r \sin \theta \end{array}\right)}_{\mathbf{v}}=\mathbf{R}(\phi) \mathbf{v} \end{aligned} \tag{4.4} \]

在这里我们使用角度和关系来展开 \(\cos (\theta+\phi)\)\(\sin (\theta+\phi)\) 。在三维中,常用的旋转矩阵是 \(\mathbf{R}_{x}(\phi), \mathbf{R}_{y}(\phi)\)\(\mathbf{R}_{z}(\phi)\) ,它们分别绕 \(x\)\(y\)\(z\) 轴旋转实体 \(\phi\) 弧度。它们由公式 \(4.5-4.7\) 给出:

\[\mathbf{R}_{x}(\phi)=\left(\begin{array}{cccc} 1 & 0 & 0 & 0 \\ 0 & \cos \phi & -\sin \phi & 0 \\ 0 & \sin \phi & \cos \phi & 0 \\ 0 & 0 & 0 & 1 \end{array}\right) \tag{4.5} \]

\[\mathbf{R}_{y}(\phi)=\left(\begin{array}{cccc} \cos \phi & 0 & \sin \phi & 0 \\ 0 & 1 & 0 & 0 \\ -\sin \phi & 0 & \cos \phi & 0 \\ 0 & 0 & 0 & 1 \end{array}\right) \tag{4.6} \]

\[\mathbf{R}_{z}(\phi)=\left(\begin{array}{cccc} \cos \phi & -\sin \phi & 0 & 0 \\ \sin \phi & \cos \phi & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{array}\right) \tag{4.7} \]

如果从 \(4 \times 4\) 矩阵中删除最底行和最右列,则将获得 \(3 \times 3\) 矩阵。对于每个绕任意轴旋转 \(\phi\) 弧度的 \(3 \times 3\) 矩阵 \(\mathbf{R}\) ,它的迹 (trace), 矩阵 中对角元素的总和) 与轴无关,是恒定的,计算公式为 [997]:

\[\operatorname{tr}(\mathbf{R})=1+2 \cos \phi \tag{4.8} \]

旋䢂矩阵的效果可以在图 \(4.4\) 中看到。使旋转矩阵 \(\mathbf{R}_{i}(\phi)\) 起作用的原因是,它绕着轴 \(i\) 旋转了 \(\phi\) 弧度,但它保留了旋转中的所有点。轴, \(i\) ,不变。注意, \(\mathbf{R}\) 也将用于表示围绕任何轴的旋䢂矩阵。上面给出的轴旋䢂矩阵可以在一系列的三个变换中使用,以执行任意轴旋转。该过程在第 \(4.2 .1\) 节中讨论。第 \(4.2 .4\) 节介绍了直接绕任意轴的旋转。

所有旋转矩阵的行列式 (determinant) 均为 1 ,并且是正交的 (orthogonal) 。这对于任意数量的这些变换的级联 (concatenations) 也成立。还有另一种求逆的方法: \(\mathbf{R}_{i}^{-1}(\phi)=\mathbf{R}_{i}(-\phi)\) ,即绕同一轴沿相反方向旋转。

示例:绕点旋转

假设我们要围绕 \(\mathrm{z}\) 轴旋转一个 \(\phi\) 弧度的对象,旋转中心为某个点 \(\mathrm{P}\) 。那么该如何进行变换?图4.2中描述了这种情况。由于绕点旋转的特 征在于该点本身不受旋转的影响,因此变换首先通过平移对象使 \(\mathbf{p}\) 与原点重合开始,这是通过 \(\mathbf{T}(-\mathbf{p})\) 完成的。此后跟随实际旋转: \(\mathbf{R}_{z}(\phi)\) 。最后,必须使用 \(\mathbf{T}(\mathbf{p})\) 将对象平移回其原始位置。然后,得到的变换 \(\mathbf{X}\) 由下式给出:

\[\mathbf{X}=\mathbf{T}(\mathbf{p}) \mathbf{R}_{z}(\phi) \mathbf{T}(-\mathbf{p}) \tag{4.9} \]

注意上面矩阵的顺序。

图4.2 围绕特定点 \(\textbf{p}\)旋转的例子。

4.1.3 缩放 Scaling

缩放矩阵 \(\mathrm{S}(\mathrm{s})=\mathrm{S}\left(s_{x}, s_{y}, s_{z}\right)\) ,分别沿 \(\mathrm{x}\)\(\mathrm{y}\)\(\mathrm{z}\) 方向按 \(s_{x}, s_{y}\)\(s_{z}\) 的缩放因子去缩放实体。这意味着可以使用缩放矩阵来放大或缩小 对象。 \(s_{i}(i \epsilon\{x, y, z\})\) 越大,则按比例缩放的实体在该方向上越大。将 \(s\) 的任何分量设置为 1 自然可以避免在该方向上缩放比例发生变 化。公式 \(4.10\) 展示了\(S\):

\[\mathbf{S}(\mathbf{s})=\left(\begin{array}{cccc} s_{x} & 0 & 0 & 0 \\ 0 & s_{y} & 0 & 0 \\ 0 & 0 & s_{z} & 0 \\ 0 & 0 & 0 & 1 \end{array}\right) \tag{4.10} \]

第 65 页的图4.4 说明了缩放矩阵的作用。如果 \(s_{x}=s_{y}=s_{z}\) ,则缩放操作称为统一操作 (uniform),否则称为非统一操作 (nonuniform)。有时,使用等向性 (isotropic) 和各向异性 (anisotropic) 缩放来代替统一和非统一性。倒数是

\[\mathbf{S}^{-1}(\mathbf{s})=\mathbf{S}\left(1 / s_{x}, 1 / s_{y}, 1 / s_{z}\right) \]

使用齐次坐标,创建统一缩放矩阵的另一种有效方法是通过操作位置 \((3,3)\) 处的矩阵元素,即右下角的元素。该值会影响齐次坐标的w 分量, 因此会缩放矩阵变换后的点 (而非方向向量) 的每个坐标。例如,要均匀地缩放 5 倍,可以将缩放矩阵中 \((0,0) ,(1,1)\)\((2 , 2)\) 的元素设置为 5 ,或将 \((3 , 3)\) 可以设置为 \(1 / 5\) 。执行此操作的两个不同矩阵如下所示:

\[\mathbf{S}=\left(\begin{array}{cccc} 5 & 0 & 0 & 0 \\ 0 & 5 & 0 & 0 \\ 0 & 0 & 5 & 0 \\ 0 & 0 & 0 & 1 \end{array}\right), \quad \mathbf{S}^{\prime}=\left(\begin{array}{cccc} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 / 5 \end{array}\right) \tag{4.11} \]

与使用 \(S\) 进行均匀缩放相反,必须始终在使用 \(\mathbf{S}^{\prime}\) 之后进行齐次化 (homogenization)。这可能是低效的,因为它涉及齐次化过程中的除 法。如果右下角的元素 (位置 \((3 , 3)\) ) 为 1 ,则不需要除法。当然,如果系统总是在不检测是否为 1 的情况下进行除法,则不会产生任何额外消耗。

\(s\) 的一个或三个分量上的负值给出一种反射矩阵(reflection matrix),也称为镜像矩阵(mirror matrix)。如果只有两个比例因子为 \(-1\) 那么我们将旋转 \(\pi\) 弧度。应当注意的是,与反射矩阵连接的旋转矩阵也是反射矩阵。因此,以下是反射矩阵:

\[\left(\begin{array}{cc} \cos (\pi / 2) & \sin (\pi / 2) \\ -\sin (\pi / 2) & \cos (\pi / 2) \end{array}\right)\left(\begin{array}{cc} 1 & 0 \\ 0 & -1 \end{array}\right)=\left(\begin{array}{cc} 0 & -1 \\ -1 & 0 \end{array}\right) \tag{4.12} \]

当检测到反射矩阵时,通常需要进行特殊处理。例如,当顶点由反射矩阵变换时,其顶点具有逆时针顺序的三角形将获得顺时针顺序。此顺序更改可能导致不正确的照明和背面剔除(backface culling)。要检测给定的矩阵是否以某种方式反射,请计算矩阵左上 3×3 个元素的行列式。如果该值为负,则矩阵是反射的(reflflective)。例如,公式4.12 中矩阵的行列式(determinant)为\(0 \cdot 0-(-1) \cdot(-1)=-1\)

示例:按特定比例缩放

缩放矩阵 \(\mathrm{S}\) 仅沿 \(\mathrm{x}\)\(\mathrm{y}\)\(\mathrm{z}\) 轴缩放。如果应在其他方向执行缩放,则需要复合变换。假设缩放应沿着正交轴右向向量 \(\mathrm{f}^{x}, \mathrm{f}^{y}\)\(\mathrm{f}^{z}\) 的轴 进行。那么首先,构造矩阵 \(\mathbf{F}\) ,以更改基底 (basis),如下所示:

\[F=\left(\begin{array}{cccc} \mathbf{f}^{x} & \mathbf{f}^{y} & \mathbf{f}^{z} & 0 \\ 0 & 0 & 0 & 1 \end{array}\right) \tag{4.13} \]

这个想法是使三个轴给定的坐标系与标准轴(standard axes)重合,然后使用标准缩放矩阵,然后变换回去。其第一步是通过乘以 \(\mathbf{F}\) 的 转置即它的逆来进行的。然后完成实际的缩放,然后再变换回去。变换如公式 \(4.14\) 所示:

\[\mathbf{X}=\mathbf{F S}(s) \mathbf{F}^{T} \tag{4.14} \]

4.1.4 剪切 Shearing

另一类变换是剪切矩阵集。这些矩阵可以,例如,用于扭曲游戏中整个场景,以产生迷幻效果或扭曲模型的外观。有六个基本剪切矩阵, 它们分别表示为 \(\mathrm{H}_{x y}(s), \mathrm{H}_{x z}(s), \mathrm{H}_{y x}(s), \mathrm{H}_{y z}(s), \mathrm{H}_{z x}(s)\)\(\mathrm{H}_{z y}(s)\) 。第一个下标用于表示剪切矩阵正在更改哪个坐标,而第二个 下标表示进行剪切的坐标。剪切矩阵 \(\mathrm{H}_{x z}(s)\) 的示例如公式 \(4.15\) 所示。注意,下标可用于在下面的矩阵中找到参数 \(s\) 的位置; \(x\) (其数 字索引为 0 ) 标识第零行,而 \(z\) (其数字索引为 2 ) 标识第二列,因此 \(s\) 位于此处:

\[\mathbf{H}_{x z}(s)=\left(\begin{array}{cccc} 1 & 0 & s & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{array}\right) \tag{4.15} \]

图4.3。用 \(\mathrm{H}_{x z}(s)\) 剪切单位平方的效果。 \(y\) 值和z值都不受转换的影响,而 \(x\) 值是但 值和s 乘以z值的总和,导致平方倾斜。这种变换是保留区域的,这可以通过虚线区域相同来看出。

将此矩阵与点 \(\mathbf{p}\) 相乘的结果是一个点: \(\left(p_{x}+s p_{z} \quad p_{y} p_{z}\right)^{T}\) 。以图形方式显示,如图4.3所示。 \(\mathbf{H}_{i j}(s)\) 的倒数 (相对于第 \(j\) 个坐标剪切 第 \(i\) 个坐标,其中 \(i \neq j\) ) 是通过在相反方向上前切产生的,即 \(\mathbf{H}_{i j}^{-1}(s)=\mathbf{H}_{i j}(-s)\)

你还可以使用略有不同的剪切矩阵:

\[\mathbf{H}_{x y}^{\prime}(s, t)=\left(\begin{array}{cccc} 1 & 0 & s & 0 \\ 0 & 1 & t & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{array}\right) \tag{4.16} \]

然而,在此,两个下标都用于表示这些坐标将被第三坐标前切。这两种不同类型的描述之间的联系是 \(\mathbf{H}_{i j}^{-1}(s, t)=\mathbf{H}_{i k}(s) \mathbf{H}_{j k}(t)\) ,其中 \(k\) 用作第三坐标的索引。该使用什么矩阵取决于个人偏好。最后,应该注意的是,由于任何剪切矩阵的行列式 \(|H|=1\) ,这是一个保留体积的变换,如图 \(4.3\) 所示。

4.1.5 变换的串联 Concatenation of Transforms

由于矩阵乘法运算的非对易性,矩阵出现的顺序很重要。因此,变换的串联被认为是顺序相关的。

作为顺序依赖的示例,请考虑两个矩阵 \(\mathbf{S}\)\(\mathbf{R}_{\mathrm{o}} \mathbf{S}(2,0.5,1)\)\(x\) 分量缩放为两倍,将y分量缩放为 0.5。 \(\mathbf{R}_{z}(\pi / 6)\)\(z\) 轴逆时针旋转 \(\pi / 6\) 弧度(在右手坐标系中从本书的页面向外指向)。这些矩阵可以用两种方法进行混合,其结果是完全不同的。这两种情况如图 \(4.4\) 所示。

图4.4。这说明了矩阵相乘时的顺序依赖性。在第一行图片中,应用旋转矩阵 \(\mathbf{R}_{z}(\pi / 6)\) ,然后缩放 \(\mathbf{S}(\mathrm{s})\) ,其中 \(\mathrm{s}=(2,0.5,1)\) 。这样, 合成后的矩阵为 \(\mathrm{S}(\mathrm{s}) \mathrm{R}_{z}(\pi / 6)\) 。在第二行图片中,以相反的顺序应用矩阵,从而得出 \(\mathrm{R}_{z}(\pi / 6) \mathrm{S}(\mathrm{s})\) 。结果明显不同。对于任澺矩阵 \(\mathrm{M}\)\(\mathrm{N}\) ,通常认为 \(\mathrm{MN} \neq \mathrm{NM}_{\text {。 }}\)

将一系列矩阵的连接转换为单个矩阵的明显原因是为了提高效率。例如,假设你的游戏场景具有数百万个顶点,并且场景中的所有对象都必须缩放,旋转并最终平移。现在,不是将所有顶点与这三个矩阵中的每一个相乘,而是将这三个矩阵连接到一个矩阵中。然后将此单个矩阵应用于顶点。该复合矩阵为 \(\mathrm{C}=\mathrm{TRS}\) 。注意这里的顺序。比例矩阵 \(\mathrm{S}\) 应该首先应用于顶点,因此在合成中显示在右侧。该排序意味着 \(\mathbf{T R S p}=(\mathbf{T}(\mathbf{R}(\mathbf{S p})))\) ,其中 \(\mathbf{p}\) 是要变换的点。顺便说一句, TRS 是场景图系统(scene graph systems)常用的顺序。

值得注意的是,尽管矩阵串联是依赖于顺序的,但是矩阵可以根据需要进行分组。例如,假设你要使用 TRSp 计算一次刚体运动变换 \(\mathrm{TR}\) 。将这两个矩阵 \((\mathrm{TR})(\mathrm{Sp})\) 分组在一起并用中间结果替换是有效的。因此,矩阵串是关联的 (associative) 。

4.1.6 刚体变换 The Rigid-Body Transform

当一个人抓住一个坚固的物体时,例如从桌子上用笔将其移动到另一个位置,也许移动到衬衫的口袋里,只有物体的方向和位置会发生变化,而物体的形状通常不会受到影响。这种仅由平移和旋转的串联组成的变换称为刚体变换。它具有保留长度,角度和惯用性的特性。

可以将任何刚体矩阵 \(\mathrm{X}\) 表示为平移矩阵 \(\mathrm{T}(\mathrm{t})\) 和旋转矩阵 \(\mathrm{R}\) 的串联。因此, \(\mathrm{X}\) 在公式 \(4.17\) 中具有矩阵的外观:

\[\mathbf{X}=\mathbf{T}(t) \mathbf{R}=\left(\begin{array}{cccc} r_{00} & r_{01} & r_{02} & t_{x} \\ r_{10} & r_{11} & r_{12} & t_{y} \\ r_{20} & r_{21} & r_{22} & t_{z} \\ 0 & 0 & 0 & 1 \end{array}\right) \tag{4.17} \]

\(\mathrm{X}\) 的倒数计算为 \(\mathrm{X}^{-1}=(\mathrm{T}(\mathrm{t}) \mathrm{R})^{-1}=\mathrm{R}^{-1} \mathrm{~T}(\mathrm{t})^{-1}\) 。因此,为了计算逆,对 \(\mathrm{R}\) 的左上 \(3 \times 3\) 矩阵进行转置,并且 \(\mathrm{T}\) 的平移值改变符号。 将这两个新矩阵以相反的顺序相乘以获得逆。计算 \(\mathrm{X}\) 的逆的另一种方法是考虑 \(\mathrm{R}\) (使 \(\mathrm{R}\) 出现为 \(3 \times 3\) 矩阵) 和 \(\mathrm{X}\) 的符号如下 (符号在第 6 页的公式 \(1.2\) 中描述):

\[\overline{\mathbf{R}}=\left(\begin{array}{lll} \mathbf{r}_{, 0} & \mathbf{r}_{, 1} & \mathbf{r}_{, 2} \end{array}\right)=\left(\begin{array}{r} \mathbf{r}_{0}^{T} \\ \mathbf{r}_{1}^{T} \\ \mathbf{r}_{2,}^{T} \end{array}\right) \tag{4.17} \]

得出:

\[\mathbf{X}=\left(\begin{array}{cc} \overline{\mathbf{R}} & \mathbf{t} \\ \mathbf{0}^{T} & 1 \end{array}\right) \]

其中, \(\mathbf{r}_{0}\) 表示旋转矩阵的第一列(即逗号表示从 0 到 2 的任何值,而第二个下标为 0 ),而 \(\mathbf{r}_{0}^{T}\) 是列矩阵的第一行。请注意, \(\mathbf{0}\) 是填充 有零的 \(3 \times 1\) 列向量。一些计算得出公式 \(4.19\) 中所示表达式的反函数:

\[\mathbf{X}^{-1}=\left(\begin{array}{cccc} \mathbf{r}_{0} & \mathbf{r}_{1} & \mathbf{r}_{2} & -\overline{\mathbf{R}}^{T} \mathbf{t} \\ 0 & 0 & 0 & 1 \end{array}\right) \tag{4.19} \]

图4.5。计算几何体变换,该变换使相机对准 \(\mathbf{c}\) 处,向量为 \(\mathbf{u}^{\prime}\) 上看点 \(\mathbf{l}_{\text {。 }}\) 为此,我们需要计算 \(\mathbf{r}\)\(\mathbf{u}\)\(\mathbf{v}_{o}\)

示例:调整相机的方向

计算机图形中的常见任务是调整相机的方向,使其对准特定位置。在这里,我们将介绍 gluLookAt () (来自OpenGL Utility Library,简称GLU) 的作用。即使现在很少使用此函数调用,该任务仍然很常见。假设胑相机位于 \(\mathrm{c}\) 处,我们希望昭相机看着目标 1 ,并且昭相机的给定方向为 \(\mathbf{u}^{\prime}\) ,如图 \(4.5\) 所示。我们要计算一个由三个向量 \(\{\mathbf{r}, \mathbf{u}, \mathbf{v}\}\) 组成的基数。我们从计算视点向量为 \(\mathbf{v}=(\mathbf{c}-1) /\|\mathbf{c}-1\|\) 开始, 即从目标到摄像机位置的归一化向量。然后可以将向右看的向量计算为 \(\mathbf{r}=-\left(\mathbf{v} \times \mathbf{u}^{\prime}\right) /\left\|\mathbf{v} \times \mathbf{u}^{\prime}\right\|_{\circ}\) 通常不能保证 \(\mathbf{u}^{\prime}\) 向量指向正上方, 因此最终的向上向量是另一个叉积 \(\mathbf{u}=\mathbf{v} \times \mathbf{r}\) ,由于 \(\mathbf{v}\)\(\mathbf{r}\) 都通过构造进行了归一化和垂直处理,因此可以保证对其进行归一化。在我们将构建的相机变换矩阵 \(\mathrm{M}\) 中,其思想是首先平移所有内容,使相机位置位于原点 \((0,0,0)\) ,然后更改基数,以使 \(\mathbf{r}\)\((1,0,0)\)\(\mathbf{u}\)\((0,1,0)\) 以及 \(\mathbf{v}\)\((0,0,1)\) 。这是通过以下公式实现:

\[\mathbf{M}=\underbrace{\left(\begin{array}{cccc} r_{x} & r_{y} & r_{z} & 0 \\ u_{x} & u_{y} & u_{z} & 0 \\ v_{x} & v_{y} & v_{z} & 0 \\ 0 & 0 & 0 & 1 \end{array}\right)}_{\text {change of basis }} \underbrace{\left(\begin{array}{cccc} 1 & 0 & 0 & -t_{x} \\ 0 & 1 & 0 & -t_{y} \\ 0 & 0 & 1 & -t_{z} \\ 0 & 0 & 0 & 1 \end{array}\right)}_{\text {translation }}=\left(\begin{array}{cccc} r_{x} & r_{y} & r_{z} & -\mathbf{t} \cdot \mathbf{r} \\ u_{x} & u_{y} & u_{z} & -\mathbf{t} \cdot \mathbf{u} \\ v_{x} & v_{y} & v_{z} & -\mathbf{t} \cdot \mathbf{v} \\ 0 & 0 & 0 & 1 \end{array}\right) . \tag{4.20} \]

注意,当将平移矩阵与基本矩阵的变化连接在一起时,平移 \(-\mathbf{t}\) 在右边,因为它应该首先应用。记住将 \(\mathbf{r} , \mathbf{u}\)\(\mathbf{v}\) 的分量放在何处的一种 方法如下。我们希望 \(\mathbf{r}\) 变成 \((1,0,0)\) ,所以当将基础矩阵的变化乘以 \((1,0,0)\) 时,我们可以看到矩阵的第一行必须是 \(\mathbf{r}\) 的元素,因为 \(\mathbf{r} \cdot \mathbf{r}=1\) 。此外,第二行和第三行必须由垂直于 \(\mathbf{r}\) 的向量组成,即 \(\mathbf{r} \cdot \mathbf{x}=0\) 。当对 \(\mathbf{u}\)\(\mathbf{v}\) 应用相同的思维时,我们得出以上基础矩阵的 变化。

4.1.7 法线变换 Normal Transform

单个矩阵可用于一致地变换点、线、三角形和其他几何图形。相同的矩阵也可以变换沿着这些线或三角形表面上的切向量。然而,这个矩阵不能总是用于变换一个重要的几何属性,即表面法线(和顶点照明法线)。图 4.6 显示了如果使用相同的矩阵会发生什么。

图 4.6。左侧是原始几何图形,三角形以及从侧面显示的法线。中间的插图显示了如果模型沿 x 轴缩放 0.5,法线使用相同的矩阵会发生什么。右图显示了法线的正确变换。

适当的方法不是使用矩阵本身相乘,而是使用矩阵的伴随项的转置相乘 [227]。伴随矩阵(adjoint)的计算在我们的在线线性代数附录中进行了描述。伴随矩阵关系始终保证存在,但法线不能保证在变换后仍是单位长度,因此通常需要将其归一化(normalized)。

变换法线的传统解法是计算逆的转置(the transpose of the inverse) [1794]。此方法通常有效。但是,完整的逆不是必需的,并且有时无法创建。逆是伴随数除以原始矩阵的行列式。如果该行列式为零,则矩阵是奇异的(singular)并且不存在逆。

即使只计算一个完整的 4×4 矩阵的伴随,也可能很昂贵,并且通常没有必要。由于法线是向量,因此平移不会对其产生影响。此外,大多数模型变换都是仿射的(affine)。它们不会更改传入的齐次坐标的 w 分量,也就是说,它们不会执行投影(projection)。在这些(常见)情况下,正常变换所需的仅是计算左上 3×3 分量的伴随矩阵。

通常甚至不需要这种伴随计算。假设我们知道变换矩阵完全由平移,旋转和统一缩放操作(无拉伸或压扁)的串联组成。可知平移不影响法线。统一的缩放因子仅改变法线的长度。剩下的就是一系列旋转,因此总是产生某种形式的最终的旋转值,仅此而已。逆的转置可用于变换法线。旋转矩阵是通过其转置矩阵为逆来定义的。代替以获得法线变换,两个转置(或两个逆)给出原始旋转矩阵。综上所述,原始变换本身也可以在这些情况下直接用于变换法线。

最后,并不总是需要完全重新归一化(renormalizing)生成的法线。如果仅平移和旋转连接在一起,则法线在通过矩阵进行变换时不会更改长度,因此不需要重新归一化。如果还连接了统一的缩放比例,则总缩放比例因子(如果已知或已提取,请参见第 4.2.3 节)可用于直接归一化所生成的法线。举个例子,如果我们知道应用了一系列缩放,使对象变大了 5.2 倍,则通过将此矩阵直接变换的法线除以 5.2 就会对其进行重新归一化。另外,为了创建一个产生归一化结果的法线变换矩阵,原始矩阵左上角的 3×3 可以除以该比例因子。

请注意,在变换之后,表面法线是从三角形得出的系统中,法线变换不是问题(例如,使用三角形边的叉积)。另外,切向量在本质上与法线不同,并且总是直接由原始矩阵变换而成。

4.1.8 逆的计算 Computation of Inverses

在许多情况下都需要逆(inverses)。例如在坐标系之间来回切换时, 根据有关变换的可用信息,我们可以使用以下三种方法之一来计算矩阵的逆:

  • 如果矩阵是单个变换或具有给定参数的简单变换序列,则该矩阵可以通过“反转参数”和矩阵顺序轻松地计算。举个例子,如果 \(\mathbf{M}=\mathbf{T}(\mathbf{t}) \mathbf{R}(\phi)\) ,则 \(\mathbf{M}^{-1}=\mathbf{R}(-\phi) \mathbf{T}(-\mathbf{t})\) 。这很简单,并且保留了变换的准确性,这在渲染巨大世界时很重要[1381]。
  • 如果已知矩阵是正交的,则 \(\mathrm{M}^{-1}=\mathrm{M}^{T}\) ,即转置为逆。旋转的任何序列都是旋转,因此是正交的。
  • 如果没有任何已知条件,则可以使用伴随方法(the adjoint method),克莱姆法则(Cramers rule),LU分解(LU decomposition) 或高斯消除法 (Gaussian elimination) 来计算逆。通常最好使用克莱姆法则和伴随方法,因为它们的分支操作较 少; 在现代体系结构上最好避免使用“if”测试。请参阅第 \(4.1 .7\) 节,了解如何使用伴随来反转变换法线。

优化时也可以考虑逆计算的目的。例如,如果将逆函数用于向量变换,则通常只需要对矩阵左上角的 3×3 部分进行反转(请参见上一节)。

4.2 特殊矩阵变换与运算 Special Matrix Transforms and Operations

在本节中,将介绍和推导一些对实时图形来说必不可少的几种矩阵变换和运算。首先,我们介绍欧拉变换(the Euler transform)及其参数提取,欧拉变换是描述方向的一种直观的方法。然后,我们会谈到从单个矩阵中提取出一组基本变换。最终,推导出一种绕任意轴旋转实体的方法。

4.2.1 欧拉变换 The Euler Transform

这种变换是构造矩阵以将自己(即相机)或任何其他实体定向到某个方向的一种直观方法。它的名字来自伟大的瑞士数学家莱昂哈德·欧拉(Leonhard Euler,1707–1783年)。

首先,必须建立某种默认的视图方向。如图 4.7 所示,它通常沿负 z 轴放置,head 沿 y 轴放置。欧拉变换是三个矩阵的乘积,即图中所示的旋转。更正式地说,表示为\(E\)的变换由公式 4.21 给出:

\[\mathbf{E}(h, p, r)=\mathbf{R}_{z}(r) \mathbf{R}_{x}(p) \mathbf{R}_{y}(h) \tag{4.21} \]

矩阵的顺序可以以 24 种不同的方式选择 [1636];这可以通过选择矩阵的顺序来实现。我们介绍这个是因为它是常用的。由于 \(\mathbf{E}\) 是旋转 的串联,因此它也显然是正交的。因此,它的逆可以表示为 \(\mathbf{E}^{-1}=\mathbf{E}^{T}=\left(\mathbf{R}_{z} \mathbf{R}_{x} \mathbf{R}_{y}\right)^{T}=\mathbf{R}_{x}^{T} \mathbf{R}_{y}^{T} \mathbf{R}_{z}^{T}\) ,当然,直接使用 \(\mathbf{E}\) 的转置会更 容易。
欧拉角 \(h , p\)\(r\) 分别表示 head,pitch 和 roll 应按其顺序旋转以及绕其各自的轴旋转多少。有时,所有角度都称为“rolls”,例如,我们的 "head"为 “ \(y\)-roll",而我们的“pitch"为“ \(x\)-roll"。另外,"head"有时也称为“yaw",例如在飞行模拟中。

这种变换很直观,因此很容易用外行的语言进行讨论。例如,改变 head 角度会使观看者摇头“no”,改变 pitch 会使他们点头,而 rolling 会使他们的 head 向侧面倾斜。我们不是在谈论围绕 x , y 和 z 轴 的旋转,而是谈论改变 head,pitch 和 roll。请注意,此变换不仅可以定向相机,还可以定向任何对象或实体。可以使用世界空间的全局轴(the global axes of the world space)或相对于局部参照系执行这些变换。

重要的是要注意,一些欧拉角的表示将 z 轴作为初始向上方向。这种差异纯粹是一种符号上的变化,尽管可能会造成混淆。在计算机图形学中,如何看待世界以及如何形成内容存在分歧:y 向上 或 z 向上。大多数制造工艺(包括3D打印)都认为 z 方向在世界空间中向上。航空和海上交通工具认为 -z 为向上。建筑和 GIS(地理信息系统,Geographic Information System 或 Geo-Information system,缩写为 GIS) 通常使用 z-up,因为建筑平面图或地图是二维的 x 和 y 。与媒体相关的建模系统通常将 y 方向视为世界坐标上的向上方向,以匹配我们始终在计算机图形学中描述相机的屏幕向上方向的方式。这两个世界向量选择之间的差异仅相差 90° 旋转(并且可能是反射),但不知道假定哪个会导致问题。在本卷中,除非另有说明,否则我们使用 y 向上的世界方向。

图4.7。欧拉变换及其与更改 head,pitch 和 roll 的方式之间的关系。显示默认视图方向,沿负z轴看,向上方向沿y轴看。

我们还想指出,相机在其视野中的向上方向与世界的向上方向没有特别关系。转动 head,视野就会倾斜,其世界空间向上方向与世界方 向不同。再举一个例子,假设世界使用 \(y-up\),而我们的相机则直视下方的地形,鸟瞰。此方向表示相机已向前倾斜 \(90^{\circ}\) ,因此其在世界空 间中的向上方向为 \((0,0,-1)\) 。在这种方向上,相机没有 \(\mathrm{y}\) 分量,而是认为 \(-\mathrm{z}\) 在世界空间中向上,但根据定义,“ \(\mathrm{y}\) 在上方”在视图空间 (view space) 中仍然适用。

欧拉角虽然适用于较小的角度变化或观看者方向,但还有其他一些严重的限制——很难将两组欧拉角组合使用。例如,在一组和另一组之间进行插值并不是对每个角度进行插值的简单问题。实际上,两组不同的欧拉角可以给出相同的方向,因此任何插值都不应旋转对象。这些是使用本章稍后讨论的替代方向表示形式(例如四元数)值得追求的一些原因。使用欧拉角,你还会遇到被称为万向节锁定(gimbal lock)的问题,这将在第 4.2.2 节中介绍。

4.2.2 从欧拉变换中提取参数 Extracting Parameters from the Euler Transform

在某些情况下,使用从正交矩阵中提取欧拉参数 \(h\)\(p\)\(r\) 的过程很有用。此过程如公式 \(4.22\) 所示:

\[\mathbf{E}(h, p, r)=\left(\begin{array}{lll} e_{00} & e_{01} & e_{02} \\ e_{10} & e_{11} & e_{12} \\ e_{20} & e_{21} & e_{22} \end{array}\right)=\mathbf{R}_{z}(r) \mathbf{R}_{x}(p) \mathbf{R}_{y}(h) \tag{4.22} \]

在这里,我们放弃了 3×3 矩阵的 4×4 矩阵,因为后者提供了旋转矩阵的所有必要信息。也就是说,等效的 4×4 矩阵的其余部分始终在右下位置包含0和一个1。

\[\mathbf{E}=\left(\begin{array}{ccc} \cos r \cos h-\sin r \sin p \sin h & -\sin r \cos p & \cos r \sin h-\sin r \sin p \cos h \\ \sin r \cos h-\cos r \sin p \sin h & \cos r \cos p & \sin r \sin h-\cos r \sin p \cos h \\ -\cos p \sin h & \sin p & \cos p \cos h \end{array}\right) \tag{4.23} \]

由此可见,pitch 参数由 \(\sin p=e_{21}\) 给出。同样,将 \(e_{01}\) 除以 \(e_{11}\) ,并类似地将 \(e_{20}\) 除以 \(e_{22}\) ,会产生以下用于 head 和 roll 参数的提取公式:

\[\frac{e_{01}}{e_{11}}=\frac{-\sin r}{\cos r}=-\tan r \quad \text { and } \quad \frac{e_{20}}{e_{22}}=\frac{-\sin h}{\cos h}=-\tan h \tag{4.24} \]

因此,如公式 \(4.25\) 所示,使用函数 \(atan2 (\mathrm{y}, \mathrm{x})\) (请参阅第1章第8页)从矩阵E提取欧拉参数h (head), \(\mathrm{p}\) (pitch) 和 \(\mathrm{r}\) (roll) :

\[\begin{aligned} &h=\operatorname{atan} 2\left(-e_{20}, e_{22}\right) \\ &p=\arcsin \left(e_{21}\right) \\ &r=\operatorname{atan} 2\left(-e_{01}, e_{11}\right) \end{aligned} \tag{4.25} \]

但是,有一种特殊情况需要处理。如果 \(\operatorname{cos p}=0\) ,则具有万向节锁定 (第 \(4.2 .2\) 节),旋转角度r和h将绕同一轴旋转 (尽管可能沿不同 的方向旋转,具体取决于 \(\mathbf{P}\) 旋转角度是 \(-\pi / 2\) 还是 \(\pi / 2\) ),因此只需导出一个角度。如果我们任意设置 \(h=0\) [1769], 我们得到

\[\mathbf{E}=\left(\begin{array}{ccc} \cos r & \sin r \cos p & \sin r \sin p \\ \sin r & \cos r \cos p & -\cos r \sin p \\ 0 & \sin p & \cos p \end{array}\right) \tag{4.26} \]

由于 \(\mathbf{p}\) 不影响第一列中的值,因此当 \(\cos p=0\) 时,我们可以使用 \(\sin r / \cos r=\tan r=e_{10} / e_{00}\) ,得出 \(r=a \tan 2\left(e_{10}, e_{00}\right)\)

请注意,根据反正弦的定义, \(-\pi / 2 \leqslant p \leqslant \pi / 2\) ,这意味着如果使用该间隔之外的 \(\mathbf{p}\) 值创建 \(\mathbf{E}\) ,则无法提取原始参数。 \(h, p\)\(r\) 不是唯 一的,意味着可以使用一组以上的欧拉参数来产生相同的变换。有关欧拉角转换的更多信息,请参见 Shoemake 的 1994 年文 章 [1636]。上面概述的简单方法可能会导致数值不隐定的问题,这在速度方面会付出一定的代价来避免 [1362]

当您使用欧拉变换时,可能会发生万向节锁定(gimbal lock) \([499,1633]\) 。这发生在旋转时,会因此失去一个自由度。举个例子,变换的顺序是 \(x / y / z\) 。假设第二次旋转我们绕 \(\mathrm{y}\) 轴旋转 \(\pi / 2\) 。这样做会旋转局部 \(\mathrm{z}\) 轴以使其与原始 \(\mathrm{x}\) 轴对齐,因此围绕 \(\mathrm{z}\) 的最终旋转是不正确的。

在数学上,我们已经在公式 \(4.26\) 中看到了万向节锁定,其中假设 \(\cos p=0\) ,即 \(p=\pm \pi / 2+2 \pi k\) ,其中 \(k\) 是整数。有了这样的 \(p\) 值, 我们失去了一个自由度,因为矩阵仅取决于一个角度 \(r+h\)\(r-h\) (但不是同时取决于两个角度)。
尽管在模型系统中通常以 \(x / y / z\) 顺序表示欧拉角,但绕每个局部轴旋转时,其他顺序也是可行的。例如,动画中使用 \(z / x / y\) ,动画和物理学中都使用 \(z / x / z\) 。所有都是指定三个单独旋转的有效方法。最后的 \(z / x / z\) 顺序在某些应用中可能会更好,因为只有当绕 \(\mathrm{x}\) 旋转 \(\pi\) 弧 度 (半旋转) 时,才会发生万向节锁定。没有完美的序列可以避免万向节锁定。尽管如此,欧拉角还是很常用的,因为动画师更喜欢曲线编辑器来指定角度如何随时间变化 [499]。

示例:约束变换

想象您拿着一把 (虚拟的) 扳手紧紧地抓住螺栓。为了将螺栓固定到位,您必须围绕 \(\mathrm{x}\) 轴旋转扳手。现在假设您的输入设备(鼠标,VR 手套,太空球等) 为扳手的运动提供了一个旋转矩阵,即一个旋转矩阵。问题在于,将这种变换应用于扳手,扳手应该只绕X轴旋转,这可能是错误的。要将输入变换 (称为 \(\mathbf{P}\) ) 限制为绕 \(\mathrm{x}\) 轴旋转,只需使用本节中介绍的方法提取欧拉角 \(h , p\)\(r\) ,然后创建一个新矩阵 \(\mathbf{R}_{x}(p)\) 。然后,这是一种受欢迎的变换,它将使扳手绕 \(\mathrm{x}\) 轴旋转(如果 \(\mathbf{P}\) 现在包含这样的运动 ) 。

4.2.3 矩阵分解 Matrix Decomposition

到目前为止,我们一直在假设我们知道所使用的变换矩阵的初始状态和历史记录的情况下进行工作。通常情况下并非如此。例如,仅连接的矩阵可以与某个变换后的对象相关联。从级联矩阵中提取各种变换的任务称为矩阵分解(matrix decomposition)。

提取变换的原因有很多。用途包括:

  • 为对象提取比例因子。
  • 查找特定系统所需的变换。(例如,某些系统可能不允许使用任意 4×4 矩阵。)
  • 确定模型是否仅经历了刚体变换。
  • 在动画中的关键帧之间进行插值,其中仅对象的矩阵可用。
  • 从旋转矩阵中删除剪切。

我们已经提出了两种分解方法,分别是为刚体变换导出平移和旋转矩阵(第4.1.6节)以及从正交矩阵导出欧拉角(第4.2.2节)。

正如我们所看到的,提取变换矩阵很简单,因为我们只需要 4×4 矩阵的最后一列中的元素。我们还可以通过检查矩阵的行列式是否为负来确定是否发生了反射。要分离出旋转,缩放和剪切,需要花费更多的精力。

幸运的是,有几篇关于该主题的文章以及在线提供的代码。Thomas [1769] 和Goldman [552,553] 各自针对各种类型的变换提出了一些不同的方法。Shoemake [1635] 改进了他们的仿射矩阵技术,因为他的算法与参考系无关,并尝试分解矩阵以获得刚体变换。

4.2.4 绕任意轴旋转 Rotation about an Arbitrary Axis

有时使用使实体绕任意轴旋转某个角度的过程会很方便。假设旋转轴 \(\mathbf{r}\) 已归一化,并且应该创建一个围绕 \(\mathbf{r}\) 旋转 \(\alpha\) 弧度的变换。
为此,我们首先变换到一个空间,这个空间里我们要围绕其旋转的轴是 \(\mathrm{x}\) 轴。这是通过一个称为 \(\mathrm{M}\) 的旋转矩阵完成的。然后执行实际的 旋转,之后使用 \(\mathrm{M}^{-1}\) 进行变换 [314]。此过程如图 \(4.8\) 所示。
为了计算 \(\mathbf{M}\) ,我们需要找到两个对 \(\mathbf{r}\) 来说正交的轴。我们专注于找到第二根轴 \(s\) ,知道第三根轴将是第一根轴和第二根轴的叉积, \(\mathbf{t}=\mathbf{r} \times \mathbf{s}\) 。一种数字稳定的方法是找到 \(\mathbf{r}\) 的最小成分 (绝对值),并将其设置为 0 。交换剩余的两个成分,然后对它们中的第一个取反 (实际上,可以否定非零分量中的任何一个)。在数学上,这表示为 [784]:

\[\begin{aligned} &\overline{\mathbf{s}}= \begin{cases}\left(0,-r_{z}, r_{y}\right), & \text { if }\left|r_{x}\right| \leqslant\left|r_{y}\right| \text { and }\left|r_{x}\right| \leqslant\left|r_{z}\right| \\ \left(-r_{z}, 0, r_{x}\right), & \text { if }\left|r_{y}\right| \leqslant\left|r_{x}\right| \text { and }\left|r_{y}\right| \leqslant\left|r_{z}\right| \\ \left(-r_{y}, r_{x}, 0\right), & \text { if }\left|r_{z}\right| \leqslant\left|r_{x}\right| \text { and }\left|r_{z}\right| \leqslant\left|r_{y}\right|\end{cases} \\ &\mathbf{s}=\overline{\mathbf{s}} /\|\overline{\mathbf{s}}\|, \\ &\mathbf{t}=\mathbf{r} \times \mathbf{s} \end{aligned} \tag{4.27} \]

这保证了 \(\overline{\mathbf{s}}\)\(\mathbf{r}\) 正交 (垂直),并且 \((\mathbf{r}, \mathbf{s}, \mathbf{t})\) 是正交的基础。Frisvad [496] 提出了一种在代码中没有任何分支的方法,该方法速度更快,但准确性较低。Max [1147] 和 Duff 等。[388] 提高了 Frisvad 方法的准确性。无论采用哪种技术,都会使用这三个向量来创建旋转矩阵:

\[\mathbf{M}=\left(\begin{array}{c} \mathbf{r}^{T} \\ \mathbf{s}^{T} \\ \mathbf{t}^{T} \end{array}\right) \tag{4.28} \]

该矩阵将向量 \(\mathbf{r}\) 变换为 \(x\) 轴,将 \(\mathbf{s}\) 变换为 \(y\) 轴,将 \(\mathbf{t}\) 变换为 \(z\) 轴。因此,然后使围绕标准化向量 \(\mathbf{r}\) 旋转 \(\alpha\) 弧度的最终变换为:

\[\mathbf{X}=\mathbf{M}^{T} \mathbf{R}_{x}(\alpha) \mathbf{M} \tag{4.29} \]

Goldman [550]提出了另一种通过 \(\phi\) 弧度绕任意归一化轴 \(\mathbf{r}\) 旋转的方法。在这里,我们只介绍他的变换:

\[\mathbf{R}=\left(\begin{array}{ccc} \cos \phi+(1-\cos \phi) r_{x}^{2} & (1-\cos \phi) r_{x} r_{y}-r_{z} \sin \phi & (1-\cos \phi) r_{x} r_{z}+r_{y} \sin \phi \\ (1-\cos \phi) r_{x} r_{y}+r_{z} \sin \phi & \cos \phi+(1-\cos \phi) r_{y}^{2} & (1-\cos \phi) r_{y} r_{z}-r_{x} \sin \phi \\ (1-\cos \phi) r_{x} r_{z}-r_{y} \sin \phi & (1-\cos \phi) r_{y} r_{z}+r_{x} \sin \phi & \cos \phi+(1-\cos \phi) r_{z}^{2} \end{array}\right) \tag{4.30} \]

在第 4.3.2 节中,我们提出了解决此问题的另一种方法,使用四元数(Quaternions)。在那个部分中,还有针对相关问题(例如从一个向量到另一个向量的旋转)的更高效的算法。

4.3 四元数 Quaternions

四元数是复数的扩展,尽管它是威廉·罗恩·汉密尔顿爵士(Sir William Rowan Hamilton)于1843年发明的,但直到1985年,Shoemake [1633]才将它们引入计算机图形学领域。四元数用于表示旋转和方向。它们在几种方面都优于欧拉角和矩阵。任何三维定向都可以表示为围绕特定轴的单个旋转。当给出了该轴和角度表示后,四元数的平移很简单,而在任一方向上的欧拉角转换都十分有挑战性。四元数可用于稳定方向和恒定插值,而欧拉角无法很好地完成这些操作。

复数(complex number)具有实部和虚部。每个复数都由两个实数表示,其中第二个实数乘以 \(\sqrt{1}\)。同样,四元数有四个部分。前三个值与旋转轴密切相关,旋转角影响所有四个部分(有关更多信息,请参见第 4.3.2 节)。每个四元数由四个实数表示,每个实数与一个不同的部分相关联。由于四元数具有四个分量,因此我们选择将它们表示为向量,但是为了区分它们,我们在它们上加了一个帽子:。\(\sqrt{1}\)我们从四元数的一些数学背景开始,然后将其用于构建各种有用的变换。

4.3.1 数学背景 Mathematical Background

四元数的定义:定义 四元数 \(\hat{\mathrm{q}}\) 可以用以下所有等效的方式定义

\[\begin{aligned} &\hat{\mathbf{q}}=\left(\mathbf{q}_{v}, q_{w}\right)=i q_{x}+j q_{y}+k q_{z}+q_{w}=\mathbf{q}_{v}+q_{w}, \\ &\mathbf{q}_{v}=i q_{x}+j q_{y}+k q_{z}=\left(q_{x}, q_{y}, q_{z}\right), \\ &i^{2}=j^{2}=k_{2}=-1, j k=-k j=i, k i=-i k=j, i j=-j i=k . \end{aligned} \tag{4.31} \]

变量 \(q_{w}\) 被称为四元数 \(\hat{\mathbf{q}}\) 的实部 (real part) 。虚部 (imaginary part) 为 \(\mathbf{q}_{v}, i, j\)\(k\) 称为虚单位 (imaginary units) 。
对于虚部 \(\hat{\mathrm{q}}\) ,我们可以使用所有法向向量运算,例如加法,缩放,点积,叉积等等。使用四元数的定义,得出两个四元数 \(\hat{\mathrm{q}}\)\(\hat{\mathrm{r}}\) 之间的 乘法运算,如下所示。注意,虚部的乘法是不可交换的。

乘法(Multiplication):

\[\begin{aligned} &\hat{\mathbf{q}} \hat{\mathbf{r}}=\left(i q_{x}+j q_{y}+k q_{z}+q_{w}\right)\left(i r_{x}+j r_{y}+k r_{z}+r_{w}\right) \\ &=i\left(q_{y} r_{z}-q_{z} r_{y}+r_{w} q_{x}+q_{w} r_{x}\right) \\ &+j\left(q_{z} r_{x}-q_{x} r_{z}+r_{w} q_{y}+q_{w} r_{y}\right) \\ &+k\left(q_{x} r_{y}-q_{y} r_{x}+r_{w} q_{z}+q_{w} r_{z}\right) \\ &+q_{w} r_{w}-q_{x} r_{x}-q_{y} r_{y}-q_{z} r_{z} \\ &=\left(\mathbf{q}_{v} \times \mathbf{r}_{v}+r_{w} \mathbf{q}_{v}+q_{w} \mathbf{r}_{v}, q_{w} r_{w}-\mathbf{q}_{v} \cdot \mathbf{r}_{v}\right) \end{aligned} \tag{4.32} \]

从该方程式可以看出,我们使用叉积和点积来计算两个四元数的乘法。
除了四元数的定义外,还需要加法,共轭,范数和恒等式的定义:

加法(Addition):

\[\hat{\mathbf{q}}+\hat{\mathbf{r}}=\left(\mathbf{q}_{v}, q_{w}\right)+\left(\mathbf{r}_{v}, r_{w}\right)=\left(\mathbf{q}_{v}+\mathbf{r}_{v}, q_{w}+r_{w}\right) \]

共轭(Conjugate):

\[\hat{\mathbf{q}}^{*}=\left(\mathbf{q}_{v}, q_{w}\right)^{*}=\left(-\mathbf{q}_{v}, q_{w}\right) \]

归一化(Norm):

\[\begin{aligned} n(\hat{\mathbf{q}})&=\sqrt{\hat{\mathbf{q}} \hat{\mathbf{q}}^{*}}=\sqrt{\hat{\mathbf{q}}^{*} \hat{\mathbf{q}}}=\sqrt{\mathbf{q}_{v} \cdot \mathbf{q}_{v}+q_{w}^{2}} \\ &=\sqrt{q_{x}^{2}+q_{y}^{2}+q_{z}^{2}+q_{w}^{2}} \end{aligned} \tag{4.33} \]

恒等式(Identity):

\[\hat{\mathbf{i}}=(\mathbf{0}, 1) \]

当简化 \(n(\hat{\mathbf{q}})=\sqrt{\hat{\mathbf{q}} \hat{\mathbf{q}}^{*}}\) 时 (如上所示),虚部将抵消,仅保留实部。归一化有时表示为 \(\|\hat{\mathbf{q}}\|=n(\hat{\mathbf{q}})\) [1105]。上面的结果是,可以导出由 \(\hat{q}^{-1}\) 表示的乘法逆。方程 \(\hat{q}^{-1} \hat{\mathbf{q}}=\hat{\mathrm{q}} \hat{\mathrm{q}}^{-1}=1\) 对于逆必须成立 (对于乘法逆来说是常见的)。我们从归一化的定义中得出一个公式:

\[n(\hat{\mathbf{q}})^{2}=\hat{\mathbf{q}} \hat{\mathbf{q}}^{*} \Leftrightarrow \frac{\hat{\mathbf{q}} \hat{\mathbf{q}}^{*}}{n(\hat{\mathbf{q}})^{2}}=1 \tag{4.34} \]

这给出了乘法逆,如下所示:

逆(Inverse):

\[\hat{\mathbf{q}}^{-1}=\frac{1}{n(\hat{\mathbf{q}})^{2}} \hat{\mathbf{q}}^{*} \tag{4.35} \]

逆公式使用标量乘法,它是从公式 \(4.3 .1\) 中的乘法得出的运算: \(s \hat{\mathbf{q}}=(\mathbf{0}, s)\left(\mathbf{q}_{v}, q_{w}\right)=\left(s \mathbf{q}_{v}, s q_{w}\right)\), \(\hat{\mathbf{q}} s=\left(\mathbf{q}_{v}, q_{w}\right)(\mathbf{0}, s)=\left(s \mathbf{q}_{v}, s q_{w}\right) ,\) 这意味着标量乘法是可交换的: \(s \hat{\mathbf{q}}=\hat{\mathbf{q}} s=\left(s \mathbf{q}_{v}, s q_{w}\right)\). 我们可以从定义中轻松得出以下规则集合:

共轭规则(Conjugate rules):

\[\begin{aligned} &\left(\hat{\mathbf{q}}^{*}\right)^{*}=\hat{\mathbf{q}} \\ &(\hat{\mathbf{q}}+\hat{\mathbf{r}})^{*}=\hat{\mathbf{q}}^{*}+\hat{\mathbf{r}}^{*} \\ &(\hat{\mathbf{q}} \hat{\mathbf{r}})^{*}=\hat{\mathbf{r}}^{*} \hat{\mathbf{q}}^{*} \end{aligned} \tag{4.36} \]

归一化规则(Norm rules):

\[\begin{aligned} &n\left(\hat{\mathbf{q}}^{*}\right)=n(\hat{\mathbf{q}}) \\ &n(\hat{\mathbf{q}} \hat{\mathbf{r}})=n(\hat{\mathbf{q}}) n(\hat{\mathbf{r}}) \end{aligned} \tag{4.37} \]

乘法定律(Laws of Multiplication)(包含以下两个):

线性度(Linearity):

\[\begin{aligned} &\hat{\mathbf{p}}(s \hat{\mathbf{q}}+t \hat{\mathbf{r}})=s \hat{\mathbf{p}} \hat{\mathbf{q}}+t \hat{\mathbf{p}} \hat{\mathbf{r}} \\ &(s \hat{\mathbf{p}}+t \hat{\mathbf{q}}) \hat{\mathbf{r}}=s \hat{\mathbf{p}} \hat{\mathbf{r}}+t \hat{\mathbf{q}} \hat{\mathbf{r}} \end{aligned} \tag{4.38} \]

关联性(Associativity):

\[\hat{\mathbf{p}}(\hat{\mathbf{q}} \hat{\mathbf{r}})=(\hat{\mathbf{p}} \hat{\mathbf{q}}) \hat{\mathbf{r}} \]

单位四元数 \(\hat{\mathbf{q}}=\left(\mathbf{q}_{v}, q_{w}\right)\) 使得 \(n(\hat{\mathbf{q}})=1\) 。由此可以得出 \(\hat{\mathbf{q}}\) 可写为

\[\hat{\mathbf{q}}=\left(\sin \phi \mathbf{u}_{q}, \cos \phi\right)=\sin \phi \mathbf{u}_{q}+\cos \phi \tag{4.39} \]

对于某些二维向量 \(\mathbf{u}_{q}\) ,例如 \(\left\|\mathbf{u}_{q}\right\|=1\) , 因为

\[\begin{aligned} n(\hat{\mathbf{q}})&=n\left(\sin \phi \mathbf{u}_{q}, \cos \phi\right)=\sqrt{\sin ^{2} \phi\left(\mathbf{u}_{q} \cdot \mathbf{u}_{q}\right)+\cos ^{2} \phi} \\ &=\sqrt{\sin ^{2} \phi+\cos ^{2} \phi}=1 \end{aligned} \tag{4.40} \]

当且仅当 \(\mathbf{u}_{q} \cdot \mathbf{u}_{q}=1=\left\|\mathbf{u}_{q}\right\|^{2}\) 。如在下一节中将看到的,单位四元数非常适合以最有效的方式创建旋转和方向。但在此之前,将为单 元四元数引入一些额外的操作。

对于复数,可以将二维单位向量写为 \(\cos \phi+i \sin \phi=e^{i \phi}\) 。四元数的等效项是

\(\hat{\mathbf{q}}=\sin \phi \mathbf{u}_{q}+\cos \phi=e^{\phi \mathbf{u}_{q}}\)

单位四元数的对数和幂函数来自公式 \(4.41\) :

对数: \(\log (\hat{\mathbf{q}})=\log \left(e^{\phi \mathbf{u}_{q}}\right)=\phi \mathbf{u}_{q}\)

幕: \(\hat{\mathbf{q}}^{t}=\left(\sin \phi \mathbf{u}_{q}+\cos \phi\right)^{t}=e^{\phi t \mathbf{u}_{q}}=\sin (\phi t) \mathbf{u}_{q}+\cos (\phi t)\).

4.3.2 四元数变换 Quaternion Transforms

图 4.9。由单位四元数 \(\hat{\mathbf{q}}=\left(\sin \phi \mathbf{u}_{q}, \cos \phi\right)\). 表示的旋转变换的图示。变换围绕轴 \(\mathbf{u}_{q}\) 旋转 \(2 \phi\) 弧度。

现在,我们将研究四元数集的子类,即单位长度的子类,称为单位四元数( unit quaternions)。关于单元四元数的最重要事实是,它们可以表示任何三维旋转,并且这种表示极其紧凑和简单。

现在,我们将说明使单元四元数对旋转和定向如此有用的原因。首先,将一个点或向量的四个坐标 \(\mathbf{p}=\left(p_{x} p_{y} p_{z} p_{w}\right)^{T}\) 放入四元数 \(\hat{\mathbf{p}}\)的分量中,并假设我们有一个单位四元数 \(\hat{\mathbf{q}}=\left(\sin \phi \mathbf{u}_{q}, \cos \phi\right)\) 。可以证明:

\[\hat{\mathbf{q}} \hat{\mathbf{p}} \hat{\mathbf{q}}^{-1} \tag{4.43} \]

绕轴 \(\mathbf{u}_{q}\) 旋转 \(\hat{\mathbf{p}}\) (并因此旋转点 \(\mathbf{P}\) ),旋转角度为 \(2 \phi\) 。注意,由于 \(\hat{\mathbf{q}}\) 是单位四元数,因此 \(\hat{\mathbf{q}}^{-1}=\hat{\mathbf{q}}^{*}\) 。见图 4.9。
\(\hat{\mathrm{q}}\) 的任何非零实数倍也表示相同的变换,这意味着 \(\hat{\mathbf{q}}\)\(-\hat{\mathbf{q}}\) 表示相同的旋转。也就是说,取反轴 \(\mathbf{u}_{q}\) 和实部 \(q_{w}\) ,将生成一个四元数,该 四元数的旋转与原始四元数的旋转完全相同。这也意味着从矩阵中提取四元数可以返回 \(\hat{\mathrm{q}}\)\(-\hat{\mathrm{q}}\)
给定两个单位四元数 \(\hat{\mathbf{q}}\)\(\hat{\mathbf{r}}\) ,首先将 \(\hat{\mathbf{q}}\) 然后应用于四元数 \(\hat{\mathbf{p}}\) (可以解释为点 \(\mathbf{P}\) ) 的连接由式 \(4.44\) 给出:

\[\hat{\mathbf{r}}\left(\hat{\mathbf{q}} \hat{\mathbf{p}} \hat{\mathbf{q}}^{*}\right) \hat{\mathbf{r}}^{*}=(\hat{\mathbf{r}} \hat{\mathbf{q}}) \hat{\mathbf{q}}(\hat{\mathbf{r}} \hat{\mathbf{q}})^{*}=\hat{\mathbf{c}} \hat{\mathbf{p}} \hat{\mathbf{c}}^{*} \tag{4.44} \]

在此, \(\hat{\mathbf{c}}=\hat{\mathbf{r}} \hat{\mathbf{q}}\) 是表示单元四元数 \(\hat{\mathbf{q}}\)\(\hat{\mathbf{r}}\) 的级联的单元四元数。

矩阵转换(Matrix Conversion)

由于通常需要组合几个不同的变换,并且大多数变换都是矩阵形式,因此需要一种将公式4.43转换为矩阵的方法。四元数 \(\hat{q}\) 可以转换成 矩阵 \(\mathbf{M}^{q}\) ,如公式 \(4.45\) [1633,1634]所示:

\[\mathbf{M}^{q}=\left(\begin{array}{cccc} 1-s\left(q_{y}^{2}+q_{z}^{2}\right) & s\left(q_{x} q_{y}-q_{w} q_{z}\right) & s\left(q_{x} q_{z}+q_{w} q_{y}\right) & 0 \\ s\left(q_{x} q_{y}+q_{w} q_{z}\right) & 1-s\left(q_{x}^{2}+q_{z}^{2}\right) & s\left(q_{y} q_{z}-q_{w} q_{x}\right) & 0 \\ s\left(q_{x} q_{z}-q_{w} q_{y}\right) & s\left(q_{y} q_{z}+q_{w} q_{x}\right) & 1-s\left(q_{x}^{2}+q_{y}^{2}\right) & 0 \\ 0 & 0 & 0 & 1 \end{array}\right) \tag{4.45} \]

在此,标量为 \(s=2 /(n(\hat{\mathbf{q}}))\) 。对于单元四元数,这简化为

\[\mathbf{M}^{q}=\left(\begin{array}{cccc} 1-2\left(q_{y}^{2}+q_{z}^{2}\right) & 2\left(q_{x} q_{y}-q_{w} q_{z}\right) & 2\left(q_{x} q_{z}+q_{w} q_{y}\right) & 0 \\ 2\left(q_{x} q_{y}+q_{w} q_{z}\right) & 1-2\left(q_{x}^{2}+q_{z}^{2}\right) & 2\left(q_{y} q_{z}-q_{w} q_{x}\right) & 0 \\ 2\left(q_{x} q_{z}-q_{w} q_{y}\right) & 2\left(q_{y} q_{z}+q_{w} q_{x}\right) & 1-2\left(q_{x}^{2}+q_{y}^{2}\right) & 0 \\ 0 & 0 & 0 & 1 \end{array}\right) \tag{4.46} \]

一旦构建了四元数,就无需计算三角函数(trigonometric functions),因此转换过程实际上是有效的。

从正交矩阵 \(\mathbf{M}^{q}\) 到单位四元数 \(\hat{\mathbf{q}}\) 的反向转换要复杂得多。此过程的关键是公式 \(4.46\) 中的矩阵具有以下差异:

\[\begin{aligned} &m_{21}^{q}-m_{12}^{q}=4 q_{w} q_{x} \\ &m_{02}^{q}-m_{20}^{q}=4 q_{w} q_{y} \\ &m_{10}^{q}-m_{01}^{q}=4 q_{w} q_{z} \end{aligned} \tag{4.47} \]

这些等式的含义是,如果 \(q_{w}\) 是已知的,则可以计算向量 \(\mathbf{v}_{q}\) 的值,从而得出 \(\hat{\mathbf{q}}\) 。的踪迹由下式计算

\[\begin{aligned} \operatorname{tr}\left(\mathbf{M}^{q}\right)&=4-2 s\left(q_{x}^{2}+q_{y}^{2}+q_{z}^{2}\right)=4\left(1-\frac{q_{x}^{2}+q_{y}^{2}+q_{z}^{2}}{q_{x}^{2}+q_{y}^{2}+q_{z}^{2}+q_{w}^{2}}\right) \\ &=\frac{4 q_{w}^{2}}{q_{x}^{2}+q_{y}^{2}+q_{z}^{2}+q_{w}^{2}}=\frac{4 q_{w}^{2}}{n(\hat{\mathbf{q}})^{2}} \end{aligned} \tag{4.48} \]

此结果将对单位四元数产生以下转换:

\[\begin{aligned} &q_{w}=\frac{1}{2} \sqrt{\operatorname{tr}\left(\mathbf{M}^{q}\right)}, \quad q_{x}=\frac{m_{21}^{q}-m_{12}^{q}}{4 q_{w}} \\ &q_{y}=\frac{m_{02}^{q}-m_{20}^{q}}{4 q_{w}}, \quad q_{z}=\frac{m_{10}^{q}-m_{01}^{q}}{4 q_{w}} \end{aligned} \tag{4.49} \]

为了具有数值稳定的例程 [1634],应该避免小数除法。因此,首先设置 \(t=q_{w}^{2}-q_{x}^{2}-q_{y}^{2}-q_{z}^{2}\) ,由此得出

\[\begin{aligned} &m_{00}=t+2 q_{x}^{2} \\ &m_{11}=t+2 q_{y}^{2} \\ &m_{22}=t+2 q_{z}^{2} \\ &u=m_{00}+m_{11}+m_{22}=t+2 q_{w}^{2} \end{aligned} \tag{4.50} \]

这又意味着 \(m_{00}, m_{11}, m_{22}\)\(u\) 中的最大值确定 \(q_{x}, q_{y}, q_{z}\), 和 \(q_{w}\) 中的哪个最大。如果 \(q_{w}\) 最大,则使用公式 \(4.49\) 得出四元数。否则,我们注意到以下情况成立:

\[\begin{aligned} &4 q_{x}^{2}=+m_{00}-m_{11}-m_{22}+m_{33} \\ &4 q_{y}^{2}=+m_{00}-m_{11}-m_{22}+m_{33} \\ &4 q_{z}^{2}=+m_{00}-m_{11}-m_{22}+m_{33} \\ &4 q_{w}^{2}=\operatorname{tr}\left(\mathbf{M}^{q}\right) \end{aligned} \tag{4.51} \]

然后,使用上述方程式的适当公式来计算 \(q_{x}, q_{y}\)\(q_{z}\) 的最大值,然后使用公式 \(4.47\) 计算 \(\hat{\mathbf{q}}\) 剩下的部分。 Schuler [1588] 提出了一种无分支但使用四个平方根的变体。

球面线性插值(Spherical Linear Interpolation)

球面线性揷值是一种操作,即在给定两个单元四元数 \(\hat{\mathrm{q}}\)\(\hat{\mathrm{r}}\) 以及参数 \(t \in[0,1]\) 的情况下,计算揷值四元数。例如,这对于动画对象很有 用。揷值相机的方向没有用,因为揷值期间相机的“向上”向量可能会倾斜,通常是一种干扰效果。 该运算的代数形式由下面的复合四元数 \(\hat{\mathrm{s}}\) 表示:

\[\hat{\mathbf{s}}(\hat{\mathbf{q}}, \hat{\mathbf{r}}, t)=\left(\hat{\mathbf{r}} \hat{\mathbf{q}}^{-1}\right)^{t} \hat{\mathbf{q}} \tag{4.52} \]

但是,对于软件实现,以下形式更合适:(下边的 slerp 代表球面线性插值 spherical linear interpolation)

\[\hat{\mathbf{s}}(\hat{\mathbf{q}}, \hat{\mathbf{r}}, t)=\operatorname{slerp}(\hat{\mathbf{q}}, \hat{\mathbf{r}}, t)=\frac{\sin (\phi(1-t))}{\sin \phi} \hat{\mathbf{q}}+\frac{\sin (\phi t)}{\sin (\phi)} \hat{\mathbf{r}} \tag{4.53} \]

为了计算该方程式所需的 \(\phi\) ,可以使用以下事实: \(\cos \phi=q_{x} r_{x}+q_{y} r_{y}+q_{z} r_{z}+q_{w} r_{w}\) [325]。对于 \(t \in[0,1]\) , slerp 函数计算 (唯一 ) 内揷四元数,它们共同构成从 \(\hat{\mathbf{q}}(t=0)\)\(\hat{\mathbf{r}}(t=1)\) 的二维单位球面上的最短弧。圆弧位于由 \(\hat{\mathbf{q}}\)\(\hat{\mathbf{r}}\) 和原点之间的平面与三维单位球面的交点形成的圆上。如图 \(4.10\) 所示。计算出的旋转四元数以固定速度绕固定轴旋转。这样的曲线具有恒定速度,因此加速度为零, 称为测地曲线(geodesic curve) [229]。球体上的大圆是通过原点和球体的平面相交而生成的,这种圆的一部分称为大圆弧(great arc)

图4.10。单位四元数表示为单位球面上的点。 slerp函数用于在四元数之间进行揷值,并且揷值路径是球面上的大弧。请注意,丛 \(\hat{\mathrm{q}}_{1}\)\(\hat{\mathrm{q}}_{2}\) 进行揷值,以及从 \(\hat{\mathrm{q}}_{1}\)\(\hat{\mathrm{q}}_{3}\) 到/ \(\hat{\mathrm{q}}_{2}\) 进行揷值,即使它们到达相同的方向,也是不一样的。

slerp 函数非常适合在两个方向之间进行揷值,并且表现良好 (固定轴,恒定速度)。使用多个欧拉角揷值时则不是这种情况。 实际上,直接计算一个 slerp 是昂贵的操作,因为它涉及调用三角函数(trigonometric functions)。Malyshau [1114]讨论了将四元数集成到渲染管线中的问题。他指出,如果不使用 slerp 而是简单地在像素着色器中对四元数进行归一化,则 90 度角的三角形方向误差最大为 4 度。栅格化三角形时,此错误率可以接受。Li [1039,1040]提供了更快的增量方法 (incremental methods) 来计算不牺牲任何准确性的 slerps。Eberly [406]提出了一种仅使用加法和乘法来计算视力的快速技术。

当方向大于两个时,我们称 \(\hat{\mathbf{q}}_{0}, \hat{\mathbf{q}}_{1}, \ldots, \hat{\mathbf{q}}_{n-1}\) 可用,我们想从 \(\hat{\mathbf{q}}_{0}\)\(\hat{\mathbf{q}}_{1}\) 再到 \(\hat{\mathbf{q}}_{2}\) 进行揷值,依此类推,直到 \(\hat{\mathbf{q}}_{n-1}\) 为止,此时 slerp可以直接使用。现在,当我们称 \(\hat{\mathrm{q}}_{i}\) 时,我们将使用 \(\hat{\mathrm{q}}_{i-1}\)\(\hat{\mathrm{q}}_{i}\) 作为 slerp 的参数。通过 \(\hat{\mathrm{q}}_{i}\) 之后,我们将使用 \(\hat{\mathrm{q}}_{i}\)\(\hat{\mathbf{q}}_{i+1}\) 作为 slerp 的参数。这将导致突然的抽搐现象出现在方向揷值中,如图 \(4.10\) 所示。这类似于线性揷补点时发生的情况。请参阅第 720 页的图 \(17.3\) 的右上部分。某些读者在阅读了第 17 章中的样条线 (splines) 后,可能希望重新阅读以下段落。

一种更好的揷值方法是使用某种样条线。我们在 \(\hat{\mathbf{q}}_{i}\)\(\hat{\mathbf{q}}_{i}+1\) 之间引入四元数 。\(\hat{\mathbf{a}}_{i}\)\(\hat{\mathbf{a}}_{i+1}\)可以在四元数 定义球形\(\hat{\mathbf{q}}_{i}, \hat{\mathbf{a}}_{i}, \hat{\mathbf{a}}_{i+1}\), 和三次揷值。令人惊讶的是,这些额外的四元数的计算如下 [404] (3) 所示:

\[\hat{\mathbf{a}}_{i}=\hat{\mathbf{q}}_{i} \exp \left[-\frac{\log \left(\hat{\mathbf{q}}_{i}^{-1} \hat{\mathbf{q}}_{i-1}\right)+\log \left(\hat{\mathbf{q}}_{i}^{-1} \hat{\mathbf{q}}_{i+1}\right)}{4}\right] \tag{4.54} \]

\(\hat{\mathrm{q}}_{i}\)\(\hat{\mathrm{a}}_{i}\) 将用于使用光滑三次样条球面揷值四元数,如公式 \(4.55\) 所示:

从上面可以看出, squad 函数是通过使用 slerp 的重复球面揷值法构建的 (有关点的重复线性揷值的信息,请参见第 \(17.1 .1\) 节)。揷值将 通过初始方向 \(\hat{\mathrm{q}}_{i}, i \epsilon[0, \ldots, n-1]\) ,但不是通过 \(\hat{\mathrm{a}}_{i}\) 来表示初始位置处的切线方向。

从一个向量旋转到另一个向量(Rotation from One Vector to Another)

常见的操作是通过最短路径从一个方向s转换到另一个方向t。四元数的数学极大地简化了此过程,并显示了四元数与该表示形式的密切关系。首先,将 \(\mathbf{s}\)\(\mathbf{t}\) 归一化。然后计算称为 \(\mathbf{u}\) 的单位旋转轴,其计算公式为\(\mathbf{u}=(\mathbf{s} \times \mathbf{t}) /\|\mathbf{s} \times \mathbf{t}\|_{\circ}\) 接下来, \(e=\mathbf{s} \cdot \mathbf{t}=\cos (2 \phi)\)\(\|\mathbf{s} \times \mathbf{t}\|=\sin (2 \phi)\) ,其中 \(2 \phi\)\(\mathbf{s}\)\(\mathbf{t}\) 之间的角度。那么表示从 \(\mathbf{s}\)\(\mathbf{t}\) 旋转的四元数为 \(\hat{\mathbf{q}}=(\sin \phi \mathbf{u}, \cos \phi)\) 。实际上,使用半角关系 和三角恒等式简化 \(\hat{\mathbf{q}}=\left(\frac{\sin \phi}{\sin 2 \phi}(\mathbf{s} \times \mathbf{t}), \cos \phi\right)\) ,得到 [1197]

\[\hat{\mathbf{q}}=\left(\mathbf{q}_{v}, q_{w}\right)=\left(\frac{1}{\sqrt{2(1+e)}}(\mathbf{s} \times \mathbf{t}), \frac{\sqrt{2(1+e)}}{2}\right) \tag{4.56} \]

\(\mathbf{s}\)\(\mathbf{t}\) 指向几乎相同的方向时,以这种方式直接生成四元数(相对于叉积 \(\mathbf{s} \times \mathbf{t}\) 的归一化)避免了数值不稳定 [1197]。当 \(\mathbf{s}\)\(\mathbf{t}\) 指向 相反的方向时,这两种方法都会出现稳定性问题,因为它们会被零除。当检测到这种特殊情况时,可以使用任何垂直于 \(\mathrm{s}\) 的旋转轴旋转到 \(\mathbf{t}_{\circ}\)
有时我们需要从 \(\mathrm{s}\)\(\mathrm{t}\) 旋转的矩阵表示。在对公式 \(4.46\) 进行一些代数和三角简化之后,旋转矩阵变为 [1233]

\[\mathbf{R}(\mathbf{s}, \mathbf{t})=\left(\begin{array}{cccc} e+h v_{x}^{2} & h v_{x} v_{y}-v_{z} & h v_{x} v_{z}+v_{y} & 0 \\ h v_{x} v_{y}+v_{z} & e+h v_{y}^{2} & h v_{y} v_{z}-v_{x} & 0 \\ h v_{x} v_{z}-v_{y} & h v_{y} v_{z}+v_{x} & e+h v_{z}^{2} & 0 \\ 0 & 0 & 0 & 1 \end{array}\right) \tag{4.57} \]

在此等式中,我们使用了以下中间计算(intermediate calculations):

\[\begin{aligned} &\mathbf{v}=\mathbf{s} \times \mathbf{t} \\ &e=\cos (2 \phi)=\mathbf{s} \cdot \mathbf{t} \\ &h=\frac{1-\cos (2 \phi)}{\sin ^{2}(2 \phi)}=\frac{1-e}{\mathbf{v} \cdot \mathbf{v}}=\frac{1}{1+e} \end{aligned} \tag{4.58} \]

可以看出,由于简化,所有平方根和三角函数都消失了,因此这是创建矩阵的有效方法。请注意,公式 \(4.57\) 的结构类似于公式 \(4.30\) 的结 构,并请注意,后一种形式是怎样做到不需要三角函数。
请注意,当 \(\mathbf{s}\)\(\mathbf{t}\) 平行或接近平行时必须小心,因为 \(\|\mathbf{s} \times \mathbf{t}\| \approx 0\) 。如果 \(\phi \approx 0\) ,那么我们可以返回单位矩阵。但是,如果 \(2 \phi \approx \pi\) ,那 么我们可以绕任何轴旋转 \(\pi\) 弧度。该轴可以作为 \(\mathrm{s}\) 与不平行于 \(\mathrm{s}\) 的任何其他向量之间的叉积找到(第 \(4.2 .4\) 节)。莫勒和休斯使用 Householder 矩阵以不同的方式处理这种特殊情况 [1233]。

4.4 顶点混合 Vertex Blending

想象一下,数字角色的手臂使用前臂和上臂两部分进行动画处理,如图 4.11 左侧所示。该模型可以使用刚体变换进行动画处理(第 4.1.6 节)。但是,这两个部分之间的关节将不会像真正的肘部。这是因为使用了两个单独的对象,因此,关节由来自这两个单独的对象的重叠部分组成。显然,最好只使用一个对象。但是,静态模型零件无法解决使接头具有柔性的问题。

顶点混合(Vertex blending)是解决此问题的一种流行解决方案 [1037,1903]。该技术还有其他几个名称,例如线性混合蒙皮(linear-blend skinning),包络(enveloping)或骨架子空间变形(skeleton-subspace deformation)。虽然此处介绍的算法的确切来源尚不清楚,但定义骨骼并使蒙皮对变化做出反应是计算机动画中的一个古老概念 [1100]。在最简单的形式中,前臂和上臂像以前一样分别进行动画处理,但是在关节处,两个部分通过有弹性的“蒙皮”相连。因此,该弹性部件将具有一组由前臂矩阵转换的顶点和另一组由上臂矩阵转换的顶点。与每个三角形使用单个矩阵相反,这导致三角形的顶点可以通过不同的矩阵进行变换。见图 4.11。

可以看出,由于简化,所有平方根和三角函数都消失了,因此这是创建矩阵的有效方法。请注意,公式 \(4.57\) 的结构类似于公式 \(4.30\) 的结 构,并请注意,后一种形式是怎样做到不需要三角函数。
请注意,当 \(\mathbf{s}\)\(\mathbf{t}\) 平行或接近平行时必须小心,因为 \(\|\mathbf{s} \times \mathbf{t}\| \approx 0\) 。如果 \(\phi \approx 0\) ,那么我们可以返回单位矩阵。但是,如果 \(2 \phi \approx \pi\) ,那 么我们可以绕任何轴旋转 \(\pi\) 弧度。该轴可以作为 \(\mathrm{s}\) 与不平行于 \(\mathrm{s}\) 的任何其他向量之间的叉积找到(第 \(4.2 .4\) 节)。莫勒和休斯使用 Householder 矩阵以不同的方式处理这种特殊情况 [1233]。

通过进一步执行这一步骤,可以使单个顶点可以通过几种不同的矩阵进行变换,并将得到的位置加权并混合到一起。这是通过为动画对象设置骨骼来完成的,其中每个骨骼的变换可能会通过用户定义的权重影响每个顶点。由于整个手臂可能是“弹性的”,即所有顶点可能受到多个矩阵的影响,因此整个网格(mesh)通常称为(骨骼上的)蒙皮(skin)。见图 4.12。许多商业建模系统都具有相同的骨架骨骼建模功能。尽管名称如此,骨骼并不一定必须是刚性的。例如,Mohr 和 Gleicher [1230]提出了添加附加关节以实现诸如肌肉隆起等效果的想法。James 和 Twigg [813]讨论了使用可以挤压和拉伸的骨骼的动画蒙皮。

在数学上,这用公式 \(4.59\) 表示,其中 \(\mathbf{p}\) 是原始顶点,而 \(\mathbf{u}(t)\) 是变换后的顶点,其位置取决于时间 \(\mathbf{t}\) :

\[\mathbf{u}(t)=\sum_{i=0}^{n-1} w_{i} \mathbf{B}_{i}(t) \mathbf{M}_{i}^{-1} \mathbf{p}, \text { where } \sum_{i=0}^{n-1} w_{i}=1, w_{i} \geq 0 \tag{4.59} \]

\(n\) 个骨骼影响 \(\mathbf{P}\) 的位置,这在世界坐标中表示出来。值 \(w_{i}\) 是顶点 \(\mathbf{p}\) 的骨骼 \(i\) 的权重。 \(\mathbf{M}_{i}\) 矩阵从初始骨骼的坐标系转换为世界坐标。 通常,骨骼的控制关节位于其坐标系的原点。例如,前臂骨骼将其肘关节移动到原点,而动画旋转矩阵将手臂的这一部分绕关节移动。 \(\mathbf{B}_{i}(t)\) 矩阵是第 \(i\) 个骨骼的世界变换,会随着时间变化以对对象进行动画处理,并且通常是多个矩阵的串联,例如以前的骨骼变换的层次 结构和局部动画矩阵。

Woodland [1903]深入讨论了一种维护和更新 \(\mathbf{B}_{i}(t)\) 矩阵动画函数的方法。每个骨骼都将一个顶点转换到相对于其自己的参昭系的位置, 并从一组计算点中揷值最终位置。在一些蒙皮讨论中末明确显示矩阵 \(\mathbf{M}_{i}\) ,而是将其视为 \(\mathbf{B}_{i}(t)\) 的一部分。我们在这里介绍它是因为它是 有用的矩阵,并且几乎总是矩阵级联过程的一部分。
在实践中,对于动画的每一帧,为每个骨骼连接矩阵 \(\mathbf{B}_{i}(t)\)\(\mathbf{M}_{i}^{-1}\) ,并且每个结果矩阵都用于变换顶点。顶点 \(\mathbf{P}\) 由不同骨骼的级联矩阵 转换,然后使用权重 \(w_{i}\) 进行混合,因此称为顶点混合(vertex blending)。权重是非负的,并且总和为 1 ,因此发生的事情是将顶点转换 到几个位置,然后在其中进行揷值。这样,对于所有的 \(i=0 \ldots n-1\) ,变换后的点 \(\mathbf{u}\) 将位于点集 \(\mathbf{B}_{i}(t) \mathbf{M}_{i}^{-1} \mathbf{p} \quad(\) 固定的 \(t)\) 的凸包中。 通常也可以使用公式 \(4.59\) 转换法线。根据所使用的变换 (例如,如果骨骼被拉伸或压扁了很多),可能需要对 \(\mathbf{B}_{i}(t) \mathbf{M}_{i}^{-1}\) 的逆进行转 置,如第 \(4.1 .7\) 节中所述。
顶点混合非常适合在 GPU 上使用。网格中的顶点集可以放置在静态缓冲区中,该缓冲区会一次发送到 GPU 并重新使用。在每个框架 中,只有骨骼矩阵会发生变化,而顶点着色器会计算它们对存储的网格的影响。这样,可以最大程度地减少在 CPU 上处理和从 CPU 传 输的数据量,从而使 GPU 可以有效地渲染网格。如果可以将模型的整个骨矩阵一起使用,则是最简单的。否则,必须拆分模型并复制一 些骨骼。或者,可以将骨骼变换存储在顶点访问的纹理中,从而避免达到寄存器存储限制。通过使用四元数表示旋转,每个变换可以仅存 储在两个纹理中 [1639]。如果可用,无序访问视图存储将允许重新使用蒙皮结果 [146]。

我们可以指定超出 \([0,1]\) 范围或不等于 1 的权重集。但是,这仅在使用某些其他混合算法 (例如变形目标,morph targets) (第 \(4.5\) 节) 时才有意义。
基本顶点融合的一个缺点是可能发生不必要的折叠,扭曲和自相交 [1037]。见图 4.13。更好的解决方案是使用双季铵盐 [872,873]。这 种执行蒙皮的技术有助于保持原始变换的刚度,因此避免了四肢的“糖果包裏”扭曲。计算量不到线性蒙皮混合的成本的 \(1.5\) 倍,并且效果 很好,这导致该技术的快速采用。然而,双四元数蒙皮会导致鼓起效果,Le 和 Hodgins [1001]提出了旋转中心蒙皮作为更好的选择。他 们基于这样的假设:局部变换应该是刚体,并且具有相似权重 \(w_{i}\) 的顶点应该具有相似的变换形式。预先为每个顶点计算旋转中心,同时 施加正交 (刚体) 约束以防止肘关节塌陷和糖果包装纸扭曲伪像(candy wrapper twist artifacts)。在运行时,该算法类似于线性混合蒙皮,因为 GPU 在旋转中心执行线性混合蒙皮,随后执行四元数混合步骤。

图4.13。左侧显示了使用线性混合蒙皮时关节处的问题。在右侧,使用双四元数混合可以改善外观。(图片由Ladislav Kavan等人提供,Paul Steed [1693]提供模型。)

4.5 形变 Morphing

在执行动画时,从一个三维模型变形到另一个三维模型可能会很有用 [28、883、1000、1005]。想象一下,一个模型在时间 \(t_{0}\) 显示,我 们希望它在时间 \(t_{1}\) 变成另一个模型。对于介于 \(t_{0}\)\(t_{1}\) 之间的所有时间,都使用某种揷值获得了连续的“混合”模型。变形的一个例子如图 \(4.14\) 所示。
变形 (Morphing) 涉及解决两个主要问题,即顶点对应问题(the vertex correspondence problem) 和揷值问题 (the interpolation problem)。给定两个任意模型,这些模型可能具有不同的拓扑 (topologies),不同的顶点数量和不同的网格连接性,通常必须从建立 这些顶点对应关系开始。这是一个困难的问题,并且在该领域已经进行了很多研究。我们推荐感兴趣的读者阅读 Alexa 的调查 [28]。
但是,如果两个模型之间已经存在一对一的顶点对应关系,则可以在每个顶点的基础上进行揷值。也就是说,对于第一个模型中的每个顶 点,在第二个模型中必须仅存在一个顶点,反之亦然。这使揷值变得容易。例如,线性揷值可以直接在顶点上使用 (其他进行揷值的方法 请参见第 \(17.1\) 节)。

图4.14。顶点变形。为每个顶点定义了两个位置和法线。在每个帧中,中间位置和法线由顶点着色器线性插值。(图片由NVIDIA Corporation提供。)

为了计算时间 \(t \in[t 0, t 1]\) 的变形顶点,我们首先计算 \(s=(t-t 0) /\left(t_{1}-t_{0}\right)\) ,然后进行线性顶点混合,

\[\mathbf{m}=(1-s) \mathbf{p}_{0}+s \mathbf{p}_{1} \tag{4.60} \]

其中 \(\mathbf{P}_{0}\)\(\mathbf{P}_{1}\) 对应于同一顶点,但是在不同的时间, \(t_{0}\)\(t_{1}\)

用户具有更直观控制的变形变体称为变形目标(morph targets)或混合形状(blend shapes) [907]。基本思想可以使用图 4.15 进行解释。

图4.15。在给定两个嘴部姿势的情况下,将计算一组差异向量以控制插值甚至外推。在变形目标中,差异向量用于将运动“添加”到中性面上。对于差异向量使用正权重,会露出笑容,而负权重会产生相反的效果。

我们从一个中性模型开始,在这种情况下,它是一张脸。让我们用 \(N\) 表示该模型。此外,我们还有一组不同的脸部姿势。在示例中只有 一个姿势,即一张笑脸。通常我们可以允许 \(k \geqslant 1\) 个不同的姿势,表示为 \(P_{i}, i \in[1, \ldots, k]\) 。作为预处理,“差异面”的计算公式为:\(D_{i}=P_{i}-N\) ,即,从每个姿势中减去中性模型。

在这一点上,我们有一个中性模型 \(N\) 和一组差异姿势 \(D_{i_{\circ}}\) 然后可以使用以下公式获得变形模型 \(\mathrm{M}\) :

\[M=N+\sum_{i=1}^{k} w_{i} D_{i} \tag{4.61} \]

这是中性模型,最重要的是,我们使用权重 \(w_{i}\) 添加所需的不同姿势的特征。对于图 4.15,设置 \(w_{1}=1\) 可使我们恰好位于揷图中间。使 用 \(w_{1}=0.5\) 给我们一个半张微笑的脸,依此类推。你可以使用负权重,也可以使用大于 1 的权重。
对于这个简单的脸部模型,我们可以添加另一只眉毛“悲伤”的脸。负重的眉毛会产生“快乐”的眉毛。由于位移是累加的,因此该眉毛姿势 可与笑脸姿势一起使用。
变形目标 (Morph targets) 是一种强大的技术,可为动画师提供很多控制,因为模型的不同特征可以独立于其他特征进行操纵。Lewis 等。[1037] 引入了姿势空间变形,它结合了顶点融合和顶点变形目标。Senior [1608]使用预先计算的顶点纹理来存储和检索目标姿势之 间的位移。支持流输出的硬件和每个顶点的 ID 允许在单个模型中使用更多目标,并且可以在GPU上专门计算效果 \([841,1074]\) 。使用低 分辨率网格,然后通过细分阶段和位移映射生成高分辨率网格,可以避免在高度详细的模型中为每个顶点蒙皮的成本 [1971]。

图 4.16。角色 Delsin 在《声名狼藉:私生子(inFAMOUS:Second Son)》中的脸部使用混合形状进行了动画处理。所有这些拍摄都使用相同的静止姿势面孔,然后修改不同的权重以使面孔看起来有所不同。(图像由 Naughty Dog LLC 提供。inFAMOUS Second son c 2014 Sony Interactive EntertainmentLLC。inFAMOUS Second Son 是 Sony Interactive Entertainment LLC 的商标。由 Sucker Punch Productions LLC开发。)

图 4.16 显示了同时使用蒙皮和变形的真实示例。Weronko 和 Andreason [1872]在《 教团 : 1886 (The Order : 1886)》中使用了蒙皮和变形。

4.6 几何缓存回放 Geometry Cache Playback

在剪辑场景(cut scenes)中,可能希望使用极高质量的动画,例如,对于无法使用上述任何方法表示的运动。天真的方法是存储所有帧 的所有顶点,从磁盘读取它们并更新网格。但是,对于短动画中使用的 30,000 个顶点的简单模型,这可能达到 \(50 \mathrm{MB} / \mathrm{s}\) 。 Gneiting [545] 提出了几种将内存成本降低到大约10%的方法。
首先,使用量化 (quantization) 。例如,位置和纹理坐标使用每个坐标的 16 位整数存储。在执行压缩后无法恢复原始数据的意义上,这 一步骤是有损的。为了进一步减少数据,进行了空间和时间预测,并对差异进行了编码。对于空间压缩,可以使用平行四边形预测 [800]。对于三角形带(triangle strip),下一个顶点的预测位置就是当前三角形在边缘周围的三角形平面中反射的三角形,从而形成平 行四边形 (parallelogram) 。与新位置的差异接下来会被编码 (encoded) 。有了良好的预测,大多数值将接近零,这对于许多常用的压缩方案是十分理想的。与 MPEG 压缩类似,在时间维度上也会进行预测。也就是说,每 \(n\) 帧执行一次空间压缩。在这两者之间,将在时间维度上进行预测,例如,如果某个特定顶点通过增量向量从帧 \(n-1\) 移动到帧 \(n\) ,则很可能以与帧 \(n+1\) 相似的量移动。这些技术减少了存储量,从而足以使该系统用于实时流数据。

4.7 投影 Projections

在实际渲染场景之前,必须将场景中的所有相关对象投影到某种平面或某种简单体积上。此后,执行裁剪和渲染(第 2.3 节)。

到目前为止,在本章中看到的变换不影响第四个坐标 \(w\) 分量。也就是说,点和向量在转换后保留了其类型。另外,在 \(4 \times 4\) 矩阵中,最 底行始终为 \(\left(\begin{array}{llll}0 & 0 & 0 & 1\end{array}\right)\) 。透视投影矩阵 (Perspective projection matrices) 是这两个属性的例外: 下一行包含矢量和点操作数,并且经常需 要齐次化 (homogenization) 过程。也就是说, \(w\) 通常不为 1 ,因此需要除以 \(w\) 才能获得非齐次点。在本节中首先讨论的正交投影 (Orthographic projection) 是一种更简单的投影类型,也很常用。它不会影响 \(w\) 分量。

在本部分中,我们假设观看者沿着相机的负 z 轴看,y 轴指向上方,x 轴指向右侧。换言之,这是一个右手坐标系。在某些文章和软件里(例如 DirectX)使用的是左手坐标系,在这些系统中,观看者沿着相机的正 z 轴看。这两种系统都是同等有效的,最后达到的效果是相同的。

图4.17。由公式 \(4.62\) 生成的简单正投影的三种不同视图。当观看者沿者负 \(z\) 轴看时,可以看到此投影,这意味䒴该投影仅跳过 (或设置 为零) \(z\) 坐标,同时保持 \(x\)\(y\) 坐标。请注意, \(z=0\) 两侧的对象都投影到投影平面上。

4.7.1 正交投影 Orthographic Projection

正交投影的特征是平行线在投影之后保持平行。当使用正交投影观看场景时,无论与相机的距离如何,对象都保持相同的大小。矩阵 \(\mathrm{P}_{o}\) ,如下所示,是一个简单的正投影投影矩阵,它使一个点的 \(x\)\(y\) 分量保持不变,而将 \(z\) 分量设置为零,即正投影在平面 \(z=0\) 上:

\[\mathbf{P}_{o}=\left(\begin{array}{cccc} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 \end{array}\right) \tag{4.62} \]

这种投影的效果如图 \(4.17\) 所示。显然, \(\mathbf{P}_{o}\) 是不可逆的,因为它的行列式 \(\left|\mathbf{P}_{o}\right|=0\) 。换句话说,变换从三维下降到二维,并且无法检索 下降的维数。使用这种正交投影进行查看时存在的问题是,它将正 \(z\) 点和负 \(z\) 值的点都投影到投影平面上。通常将 \(z\) 值 (以及 \(x\)\(y\) 值) 限制为一定的间隔是很有用的,例如从 \(n\) (近平面) 到 \(f\) (远平面)。这是下一个变换的目的。
用于执行正交投影的更常见矩阵由六元组 \((l, r, b, t, n, f)\) 表示,分别表示左侧(left),右侧(right),底部 (bottom),顶部 (top), 近侧 (near) 和远侧 (far) 平面。该矩阵缩放并将由这些平面形成的与轴对齐的边界框 (axis-aligned bounding box) (缩写为 AABB; 请参见第22.2节中的定义) 转换为以原点为中心的与轴对齐的立方体。 \(\mathrm{AABB}\) 的最小角为 \((l, b, n)\) ,最大角为是 \((r, t, f)\) 。重要的是要意识到 \(n>f\) ,因为我们正在向下看 \(z\) 轴的负空间。我们的常识是,接近值应该比远端值低,因此,可以让用户 按原样提供它们,然后在内部对它们取反 (negate)。
在 OpenGL 中,与轴对齐的立方体的最小角为 \((-1,-1,-1)\) ,最大角为 \((1,1,1)\) 。在 DirectX 中,范围是 \((-1,-1,0)\)\((1,1,1)\) 。此立 方体称为规范视图体 (canonical view volume),而该体积中的坐标称为规范化设备坐标 (normalized device coordinates) 。变换过程 如图 4.18 所示。变换为标准视图体积的原因是在此处能够更有效地执行裁剪(clipping)。

图4.18。变换规范视图体(canonical view volume)上的轴对齐框(axis-aligned box)。首先平移左侧的框,使其中心与原点重合。然后将其缩放以获取规范视图体的大小,如右侧所示。

变换为标准视图体(canonical view volume)后,将要渲染的几何图形的顶点裁剪(clipped)到该立方体上。最后,通过将剩余的单位正方形映射到屏幕来渲染不在立方体之外的几何图形。此正交变换如下所示:

\[\begin{aligned} \mathbf{P}_{o}&=\mathbf{S}(\mathbf{s}) \mathbf{T}(\mathbf{t})=\left(\begin{array}{cccc} \frac{2}{r-l} & 0 & 0 & 0 \\ 0 & \frac{2}{t-b} & 0 & 0 \\ 0 & 0 & \frac{2}{f-n} & 0 \\ 0 & 0 & 0 & 1 \end{array}\right)\left(\begin{array}{cccc} 1 & 0 & 0 & -\frac{l+r}{2} \\ 0 & 1 & 0 & -\frac{t+b}{2} \\ 0 & 0 & 1 & -\frac{f+n}{2} \\ 0 & 0 & 0 & 1 \end{array}\right) \\ &=\left(\begin{array}{cccc} \frac{2}{r-l} & 0 & 0 & -\frac{r+l}{r-l} \\ 0 & \frac{2}{t-b} & 0 & -\frac{t+b}{t-b} \\ 0 & 0 & \frac{2}{f-n} & -\frac{f+n}{f-n} \\ 0 & 0 & 0 & 1 \end{array}\right) . \end{aligned} \tag{4.63} \]

如该公式所示, \(\mathbf{P}_{o}\) 可以写为平移的串联 \(\mathbf{T}(\mathbf{t})\) ,后面是缩放矩阵 \(\mathbf{S}(\mathbf{s})\) ,其中\(\mathbf{s}=(2 /(r-l), 2 /(t-b), 2 /(f-n))\)\(\mathbf{t}=(-(r+l) / 2,-(t+b) / 2,-(f+n) / 2)\) 。该矩阵是可逆的,(5),即 \(\mathbf{P}_{o}^{-1}=\mathbf{T}(-\mathbf{t}) \mathbf{S}((r-l) / 2,(t-b) / 2,(f-n) / 2)\)
在计算机图形学中,投影后最常使用左手坐标系(left-hand coordinate system),即对于视口, \(x\) 轴向右, \(y\) 轴向上,而 \(z\) 轴进入视 口。因为按昭我们定义 AABB 的方式,远值(far)小于近值(near),所以正交变换(orthographic transform)将始终包含镜像变换 (mirroring transform)。要看到这一点,可以说原始的AABB尺寸与目标尺寸相同,即规范的视图体积。然后,AABB 的坐标是 \((l, b, n)\)\((-1,-1,1)\)\((r, t, f)\)\((1,1,-1)\) 。将其应用于公式 \(4.63\) 可得出

\[\mathbf{P}_{o}=\left(\begin{array}{cccc} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & -1 & 0 \\ 0 & 0 & 0 & 1 \end{array}\right) \tag{4.64} \]

这是一个镜像矩阵 (mirroring matrix)。正是这种镜像将右手观察坐标系 (从负 \(z\) 轴向下看) 转换为左手标准化设备坐标。
DirectX 将 \(z\) 深度 ( \(z\)-depth) 映射到 \([0,1]\) 范围,而不是OpenGL的 \([-1,1]\) 。这可以通过在正交矩阵之后应用简单的缩放和平移矩阵来实 现,即

\[\mathbf{M}_{s t}=\left(\begin{array}{cccc} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0.5 & 0.5 \\ 0 & 0 & 0 & 1 \end{array}\right) \tag{4.65} \]

因此,Direct \(X\) 中使用的正交矩阵为

\[\mathbf{P}_{o[0,1]}=\left(\begin{array}{cccc} \frac{2}{r-l} & 0 & 0 & -\frac{r+l}{r-l} \\ 0 & \frac{2}{t-b} & 0 & -\frac{t+b}{t-b} \\ 0 & 0 & \frac{1}{f-n} & -\frac{n}{f-n} \\ 0 & 0 & 0 & 1 \end{array}\right) \tag{4.66} \]

由于 DirectX 使用行优先格式编写矩阵,因此通常以转置形式呈现。

4.7.2 透视投影 Perspective Projection

透视投影比正交投影更复杂的变换是透视投影,它通常在大多数计算机图形应用程序中使用。在这里,平行线在投影后通常不平行; 相反,它们可能会在极端情况下收敛到单个点。透视更紧密地匹配我们如何感知世界,即,更远的物体更小。

首先,我们将对投影到平面 \(z=-d, d>0\) 上的透视投影矩阵进行推导。我们从世界空间中推导,以简化对世界视图转换的理解。此推 导之后是例如 OpenGL [885] 中使用的更常规的矩阵。

图4.19。用于导出透视投影矩阵 (perspective projection matrix) 的符号。将点 \(\mathbf{p}\) 投影到平面 \(z=-d, d>0\) 上,得出投影点 \(\mathbf{q}\) 。投影是 从摄影机位置的角度进行的,在本例中为原点。右侧的 \(x\) 分量显示了推导中使用的相似三角形。

假设摄像机 (视点) 位于原点,并且我们要将一个点 \(\mathbf{p}\) 投影到平面 \(z=-d, d>0\) ,从而产生一个新点 \(q=\left(q_{x}, q_{y},-d\right)\) 。图4.19 描绘了这种情况。从该图所示的相似三角形中,得出 \(\mathbf{q}\)\(x\) 分量的以下推导:

\[\frac{q_{x}}{p_{x}}=\frac{-d}{p_{z}} \Leftrightarrow q_{x}=-d \frac{p_{x}}{p_{z}} \tag{4.67} \]

\(\mathrm{q}\) 的其他分量的表达式为 \(q_{y}=-d p_{y} / p_{z}\) (类似于 \(q_{x}\) 获得), \(q_{z}=-d\) 。与上面的公式一起,它们给出了透视投影矩阵 \(\mathbf{P}_{p}\) ,如下所示:

\[\mathbf{P}_{p}=\left(\begin{array}{cccc} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & -1 / d & 0 \end{array}\right) \tag{4.68} \]

该矩阵可产生正确的透视投影,可通过以下方式确认

\[\mathbf{q}=\mathbf{P}_{p} \mathbf{p}=\left(\begin{array}{cccc} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & -1 / d & 0 \end{array}\right)\left(\begin{array}{c} p_{x} \\ p_{y} \\ p_{z} \\ 1 \end{array}\right)=\left(\begin{array}{c} p_{x} \\ p_{y} \\ p_{z} \\ -p_{z} / d \end{array}\right) \Rightarrow\left(\begin{array}{c} -d p_{x} / p_{z} \\ -d p_{y} / p_{z} \\ -d \\ 1 \end{array}\right) \tag{4.69} \]

最后一步来自以下事实:整个矢量除以 \(w\) 分量 (在这种情况下为 \(-p_{z} / d\) ),最后得到 1。由于我们要投影到该平面上,因此所得的 \(z\) 值始终为 \(-d\)

从直觉上讲,很容易理解为什么齐次坐标允许投影。齐次化过程的一种几何解释是将点 \(\left(p_{x}, p_{y}, p_{z}\right)\) 投影到平面 \(w=1\) 上。
与正交变换 (orthographic transformation) 一样,还有一个透视变换(perspective transform),而不是实际投影到平面 (不可逆) 上, 而是将视雉从视雉变换为前述的规范视像体。在这里,视雉视点假定从 \(z=n\) 开始并在 \(z=f\) 结束,且 \(0>n>f\)\(z=n\) 处的矩形的 最小角为 \((l, b, n)\) ,最大角为 \((r, t, n)\) 。如图 \(4.20\) 所示。

图4.20。矩阵 \(\mathbf{P}_{p}\) 将视雉体 (view frustum) 转换为单位立方体,称为标准视域 (canonical view volume)

参数 \((l, r, b, t, n, f)\) 确定摄像机的视雉 (view frustum) 。水平视野由视雉的左右平面(由 \(l\)\(r\) 决定) 之间的角度确定。以相同的方式,垂直视野由顶平面和底平面之间的角度(由 \(t\)\(b\) 确定)确定。视野越大,相机“看得越多”。可以通过 \(r \neq-l\)\(t \neq-b\) 来创建不对称视雉 (Asymmetric frusta) 。不对称视雉可用于立体观看(stereo viewing)和虚拟现实(virtual reality)(第 \(21.2 .3\) 节)。
视野 (the field of view) 是提供场景感的重要因素。与计算机屏幕相比,眼睛本身具有物理视野。这种关系是

\[\phi=2 \arctan (w /(2 d)) \tag{4.70} \]

其中 \(\phi\) 是视野, \(w\) 是垂直于视线的物体的宽度, \(d\) 是到物体的距离。例如,一个 25 英寸的显示器大约为 22 英寸宽。在 12 英寸远处, 水平视野为 85 度;在 20 英寸处为 58 度; 在 30 英寸, 40 度 可以使用相同的公式将摄像机镜头的尺寸转换为视野,例如,对于 \(35 \mathrm{~mm}\) 摄像机的标准 \(50 \mathrm{~mm}\) 镜头 (镜框尺寸为 \(36 \mathrm{~mm}\) ), \(\phi=2 \arctan (36 /(2.50))=39.6\) 度。
与物理设置相比,使用更窄的视野将减少透视效果,因为观看者将放大场景。设置较宽的视野将使对象看起来失真 (例如使用广角相机镜 头),尤其是在屏幕边缘附近,并且会放大附近对象的比例。然而,较宽的视野使观看者感觉到物体更大并且更令人印象深刻,并且具有 向用户提供有关周围环境的更多信息的优点。
公式 \(4.71\) 给出了将视雉转化为单位立方体的透视变换矩阵:

\[\mathbf{P}_{p}=\left(\begin{array}{cccc} \frac{2 n}{r-l} & 0 & -\frac{r+l}{r-l} & 0 \\ 0 & \frac{2 n}{t-b} & -\frac{t+b}{t-b} & 0 \\ 0 & 0 & \frac{f+n}{f-n} & -\frac{2 f n}{f-n} \\ 0 & 0 & 1 & 0 \end{array}\right) \tag{4.71} \]

将转换应用到一个点后,我们将得到另一个点 \(\mathbf{q}=\left(q_{x}, q_{y}, q_{z}, q_{w}\right)^{T}\) 。此时的 \(w\) 分量 \(q_{w}\) (通常) 将为非零且不等于1。要获得投影点P, 我们需要除以 \(q_{w}\) ,即

\[\mathbf{p}=\left(q_{x} / q_{w}, q_{y} / q_{w}, q_{z} / q_{w}, 1\right) \tag{4.72} \]

矩阵 \(\mathbf{P}_{p}\) 总是看到 \(z=f\) 映射为 \(+1, z=n\) 映射为 \(-1\)
远平面以外的对象将被裁切,因此不会出现在场景中。另外,透视投影可以令远平面取到无穷远,这使公式 \(4.71\) 变为

\[\mathbf{P}_{p}=\left(\begin{array}{cccc} \frac{2 n}{r-l} & 0 & -\frac{r+l}{r-l} & 0 \\ 0 & \frac{2 n}{t-b} & -\frac{t+b}{t-b} & 0 \\ 0 & 0 & 1 & -2 n \\ 0 & 0 & 1 & 0 \end{array}\right) \tag{4.73} \]

综上所述,应用透视变换 (以任何形式) \(\mathbf{P}_{p}\) ,然后进行裁腮和齐次化 (除以 \(w\) ),从而得到标准化的设备坐标。
为了获得在 OpenGL 中使用的透视变换,出于与正交变换相同的原因,首先将其与 \(\mathbf{S}(1,1,-1,1)\) 相乘。这仅会使公式 \(4.71\) 第三栏中的 值取反。在应用此竸像侃换之后,将近距和远距值输入为正值,且 \(0<n^{\prime}<f^{\prime}\) ,如传统上将其呈现给用户一样。但是,它们仍然代表沿 世界负 \(z\) 轴 (即视线方向) 的距离。出于参考目的,以下是 OpenGL 中的公式:

\[\mathbf{P}_{\text {OpenGL }}=\left(\begin{array}{cccc} \frac{2 n^{\prime}}{r-l} & 0 & \frac{r+l}{r-l} & 0 \\ 0 & \frac{2 n^{\prime}}{t-b} & \frac{r+b}{t-b} & 0 \\ 0 & 0 & -\frac{f^{\prime} \prime}{f^{\prime}-n^{\prime}} & -\frac{2 f^{\prime} n^{\prime}}{f^{\prime}-n^{\prime}} \\ 0 & 0 & -1 & 0 \end{array}\right) \tag{4.74} \]

一个更简掸的设置是仅提供垂直视场 \(\phi\), 宽高比 \(a=w / h\) (其中 \(w \times h\) 是屏幕分辨率), \(n^{\prime}\)\(f^{\prime}\) 。这导致了

\[\mathbf{P}_{O p e n G L}=\left(\begin{array}{cccc} c / a & 0 & 0 & 0 \\ 0 & c & 0 & 0 \\ 0 & 0 & -\frac{f^{\prime}+n^{\prime}}{f^{\prime}-n^{\prime}} & -\frac{2 f^{\prime} n^{\prime}}{f^{\prime}-n^{\prime}} \\ 0 & 0 & -1 & 0 \end{array}\right) \tag{4.75} \]

其中 \(c=1.0 / \tan (\phi / 2)\) 。该矩阵的作用与旧的 gluperspective() 完全一样,后者是OpenGL Utility Library (GLU) 的一 部分。

某些API (例如 DirectX) 将近平面映射到 \(z=0\) (而不是 \(z=-1\) ),而将远平面映射到 \(z=1\) 。此外,DirectX 使用左手坐标系来定义 其投影矩阵。这意味着 DirectX 沿 \(z\) 轴正方向看,并以正数表示近和远值。这是 DirectX 公式:

\[\mathbf{P}_{p[0,1]}=\left(\begin{array}{cccc} \frac{2 n^{\prime}}{r-l} & 0 & -\frac{r+l}{r-l} & 0 \\ 0 & \frac{2 n^{\prime}}{t-b} & -\frac{t+b}{t-b} & 0 \\ 0 & 0 & \frac{f^{\prime}}{f^{\prime}-n^{\prime}} & -\frac{f^{\prime} n^{\prime}}{f^{\prime}-n^{\prime}} \\ 0 & 0 & 1 & 0 \end{array}\right) \tag{4.76} \]

DirectX 在其文档中使用行优先形式,因此此矩阵通常以转置形式发送。
使用透视变换的一种效果是,计算出的深度值不会随输入 \(\mathbf{p}_{z}\) 值线性变化。使用公式 \(4.74-4.76\) 中的任何一个乘以点 \(\mathbf{P}\) 我们可以看到

\[\mathbf{v}=\mathbf{P}_{\mathbf{p}}=\left(\begin{array}{c} \ldots \\ \ldots \\ d p_{z}+e \\ \pm p_{z} \end{array}\right) \tag{4.77} \]

其中省略了 \(v_{x}\)\(v_{y}\) 的详细信息,并且常数 \(d\)\(f\) 取决于所选矩阵。例如,如果我们使用公式 \(4.74\) ,则 \(d=-\left(f^{\prime}+n^{\prime}\right) /\left(f^{\prime}-n^{\prime}\right)\)\(e=-2 f^{\prime} n^{\prime} /\left(f^{\prime}-n^{\prime}\right)\)\(v_{x}=-p_{z}\) 。为了获得归一化设备坐标 (NDC) 中的深度,我们需要除以 \(w\) 分量, 结果是

\[z_{N D C}=\frac{d p_{z}+e}{-p_{z}}=d-\frac{e}{p_{z}} \tag{4.78} \]

其中 \(z_{N D C} \epsilon[-1,+1]\) 用于 OpenGL投影。可以看出,输出深度 \(z_{N D C}\) 与输入深度 \(p_{z}\) 成反比。
例如,如果 \(n^{\prime}=10\)\(f^{\prime}=110\) (使用 OpenGL 术语),则当 \(p_{z}\) 在负 \(z\) 轴 (即中点) 下沿 60 个单位时,归一化设备坐标深度值为 \(0.833\) ,而不是 0 。图 \(4.21\) 显示了改变近平面到原点的距离的影响。近平面和远平面的放置会影响 \(\mathrm{z}\) 缓冲区的精度。第 \(23.7\) 节将进一步 讨论这种效果。

图4.21。改变近平面到原点的距离的影响。距离 \(f^{\prime}-n^{\prime}\) 保持恒定为 100 。随着近平面变得更靠近原点,更接近远平面的点将使用较小范 围的归一化设备坐标 (NDC) 深度空间。这具有使 \(z\) 缓冲区在较大距离处的准确性降低的效果。

有几种增加深度精度的方法。一种常见的方法 (我们称为反向 \(z\) , reversed \(z\) ) 是使用浮点深度或整数存储 \(1.0-z_{N D C}\) [978]。比较如图 \(4.22\) 所示。Reed [1472] 通过仿真显示,使用具有反向 z 的浮点缓冲区可提供最佳精度,这也是整数深度缓冲区 (每个深度通常具有24 位) 的首选方法。对于标准映射(即非反向 \(z\) ),如 Upchurch 和 Des brun [1803]所建议的,在变换中分离投影矩阵可降低错误率。例 如,在 \(\mathbf{T}=\mathbf{P M}\) 的情况下,使用 \(\mathbf{P}(\mathbf{M p})\) 比使用 \(\mathbf{T p}\) 更好。同样,在 \([0.5 , 1.0]\) 范围内, \(\mathrm{pp} 32\)int24 的精度非常相似,因为 \(\mathrm{fp} 32\) 具 有 23 位的尾数。使 \(z_{N D C}\)\(1 / p_{z}\) 成比例的原因是,它使硬件更简单,并且深度压缩更成功,这将在 \(23.7\) 节中详细讨论。

图4.22。使用 DirectX 变换设置深度墴冲区的不同方法,即 \(z_{N D C} \in[0,+1]\) 。左上方: 标准整数深度㹒冲区,此处显示为 4 位精度 \((\) 因此 \(y\) 轴上有 16 个标记)。右上角:远平面设置为 \(\infty\) ,两个轴上的微小偏移表明这样做不会损失大多精度。左下:具有 3 个指数位和 3 个尾数 位用于浮点深度。注意,分布在 \(\mathrm{y}\) 轴上是非线性的,这使得在 \(\mathrm{x}\) 轴上的分布更糟。右下角: 反转的浮点深度,即 \(1-z_{N D C}\) ,结果分布更 好。(揷图由内森里德 (Nathan Reed) 提供。)

Lloyd [1063]提出使用深度值的对数来提高阴影贴图的精度。Lauritzen 等。[991]使用前一帧的 \(z\) 缓冲区( \(z\)-buffer)来确定最大近平面和 最小远平面。对于屏幕空间深度,Kemen [881]建议对每个顶点使用以下重新映射:

\[\begin{aligned} &z=w\left(\log _{2}\left(\max \left(10^{-6}, 1+w\right)\right) f_{c}-1\right), \quad[\text { OpenGL }] \\ &z=w \log _{2}\left(\max \left(10^{-6}, 1+w\right)\right) f_{c} / 2, \quad[\text { DirectX }] \end{aligned} \tag{4.79} \]

其中 \(\mathrm{w}\) 是投影矩阵之后的顶点的 \(w\) 值,而 \(z\) 是顶点着色器的输出 \(z\) 。常数 \(f_{\mathrm{c}}\)\(f_{c}=2 / \log _{2}(f+1)\) ,其中 \(f\) 为远平面。当仅在顶点着色 器中应用此变换时,深度仍将由 GPU 在顶点的非线性变换深度之间在三角形上线性揷值(公式 4.79)。由于对数是单调函数,因此只要 分段线性揷值与精确的非线性变换深度值之间的差异较小,遮挡剔除硬件和深度压缩技术仍将起作用。在大多数情况下,具有足够的几何 细分的情况是正确的。但是,也可以对每个片段应用转换。这是通过输出每个顶点的值 \(e=1+w\) 来完成的,然后由 GPU 在三角形上进 行揷值。然后,像素着色器将片段深度修改为 \(\log ^{2}\left(e_{i}\right) f_{c} / 2\) ,其中 \(e_{i}\)\(e\) 的内揷值。当 GPU 中没有浮点深度并且使用深度较大的距离 进行渲染时,此方法是不错的选择。

Cozzi [1605]建议使用多视雉 (multiple frusta),这可以提高准确度以有效地达到任何期望的比率。视雉在深度方向上分为几个不重叠的 较小的子视锥,它们的联合恰好是视雉。子视雉表以从后到前的顺序渲染。首先,清除颜色和深度缓冲区,并将所有要渲染的对象分类到 它们重叠的每个子视雉中。对于每个子视锥,设置其投影矩阵,清除深度缓冲区,然后渲染与子视雉重叠的对象。

进一步阅读和资源

沉浸式的线性代数网站 [1718]提供了一本有关该主题基础知识的互动书籍,通过鼓励您操纵这些数字来帮助建立直觉。来自 realtimerendering.com 的其他交互式学习工具和变换代码库也已链接至此。

Farin和Hansford的《The Geometry Toolbox》 [461]是一本可以毫不费力地增强人们对矩阵直觉的书籍。另一本有用的著作是Lengyel 的 《Mathematics for 3D Game Programming and Computer Graphics》 [1025]。若想从不同的角度来看,那么也有许多计算机图形学的著作,例如 Hearn 和 Baker [689],Marschner 和 Shirley [1129] 和 Hughes 等人的著作 [785] 介绍了矩阵基础知识。Ochiai 等人的课程 [1310]介绍了矩阵基础以及矩阵的指数和对数,以及用于计算机图形学的知识。Graphics Gems 系列 [72,540,695,902,1344]提出了各种与变换相关的算法,并且在线提供了许多此类算法的代码。Golub 和 Van Loan 的 Matrix Computations [556]通常是认真研究矩阵技术的起点。可以在 Lewis 等人的 SIGGRAPH 论文 [1037]中阅读有关骨骼子空间(skeleton-subspace)变形/顶点混合(deformation/vertex blending) 和形状插值(shape interpolation)的更多信息。

哈特等。[674]和汉森 [663]提供了四元数的可视化。Plet inckx [1421]和 Schlag [1566]提出了在一组四元数之间平滑内插的不同方法。Vlachos 和 Isidoro [1820]推导了四元数的C2插值公式。另外,与四元数插值有关的是沿曲线计算一致坐标系的问题, 是由Dougan [374]处理的。

Alexa [28] 和 Lazarus and Verroust [1000]对许多不同的变形技术进行了调查。Parent’s book [1354]是有关计算机动画技术的绝佳来源。

posted @ 2021-12-14 22:05  straywriter  阅读(95)  评论(0编辑  收藏  举报