写在前面
上一节介绍了向量和矩阵,本节将熟悉坐标、线性变换、仿射变换以及坐标转换等概念和计算方法,这些内容对后续的学习将会有很大帮助。部分内容不是OpenGL编程初学者所必须掌握的,可以在以后需要时再回头来看。
这里是对这些知识点的一个总结,旨在对他们有个整体把握,后面具体应用时会使用这些概念。内容尽量以例子形式说明,仅在必要时会给出数学证明。一个主题往往涉及过多内容,对于文中省略的部分,请参考相应的教材。
通过本节可以了解到
坐标是在特定坐标系下表示物体位置的方法,一谈到坐标,必定是在某个坐标系下给定的。例如经纬度坐标是相对于地球的球面坐标系统给出的。
坐标系则给出了一个参考框架,在这个框架里面,定义其他位置相对于一个起始点(这个起始点称作原点 O )的位置。同一位置,在不同的坐标系下会有不同的坐标,例如你所在城市以经纬度坐标表示,是相对于地球坐标系来给定的,如果从太阳系或者银河系来给定,又会是另外一个不同的值。
常见的坐标系包括:笛卡尔坐标系,极坐标系,球面坐标系等。
如下图所示的我们熟悉的,2D笛卡尔坐标系(来自wiki):
定义一个坐标系需要指定(参考自Objects in Motion):
这里,维度已由基向量维数确定,合法区域一般是无穷的,但是在图形处理中某些坐标空间是有限的,例如规范化设备空间(后面其他文章会介绍)。作为一个了解,基向量不一定非得正交,如下图所示:
对于一般情况,我们只需要记住:
坐标系=(基向量,原点 O )
对于任意2个2D坐标系,我们通过旋转、移动翻转可以将两个坐标系xy轴的指向相同。
但是对于3D坐标系,任意两个坐标系却不能等价。实际上,存在两种完全不同的3D坐标系:左手坐标系和右手坐标系。如果同属于左手坐标系或者右手坐标系,则可以通过旋转来重合,否则不可以。
判断一个坐标系是否属于右手系,可以拿出右手,然后右手的大拇指代表+x轴指向,食指代表+y轴指向,中指代表+z轴指向,你可以转动你的右手来匹配这个坐标系,如果能匹配则是右手坐标系,否则是左手坐标系。判断左手坐标系的方法类似。如下图所示为判断右手坐标系的方法(来自OpenGL coordinate system):
关于左右手坐标系理解还可以可参考下图(来自《3D数学基础》):
如上右图右手坐标系,这里拇指、食指、其余手指分别代表x,y,z轴的正方向。高等数学教材上使用的是右手坐标系。
同样还存在一个左手规则和右手规则,可以用于判断当物体绕轴旋转时正方向的判定问题。
对于左手规则,确定一个旋转轴后,左手握成拳头,拇指指向旋转轴的正方向,则其余手指弯曲方向即为旋转的正方向。从旋转轴正向末端来看,正向旋转是顺时针的。对于右手规则,有同样的方法。可参见下图:
左手右手规则在不同场合有着不同作用。上一节,我们使用右手规则判断了叉积的结果向量的方向。
注意OpenGL中坐标系 OpenGL中的物体、世界、照相机坐标系都属于右手坐标系,而规范化设备坐标系使用左手坐标系。笼统地说OpenGL使用右手坐标系是不合适的。这些坐标系后面会介绍。关于这个问题可以参考SO.
坐标是在指定坐标系中,相对于原点 O 给出的一个位置。这个位置可以用有序实数对表示(有些坐标系中可能使用复数),注意数对中的数字顺序对结果有影响。上面提到坐标系=(基向量,原点 O ),3D坐标系用向量表示为:
⎧⎩⎨⎪⎪i=(ax,by,bz)j=(bx,by,bz)k=(cx,cy,cz)(基向量)
O=(Ox,Oy,Oz)(坐标原点)
这样在坐标系中一点 P 与原点 O 构成的向量:
r=OP→=xi+yj+zk
这时称 (x,y,z) 为点 P 的坐标,这个坐标也可以表示向量 OP→ .
一般地使用的3D笛卡尔坐标系使用标准基向量和坐标原点:
⎧⎩⎨i=(1,0,0)j=(0,1,0)k=(0,0,1)(标准基向量)
O=(0,0,0)(标准坐标原点) .
从上面可以看到,在一个坐标系中,求取坐标的过程,是一个向量分解的过程。求取一个位置在另一个不同的坐标系中的坐标,则需要进行坐标转换。后面会介绍。
使用坐标系统便能以解析几何的形式来研究空间几何。通过建立一个坐标系使得空间中点用有序实数组表示,空间图形用方程表示,这样能方便地研究几何图形的性质。
了解线性变换、仿射变换以及坐标转换,对于后面学习图形编程中的模型变换方法有很大帮助,因此这里予以介绍。要了解这些概念,需要一些其他概念的支撑,这里逐一介绍。每一个概念以定义结合示例的形式给出,如果暂时没有理解清楚,可以暂时跳过,以后回过头来再看或者参考这个主题的其他资料。本部分最后会给出完整的线性变换示例。
向量组是一组向量的集合,例如 α1,α2,⋯,αm 表示一个由m个 n×1 的矩阵(n维列向量)组成的列向量组。对应的也有行向量组的概念。如果存在一组实数 λ1,λ2,⋯,λm ,使得向量 β 满足下式:
β=λ1α1+λ2α2+⋯+λmαm(1)
则称向量 β 是 α1,α2,⋯,αm 的线性组合,或者说 β 由 α1,α2,⋯,αm 线性表示。上述求解点 P 坐标时向量 r 分解为向量组 i,j,k 表示的过程,就是一个找出线性表示系数的过程。
对于向量组 α1,α2,⋯,αm ,如果存在不全为零的数 λ1,λ2,⋯,λm ,使下面的等式成立:
λ1α1+λ2α2+⋯+λmαm=0(2)
则称向量组 α1,α2,⋯,αm 线性相关(linearly dependent), 否则称为线性无关( linearly independent)。也就是要使向量组 α1,α2,⋯,αm 线性无关,那么所有的系数 λi 都必须为0。
线性相关的一种几何解释 来自math.stackexchange:
假定你有一组向量 {x1,x2,⋯,x5} ,你从某个点出发,沿着 x1 走动一段距离,然后沿着 x2 走动一段距离, 最后沿着 x5 走动一段距离,最终你又回到了出发点(这里表明存在 λ1x1+λ2x2+λ5x5=0 ,即式2成立)。这就说明 {x1,x2,⋯,x5} 是线性相关的。
当然存在其他方法,判断向量组线性相关性,感兴趣的可以参考线性代数教材。
向量空间(也叫做线性空间)是对我们经常使用的2D和3D空间一般规律的拓展,它的定义主要反映的是满足一系列的运算规律,例如交换律和结合律等。由于这个定义包含较多规则,不在此列出,感兴趣的可以参考vector space。
如果在线性空间V中存在n个线性无关的向量 α1,α2,⋯,αn 使得V中任意元素 α 都能由他们线性表示,则称 α1,α2,⋯,αn 为V的一个基。基所含向量个数n称为线性空间V的维数,并称V为n维线性空间。
例如2D空间中,二维向量组 i=(1,0),j=(0,1) 是它的一个基;在3D空间中,向量组: i=(1,0,0),j=(0,1,0),k=(0,0,1) 是它的一个基。类似的可以推广到n维向量空间的基。
设 α1,α2,⋯,αn 是n维线性空间V的一个基,若任取 α∈V ,总有且仅有一组有序实数 x1,x2,⋯,xn ,使得:
α=x1α1+x2α2+⋯+xnαn=(α1,α2,⋯,αn)⎡⎣⎢⎢⎢⎢x1x2⋮xn⎤⎦⎥⎥⎥⎥(3)
成立,则称这组有序数 x1,x2,⋯,xn 为元素 α 在基 α1,α2,⋯,αn 下的坐标,记作 (x1,x2,⋯,xn)T 。
这里元素 α 用基向量组的线性组合来表示,坐标就是线性组合的系数。例如向量 a=(3,4)=3e1+4e2 ,其中 e1=(1,0),e2=(0,1) 为标准基,则a的坐标为 (3,4)T 。
变换这个词类似于函数,即将一个定义域里的输入量转化为值域里的另一个值,变换就是一个映射关系,一种规则。线性变换的一些性质对于后续学习3D模型变换时,理解起来将会更容易。
线性变换 T:U→V 是一个函数,将定义域U中元素,映射到值域V中,并满足下列两个条件(参考Definition LT):
1)可加性 对任意 u1,u2∈U ,都满足: T(u1+u2)=T(u1)+T(u2)
2)齐次性 对任意 u∈U 和任意标量 k ,都满足: T(ku)=kT(u)
可以利用上面的两个条件,即可加性和齐次性条件验证一个变换是否是线性变换。
线性变换示例(来自Example ALT)
T(⎡⎣⎢x1x2x3⎤⎦⎥)=[2x1+x3−4x2]
首先验证其是否满足可加性:
然后验证是否满足齐次性:
对一个线性变换 T ,存在一个矩阵 A 与之对应,变换表示为 T(x)=Ax ,其中 x 为列向量。
证明: 1.首先证明充分性。
当 T 是线性变换时,对于 x∈U ,其中
例如矩阵 A 如下(下面的例子来自:Matrices and linear transformations):
A=[1301−12].
对应的线性变换为:
T(x)=Ax=[1301−12]⎡⎣⎢xyz⎤⎦⎥=[x−z3x+y+2z]=(x−z,3x+y+2z).
由给定的线性变换:
f(x,y)=(2x+y,y,x−3y)
要找到对应的矩阵A,由上面定理证明时可知,标准矩阵A的内容为:
A=(f(e1),f(e2)))
其中 f(e1) 计算如下:
f(e1)=f(1,0)=(2,0,1)=⎡⎣⎢201⎤⎦⎥.
f(e2) 计算如下:
f(e2)=f(0,1)=(1,1,−3)=⎡⎣⎢11−3⎤⎦⎥.
所以最终计算得到矩阵 A 如下:
A=⎡⎣⎢20111−3⎤⎦⎥.
后面会详细介绍矩阵A的计算方法。在此之前,先来看下线性变换的应用。
线性变换在3D图形中模型变换部分应用很多,例如旋转、错切(shear)、缩放等操作都是线性变换。在OpenGL中使用矩阵操作来表示这些线性变换。例如2D平面上绕原点的旋转 θ 角度的操作如下图所示:
通过利用极坐标系表示 x,y和x′,y′ 并利用三角函数公式(具体过程略,有兴趣可以自行推导,后面会介绍另外一个更简洁的方法),得到旋转矩阵为:
[x′y′]=[cosθsinθ−sinθcosθ][xy]
那么矩阵:
A(θ)=[cosθsinθ−sinθcosθ] 表示的就是这个围绕原点旋转 θ 角度的线性变换。
使用矩阵表达变换的优势 通过使用矩阵形式,能够级联对物体顶点的变换,例如先执行旋转,后执行缩放,则表示为: T(x)=TsTrx=(TsTR)x ,可以在对描述物体组成的多个顶点执行变换之前,先计算出矩阵 (TsTR) ,从而节省执行变换需要的时间;同时也能表示为逆操作,例如旋转 −θ 角度, T(x)=A(−θ)x 。只是在OpenGL中使用的标准矩阵 A 是4x4的,后面会介绍原因和具体构造方法。
线性变换由基及变换后基的值唯一确定,通过计算线性变换后基的值可以得到线性变换对应的矩阵A。 这是本节讲述线性变换最重要的结论。
定理: 设 u1,u2,⋯,un 是线性空间U的一个基,线性空间V包含向量 v1,v2,⋯,vn (可以相同)。那么存在唯一的线性变换 T:U→V 使得: T(ui)=vi(1≤i≤n) 。
上面的定理证明,感兴趣的可以参考 Theorem LTDB,不在此处给出详细过程。
这个定理告诉我们: 只要知道了线性空间U的给定基 u1,u2,⋯,un 在线性变换 T 下对应的值 T(u1),T(u2),⋯,T(un) ,线性变换 T 也就由 ui 及 T(ui) 的对应关系确定了。
这个定理的作用就好比,两点确定一条直线。因此我们可以通过计算:
A=(T(u1),T(u2),⋯,T(un)) 来获取线性变换 T 对应的矩阵 A 。
也就是说,矩阵 A 的列向量,由 T(ui) 在基 u1,u2,⋯,un 下的坐标唯一确定。同时给出一个矩阵 A 作为线性变换 T 在基 u1,u2,⋯,un 下的矩阵,也就给出了该基在线性变换 T 下对应的值 T(u1),T(u2),⋯,T(un) ,从而确定了线性变换 T 。这表明线性变换 T 与矩阵之间存在一一对应关系,上面已经证明了这个结论。
例如对于上面的旋转矩阵,从基和转换后基的角度,也就是从x,y轴来看, x 转换后为 x′ , y 转换后为 y′ ,如下图所示(doitpoms.ac.uk):。
利用三角函数公式可以计算转换后 x′,y′ 对应的坐标,如下图所示(来自:Rotations and Infinitesimal Generators):
。
因此可以顺利的写出旋转矩阵为:
R(θ)=[cosθsinθ−sinθcosθ] 。
矩阵第一列即为 x 转换后的 x′ 轴的对应的坐标,第二列即为 y 转换后的 y′ 轴对应的坐标。显然这个计算方法,比利用极坐标公式来得快。
在后面使用OpenGL模型变换矩阵时,经常要使用到这个方法,理解了这一点后面理解模型变换矩阵就会变得简单。
一个位置,在不同的坐标系里有不同的坐标。设 u={u1,u2,⋯,un} 和 v={v1,v2,⋯,vn} 是n维线性空间V的两个基。两个基之间可以互相表示。因此存在9个标量 λij ,使得:
u1=λ11v1+λ12v2+λ13v3
u2=λ21v1+λ22v2+λ23v3
u3=λ31v1+λ32v2+λ33v3
λij 写成矩阵形式得到:
将式子a代入式子b得到:
a=MTb(c)
b=(MT)−1a=Ta(d)
式子c和d给出了向量 w 的两个坐标之间的转换公式。
上面的矩阵 MT :
从式子e和f来看,我们可以得出,要在两个基之间转换坐标,只需要求出一个基在另一个基里的坐标表示即可,这个坐标表示构成矩阵 MT 或者 (MT)−1 。
示例
已知基 u 为:
u1=(1,2,1,0),u2=(3,3,3,0),u3=(2,−10,0,0),u4=(−2,1,−6,2)
基 v 为:
v1=(1,2,1,0),v2=(1,−1,1,0),v3=(1,0,−1,0),v4=(0,0,0,2)
容易求出:
v1=u1
v2=−2u1+u2
v3=11u1−4u2+u3
v4=−27u1+11u2−2u3+u4
即:
也可得:
u1=v1
u2=2v1+v2
u3=−3v1+4v2+v3
u4=−v1−3v2+2v3+v4
即:
给定向量 ω=(6,−1,2,2) ,容易验证:
ω=v1+3v2+2v3+v4
也就是:
[w]v=⎡⎣⎢⎢⎢1321⎤⎦⎥⎥⎥
那么:
容易验证:
(1) w=−10u1+6u2+u4
(2) [u]−1v=[v]u
线性变换无法表达一类重要的变换——平移变换。平移变换表达的是对于点 p=(x,y,z) 经过 d=(αx,αy,αz) 所表示的位移后得到点 p′=(x′,y′,z′) 的过程,表示为:
p′=p+d
我们尝试寻找变换 T 满足: T(x)=Ax=p′=⎡⎣⎢x+αxy+αyz+αz⎤⎦⎥
当 d≠0 时,上式中 T(0)≠0 ,由式子1可知,这不是线性变换。因此需要引入仿射变换的概念。
仿射变换与线性变换不同之处在于,线性变换保持原点位置不变,而仿射变换可以改变原点的位置。仿射变换包括线性变换,例如旋转,缩放等变换,特殊地是仿射变换包括平移变换。例如 f(x)=2x 是一个线性变换,也是仿射变换;而 f(x)=2x+3 是仿射变换。感兴趣的可以参考What is the difference between linear and affine function。一般而言,仿射变换是 线性变换+平移变换。
使用3x3矩阵无法表达平移变换(当 d≠0 时方程 T(x)=A3×3x=p′ 无解,感兴趣可以参考Reason for homogeneous),但是以一个统一的方式表达变换,在计算中将便于计算,因此需要引入齐次坐标系的概念来完成这个目标。从编程角度来讲,在OpenGL中引入齐次坐标系主要是为了表达平移变换和投影变换中的透视除法。如果你要从数学角度理解齐次坐标系,这对数学要求较高,将会涉及到射影几何等概念,感兴趣的话可以参考The Truth Behind Homogeneous Coordinates。
齐次坐标是在原来坐标的基础上添加了一个 w 成分。在3D中使用 (x,y,z) 既能表示点,又能表示向量,容易引起混淆。使用齐次坐标能够克服这个困难。具体做法是,在由原点 P0 和基 v1,v2,v3 所定义的坐标系中,任意点 P 可以表示为:
P=α1v1+α2v2+α3v3+P0 。
定义标量0和1与点的乘法为:
0.P=0,1.P=P ,则可以把点P表示为:
P=[α1α2α31]⎡⎣⎢⎢⎢v1v2v3P0⎤⎦⎥⎥⎥
可以把向量 w=β1v1+β2v2+β3v3 表示为:
w=[β1β2β30]⎡⎣⎢⎢⎢v1v2v3P0⎤⎦⎥⎥⎥ 。
因此,使用齐次坐标系表示点形式为:
P=⎡⎣⎢⎢⎢α1α2α31⎤⎦⎥⎥⎥
表示向量为:
w=⎡⎣⎢⎢⎢⎢β1β2β30⎤⎦⎥⎥⎥⎥
这里齐次坐标的 w 成分为0或者1,在后面投影变换的透视除法时还会看到 w 成分的其他值。
对于上面的平移变换:
T(x)=Ax=p′=⎡⎣⎢x+αxy+αyz+αz⎤⎦⎥
当 A 为4x4矩阵时,使用齐次坐标,x表示为 p=(x,y,z,1) , d=(αx,αy,αz,0) , p′=(x′,y′,z′,1) 。
可以计算得到 A 为:
前面介绍的基变换与坐标变换,没有考虑到原点,两个坐标系的原点位置可能不同,因此需要使用4x4矩阵调整下上面的结果。对于参考点 P0 和基 u={u1,