"What if angry vectors veer Round your sleeping head, and form. There’s never need to fear Violence of the poor world’s abstract storm." - Robert Penn Warren
transform(变换)是能把点,向量,颜色转换的一种运算(或操作)。可用来定位,重塑,动画:物体灯光或者摄像机。
线性变换(linear transform)可以保持向量加法和标量乘法,即要满足$ f(x) + f(y) = f (x+y)$, $ kf(x) = f(kx)$ 。如$f(x)=5x$是把一个向量的每个元素都乘以5的变换。所有线性变换都可以用一个3×3的矩阵表示。
平移操作如f(v) = v + v'不是线性变换,但这是一个有用的操作平移,可以用仿射变换(affine transform)来完成线性变换和平移的组合。一般表示为一个4×4的矩阵。
用$v=(v_x\ v_y\ v_z\ 0)^\top$表示方向向量,用$v=(v_x\ v_y\ v_z\ 1)^\top$表示坐标点。
所有平移旋转缩放反射和剪切矩阵都是仿射矩阵。一个仿射变换可以是仿射变换序列的级联。
4.1 Basic Transform(基本变换)
符号 | 名称 | 特性 |
---|---|---|
$T(t)$ | translation matrix 平移矩阵 | 移动点。仿射矩阵。 |
$R_x(\rho)$ | rotation matrix 旋转矩阵 | 围绕x轴旋转$\rho$弧度。正交,仿射。 |
$R$ | rotation matrix 旋转矩阵 | 任意旋转矩阵。正交,仿射。 |
$S(s)$ | scaling matrix 缩放矩阵 | 沿x,y,z轴缩放s倍。仿射矩阵。 |
$H_{ij}(s)$ | shear matrix 剪切矩阵 | 关于组件$j$,以剪切因子$s$剪切组件$i$。$i,j\in {x,\ y,\ z}$,仿射矩阵。 |
$E(h,p,r)$ | Euler transform 欧拉变换 | 由欧拉角的head(yaw),pitch,roll决定的方向矩阵。正交,仿射。 |
$P_o(s)$ | orthographic projection 正交投影 | 平行投影到一个平面或一个体积。仿射矩阵。 |
$P_p(s)$ | perspective projection 透视矩阵 | 透视投影到一个平面或一个体积。 |
$slerp(\boldsymbol{\hat{q}},\boldsymbol{\hat{r}},t)$ | slerp transform | 根据给定四元数$\boldsymbol{\hat{q}}$和$\boldsymbol{\hat{r}}$,以及参数t创建一个插值的四元数。 |
4.1.1 Translation(平移)
变换向量$\boldsymbol{t} = (t_x,\ t_y,\ t_z) => \boldsymbol{T}(\boldsymbol{t}) = \boldsymbol{T}(t_x,\ t_y,\ t_z) = \begin{pmatrix} 1\ 0\ 0\ t_x\\0\ 1\ 0\ t_y\\ 0\ 0\ 1\ t_z\\0\ 0\ 0\ 1\\ \end{pmatrix}$
给定一个点$\boldsymbol{p}=(p_x,\ p_y,\ p_z,\ 1)$, 使用$\boldsymbol{T}(\boldsymbol{t})$变换后为$\boldsymbol{p'}(p_x + t_x,\ p_y + t_y,\ p_z + t_z,\ 1)$,
给定一个方向向量$\boldsymbol{v}=(v_x,\ v_y,\ v_z,\ 0)$, 使用$\boldsymbol{T}(\boldsymbol{t})$变换后没有变化。但是其它仿射变换对方向和点都起到相同的作用。在本书中我们使用column-major的形式书写矩阵,关于column-major和row-major只是矩阵的阅读顺序不同。详见https://en.wikipedia.org/wiki/Row-_and_column-major_order。
4.1.2 Rotation(旋转)
类似平移变换,旋转变换也是一个刚体变换。定位矩阵(orientation matrix)是一个和摄像机视角或物体相关联的旋转矩阵,定义了他们空间中的方向,也就是向上和向前的方向。
2D空间中,设一个向量$\boldsymbol{v} = (v_x,\ v_y)$,我们用参数化的形式表示为$\boldsymbol{v} = (r\cos{\theta},\ r\sin{\theta})$。若想旋转此向量$\phi$弧度(逆时针方向),我们会得到$u=(r\cos(\theta+\phi),\ r\sin(\theta+\phi))$,公式可以重写为:
$$
\boldsymbol{u} = \begin{pmatrix}
r\cos(\theta+\phi)\\
r\sin(\theta+\phi)
\end{pmatrix}
= \begin{pmatrix}
r\cos\theta\cos\phi-\sin\theta\sin\phi \\
r\sin\theta\cos\phi+\cos\theta\sin\phi
\end{pmatrix}
= \underbrace{\begin{pmatrix}
\cos\theta & -\sin\phi \\
\sin\theta\ & \cos\phi
\end{pmatrix}}_{\boldsymbol{R}(\phi)}
\underbrace{\begin{pmatrix}
r\cos\theta \\
r\sin\theta
\end{pmatrix}}_{\boldsymbol{v}} \\\\
{\boldsymbol{v}} = \boldsymbol{R}(\phi)\boldsymbol{v}
$$
即推导出2D空间的旋转矩阵。
3D空间中一般用$\boldsymbol{R}_x(\phi)$,$\boldsymbol{R}_y(\phi)$,$\boldsymbol{R}_z(\phi)$表示绕对应轴旋转的旋转矩阵,他们是:
$$
\boldsymbol{R}_x(\phi) = \begin{pmatrix}
1 & 0 & 0 & 0 \\
0 & \cos\phi & -\sin\phi & 0 \\
0 & \sin\phi & \cos\phi & 0 \\
0 & 0 & 0 & 1
\end{pmatrix},
$$
$$
\boldsymbol{R}_y(\phi) = \begin{pmatrix}
\cos\phi & 0 & \sin\phi & 0 \\
0 & 1 & 0 & 0 \\
-\sin\phi & 0 & \cos\phi & 0 \\
0 & 0 & 0 & 1
\end{pmatrix},
$$
$$
\boldsymbol{R}_z(\phi) = \begin{pmatrix}
\cos\phi & -\sin\phi & 0 & 0 \\
\sin\phi & \cos\phi & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1
\end{pmatrix}
$$
去掉最下和最右的一行和一列,我们得到一个3×3的矩阵,他们对角线的和(trace)都是:
$$
tr(\boldsymbol{R}) = 1 + 2\cos\phi
$$
所有旋转矩阵或他们任意数量的串联都是正交的。$\boldsymbol{R}_i^{-1}(\phi) = \boldsymbol{R}_i(-\phi)$。
上图是使物体以p为中心沿着z轴旋转$\phi$弧度一个object的例子:$\boldsymbol{X}=\boldsymbol{T}(\boldsymbol{p})\boldsymbol{R}_z(\phi)\boldsymbol{T}(\boldsymbol{-p})$,注意矩阵的顺序。
4.1.3 Scaling(缩放)
以$\boldsymbol{s}=(\boldsymbol{s}_x,\boldsymbol{s}_y,\boldsymbol{s}_z)$为因子分别沿着对应轴进行缩放。
$$
\boldsymbol{S}(\boldsymbol{s}) = \begin{pmatrix}
s_x & 0 & 0 & 0 \\
0 & s_y & 0 & 0 \\
0 & 0 & s_z & 0 \\
0 & 0 & 0 & 1
\end{pmatrix}
$$
如果$s_x = s_y = s_z$,则称该矩阵为均匀的(uniform)反之则是不均匀的(nonuniform)。$\boldsymbol{S}^{-1}(s) = \boldsymbol{S}(1/s_x,\ 1/s_y,\ 1/s_z)$。在齐次坐标中,右下角的1称为$w$-分量,若$\boldsymbol{S}$代表uniform scaling,$\boldsymbol{S'}$可以代表齐次坐标下的缩放矩阵。
$$
\boldsymbol{S}(\boldsymbol{s}) = \begin{pmatrix}
5 & 0 & 0 & 0 \\
0 & 5 & 0 & 0 \\
0 & 0 & 5 & 0 \\
0 & 0 & 0 & 1
\end{pmatrix},
\boldsymbol{S'}(\boldsymbol{s}) = \begin{pmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1/5
\end{pmatrix}
$$
若s的三个值中有一个或三个负值,则得到一个反射矩阵(reflection matrix),或者称为镜像矩阵(mirror matrix)。如果只有两个值为负值,则相当于旋转180度。串联一个旋转矩阵和一个镜像矩阵得到的还是镜像矩阵:
$$
\underbrace{
\begin{pmatrix}
\cos(\pi/2) & \sin(\pi/2) \\
-\sin(\pi/2) & \cos(\pi/2)
\end{pmatrix}
}_{\text{rotation}}
\underbrace{
\begin{pmatrix}
1 & 0 \\
0 & -1
\end{pmatrix}
}_{\text{reflection}}
= \begin{pmatrix}
0 & -1 \\
-1 & 0
\end{pmatrix}
$$
镜像矩阵常常导致一些问题,比如一个逆时针方向排序顶点的三角形用镜像矩阵变换后会变成顺时针顶点排序。想要确定矩阵为镜像矩阵,可以计算左上3×3矩阵的行列式为负。
沿给定方向缩放,给定方向向量$(\boldsymbol{f^x},\boldsymbol{f^y},\boldsymbol{f^z})$,首先构造矩阵$\boldsymbol{F} = \begin{pmatrix}
\boldsymbol{f^x} & \boldsymbol{f^x} & \boldsymbol{f^x} & 0 \\
0 & 0 & 0 & 1
\end{pmatrix}$,则变换为:
$$
\boldsymbol{X} = \boldsymbol{FS(s)F}^\top
$$
4.1.4 Shearing(剪切)
剪切矩阵可以用在游戏中模拟整个场景扭曲的迷幻效果或者扭曲一个模型的外观等。有6个基本的剪切模型,分别是$H_{xy}(s),H_{xz}(s),H_{yx}(s),H_{yz}(s),H_{zx}(s),H_{zy}(s)$.第一个下标指定哪个坐标值发生变化,第二个下标指定执行变化的坐标。如下图,y,z值不变,x值改变,新的x值为s与x、z的乘积:
该矩阵可写为:(s的位置由x指定了第index=0行,z指定了第index=2列)
$$
\boldsymbol{H}_{xz}(s) = \begin{pmatrix}
1 & 0 & s & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1 \\
\end{pmatrix}
$$
用它和一个已知点$\boldsymbol{p}$做乘法,得到$(p_x+sp_z\ p_y\ p_z)^\top$。$\boldsymbol{H}^{-1}_{ij}(s)=\boldsymbol{H}_ij(-s)$。
可以把剪切矩阵做一点改变,意为两个下标所指坐标值都要由第三个坐标剪切:
$$
\boldsymbol{H'}_{xy}(s,t) = \begin{pmatrix}
1 & 0 & s & 0 \\
0 & 1 & t & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1 \\
\end{pmatrix}
$$
用一种通用的方法可以写作: $\boldsymbol{H}_{ij}(s,t)=\boldsymbol{H}_{ik}(s)\boldsymbol{H}_{jk}(t)$ ,这里的k是第三个坐标,任何满足$\vert\boldsymbol{H}\vert=1$的剪切变换都可以保持体积。
一般对任意矩阵$\boldsymbol{MN}$有$\boldsymbol{MN}\not=\boldsymbol{NM}$:
4.1.5 Concatenation of Transforms(变换的串联)
由于矩阵乘法的不可交换性,矩阵的串联和矩阵相乘的顺序相关,如上图所示。
把矩阵串联成一个的主要目的在于执行效率。合成后的矩阵为:(顺序从右到左执行)
$$
\boldsymbol{C} = \boldsymbol{TRS}\ =>\ \boldsymbol{TRSp} = \boldsymbol{(T(R(Sp)))}
$$
该矩阵可以满足结合律的需求,例如拆分为$\boldsymbol{(TR)(Sp)}$。
4.1.6 The Rigid-Body Transform
只有位移和方向发生变化的变换。
- $\boldsymbol{X}=\boldsymbol{T(t)R}$,
- $\boldsymbol{X}^{-1}=\boldsymbol{(T(t)R)}^{-1}=\boldsymbol{R}^{-1}\boldsymbol{T}(t)^{-1}=\boldsymbol{R}^\top\boldsymbol{T(-t)}$
- ($\overline{\boldsymbol{R}}=(\boldsymbol{r}_{,0}\ \boldsymbol{r}_{,1}\ \boldsymbol{r}_{,2}) = \begin{pmatrix}\boldsymbol{r}_{0,}\\ \boldsymbol{r}_{1,}\\ \boldsymbol{r}_{2,}\\ \end{pmatrix}$) => $\boldsymbol{X}^{-1}=\begin{pmatrix} \boldsymbol{r}_{0,} & \boldsymbol{r}_{1,} & \boldsymbol{r}_{2,} & -\overline{\boldsymbol{R}}^\top \boldsymbol{t} \\ 0&0&0&1 \end{pmatrix} $
一个普遍的例子,计算摄像机的u,v,r坐标:
如图所示,设摄像机位置为$\boldsymbol{c}$,我们希望摄像机朝向(look at)目标$\boldsymbol{l}$,一个给定的摄像机向上的方向是$\boldsymbol{u'}$,我们希望计算一个由三个变量组成的向量${r,u,v}$
- step1: $\boldsymbol{v}=(\boldsymbol{c-1})/|\boldsymbol{c-l}|$
- step2: $\boldsymbol{r} = -(\boldsymbol{v}\times\boldsymbol{u'})/|\boldsymbol{v}\times\boldsymbol{u'}|$,$\boldsymbol{r}$代表向右的向量
- step3: 由于$\boldsymbol{u'}$不能保证准确指向上,所以最终的向上$\boldsymbol{u=v\times{r}}$
我们构建一个摄像机变换矩阵$\boldsymbol{M}$,首先平移所有物体使摄像机至于原点,然后使$\boldsymbol{r}$和$(1,0,0)$重合,$\boldsymbol{u}$和$(0,0,1)$重合:
$$
\boldsymbol{M}= \underbrace{\begin{pmatrix}
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{pmatrix}}_{\text{基础变化 (change of basis)}}
\underbrace{\begin{pmatrix}
1 & 0 & 0 & -t_x \\
0 & 1 & 0 & -t_y \\
0 & 0 & 1 & -t_z \\
0 & 0 & 0 & 1 \\
\end{pmatrix}}_{\text{位移(translation)}}
= \begin{pmatrix}
r_x & r_y & r_z & \boldsymbol{-t}\cdot\boldsymbol{r} \\
u_x & u_y & u_z & \boldsymbol{-t}\cdot\boldsymbol{u} \\
v_x & v_y & v_z & \boldsymbol{-t}\cdot\boldsymbol{v} \\
0 & 0 & 0 & 1 \\
\end{pmatrix}
$$
4.1.7 Normal Transform (法线变换)
一个矩阵可以始终可以变换点线三角或者其他几何体,也可以沿着线段或者三角形表面变换切线向量(tangent vector),但是不总能用于变换表面法线(或者顶点光照法线)。正确的方法是使用伴随矩阵(matrix's adjoint)的转置(transpose)。另一种传统做法是使用顶点变换矩阵的逆转置矩阵。但是逆矩阵不一定存在且效率较低。
多数变换是仿射的,所以由齐次坐标传入的w分量的值不会发生改变,即他们不执行projection。所以只要计算伴随矩阵左上的3×3部分即可。
甚至有时伴随矩阵的计算都是不需要的。设变换矩阵由位移旋转和统一缩放(不形变)组成。位移不改变法线,缩放不影响法线方向,只改变长度,只剩旋转。旋转矩阵本身的转置就是它的逆。所以在这种情况下直接用两个转置(或两个逆)旋转矩阵的组合去计算即可。
重算法线也不是经常需要,如果没有缩放,只有旋转和位移的话;如果有统一缩放,我们可以用缩放因子直接去除法线。
4.1.8 Computation of Inverses (逆的运算)
- 简单变换如$\boldsymbol{M=T(t)R}(\phi)$,它的逆是$\boldsymbol{M^{-1}}=\boldsymbol{R}(-\phi)\boldsymbol{T(-t)}$。
- 若矩阵是正交的,则$\boldsymbol{M^{-1}=M^\top}$,任意序列的旋转矩阵都是旋转矩阵,也是正交矩阵。
- 如果没有已知,则伴随矩阵的方法,克莱姆法则(Cramer's rule),分解法(LU decomposition)或者高斯消元法(Gaussian Elimination)可以用于计算逆矩阵。(前两种更推荐)。
4.2 Special Matrix Transforms and Operations(特殊的矩阵变换和计算)
4.2.1 The Euler Transform(欧拉变换)
首先确定观察方向为-z轴方向,头顶方向为y轴方向,如图:
$$
\boldsymbol{E}(h,p,r)=\boldsymbol{R}_z(r)\boldsymbol{R}_x(p)\boldsymbol{R}_y(h)
$$
矩阵的排序可以有24中不同方式,上面这种更常用。h,p,r代表基于head(或yaw),pitch,roll的旋转量。有时候存在z-up或是y-up的分歧,3D打印工业,航海航空等可能认为z轴朝上,而媒体相关常常将y轴作为向上。本书使用y-up。
欧拉角的限制:两个欧拉角难以组合使用,万向锁问题。
4.2.2 Extracting Parameters from the Euler Transform(从欧拉变换中提取参数)
一些情况下,我们希望在欧拉矩阵中提取h,p,r,在这里我们废弃了4×4矩阵是因为所有有效信息都包含在了左上的3×3矩阵中。
$$
\boldsymbol{E}(h,p,r)= \begin{pmatrix}
e_{00} & e_{01} & e_{02} \\
e_{10} & e_{11} & e_{12} \\
e_{20} & e_{21} & e_{22}
\end{pmatrix}
= \boldsymbol{R}_z(r)\boldsymbol{R}_x(p)\boldsymbol{R}_y(h)
$$
展开后得到:
$$
\boldsymbol{E}(h,p,r)=
\begin{pmatrix}
\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{pmatrix}
$$
得到:
$$
h = atan2(-e_{20}, e_{22}) \\
p = \arcsin(e_{21}) \\
r = atan2(-e_{01}, e_{11})
$$
如果$\cos{p}=0$ ,会产生万向锁问题,导致r和h绕同一个轴转动。万向锁指的是在进行旋转时,失去了一个方向的自由度。尽管如此还是被广泛使用,比如动画师喜欢用曲线编辑器调整角度如何随时间变化。
4.2.3 Matrix Decomposition(矩阵分解)
从串联的矩阵中检索各种变换的任务叫做矩阵分解。以下情况可能会用到:
- 对物体提取缩放因子。
- 为一个特殊的系统找到一个变换,有些系统可能不支持专有的4×4矩阵。
- 判断一个模型是否只经历了刚体变换。
- 在只可以操作矩阵的时候在关键帧间插值
- 从一个旋转矩阵中剔除剪切。
(这里的内容详见原书参考)
4.2.4 Rotation about an Arbitrary Axis(关于任意轴的旋转)
创建一个变换使物体围绕一个归一化的向量$\boldsymbol{r}$旋转$\alpha$弧度。
首先我们先使用一个旋转矩阵$\boldsymbol{M}$把$\boldsymbol{r}$和x轴对齐,我们使用$\boldsymbol{M}^{-1}$退回这个操作。为计算$\boldsymbol{M}$,我们需要找到两个向量使对彼此以及对$\boldsymbol{r}$都正交且归一化。$\boldsymbol{t}=\boldsymbol{r}\times\boldsymbol{s}$。可以找到$\boldsymbol{r}$中绝对值比较小那个设为0,交换剩余两个值,然后把在前面的值设为负。数学描述为:
$$
\overline{\boldsymbol{s}}=
\begin{cases}
(0,-r_z,r_y),
if\
\vert{r_x}\vert\le\vert{r_y}\vert \
\ and \
\vert{r_x}\vert\le\vert{r_y}\vert, \\
(-r_z,0,r_y),
if\
\vert{r_y}\vert\le\vert{r_x}\vert \
\ and \
\vert{r_y}\vert\le\vert{r_z}\vert, \\
(-r_y,r_x,0),
if\
\vert{r_z}\vert\le\vert{r_x}\vert \
\ and \
\vert{r_z}\vert\le\vert{r_y}\vert,
\end{cases}
\\
\boldsymbol{s}=\overline{\boldsymbol{s}}/\vert\vert\overline{\boldsymbol{s}}\vert\vert,
\\
\boldsymbol{t} = \boldsymbol{r}\times\boldsymbol{s}
$$
还有一些其他算法也可以进行此类计算,请查看原书资源部分。
用得到的三个向量创建矩阵$\boldsymbol{M}$:
$$
\boldsymbol{M}=
\begin{pmatrix}
\boldsymbol{r}^\top\\
\boldsymbol{s}^\top\\
\boldsymbol{t}^\top\\
\end{pmatrix}
$$
得出最终矩阵:
$$
\boldsymbol{X} = \boldsymbol{M}^\top\boldsymbol{R}_x(\alpha)\boldsymbol{M}
$$
还有一个来自Goldman的矩阵方法:
$$
\boldsymbol{R}=
\begin{pmatrix}
\cos\phi+(1-\cos\phi)r^2_x &
(1-\cos\phi)r_xr_y-r_z\sin\phi &
(1-\cos\phi)r_xr_z+r_y\sin\phi \\
(1-\cos\phi)r_xr_y+r_z\sin\phi &
\cos\phi + (1-\cos\phi)r^2_y &
(1-\cos\phi)r_yr_z-r_x\sin\phi \\
(1-\cos\phi)r_xr_z-r_y\sin\phi &
(1-\cos\phi)r_yr_z+r_x\sin\phi &
\cos\phi+(1-\cos\phi)r^2_z
\end{pmatrix}
$$
4.3 Quaternions(四元数)
四元数在很多方面都优越于欧拉角或矩阵,用于标识旋转或方向。任意三维方向可以表示为一个对特定轴的旋转。给定的轴或者角度可以简单的转换为四元数或者由四元数转换。四元数还可以用于插值(欧拉角不行)。
一个复数(complex number)有实部(real part)和虚部(imaginary part),类似的,四元数的前三个值和旋转轴密切相关,旋转角度影响四个部分。后面我们用$\hat{\boldsymbol{q}}$表示四元数。
4.3.1 Mathematical Background(数学背景)
定义:
$$
\begin{align}
&\hat{\boldsymbol{q}}
=(\boldsymbol{q}_v,q_w)=iq_x + jq_y + kq_z + q_w = \boldsymbol{q}_v+q_w,\\
&\boldsymbol{q}_v = iq_x + jq_y + kq_z = (q_x,q_y,q_z), \\
&i^2 = j^2 = k^2 = -1,jk=-kj=i,ki=-ik=j,ij=-ji=k
\end{align}
$$
$q_w$被称为四元数的实部,$\boldsymbol{q}_v, i, j,k$称为四元数的虚部。
根据定义,两个四元数$\hat{\boldsymbol{q}},\hat{\boldsymbol{r}}$ 的乘法,注意乘法中虚部是不满足交换律的:
$$
\begin{align}
\hat{\boldsymbol{q}}\hat{\boldsymbol{r}}
=&(iq_x + jq_y + kq_z + q_w)(ir_x + jr_y + kr_z + r_w) \\
=&\quad i(q_yr_z-q_zr_y+r_wq_x+q_wr_x) \\
&+ j(q_zr_x-q_xr_z+r_wq_y+q_wr_y) \\
&+ k(q_xr_y-q_yr_x+r_wq_z+q_wr_z) \\
&+ q_wr_w-q_xr_x-q_yr_y-q_zr_z \\
=&(\boldsymbol{q}_v\times\boldsymbol{r}_v + r_w\boldsymbol{q}_v + q_w\boldsymbol{r}_v, q_w r_w-\boldsymbol{q}_v\cdot\boldsymbol{r}_v)
\end{align}
$$
其他:
$$
\begin{align}
&\text{加法(Addition):}
&&\hat{\boldsymbol{q}}+\hat{\boldsymbol{r}}
=(\boldsymbol{q_v}+\boldsymbol{r_v},q_w+r_w) \\
&\text{共轭(Conjugate):}
&&\hat{\boldsymbol{q_v}}^* = (\boldsymbol{q_v},q_w)^* = (-\boldsymbol{q_v},q_w) \\
&\text{归一化:}
&&n(\hat{\boldsymbol{q}})
= \sqrt{\hat{\boldsymbol{q}}\hat{\boldsymbol{q}}^*}
= \sqrt{\hat{\boldsymbol{q}}^*\hat{\boldsymbol{q}}}
= \sqrt{\boldsymbol{q}_v\cdot\boldsymbol{q}_v + q_w^2}
= \sqrt{q_x^2+q_y^2+q_z^2+q_w^2} \\
&\text{同一性(Identity,即没有旋转):}
&&\hat{\boldsymbol{i}}=(\boldsymbol{0},1) \\
&\text{标量乘法(Scalar Multiplication):}
&&s\hat{\boldsymbol{q}}
= (\boldsymbol{0},s)(\boldsymbol{q}_v,q_w)
= (s\boldsymbol{q}_v, sq_w)
\end{align}
$$
归一化公式简化后,虚部抵消了只剩下实部。以上公式还可以导出乘法的逆$\hat{\boldsymbol{q}}^{-1}$,即$\hat{\boldsymbol{q}}^{-1}\hat{\boldsymbol{q}}=\hat{\boldsymbol{q}}\hat{\boldsymbol{q}}^{-1} = 1$:
$$
n(\hat{\boldsymbol{q}})^2=\hat{\boldsymbol{q}}\hat{\boldsymbol{q}}^*
\Longleftrightarrow
\frac{\hat{\boldsymbol{q}}\hat{\boldsymbol{q}}^*}{n(\hat{\boldsymbol{q}})^2} = 1 \\
\Downarrow \\
\hat{\boldsymbol{q}}^{-1}
=\frac{1}{n(\hat{\boldsymbol{q}})^2}\hat{\boldsymbol{q}}^*
$$
以下定理基于定义很容易证明:
$$
\begin{align}
&共轭法则(Conjugate Rules):
&&(\hat{\boldsymbol{q}}^*)^* = \hat{\boldsymbol{q}} \\
&&& (\hat{\boldsymbol{q}}+\hat{\boldsymbol{r}})^*
=\hat{\boldsymbol{q}}^*+\hat{\boldsymbol{r}}^* \\
&&& (\hat{\boldsymbol{q}}\hat{\boldsymbol{r}})^*
=\hat{\boldsymbol{r}}^*\hat{\boldsymbol{q}}^* \\
&\text{归一法则(Norm Rules):}\\
&&& n(\hat{\boldsymbol{q}}^*)=n(\hat{\boldsymbol{q}}) \\
&&& n(\hat{\boldsymbol{q}}\hat{\boldsymbol{r}})
=n(\hat{\boldsymbol{q}})n(\hat{\boldsymbol{r}}) \\
&\text{乘法法则(Laws of Multiplication): }\\
&\text{线性关系(Linearity):}\\
&&& \hat{\boldsymbol{p}}(s\hat{\boldsymbol{q}}+t\hat{\boldsymbol{r}})
= s\hat{\boldsymbol{p}}\hat{\boldsymbol{q}}
+t\hat{\boldsymbol{p}}\hat{\boldsymbol{r}} \\
&&& (s\hat{\boldsymbol{p}}+t\hat{\boldsymbol{q}})\hat{\boldsymbol{r}}
= s\hat{\boldsymbol{p}}\hat{\boldsymbol{r}}
+t\hat{\boldsymbol{q}}\hat{\boldsymbol{r}} \\
&\text{结合律(Associativity):}\\
&&& \hat{\boldsymbol{p}}(\hat{\boldsymbol{q}}\hat{\boldsymbol{r}})
= (\hat{\boldsymbol{p}}\hat{\boldsymbol{q}})\hat{\boldsymbol{r}}
\end{align}
$$
如果$n(\hat{\boldsymbol{q}})=1$,则$\hat{\boldsymbol{q}}$是一个单位四元数(unit quaternion),那么可以写成:
$$
\hat{\boldsymbol{q}}
=(\sin\phi\boldsymbol{u}_q,\cos\phi)
=\sin\phi\boldsymbol{u}_q+\cos\phi
$$
对于$\boldsymbol{u}_q$满足$\Vert\boldsymbol{u}_q\Vert=1$,由于:
$$
n(\hat{\boldsymbol{q}})=n(\sin\phi\boldsymbol{u}_q,\cos\phi)
=\sqrt{\sin^2\phi(\boldsymbol{u}_q\cdot\boldsymbol{u}_q)+\cos^2\phi}
=\sqrt{\sin^2\phi+\cos^2\phi}
=1
$$
当且仅当$\boldsymbol{u}_q\cdot\boldsymbol{u}_q=1=\Vert\boldsymbol{u}_q\Vert^2$时成立。$\boldsymbol{u}_q$非常适合在一些情况下快速创建旋转或者定位。
一个2D单位向量可以写为$\cos\phi+i\sin\phi=e^{i\phi}$,四元数则是:
$$
\hat{\boldsymbol{q}}=\sin\phi\boldsymbol{u}_q+\cos\phi=e^{\phi\boldsymbol{u}_q}
$$
其他一些关于单位四元数的方法:
$$
\begin{align}
对数:&\log(\hat{\boldsymbol{q}})=\log(e^{\phi\boldsymbol{u}_q})=\phi\boldsymbol{u}_q\\
乘方:&\hat{\boldsymbol{q}}^t
=(\sin\phi\boldsymbol{u}_q+\cos\phi)^t
=e^{\phi t\boldsymbol{u}_q}
=\sin(\phi t)\boldsymbol{u}_q+\cos(\phi t)
\end{align}
$$
4.3.2 Quaternion Transforms(四元数变换)
上图显示了一个单位四元数$\hat{\boldsymbol{q}}=(\sin\phi\boldsymbol{u}_q,\cos\phi)$,绕着$\boldsymbol{u}_q$旋转$2\phi$弧度。
一个单位四元数可以代表任意三维旋转。
我们把一个点$\boldsymbol{p}=(p_x,p_y,p_z,p_w)^\top$放到四元数$\boldsymbol{p}$中,然后假设我们有一个单位四元数$\hat{\boldsymbol{q}}=(\sin\phi\boldsymbol{u}_q,\cos\phi)$,可以证明$\hat{\boldsymbol{q}}\hat{\boldsymbol{p}}\hat{\boldsymbol{q}}^{-1}$使得点$\boldsymbol{p}$围绕轴$\boldsymbol{u}_q$旋转了$2\phi$角度,且$\hat{\boldsymbol{q}}$是一个单位四元数,所以$\hat{\boldsymbol{q}}^{-1}=\hat{\boldsymbol{q}}^*$,任意$\hat{\boldsymbol{q}}$的非零实数倍都表示着相同的变换,也就是$\hat{\boldsymbol{q}}$和$-\hat{\boldsymbol{q}}$表示同一个变换,再进一步说,忽略轴,$\boldsymbol{u}_q$和实部$q_w$就组成了和原始四元数一样的旋转。还意味着从矩阵中提取四元数可以得到$\hat{\boldsymbol{q}}$或$-\hat{\boldsymbol{q}}$。
给定两个单位四元数$\hat{\boldsymbol{q}}$和$\hat{\boldsymbol{r}}$,对点$\hat{\boldsymbol{p}}$应用$\hat{\boldsymbol{q}}$再应用$\hat{\boldsymbol{r}}$,可以表示为:
$$
\hat{\boldsymbol{r}}
(\hat{\boldsymbol{q}}\hat{\boldsymbol{p}}\hat{\boldsymbol{q}}^*)
\hat{\boldsymbol{r}}^*
=(\hat{\boldsymbol{r}}\hat{\boldsymbol{q}})
\hat{\boldsymbol{p}}(\hat{\boldsymbol{q}}\hat{\boldsymbol{r}})^*
=\hat{\boldsymbol{c}}\hat{\boldsymbol{p}}\hat{\boldsymbol{c}}^*
\ where \
(\hat{\boldsymbol{c}}=\hat{\boldsymbol{r}}\hat{\boldsymbol{q}})
$$
Matrix Conversion(矩阵的转换)
由于实际使用中我们需要串联很多不同的矩阵,于是我们要把$\hat{\boldsymbol{q}}\hat{\boldsymbol{p}}\hat{\boldsymbol{q}}^{-1}$转换成一个矩阵。
$$
\boldsymbol{M}^q =
\begin{pmatrix}
1-s(q_y^2+q_z^2) & s(q_xq_y-q_wq_z) & s(q_xq_z+q_wq_y) & 0 \\
s(q_xq_y+q_wq_z) & 1-s(q_x^2+q_z^2) & s(q_yq_z-q_wq_x) & 0 \\
s(q_xq_z-q_wq_y) & s(q_yq_z+q_wq_x) & 1-s(q_x^2+q_y^2) & 0 \\
0 & 0 & 0 & 1
\end{pmatrix}
$$
其中标量$s=2/(n(\hat{\boldsymbol{q}}))^2$,对于单位四元数,则上述矩阵可以简化为:
$$
\boldsymbol{M}^q =
\begin{pmatrix}
1-2(q_y^2+q_z^2) & 2(q_xq_y-q_wq_z) & 2(q_xq_z+q_wq_y) & 0 \\
2(q_xq_y+q_wq_z) & 1-2(q_x^2+q_z^2) & 2(q_yq_z-q_wq_x) & 0 \\
2(q_xq_z-q_wq_y) & 2(q_yq_z+q_wq_x) & 1-2(q_x^2+q_y^2) & 0 \\
0 & 0 & 0 & 1
\end{pmatrix}
$$
四元数不涉及任何三角函数的计算,所以很高效。
从矩阵到单位四元数的反向转换会更复杂一些,此过程的关键是以下公式和矩阵公式的一些差异:
$$
m_{21}^q-m_{12}^q=4q_wq_x \\
m_{02}^q-m_{20}^q=4q_wq_y \\
m_{10}^q-m_{01}^q=4q_wq_z
$$
如果 $q_w$ 已知,则可通过上述方程求出四元数。矩阵的迹(trace of matrix)计算如下:
$$
tr(\boldsymbol{M}^q)
=4-2s(q_x^2+q_y^2+q_z^2)
=4(1-\frac{q_x^2+q_y^2+q_z^2}{q_x^2+q_y^2+q_z^2+q_w^2})
=\frac{4q_w^2}{q_x^2+q_y^2+q_z^2+q_w^2}
=\frac{4q_w^2}{(n(\hat{\boldsymbol{q}}))^2}
$$
则:
$$
\begin{align}
&q_w = \frac12\sqrt{tr(\boldsymbol{M}^q)},&q_x=\frac{m_{21}^q-m_{12}^q}{4q_w} \\
&q_y=\frac{m_{02}^q-m_{20}^q}{4q_w},&q_z=\frac{m_{10}^q-m_{01}^q}{4q_w}
\end{align}
$$
为了使得数值更稳定,应该避免使用过小的数值进行除法运算。因此首先设$t=q_w^2-q_x^2-q_y^2-q_z^2$,然后:
$$
m_{00}=t+2q_x^2,\\
m_{11}=t+2q_y^2,\\
m_{22}=t+2q_z^2,\\
u=m_{00}+m_{11}+m_{22}=t+2q_w^2
$$
反过来说$m_{00,11,22}$中最大的值和u共同决定了$q_x,q_y,q_z,q_w$中哪个最大,如果$q_w$最大,则用更上面的公式计算,其他情况则要注意:
$$
4q_x^2=+m_{00}-m_{11}-m_{22}+m_{33},\\
4q_y^2=-m_{00}+m_{11}-m_{22}+m_{33},\\
4q_z^2=-m_{00}-m_{11}+m_{22}+m_{33},\\
4q_w^2=tr(\boldsymbol{M}^q)
$$
可以用这种方式找到最大值,然后再用来当除数,去计算其他值。
Spherical Linear Interpolation(球面线性插值)
球面线性插值是一种计算,给定两个单位四元数$\hat{\boldsymbol{q}}$和$\hat{\boldsymbol{r}}$,以及参数$t\in[0,1]$,计算一个插值后的四元数。这对于动画很有用,对于摄像机的方向则不太适用,因为摄像机的向上方向会在插值过程中变得倾斜。代数表示为:
$$
\hat{\boldsymbol{s}}(\hat{\boldsymbol{q}},\hat{\boldsymbol{r}},t)
=(\hat{\boldsymbol{r}}\hat{\boldsymbol{q}}^{-1})^t\hat{\boldsymbol{q}}
$$
对于软件,一般表示为slerp
:
$$
\hat{\boldsymbol{s}}(\hat{\boldsymbol{q}},\hat{\boldsymbol{r}},t)
=slerp(\hat{\boldsymbol{q}},\hat{\boldsymbol{r}},t)
=\frac{\sin(\phi(1-t))}{\sin\phi}\hat{\boldsymbol{q}}
+\frac{\sin(\phi t)}{\sin\phi}\hat{\boldsymbol{r}}
$$
欲求$\phi$,可以用$\cos\phi=q_xr_x+q_yr_y+q_zr_z+q_wr_w$来计算,对$t\in[0,1]$,slerp
函数构成了从$\hat{\boldsymbol{q}}(t=0)$到$\hat{\boldsymbol{r}}(t=1)$的四维单位球体球面上的最短弧(当且仅当这里的两个四元数不完全反向)。这个弧在$\hat{\boldsymbol{q}}(t=0)$和$\hat{\boldsymbol{r}}(t=1)$以及球的原点构成的平面与球体相交形成的圆上,这个圆上面的一部分被称为大弧(a great arc)。
计算出的四元数以恒定速度绕轴旋转,像这样具有恒定速度和0加速度的曲线,被称作测地曲线(geodesic curve)。
由于连续的四元数变换会有一些抖动的问题,更好的插值方式可能是用到一些样条。我们再四元数$\hat{\boldsymbol{q}}i$和$\hat{\boldsymbol{q}}{i+1}$中间引入两个四元数$\hat{\boldsymbol{a}}i$和$\hat{\boldsymbol{a}}{i+1}$,可以通过这四个四元数定义一个球面三次插值。
$$
\hat{\boldsymbol{a}}_i=
\hat{\boldsymbol{q}}_i\exp
\left[
-\frac{\log(\hat{\boldsymbol{q}}_i^{-1}\hat{\boldsymbol{q}}_{i-1})
+\log(\hat{\boldsymbol{q}}_i^{-1}\hat{\boldsymbol{q}}_{i+1})}
{4}
\right]
$$
使用三次样条曲线,$\hat{\boldsymbol{q}}_i$和$\hat{\boldsymbol{a}}_i$用于球面插值四元数:
$$
squad(
\hat{\boldsymbol{q}}_i,
\hat{\boldsymbol{q}}_{i+1},
\hat{\boldsymbol{a}}_i,
\hat{\boldsymbol{a}}_{i+1})
=slerp(slerp(\hat{\boldsymbol{q}}_i,\hat{\boldsymbol{q}}_{i+1},t),
slerp(\hat{\boldsymbol{a}}_i,\hat{\boldsymbol{a}}_{i+1},t),
2t(1-t))
$$
squad
函数通过重复使用slerp
插值进行计算,插值会通过所有$\hat{\boldsymbol{q}}_{i...}$,但不会通过$\hat{\boldsymbol{a}}_{i...}$,它们是用来表示初始方向的切线方向的。
Rotation from One Vector to Another(从一个向量旋转到另一个)
使向量$\boldsymbol{s}$指向另一个向量$\boldsymbol{t}$的运算,首先归一化两个向量。然后计算单位旋转轴$\boldsymbol{u}=(\boldsymbol{s}\times\boldsymbol{t})/\Vert\boldsymbol{s}\times\boldsymbol{t}\Vert$,然后$e=\boldsymbol{s}\cdot\boldsymbol{t}=\cos(2\phi),\Vert\boldsymbol{s}\times\boldsymbol{t}\Vert=\sin(2\phi)$其中$2\phi$是两个向量间的夹角。则表示从$\boldsymbol{s}$转向$\boldsymbol{t}$的四元数可以表示为$\hat{\boldsymbol{q}}=(\sin\phi\boldsymbol{u},\cos\phi)$。使用半角公式和三角恒等式简化后得到:
$$
\hat{\boldsymbol{q}}=(\boldsymbol{q}_v,q_w)
=\left(
\frac{1}{\sqrt{2(1+e)}}(\boldsymbol{s}\times\boldsymbol{t}),
\frac{\sqrt{2(1+e)}}{2}
\right)
$$
矩阵化的方式,注意它不需要计算三角函数:
$$
\boldsymbol{R(s,t)}=
\begin{pmatrix}
e+hv_x^2 & hv_xv_y-v_z & hv_xv_z + v_y & 0 \\
hv_xv_y + v_z & e+hv_y^2 & hv_yv_z-v_x & 0 \\
hv_xv_z-v_y & hv_yv_z+v_x & e+hv_z^2 & 0 \\
0&0&0&1
\end{pmatrix} where \ \
\begin{align}
& \boldsymbol{v} = \boldsymbol{s} \times \boldsymbol{t} \\
& e = \cos(2\phi)=\boldsymbol{s}\cdot\boldsymbol{t} \\
& h = \frac{1-\cos(2\phi)}{\sin^2(2\phi)}
= \frac{1-e}{\boldsymbol{v}\cdot\boldsymbol{v}}
= \frac{1}{1+e}
\end{align}
$$
若两个向量趋近平行,则我们可以返回单位矩阵;若两个向量相差180度,则可以围绕任意轴旋转$\pi$,该轴可以基于$\boldsymbol{s}$与任意不与$\boldsymbol{s}$平行的向量之间的叉积。
4.4 Vertex Blending(顶点绑定)
假设一条胳膊由前臂和上臂组成,如果由两个部分组成,使用刚体变换模拟肘部旋转,则不太真实。顶点绑定(Vertex blending)用于解决此类问题。这种技术还有其他几种名字linear-blend skinning,enveloping或者skeleton-subspace deformation。
一种简单的形式前臂和上臂照原来形式变换,但是在衔接处使用弹性的“皮肤”连接,因此这个皮肤的一部分顶点将由前臂的矩阵变换,另一部分由上臂的矩阵变换。更进一步,我们可以让单个顶点接收若干矩阵的变换,所得到的位置加权混合在一起。这是通过对动画对象设置骨骼实现的。整个手臂可能都是这样的,所以在骨骼上的网格我们称作“皮肤”。
数学描述,若$p$是原始顶点,$\boldsymbol{u}(t)$是根据时间$t$变换后的坐标:
$$
\boldsymbol{u}(t)
=\sum\limits_{i=0}^{n-1}w_i\boldsymbol{B}_i(t)\boldsymbol{M}_i^{-1}\boldsymbol{p}
\quad
\text{where}
\quad
\sum\limits_{i=0}^{n-1}w_i=1,w_i\ge0
$$
有n个骨骼影响着$\boldsymbol{p}$点,以世界坐标描述。$w_i$表示第$i$个骨骼对于点$p$的权重,矩阵$\boldsymbol{M}_i$把骨骼的初始坐标转换为世界坐标。通常骨骼在其坐标系的原点处具有控制关节(controlling joint)。例如前臂骨骼将肘关节移动至原点,旋转矩阵再将手臂的这个部分围绕关节旋转。$\boldsymbol{B}_i(t)$是随时间变化的第$i$个骨骼的世界变换,通常由一串矩阵构成,例如先前的骨骼变换层次结构以及局部动画矩阵。
每个骨骼在每一帧于自己的坐标系中将一个顶点进行变换,最终结果由计算出的点集插值而来。由于矩阵$\boldsymbol{M}_i$总是矩阵链接中的一部分,所以这里把它单提出来,也有时候它也会合并到$\boldsymbol{B}_i(t)$矩阵中。
由于权重都是非负的且总和为1,也就是顶点被变换到若干的部分再进行插值。所以计算出的结果点$\boldsymbol{u}$在固定时间点($t$为定值)将在点集$\boldsymbol{B}_i(t)\boldsymbol{M}_i^{-1}\boldsymbol{p},i=0...n-1$围成的凸多边形内。
顶点混合非常适合在GPU上面计算,网格中的顶点可以放置在静态缓存中一次性发送给GPU并反复利用。在每一帧中,只有骨骼矩阵发生了改变,然后由顶点着色器计算这些顶点。这样由CPU传输的数据量被降到最低,使GPU得以高效渲染网格。如果模型的整个骨矩阵可以一起使用,这是最简单的;否则必须拆分模型并复制一些骨骼。或者,骨骼变换可以存储在顶点访问的纹理中,这样可以避免达到寄存器存储限制。
在使用例如变形等其他混合算法时,可以使权重和大于1。
简单的顶点混合会发生不希望的折叠、扭曲和自交叉等问题,更好的方法是使用双四元数蒙皮。这种技术有助于保持原本皮肤的刚性,避免发生"candy wrapper"四肢扭曲。计算成本小于线性混合的1.5倍而且结果不错,使这项技术快速得以广泛使用。然而双四元数蒙皮可能导致膨胀问题,后Le和Hodgins 提出的旋转中心蒙皮成为更好的选择。这种方法依赖于局部变换是刚体的假设,并且顶点具有相似的权重和相似的变换。每个顶点的旋转中心被预计算,同时施加正交(刚体)约束(orthogonal/rigidbody constraint)以防止肘部塌陷(collapse)和糖果包裹物扭曲(candy wrapper)。该算法在GPU中于中心点执行线性混合蒙皮,再加一个四元数的混合步骤。
4.5 Morphing(变形)
执行动画,从一个3D模型变成另一个3D模型时很有用。顶点涉及解决两个主要问题,顶点对应问题和插值问题。
给定两个可能具有不同拓扑,不同顶点数和不同网格连通性的任意模型,我们一般需要先设置这些顶点对应的关系。这是一个难题,在此领域已经有很多研究。
若已经存在一对一的顶点关系,则可以每个顶点做插值。为计算关于时间$t\in\left[t_0,t_1\right]$的变形顶点,我们首先要计算$s=(t-t_0)/(t_1-t_0)$,然后是线性顶点混合:
$$
\boldsymbol{m}=(1-s)\boldsymbol{p}_0+s\boldsymbol{p}_1
$$
这里$\boldsymbol{p}_0$和$\boldsymbol{p}_1$指的是相同点在不同时间$\boldsymbol{t}_0$和$\boldsymbol{t}_1$所对应的位置。
变形目标(Morph target)或者融合变形(blend shapes)可以为用户操作变形提供更直观的控制。
我们用$\cal{N}$表示一个中性的模型脸,然后有一个笑脸模型$\cal{P}_i,i\in[1,...,k]$(可以是多个),作为预处理,差异模型为:$\cal{D_i=P_i-N}$,原始模型从每个pose中被减去。
然后变形后的模型$\cal{M}$为:
$$
\cal{M=N+\sum\limits_{i=1}^kw_iD_i}
$$
我们可以根据权重$w_i$根据需要添加不同姿势的特征。
4.6 Geometry Cache Playback(几何缓存的播放)
在一些镜头中,我们需要使用质量极高的动画。一种想法是将所有帧的所有顶点都存储在磁盘中,读取他们来更新网格。但是对于一个有30000点的简单模型来说,需要每秒50MB的速度。Gneiting提出了几种将内存成本降低到10%左右的方法。
对所有位置和纹理坐标使用16-bit整数存储。这一步压缩操作是有损的。然后进行空间和时间的预测并编码差异,对于空间压缩可以使用平行四边形预测(parallelogram prediction)。对于一个三角形网格,下个顶点的预测位置只是简单的在当前三角形周围的三角形所在平面中反射当前三角形,形成平行四边形。然后编码这个新位置的差异,通过良好的预测,大多数差异接近0,所以这是许多常用压缩方案的理想选择。与MPEG压缩类似,预测也在时间维度上执行。也就是说每n帧执行空间压缩。在两帧之间进行时间维度预测,如某顶点移动了从帧n-1到帧n的增量矢量,可能在n+1帧的时候移动相似的量。这些技术充分减少了存储,使该系统可以用于实时数据传输。
4.7 Projections(投影)
目前我们看到的变换中,$w$分量都不受影响,也就是在变换之后点和向量保留了他们的类型。此外矩阵的底始终为$(0,0,0,1)$。
投影矩阵则不同,底行包括矢量和操作点的数字,通常使用均匀化过程(homogenization process)。也就是说$w$分量通常不为1,需要除以$w$来获得非均匀点。
本节假设观察者沿着摄像机$-z$轴观察,$y$朝上,$x$轴朝右,这是一个右手坐标系。DirectX等使用左手系统。
4.7.1 Orthographic Projection(正交投影)
正交投影后平行线保持平衡,观看场景时,对象保持相同大小。$\boldsymbol{P}_o$是一个正交投影矩阵,它使一个点的x,y分量保持不变,z分量设为0:
$$
\boldsymbol{P}_o=\begin{pmatrix} 1&0&0&0\\0&1&0&0\\0&0&0&0\\0&0&0&1 \end{pmatrix}
$$
效果如上图所示,$\boldsymbol{P}_o$是不可逆的因为$\vert\boldsymbol{P}_o\vert=0$。换句话说,从三维降到二维,无法恢复已删除的维度。使用这种正交投影存在的问题是他将无论正负的z值都投影到平面上,将z值(以及x,y值)限制为某个间隔通常很有用。
一个更常见的矩阵由6元祖组成:$(l,r,b,t,n,f)$,分别表示左右底顶近平面和远平面。此矩阵将这些平面形成的轴对齐包围盒(axis-aligned bounding box,AABB)缩放并转换为以原点为中心的轴对齐立方体(axis-aligned cube)。AABB中最小的角是$(l,b,n)$,最大的角是$(r,t,f)$,要认识到$n>f$,因为在空间中俯瞰负z轴。常识认为far比near要大,所以可以让用户如此做,由计算时再翻转过来。
在OpenGL中的最小角和最大角分别是$(-1,-1,-1)$和$(1,1,1)$,而DirectX中是$(-1,-1,0)$和$(1,1,1)$。这个立方体称为标准视域体(canonical view volume)在体积内的坐标称为归一化设备坐标(normalized device coordinates)。转换过程如图所示:
转换为标准视域体后,要渲染的几何体的顶点要根据立方体进行裁剪。最终将在立方体映射到屏幕上以渲染在立方体内部的几何体,此正交变换如下:
$$
\boldsymbol{P}_o=\boldsymbol{S(s)T(t)}=
\begin{pmatrix}
\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{pmatrix}
\begin{pmatrix}
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{pmatrix}
=\begin{pmatrix}
\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{pmatrix}
$$
由等式可以看出,该矩阵还可以写为一个平移矩阵$\boldsymbol{T(t)}$和一个缩放矩阵$\boldsymbol{S(s)}$的串联,其中$\boldsymbol{s}=(2/(r-l),2/(t-b),2/(f-n)),\boldsymbol{t}=(-(r+l)/2,-(t+b)/2,-(f+n)/2)$。矩阵是可逆的:$\boldsymbol{P}_s^{-1}=\boldsymbol{T(-t)S}((r-l)/2,(t-b)/2,(f-n)/2)$。
在计算机图形学中,在投影后最常使用左手坐标系(投影前是右手,OpenGL),即在视口(viewport)下,x轴向右,y轴向上,z轴向着视口远方。由于远值小于近值,正交投影将始终包含一个镜像变换。
为验证这一点我们使原始AABB和canonical view volume等大小,(此时的AABB坐标为$(l,b,n)=(-1,-1,1)and(r,t,f)=(1,1,-1)$)然后得到:
$$
\boldsymbol{P}_o=\begin{pmatrix} 1&0&0&0\\0&1&0&0\\0&0&-1&0\\0&0&0&1 \end{pmatrix}
$$
这是一个镜像矩阵。使右手坐标系转换到了左手坐标系。但在DirectX中有所不同,由于z轴的映射不同,我们可以在正交矩阵后增加一个平移矩阵和一个缩放矩阵:
$$
\boldsymbol{M}_{st}=
\begin{pmatrix}
1&0&0&0\\
0&1&0&0\\
0&0&0.5&0.5\\
0&0&0&1\\
\end{pmatrix}
$$
然后得到对于DirectX的正交矩阵为:
$$
\boldsymbol{P}_{o[0,1]}=
\begin{pmatrix}
\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{pmatrix}
$$
它通常以转置的形式出现,因为DirectX使用row-major书写矩阵。
4.7.2 Perspective Projection(透视投影)
透视接近匹配我们感知世界的方式,近大远小。平行线在投影后通常不平衡,在极端情况下收敛到一点。
首先,我们将为投影到$z=-d,d>0$的透视投影矩阵提供指导性推导。我们从世界空间派生,以简单理解世界到视图转换的过程。同样以OpenGL的形式推导。
假设摄像机位于坐标原点,我们要将一个点$\boldsymbol{p}$投影到平面$z=-d,d>0$上,产生一个新的点$\boldsymbol{q}=(q_x,q_y,-d)$。如下图:
以图中的相似三角形进行推导:
$$
\frac{q_x}{p_x}=\frac{-d}{p_z} \Longleftrightarrow q_x=-d\frac{p_x}{p_z},
q_y=-d\frac{p_y}{p_z},q_z=-d \\
\Downarrow \\
\boldsymbol{P}_p=
\begin{pmatrix}
1&0&0&0\\
0&1&0&0\\
0&0&1&0\\
0&0&-\frac{1}{d}&0
\end{pmatrix}
$$
我们用下列等式检验矩阵:
$$
\boldsymbol{q}=\boldsymbol{P}_p\boldsymbol{p}
=\begin{pmatrix}
1&0&0&0\\
0&1&0&0\\
0&0&1&0\\
0&0&-\frac{1}{d}&0
\end{pmatrix}
\begin{pmatrix}
p_x\\
p_y\\
p_z\\
1
\end{pmatrix}
=\begin{pmatrix}
p_x\\
p_y\\
p_z\\
-p_z/d
\end{pmatrix}
\Rightarrow
\begin{pmatrix}
-dp_x/p_z\\
-dp_y/p_z\\
-d\\
1
\end{pmatrix}
$$
最后一步使向量除以w分量,使得$z$值为$-d$。
均匀化过程的一种几何解释是它将点 $(p_x, p_y, p_z)$ 投影到平面$w=1$上。
与正交变换一样,还存在透视变换,它并不投影到平面上(不可逆的),而是将是椎体转换为先前描述的canonical view volume。
这里假设视锥体(view frustum)从$z=n$开始到$z=f$结束,其中$0\gt n\gt f$。在$z=n$处的矩形具有最小角并处于$(l,b,n)$,在$(r,t,n)$具有最大角。如图:
参数$(l,r,b,t,n.f)$确定摄像机的视锥体。水平视场角(horizontal field of view)由左右平面(由$l,r$确定)之间的角度确定,垂直视场角(vertical field of view)由上下平面(由$t,b$确定)之间的角度确定。视场角(FOV,field of view)越大,摄像机视野越大,看到越多。不对称的截锥体(平截头体,frusta)可以由$(r\ne-l)or(t\ne-b)$创建,这种截锥体可以用于立体显示(stereo viewing)或虚拟现实(VR,virtual reality)。
FOV是提供场景感的重要因素。眼睛本身也具有物理性的FOV,他们的关系是:
$$
\phi = 2\arctan(w/(2d))
$$
其中$\phi$是FOV,$w$是垂直于视线的宽度,$d$是到物体的距离。如25寸显示器的宽约22寸,距离12英寸,水平视野为85度。在20英寸处,是58度。30英寸40度。这个公式还可以用摄像机镜头参数计算FOV。更小的FOV使透视效果更少,更宽的FOV会使对象发生扭曲(像广角相机镜头),尤其靠近屏幕边缘的物体会变得更大。但能够提供更多的关于场景的信息。
把视锥体变换到一个单位矩形中的透视变换矩阵为:
$$
\boldsymbol{P}_p=
\begin{pmatrix}
\frac{2n}{r-l}&0&-\frac{r+l}{r-l}&0\\
0&\frac{2n}{t-b}&-\frac{t+b}{t-b}&0\\
0&0&\frac{f+n}{f-n}&-\frac{2fn}{f-n}\\
0&0&1&0
\end{pmatrix}
$$
对一个点应用该变换后得到点$\boldsymbol{q}=(q_x,q_y,q_z,q_w)^\top$,$w$分量一般不为0且不为1,所以要除以该分量得到投影后的点 $\boldsymbol{p}$:
$$
\boldsymbol{p}=(p_x/p_w,p_y/p_w,p_z/p_w,1)
$$
矩阵$\boldsymbol{P}_p$中的$z=f$总是映射到+1,$z=n$总是映射到-1。远平面之外的物体将被裁剪,不会出现在场景中。透视投影可以处理远平面为无限远的情况,这时透视变换矩阵为:
$$
\boldsymbol{P}_p=
\begin{pmatrix}
\frac{2n}{r-l}&0&-\frac{r+l}{r-l}&0\\
0&\frac{2n}{t-b}&-\frac{t+b}{t-b}&0\\
0&0&1&-2n\\
0&0&1&0
\end{pmatrix}
$$
总结下,透视变换应用后,进行了剪切和均匀化(除以$w$分量),然后计算归一化设备坐标。
为了在OpenGL中使用透视变换,首先乘以$\boldsymbol{S}(1,1,-1,1)$,做镜像,这一步使得矩阵第三列颠倒正负。然后near和far的值以正值输入,$0\lt n'\lt f'$,就像它们呈现给用户的方式一样。它们仍表示着沿着-z轴也就是视野方向的距离:
$$
\boldsymbol{P}_{OpenGL}=
\begin{pmatrix}
\frac{2n'}{r-l}&0&\frac{r+l}{r-l}&0\\
0&\frac{2n'}{t-b}&\frac{t+b}{t-b}&0\\
0&0&-\frac{f'+n'}{f'-n'}&-\frac{2f'n'}{f'-n'}\\
0&0&-1&0
\end{pmatrix}
$$
还有一种简单的形式,FOV表示为$\phi$,屏幕长宽比为$a=w/h$:
$$
\boldsymbol{P}_{OpenGL}=
\begin{pmatrix}
\frac{c}{a}&0&0&0\\
0&c&0&0\\
0&0&-\frac{f'+n'}{f'-n'}&-\frac{2f'n'}{f'-n'}\\
0&0&-1&0
\end{pmatrix}
where \
c=1.0/\tan(\phi/2)
$$
这个矩阵就是老的OpenGL函数gluPerspective()
所执行的内容,它属于OpenGL Utility Library(GLU)。
一些API(如DirectX)把近平面映射到$z=0$(OpenGL是-1),远平面映射到$z=1$,所以DirectX不需要做镜像,它使用左手坐标系,等式为:
$$
\boldsymbol{P}_{p[0,1]}=
\begin{pmatrix}
\frac{2n'}{r-l}&0&-\frac{r+l}{r-l}&0\\
0&\frac{2n'}{t-b}&-\frac{t+b}{t-b}&0\\
0&0&\frac{f'}{f'-n'}&-\frac{f'n'}{f'-n'}\\
0&0&1&0
\end{pmatrix}
where \
c=1.0/\tan(\phi/2)
$$
使用透视变换后,计算出的深度值不会随点的z值线性变化:
$$
\boldsymbol{v}=\boldsymbol{P}\boldsymbol{p}=
\begin{pmatrix}
...\\
...\\
dp_z+e\\
\pm{p_z}
\end{pmatrix}
$$
其中$d,e$的取值取决于使用哪个矩阵。例如我们使用OpenGL的矩阵时(第一个),$d=-(f'+n')/(f'-n'),e=-2f'n'/(f'-n'),v_w=-p_z$。(这里原书应该是有勘误的)。除完分量后得:
$$
z_{NDC}=\frac{dp_z+e}{-p_z}=d-\frac ep_z
$$
可以看出,$z_{NDC}$和输入的$p_z$成反比。
不同的near和far设置可以影响深度的精度,上图显示了改变近平面距离原点距离的效果。
有几种方法可以提高深度的精度。一种常见的我们称之为反向z(reversed z),是使用浮点数或整型数存储$1.0-z_{NDC}$:
(后面介绍了很多优化的方法,有兴趣的朋友可以阅读原书)
Further Reading and Resources
- immersive linear algebra 网站
- Farin and Hansford’s The Geometry Toolbox
- Lengyel’s Mathematics for 3D Game Programming and Computer Graphics
- Hearn and Baker/Marschner and Shirley/Hughes et al. 的文章
- Graphics Gems 系列
- Golub and Van Loan’s Matrix Computations
- SIGGRAPH paper
...
(Chapter4 end.)