第四章 变换
变换是一种操作,它接受点,向量或颜色等实体,并以某种方式对其进行转换。计算机图形学领域中,掌握转换是很重要的。有了他们,你能够定位,重塑,以及为对象,光照和相机设置动画。你要确保所有的计算都在相同的坐标系统中,以不用的方式将对象投影到平面上。 这些只是可以通过转换执行的操作中的一部分,但足以证明转换角色在实时图形或任何形式的计算机图形中的重要性。
线性变换是保留矢量加法和标量乘法的变换。 具体来说,
例如,
f
(
x
)
=
5
x
f(x)=5x
f(x)=5x是一个把向量的每个元素和5相乘的变换。为了证明这是线性的,需要满足两个条件(公式4.1和4.2)。第一个条件成立,两个向量和5相乘加起来的结果和两个向量先加起来再和5相乘结果一样。标量乘法条件(方程式4.2)显然已满足。 此功能称为缩放变换,因为它可以更改对象的缩放比例(大小)。 旋转变换是另一个线性变换,它使向量绕原点旋转。 缩放和旋转变换(实际上是三元素矢量的所有线性变换)都可以使用3×3矩阵表示。
然而矩阵的大小通常是不够大的。三元向量X的一个运算例如 f ( x ) = x + ( 7 , 3 , 2 ) f(x) = x+(7,3,2) f(x)=x+(7,3,2)不是线性的,在两个单独的向量执行此功能相当于把 ( 7 , 3 , 2 ) (7,3,2) (7,3,2)的每个值相加两次得到结果,在一个向量上加上一个固定的向量会执行平移,所有的都会移动相同的量。这是常用的变换,我们会结合其他的变换,把尺寸变为原对象的一半,然后移动到其他位置。到目前为止,将函数保持在简单的形式上使得很难轻松地将它们组合在一起。
结合线性变换和平移可以进行仿射变换,通常使用4X4的矩阵。仿射变换就是先执行线性变换在进行平移。为了表示四元向量我们使用齐次标记法,使用同一种方式表示点和方向(用小写加粗字母)。方向向量用 v = ( v x v y v z 0 ) T v = (v_x v_y v_z 0)^T v=(vxvyvz0)T表示,点用 v = ( v x v y v z 1 ) T v = (v_x v_y v_z 1)^T v=(vxvyvz1)T表示,
所有的平移,旋转,缩放,镜像以及剪切矩阵都是仿射变换。仿射矩阵的主要特征是它保留了线的平行性,但不一定保留长度和角度。 线性变换也可以是单个线性变换的级联的任何序列。
本章节会从最基本必要的仿射变换开始。这部分内容可以被认为简单转换的“参考手册”。然后描述了更专业的矩阵,随后对四元数(一种强大的转换工具)进行了讨论和描述。然后是顶点融合和变形,这是表达网格动画的两种简单但有效的方法。最后,描述了投影矩阵。表4.1总结了大多数这些转换,它们的符号,功能和特性,其中正交矩阵是其逆矩阵为转置矩阵。
变换是用于操纵几何的基本工具。大多数图形应用程序编程接口允许用户设置任意矩阵,有时库可以与实现本章讨论的许多转换的矩阵运算一起使用。但是,仍然有必要了解函数调用背后的实际矩阵及其相互作用。知道这样一个函数调用之后矩阵的功能是一个开始,但是了解矩阵本身的属性将使您更进一步。例如,这种理解使您可以辨别何时处理正交矩阵(正交是其转置),从而可以更快地进行矩阵求逆。这样的知识可以导致加速代码。
4.1 基本转换
这部分内容描述了最基本的转换,例如平移,旋转,缩放,裁剪,变换级联,刚体变换,法线变换以及逆矩阵的计算。这些内容对于后面章节的学习是必要的。我们从最简单的变换——平移开始。
4.1.1 平移
从一个地方变换到另一个地方使用平移矩阵
T
T
T表示。这个矩阵可以使用一个向量表示
t
=
(
t
x
,
t
y
,
t
z
)
t=(t_x,t_y,t_z)
t=(tx,ty,tz),T如等式4.3所示
如图4.1所示是平移变换的一个例子。容易证明将
p
=
(
p
x
,
p
y
,
p
z
,
1
)
p =(p_x,p_y,p_z,1)
p=(px,py,pz,1)和
T
(
t
)
T(t)
T(t)相乘会得到
p
=
(
p
x
+
t
x
,
p
y
+
t
y
,
p
z
+
t
z
,
1
)
p =(p_x+t_x,p_y+t_y,p_z+t_z,1)
p=(px+tx,py+ty,pz+tz,1),显然是平移。注意
v
=
(
v
x
,
v
y
,
v
z
,
0
)
v =(v_x,v_y,v_z,0)
v=(vx,vy,vz,0)和
T
(
t
)
T(t)
T(t)相乘不会有影响,因为方向向量不能被平移。相反,其余的有效变换都会影响点和向量。 平移矩阵的逆是
T
−
1
(
t
)
=
T
(
−
t
)
T^{-1}(t)= T(-t)
T−1(t)=T(−t),即向量t取反。
4.1.2 旋转
旋转变换是将一个向量(位置或方向)绕经过原点的给定轴旋转给定角度。像平移矩阵一样,这是一个刚体变换。他保持了变换后点之间的距离以及惯用性 (他不会引起左右的变换)。这两种类型的转换在计算机图形中定位和定向对象十分有用。方向矩阵是与摄影机视图或对象相关联的旋转矩阵,用于定义其在空间中的方向,即其向上和向前的方向。
在二维中,旋转矩阵很容易得到。假设我们有一个向量
v
=
(
v
x
,
v
y
)
v=(v_x,v_y)
v=(vx,vy),我们用极坐标表示成
v
=
(
v
x
,
v
y
)
=
(
r
c
o
s
θ
,
r
s
i
n
θ
)
v=(v_x,v_y)=(rcosθ ,rsinθ)
v=(vx,vy)=(rcosθ,rsinθ),如果我们旋转这个向量φ角度,我们能得到
u
=
(
r
c
o
s
(
θ
+
φ
)
,
r
s
i
n
(
θ
+
φ
)
)
u = (rcos(θ + φ),rsin(θ + φ))
u=(rcos(θ+φ),rsin(θ+φ)),可以写作
在这里我们使用角度和关系来展开
c
o
s
(
θ
+
φ
)
cos(θ+φ)
cos(θ+φ)和
s
i
n
(
θ
+
φ
)
sin(θ+φ)
sin(θ+φ)。在三维空间中,通常使用旋转矩阵
R
x
(
φ
)
R_x(φ)
Rx(φ),
R
y
(
φ
)
R_y(φ)
Ry(φ),
R
z
(
φ
)
R_z(φ)
Rz(φ),它们分别绕x,y和z轴旋转实体φ弧度
如果删除4X4矩阵的最底行和最右列,会获得一个3X3矩阵.对于每个绕任意轴旋转φ弧度的3×3旋转矩阵R,迹线(矩阵中对角元素的总和)与该轴无关,是恒定的,计算公式为:
t
r
(
R
)
=
1
+
2
c
o
s
φ
tr(R)=1+2cosφ
tr(R)=1+2cosφ.
旋转矩阵的效果如图4.4所示。 R i ( φ ) R_i(φ) Ri(φ)表征旋转矩阵,除了绕着轴 i i i旋转φ弧度外,还在于它的所有点都在旋转轴上, i i i不变。 注意,R也将用于表示围绕任何轴的旋转矩阵。 上面给出的轴旋转矩阵可以在一系列三个变换中使用,以执行任意轴旋转。 该过程在第4.2.1节中讨论。 第4.2.4节介绍了直接绕任意轴旋转。
所有旋转矩阵的行列式均为1,并且是正交的。 这对于任何数量的这些转换的串联也成立。 有另一种求逆的方法: R i − 1 ( φ ) = R i ( − φ ) R_i^{-1}(φ)= R_i(-φ) Ri−1(φ)=Ri(−φ),即绕同一轴沿相反方向旋转。
示例:绕点旋转。 假设我们想要一个对象绕着z轴旋转一个φ弧度,并且旋转中心是某个点p。 什么是转换? 图4.2中描述了这种情况。 由于绕点旋转的特征在于该点本身不受旋转的影响,因此变换首先通过平移对象使p与原点重合开始,这是通过
T
(
−
p
)
T(-p)
T(−p)完成的。 此后跟随实际旋转:
R
z
(
φ
)
R_z(φ)
Rz(φ)。 最后,必须使用
T
(
p
)
T(p)
T(p)将对象平移回其原始位置。 然后,得到的变换X为
X
=
T
(
p
)
R
z
(
φ
)
T
(
−
p
)
X=T(p)R_z(φ)T(-p)
X=T(p)Rz(φ)T(−p).
4.1.3 缩放
缩放矩阵,
S
(
s
)
=
S
(
s
x
,
s
y
,
s
z
)
S(s)=S(s_x,s_y,s_z)
S(s)=S(sx,sy,sz),是将一个实体各自沿着x,y,z方向缩放因子
s
x
,
s
y
,
s
z
s_x,s_y,s_z
sx,sy,sz,这意味着缩放矩阵可以用来放大或缩小对象。
s
i
(
i
∈
{
x
,
y
,
z
}
)
s_i(i∈\{x,y,z\})
si(i∈{x,y,z})越大,则缩放实体在该方向上获得的越大。 将s的任何分量设置为1自然可以避免该方向缩放的更改。 公式4.10显示S:
图4.4所示为缩放矩阵的效果,如果
s
x
=
s
y
=
s
z
s_x = s_y = s_z
sx=sy=sz,则缩放操作称为统一操作,否则称为非统一操作。 有时,使用等向性和各向异性缩放来代替均匀和非均匀性。 倒数是
S
−
1
(
s
)
=
S
(
1
/
s
x
,
1
/
s
y
,
1
/
s
z
)
S^{-1}(s)= S(1 / {s_x},1 /{s_y},1 / {s_z})
S−1(s)=S(1/sx,1/sy,1/sz)。
使用齐次坐标,创建统一缩放矩阵的另一种有效方法是通过操作位置(3,3)处的矩阵元素,即右下角的元素。 该值影响齐次坐标的w分量,因此缩放由矩阵转换的点(而非方向矢量)的每个坐标。 例如,要均匀地缩放5倍,可以将缩放矩阵中(0,0),(1,1)和(2,2)的元素设置为5,或者将(3, 3)可以设置为1/5。 执行此操作的两个不同矩阵如下所示:
与使用S进行均匀缩放相反,必须始终在使用S’之后进行齐次化。 这可能是无效的,因为它涉及齐次化过程中的分歧。 如果右下角的元素(位置(3,3))为1,则不需要除法。 当然,如果系统总是在不进行1测试的情况下进行除法,则不会产生任何额外消耗。
s的一个或三个分量上的负值给出了一种反射矩阵,也称为镜像矩阵。 如果只有两个比例因子为-1,那么我们将旋转π弧度。 应当注意,与反射矩阵连接的旋转矩阵也是反射矩阵。 因此,以下是反射矩阵:
反射矩阵通常需要特殊处理。 例如,当顶点通过反射矩阵进行变换时,其顶点具有逆时针顺序的三角形将获得顺时针的顺序。 此顺序更改可能导致不正确的照明和背面剔除。 要检测给定的矩阵是否以某种方式反映,计算矩阵左上3×3个元素的行列式。 如果该值为负,则矩阵是反射的。 例如,公式4.12中矩阵的行列式为
0
⋅
0
−
(
−
1
)
⋅
(
−
1
)
=
−
1
0·0-({ -1})·({ -1})={ -1}
0⋅0−(−1)⋅(−1)=−1。
示例:沿特定方向缩放。 缩放矩阵S仅沿x,y和z轴缩放。 如果应在其他方向执行缩放,则需要复合转换。 假设缩放应沿着正交,右向向量
f
x
,
f
y
和
f
z
f_x,f_y和f_z
fx,fy和fz的轴进行。 首先,构造矩阵F,如下所示:
使三个轴给定的坐标系与标准轴重合,然后使用标准缩放矩阵,然后变换回去。 第一步是通过与F的转置相乘,即乘以F的倒数来进行的。然后完成实际的缩放,然后进行转换。 转换如公式4.14所示:
X
=
F
S
(
s
)
F
T
X=FS (s)F^T
X=FS(s)FT
4.1.4 裁剪
另一个变换就是设置裁剪矩阵。例如,这些可以用于游戏中以扭曲整个场景,以产生迷幻效果或扭曲模型的外观。 有六个基本剪切矩阵,分别表示为
H
x
y
(
s
)
,
H
x
z
(
s
)
,
H
y
x
(
s
)
,
H
y
z
(
s
)
,
H
z
x
(
s
)
和
H
z
y
(
s
)
H_{xy}(s),H_{xz}(s),H_{yx}(s),H_{yz}(s),H_{zx}(s)和H_{zy}(s)
Hxy(s),Hxz(s),Hyx(s),Hyz(s),Hzx(s)和Hzy(s)。 第一个下标用于表示剪切矩阵正在更改哪个坐标,而第二个下标表示进行剪切的坐标。 公式4.15给出了一个剪切矩阵
H
x
z
(
s
)
H_{xz}(s)
Hxz(s)的示例。 注意下标可用于在下面的矩阵中找到参数s的位置; x(其数字索引为0)标识第零行,而z(其数字索引为2)标识第二列,因此s位于此处:
点P和该矩阵相乘的结果是
(
p
x
+
s
p
z
,
p
y
,
p
z
)
T
(p_x+sp_z,p_y,p_z)^T
(px+spz,py,pz)T
。以图形方式显示,如图4.3所示。
H
i
j
(
s
)
H_{ij}(s)
Hij(s)的逆矩阵(相对于第j个坐标剪切第i个坐标,其中
i
≠
j
i ≠ j
i=j)是通过在相反方向上剪切产生的,即
H
i
j
−
1
(
s
)
=
H
i
j
(
s
)
H^{-1}_{ij}(s)=H_{ij}(s)
Hij−1(s)=Hij(s)。
你也可以用另一个剪切矩阵表示。
然而,在此,两个下标都用于表示这些坐标将被第三坐标剪切。 这两种不同描述之间的联系是
H
i
j
′
(
s
,
t
)
=
H
i
k
(
s
)
H
j
k
(
t
)
H^{'}_{ij}(s,t)=H_{ik}(s)H_{jk}(t)
Hij′(s,t)=Hik(s)Hjk(t),其中k用作第三坐标的索引。 最后,应该注意的是,由于任何剪切矩阵的行列式| H | = 1,这是一个保留体积的转换,如图4.3所示。
4.1.5 级联变换
由于矩阵的乘法是没有交换律的,所以矩阵相乘的顺序很重要。级联变换就是依赖于矩阵的顺序的。
依赖顺序的一个例子是,思考两个矩阵S和R。
S
(
2
,
0.5
,
1
)
S(2,0.5,1)
S(2,0.5,1)在X分量上缩放因子为2Y分量因子0.5。
R
z
(
π
/
6
)
R_z(π/6)
Rz(π/6) 沿着Z坐标逆时针旋转π/6,如图4.4所示。将一系列矩阵连接成单个矩阵的原因是为了提高效率。 例如,假设您的游戏场景具有数百万个顶点,并且场景中的所有对象都必须缩放,旋转和平移。 现在,不是将所有顶点与这三个矩阵中的每一个相乘,而是将这三个矩阵连接到一个矩阵后应用于顶点。 该复合矩阵为C = TRS。 注意这里的顺序。 比例矩阵S应该首先应用于顶点,因此在合成中显示在右侧。 此排序意味着
T
R
S
p
=
(
T
(
R
(
S
p
)
)
)
TRSp =(T(R(Sp)))
TRSp=(T(R(Sp))),其中p是要转换的点。 顺便说一下,TRS是场景图系统常用的顺序。
虽然矩阵级联取决于顺序,但是可以根据需要对矩阵进行分组。 例如,假设您要使用TRSp计算一次刚体运动变换TR。 将这两个矩阵
(
T
R
)
(
S
p
)
(TR)(Sp)
(TR)(Sp)分组在一起并用中间结果替换是有效的。
4.1.6 刚体变换
当一个人从桌上拿起一支钢笔放进衬衣口袋中,这个物体仅仅只是改变了位置和方向,他的形状是不会被影响的。这样的变化,只由平移和旋转矩阵的级联变化组成的叫做刚体变换。它可以保持对象的长度,角度外貌等。
任何一个刚体矩阵X可以写成一种平移矩阵T(t)和一个旋转矩阵R的级联。如等式4.17
X的逆矩阵计算
X
−
1
=
(
T
(
t
)
R
)
−
1
=
R
−
1
T
(
t
)
−
1
=
R
T
T
(
−
t
)
X^{-1}= (T(t)R)^{-1}= R^{-1}T(t)^{-1} = R^TT(−t)
X−1=(T(t)R)−1=R−1T(t)−1=RTT(−t).为了计算逆矩阵需要将旋转矩阵左上3X3矩阵进行转置,并将平移矩阵的值取反。这两个新的矩阵以和原来顺序相反相乘在一起就能获得渲染矩阵。另一个计算X逆矩阵的方法是使R出现为3×3矩阵和X的符号如下(符号在第6页的公式1.2中描述):
其中
r
,
0
r_{,0}
r,0表示旋转矩阵的第一列(即逗号表示从0到2的任何值,而第二个下标为0),而
r
0
T
r^T_0
r0T是列矩阵的第一行。 请注意,0是一个3×1的列矢量,填充了零。 计算得出公式4.19中所示表达式的逆矩阵:
例子:旋转相机。图形学中经常会旋转相机使其看向一个特定的位置。我们将介绍gluLookAt()(来自OpenGL Utility Library,简称GLU)的作用。即使现在很少使用此函数调用,该任务仍然很常见。假设摄像机位于c处,我们希望摄像机看着目标l,并且摄像机的给定方向为u’,如图4.5所示。
我们要计算一个由三个向量
r
,
u
,
v
{r,u,v}
r,u,v组成的基数。我们从计算视点向量为
v
=
(
c
−
1
)
/
∣
∣
c
−
l
∣
∣
v =(c-1)/ || c-l ||
v=(c−1)/∣∣c−l∣∣开始,即从目标到摄像机位置的归一化向量。然后可以将向右看的向量计算为
r
=
−
(
v
×
u
′
)
/
∣
∣
v
×
u
′
∣
∣
r =-(v×u')/ || v×u'||
r=−(v×u′)/∣∣v×u′∣∣。通常不能保证u’向量正好指向上,因此最终的向量是另一个叉积
u
=
v
×
r
u = v×r
u=v×r,由于v和r都通过构造进行了归一化和垂直处理,因此可以保证归一化。在我们将要构造的相机变换矩阵M中,其思想是首先转换所有内容,使相机位置位于原点(0,0,0),然后更改基数,以使r(1,0,0),u(0,1,0)和v(0,0,1)。这是通过
请注意,当将平移矩阵与基本矩阵的变化连接在一起时,平移矩阵-t在右边,因为它应该首先应用。 记住将r,u和v的分量放在哪里的一种方法如下。 我们希望r变成(1,0,0),所以当将基础矩阵的变化乘以(1,0,0)时,我们可以看到矩阵的第一行必须是r的元素,因为r· r =1。此外,第二行和第三行必须由与r垂直的向量组成,即r·x =0。当对u和v应用相同的思维时,我们得出基矩阵的变化 以上。
4.1.7 法线转换
一个矩阵可以用来转换点,线,三角形和其他的几何体。同一个矩阵也可以沿这些线或在三角形的曲面上变换切向量。然而,这些矩阵不能始终用来变换一个重要的几何特性,即表面法线(和顶点照明法线)。图4.6所示是使用同一个矩阵进行变换的结果。
正确的方法是用矩阵的伴随矩阵的转置矩阵而不是原本的矩阵。伴随矩阵始终存在,法线不能保证在变换之后具有单位长度,因此具有将其标准化。
转换法线的传统答案是计算逆矩阵的转置。此方法通常有效。但是,完全逆不是必需的,并且有时无法创建。逆矩阵是伴随数除以原始矩阵的行列式。如果该行列式为零,则矩阵是奇异的,不存在逆矩阵。
甚至计算整个4X4矩阵的伴随矩阵通常没必要。因为法线是矢量,平移并不会受到影响。因此,大多数现代变换是仿射的。他们不会改变齐次坐标的W分量,也不会执行投影。在这些情况下,法线变换只需要计算左上的3X3部分的伴随。
甚至有时候伴随矩阵计算都是不需要的。我们知道转换矩阵是由平移,旋转和统一缩放运算组成的,平移不会影响法线,统一缩放只会影响法线的长度。剩下的就是一系列旋转,总是产生某种形式的净旋转,仅此而已。
转置矩阵的逆矩阵可以用来转换法线,旋转矩阵由转置矩阵为其逆矩阵来定义。代替以获得正态变换,两个转置矩阵(或两个逆矩阵)给出原始旋转矩阵。原始变换本身也可以在这些情况下直接用于变换法线。
最后,并不总是需要完全标准化生成的法线。如果仅平移和旋转连接在一起,则法线在通过矩阵进行变换时不会更改长度,因此不需要重新归一化。如果还连接了统一的缩放变换,则可以使用整体缩放比例因子直接对生成的法线进行标准化。例如,如果我们知道应用了一系列缩放,使对象变大了5.2倍,直接通过此矩阵转换的法线可以通过将它们除以5.2重新进行归一化。另外,要创建一个将产生归一化结果的法线变换矩阵,可以将原始矩阵的左上3×3除以该比例因子一次。
请注意,法线变换在变换后从三角形派生表面法线(例如,使用三角形边缘的叉积)的系统中不是问题。切向量在本质上与法线不同,并且总是由原始矩阵直接转换。
4.1.8 逆矩阵的运算
在很多情况下都需要逆矩阵,例如,当改变坐标系统的前面和后面。下面三种方法在计算逆矩阵时都可以用到:
1.如果矩阵是单个变换或具有给定参数的简单变换序列,则可以通过“反转参数”和矩阵顺序轻松地计算矩阵。 例如,如果
M
=
T
(
t
)
R
(
φ
)
M = T(t)R(φ)
M=T(t)R(φ),则
M
−
1
=
R
(
−
φ
)
T
(
−
t
)
M^{-1} = R(-φ)T(-t)
M−1=R(−φ)T(−t)。 这很简单,并且保留了转换的准确性,这在渲染大世界时非常重要。
2.如果已知矩阵是正交的,则
M
−
1
=
M
T
M^{-1} = M^T
M−1=MT,即转置矩阵为逆矩阵。 旋转的任何序列都是旋转,因此是正交的。
3.如果什么条件也不知道,则可以使用伴随方法,克莱默法则,LU分解或高斯消除法来计算逆矩阵。 通常最好使用克莱默法则和伴随方法,因为它们的分支操作较少; 在现代体系结构上最好避免使用“如果”测试。 请参阅第4.1.7节,了解如何使用伴随来反转变换法线。
优化时也可以考虑逆计算的目的。 例如,如果将逆函数用于矢量转换,则通常只需要逆矩阵的3×3左上部分(请参见上一节)。
4.2 特殊的矩阵转换和运算
在本章节中,将介绍和导出对实时图形必不可少的几种矩阵变换和运算。 首先,我们介绍欧拉变换(及其参数提取),这是描述方向的直观方法。 然后,我们谈到从单个矩阵中检索一组基本变换。 最终,得出一种绕任意轴旋转实体的方法。
4.2.1 欧拉转换
这种转换是使矩阵自身旋转(例如相机)或者其他实体旋转到特定的方向的直观的方法。
首先,必须先建立默认的视图视角。它通常沿负z轴放置,头部沿y轴放置。 欧拉变换是三个矩阵的乘积,即图4.7中所示的旋转。 更正式地说,表示为E的变换由公式4.21给出:
矩阵的顺序有二十四种不同的方式选择;我们展示这个是因为这种顺序最常用。因为E是一系列旋转的结合,所以必然是正交的。因此它的逆矩阵可以表示为
E
−
1
=
E
T
=
(
R
z
R
x
R
y
)
T
=
R
y
T
R
x
T
R
z
T
E^{−1} = E^T = (R_zR_xR_y)^T = R^T_y R^T_ x R^T _z
E−1=ET=(RzRxRy)T=RyTRxTRzT,直接使用E的转置就很简单。
欧拉角h,p和r表示偏航,俯仰和横滚应围绕其各自的轴旋转的角度。 有时,所有角度都称为“侧倾”,例如,我们的“头”为“ y侧倾”,而我们的“俯仰”为“ x侧倾”。 同样,“头部”有时也称为“偏航”,例如在飞行模拟中。
这种转换很直观,因此很容易用外行的语言进行讨论。 例如,改变头角度可使观看者摇头,改变俯仰使其点头,而滚动则使他们的头向侧面倾斜。 与其谈论绕x轴,y轴和z轴的旋转,不如谈论改变头,俯仰和横滚。 请注意,此变换不仅可以定向摄影机,还可以定向任何对象或实体。 可以使用世界空间的全局轴或相对于局部参照系执行这些变换。
一些欧拉角的表示将z轴作为初始上方向,这种差异纯粹是一种符号变化,尽管可能会引起混淆。在计算机图形学中,人们对世界的看法以及内容的形成方式有一个划分:y向上还是z向上。大多数制造过程,包括3D打印,Z方向认为是世界空间的上方向;航空和海上车辆认为−z向上。建筑和地理信息系统通常使用Z向上,因为建筑平面图或地图是二维的x和y。与媒体相关的建模系统通常将y方向视为世界坐标系中的上方向,这与我们在计算机图形学中描述相机屏幕向上方向的方式相匹配。两种世界坐标中向上方向之间的区别仅仅是90度旋转,但不知道哪个会导致问题。在本卷中,除非另有说明,否则我们使用y向上的世界方向。
我们想要指出在相机在其视图空间中的上方向与世界的向上方向没有特别的关系。当你转动头,你的视野跟着倾斜,那么它在世界空间的上方向与世界坐标的上方向不同。另一个例子,世界使用Y作为上方向,我们的相机直接向下看下面的地形,一个鸟瞰视图。此方向表示摄影机已向前倾斜90°,因此其在世界空间中的向上方向为 ( 0 , 0 , − 1 ) (0,0,−1) (0,0,−1)。在这个方向上,相机没有y分量,而是认为−z在世界空间中是向上的,但是在视图空间中,“y向上”通过定义仍然正确。
尽管对于小角度变化或者观察者方向很有用,欧拉角有一些其他严重的限制。很难去结合两组欧拉角一起使用。例如,一组和另一组之间的插值不是简单的每个角度进行插值的问题,事实上,两组不同的欧拉角可以给出相同的方向,所以任何插值都不能够使得对象旋转。这些时使用四元数进行表示方向的原因。使用欧拉角,会遇到万向锁问题,这将在第4.2.2节中解释。
4.2.2 欧拉角中提取参数
在某些情况下,需要一个能从正交矩阵中提取欧拉参数h、p和r的程序。该程序如方程式4.22所示:
这里我们不使用4X4矩阵而是用3X3矩阵,因为3X3矩阵提供了旋转矩阵的所有必要信息。也就是说,等价的4×4矩阵的其余部分都是零,在最右下角有一个1。
将等式4.22中的三个旋转矩阵串联起来得到
从这个式子可以明显看到俯仰参数由
sin
p
=
e
21
\sin p = e_{21}
sinp=e21给出,同样,将
e
01
e_{01}
e01除以
e
11
e_{11}
e11,以及同样地将
e
20
e_{20}
e20除以
e
22
e_{22}
e22,可以得到以下偏航和横滚参数的提取公式:
因此,欧拉参数h(偏航)、p(俯仰)和 r(横摇)是使用函数
a
t
a
n
2
(
y
,
x
)
atan2(y,x)
atan2(y,x)从矩阵E中提取的,如等式4.25所示:
不过需要处理一个特殊情况。如果
cos
p
=
0
\cos p=0
cosp=0,我们有万向锁(第4.2.2节),旋转角 r 和 h 将围绕同一轴旋转(尽管可能在不同的方向上,取决于p旋转角是−π/2还是π/2),因此只需要导出一个角度。如果我们任意设置h=0,我们得到
因为p不会影响第一列的值,因此当
c
o
s
p
=
0
cos p = 0
cosp=0时,我们可以通过
sin
r
/
cos
r
=
tan
r
=
e
10
/
e
00
\sin r / \cos r = \tan r = e_{10} / e_{00}
sinr/cosr=tanr=e10/e00,得出
r
=
a
t
a
n
2
(
e
10
,
e
00
)
r = atan2(e_{10},e_{00})
r=atan2(e10,e00)。
注意 arcsin \arcsin arcsin的定义, − π / 2 ≤ p ≤ π / 2 −π/2 ≤ p ≤ π/2 −π/2≤p≤π/2,意味着如果使用该范围之外的p值创建E,则无法提取原始参数。h,p和r不是唯一的意味着有多组欧拉参数可以产生相同的变换。上面概述的简单方法可能会导致数值不稳定的问题,这在速度方面会有所损失。
当你使用欧拉变换时,可能会产生万向锁。这会导致旋转失去一个自由度。例如,按照x/y/z的顺序变换。仅绕y轴旋转π/ 2,即第二次旋转。 这样做会旋转局部z轴以使其与原始x轴对齐,因此围绕z的最终旋转是多余的。
数学上,我们可以在式子4.26中看到万向锁,我们假设 cos p = 0 , 即 p = ± π / 2 + 2 π k \cos{p}=0,即p = ±π/2 + 2πk cosp=0,即p=±π/2+2πk,当k是一个整数。使用这个p值,我们会失去一个自由度,因为矩阵只依赖一个角度,r + h 或者 r − h(但是不会同时依赖这两个)。
尽管在模型坐标中以x/y/z的顺序表示欧拉角,但是绕着每个坐标旋转时,其他顺序也是可以的。例如,在动画中使用z/x/y,在动画和物理中使用z/x/z。所有的方法都能有效指定三个单独旋转。 最后的z / x / z顺序在某些应用中可能更好,因为只有当绕x旋转π弧度(半旋转)时,才会发生万向锁。 没有完美的序列可以避免万向锁。 尽管如此,通常还是使用欧拉角,因为动画师更喜欢曲线编辑器来指定角度如何随时间变化。
例如:约束变换。想象你拿着一个虚拟的紧握螺栓的扳手,为了得到这个螺栓,你必须绕着x轴旋转扳手。现在假设你的输入设备(鼠标,VR手套,太空球等)给你一个旋转矩阵,用来旋转扳手。问题是如果直接将变换应用到扳手上好像是错的,因为他只能绕着x轴旋转,为了限制输入变换,称为P,是围绕x轴的旋转,只需使用本节中描述的方法提取欧拉角h,p和r,然后创建一个新矩阵
R
x
(
p
)
R_x(p)
Rx(p)。然后,这是将使扳手绕x轴旋转的常见转换。
(如果P现在包含这样的运动)。
4.2.3 矩阵分解
到目前为止,我们一直在假设我们知道所使用的变换矩阵的来源和历史的情况下工作,通常情况并非如此。
例如,级联矩阵可以与某个变换后的对象相关联。 从级联矩阵中恢复各种变换的过程称为矩阵分解。
恢复一组转换的原因很多。 用途包括:
• 提取对象的比例因子。
• 查找特定坐标系所需的转换。 (例如,某些坐标系可能不允许使用任意4×4矩阵。)
• 确定模型是否仅经历了刚体变换。
• 在动画中的关键帧之间进行插值,其中仅对象的矩阵可用。
• 从旋转矩阵上移除剪切。
我们已经提出了两种分解方法,分别是为刚体变换导出平移和旋转矩阵(第4.1.6节),以及从正交矩阵导出欧拉角(第4.2.2节)。
如我们所见,恢复平移矩阵很简单,因为我们只需要4×4矩阵的最后一列中的元素。 我们还可以通过检查矩阵的行列式是否为负来确定是否发生了反射。 要分离出旋转,缩放和剪切,需要花费更多的精力。
4.2.4 任意轴的旋转
有时,通过任意轴旋转实体的过程是很方便的。假设旋转轴r已归一化,那么旋转矩阵可以通过绕r旋转α来定义。
为此,我们首先变换到一个空间,我们要围绕其旋转的轴是x轴。 这是通过一个称为M的旋转矩阵完成的。然后执行旋转后再使用
M
−
1
M_{-1}
M−1变换回去。 此过程如图4.8所示。
我们需要两个正交的轴,r轴和另一个,重点找到第二个轴s后,就可以通过第一个轴和第二个轴的叉积得到第三个轴t。 一种数字稳定的方法是找到r的最小部分(绝对值),并将其设置为0。交换剩余的两个部分,然后取反它们中的第一个(实际上,两个非零成分都可以被取反)。 在数学上,这表示为:
能够保证r和s正交,且 (r, s, t)是一组标准正交基。Frisvad提出了一种在代码中没有任何分支的方法,该方法速度快但准确性较低。 马克斯和达夫(Duff)等提高了Frisvad方法的准确性。 无论采用哪种技术,都会使用这三个向量来创建旋转矩阵:
矩阵变换将向量r应用于x轴,s应用于y轴,t应用于z轴。 因此,围绕标准化向量r旋转α弧度的最终变换如下
简而言之,这意味着我们使用M矩阵进行变换使得r为x轴,然后我们使用
R
x
(
α
)
R_x(α)
Rx(α)绕着这个x轴旋转α弧度,我们再使用M的逆矩阵变换回去,使用M的转置是因为M是正交矩阵,正交矩阵的逆矩阵等于转置矩阵。
另一个绕任意标准轴r旋转α弧度的表示方法由GoldMan提出。我们这里仅仅展现他的最终结果:
4.3.2节会讨论使用四元数解决此问题,还会讨论其他相关的高效算法,例如由一个向量旋转到另一个向量。
4.3 四元数
尽管四元数于1843年由Sir William Rowan Hamiltion作为复数的扩展引入的,直到1985年才由Shoemake进入计算机图形领域。四元数用来表示旋转和朝向,在某些时候比欧拉角和矩阵更加高级。任何三维定向可以表示成绕着特定轴的单个旋转。给定轴和角度的表示,四元数的表示十分直截了当,
而在任一方向上的欧拉角转换都具有挑战性。四元数可用于方向的稳定和恒定插值,而欧拉角无法很好地完成这些操作。
复数具有实部和虚部。每个都由两个实数表示,第二个实数乘以
−
1
\sqrt{-1}
−1。同样,四元数有四个部分,前三个值与旋转轴密切相关,
旋转角度会影响四个部分(有关更多信息,请参见第4.3.2节)。每个四元数由四个实数表示,每个实数与一个不同的部分相关联。由于四元数具有四个分量,因此我们选择将它们表示为矢量,但是为了区分它们,我们在它们上加了一个帽子:
q
^
\hat{q}
q^。我们从四元数的数学背景开始,用以构建有用的变换。
4.3.1 数学背景
首先是四元数的定义。
定义:四元数
q
^
\hat{q}
q^可以用以下的方式定义。
变量
q
w
q_{w}
qw称为四元数的实部。虚部是
q
v
q_{v}
qv,i,j,k称之为虚部单位。
只看虚部
q
v
q_{v}
qv,我们可以用所有常规向量运算,例如加减,点乘,叉乘等。用四元数的定义,根据四元数的定义,两个四元数的乘法如下所示。注意虚部单位之间的乘法是不可交换的。
在等式中所见,我们用叉乘和点乘计算两个四元数的乘法。
根据四元数的定义,加法、共轭、模和单位四元数的定义如下:
当我们化简
n
(
q
^
)
=
q
^
q
^
∗
n(\hat{q})=\sqrt{\hat{q}\hat{q}^∗}
n(q^)=q^q^∗之后,虚部被消去只留下了实部,摸的定义也可以用
∣
∣
q
^
∣
∣
=
n
(
q
^
)
||\hat{q}||=n(\hat{q})
∣∣q^∣∣=n(q^)。进过一系列推导之后可以推导出四元数的逆。等式
q
^
−
1
q
^
=
q
^
q
^
−
1
=
1
\hat{q}^{−1}\hat{q}= \hat{q}\hat{q}^{-1}= 1
q^−1q^=q^q^−1=1成立。我们可以由四元数模的定义开始推导:
四元数的逆如下所示:
计算四元数的逆的公式用到了标量乘法,可以有公式4.3.1的乘法导出:
s
q
^
=
(
0
,
s
)
(
q
v
,
q
w
)
=
(
s
q
v
,
s
q
w
)
s\hat{q}=(0,s)(q_v,q_w)=(sq_v,sq_w)
sq^=(0,s)(qv,qw)=(sqv,sqw),和
q
^
s
=
(
q
v
,
q
w
)
(
0
,
s
)
=
(
s
q
v
,
s
q
w
)
\hat{q}s=(q_v,q_w)(0,s)=(sq_v,sq_w)
q^s=(qv,qw)(0,s)=(sqv,sqw),这就意味着标量乘法是可交换的:
s
q
^
=
q
^
s
=
(
s
q
v
,
s
q
w
)
s\hat{q}=\hat{q}s=(sq_v,sq_w)
sq^=q^s=(sqv,sqw)。
下面的规则集合都是由四元数的定义推导出来的:
单位四元数
q
^
=
(
q
v
,
q
w
)
\hat{q} =(q_v,q_w)
q^=(qv,qw)使得
n
(
q
^
)
=
1
n(\hat{q})=1
n(q^)=1。由此得出
q
^
\hat{q}
q^可以写成
其中三维向量
u
q
u_q
uq,满足
∣
∣
u
q
∣
∣
=
1
||u_q||=1
∣∣uq∣∣=1,因为
当且仅当
u
q
⋅
u
q
=
1
=
∣
∣
u
q
∣
∣
2
u_q⋅u_q=1=||u_q||^2
uq⋅uq=1=∣∣uq∣∣2。接下来的章节可以看到,单位四元数非常适合用于创建旋转和朝向表示。但是在这之前,还如要介绍一些其他的单位四元数操作。
对复数而言,两维的单位向量可以表示为
cos
ϕ
+
i
sin
ϕ
=
e
i
ϕ
\cos ϕ+i\sinϕ=e^{iϕ}
cosϕ+isinϕ=eiϕ(欧拉公式)。对四元数就是:
由公式4.41可以推导出单位四元数的对数和指数函数:
4.3.2 四元数变换
我们接下来学习四元数集合的子集,即单位长度的子集,称为单位四元数。 单元四元数可以表示任何三维旋转,并且这种表示极其简单。
我们将讨论为何单位四元数表示旋转和定向时候十分有用。首先,将四个元素的点或者向量
p
=
(
p
x
,
p
y
,
p
z
,
p
w
)
T
p=(p_x,p_y,p_z,p_w)^T
p=(px,py,pz,pw)T的各个分量带入四元数
p
^
\hat p
p^,然后假设我们有单位四元数
q
^
=
(
sin
ϕ
u
q
,
cos
ϕ
)
\hat q=(\sinϕu_q,\cosϕ)
q^=(sinϕuq,cosϕ)。
表示将四元数
p
^
\hat p
p^(也就是点p)绕轴
u
q
u_q
uq旋转2ϕ。注意到
q
^
\hat q
q^是单位四元数,所以
q
^
−
1
=
q
^
∗
\hat q^{−1}=\hat q^{*}
q^−1=q^∗,如图4.9所示。
任意非0实数乘以四元数
q
^
\hat q
q^表示相同的变换,这就意味着
q
^
\hat q
q^和
−
q
^
-\hat q
−q^表示相同的旋转。也就是,将旋转轴
u
q
u_q
uq反向,实数
q
w
q_w
qw取反,得到的四元数和原始四元数的旋转效果相同。这也就意味着从矩阵中提取四元数可以返回
q
^
\hat q
q^或
−
q
^
-\hat q
−q^。
给定两个单位四元数
q
^
\hat q
q^和
r
^
\hat r
r^,首先用
q
^
\hat q
q^乘以四元数
p
^
\hat p
p^,再用
r
^
\hat r
r^乘之,如公式4.44所示:
这里
c
^
=
r
^
q
^
\hat c=\hat r\hat q
c^=r^q^是表示四元数的结合的四元数。
矩阵转换
因为有时需要将几个不同的变换结合在一起,并且大多数是以矩阵的形式,因此需要一种将等式4.43转换为矩阵的方法。四元数
q
^
\hat q
q^可以转换成矩阵
M
q
M^q
Mq,如4.45.
这里,标量
s
=
2
/
(
n
(
q
^
)
)
2
s=2/(n(\hat q))^2
s=2/(n(q^))2。对于单位四元数来说可以简化成:
四元数一旦构造好了之后,不需要计算任何三角函数,所以这个转换过程十分高效。
从正交矩阵
M
q
M^q
Mq转换到单位四元数的过程要麻烦一点。关键是等式4.46中矩阵的区别:
这些等式的含义是如果
q
w
q_w
qw是已知的,那么可以计算出向量
v
q
v_q
vq的值,然后推导出
q
^
\hat q
q^。
M
q
M^q
Mq的迹可以由下式计算得出:
如下结果可以计算出最终的单位四元数:
为了得到数值上的稳定,需要避免除以极小数。因此记
t
=
q
w
2
−
q
x
2
−
q
y
2
−
q
z
2
t=q_w^2−q_x^2−q_y^2−q_z^2
t=qw2−qx2−qy2−qz2,由此可得:
意味着
m
00
,
m
11
,
m
22
,
u
m_{00},m_{11},m_{22},u
m00,m11,m22,u中的最大值决定着
q
x
,
q
y
,
q
z
,
q
w
q_x,q_y,q_z,q_w
qx,qy,qz,qw中的最大值。如果
q
w
q_w
qw是其中最大的值,那么可以使用公式4.49来推导四元数。反之,得到如下信息: