记得以前看过别人写的一篇文章,讲如何直观的去理解矩阵的,用一种虽然很不严密也很不形式化的语言来直观的描述了一下矩阵,对于新手理解矩阵有不少帮助。链接:
由于上课以及学习图形学的两个原因,我跟矩阵断断续续的又耗了小半年,其间其他恶心事情无数,各种间断……唉,不扯了。既然学了,就再随便写点自己的,结合着图形学所用到的矩阵来写。程度么,接着上述链接里的文章继续写。当然,也都是些很基本的内容。
坐标基的矩阵定义
在一开始提到的文章三中,提到了向量与矩阵乘积Mx,我们可以把这个形式的矩阵M理解为对向量x的基——即坐标系的声明,向量x可看作空间的一点。乘积的结果向量,就是x在标准正交基I下的空间坐标。也就是说,假设y=Mx,那么y其实是Iy——x在正交基下的坐标。也可以这么理解,Mx相当于用用矩阵M作为变换矩阵对x进行了变换,其结果是x在I下的坐标。
不过这里还是不够明白,M中的每个基向量,又是定义在哪里的。为什坐标系的声明可以看作一次变换。
为了说明白这个东西,先转换一下内容,说说坐标变换:
坐标变换
一个坐标x在I下,其数值就是最终的坐标。但如果我们把x放到M下,Mx的数值才是最终的坐标。再或者,如果要获得一个在M下的坐标x’,其在I下的坐标为x,那这个x’是多少。这些可以用坐标变换来表达,在开始链接的文章中也讲到过。一个在变换T(x),其M下的变换矩阵为A,则变换过程可以表示为MAx,其位置就是MAx,而变换后的坐标,从数值上来讲也可以认为是相对位置没有变而基变了:从x以M为基变换到了x以MA为基。变换是相对的。所以这个变换T的矩阵A,也可以理解为是对基M的一个变换。
那么,就来扯一扯这个相互关系吧。
不同基下的坐标
假设有基M和其下的坐标y,一个变换T以及其M下的变换矩阵A,变换后的位置是MAy,如果这里把MA看作基N,那么N=MA,M=NA-1。如果想要一个在M下的坐标x,其最终位置和MAy相同,从Ny=MAy=Mx看,y在数值上就应该是Ax,x也就等于A-1y。就如上面说的:对于一个坐标的变换(左乘),可以理解成将坐标的基进行更换(右乘)。
那如何理解对坐标的变换用左乘,对基的变换是右乘呢?
对坐标的变换是用变换矩阵左乘,这个过程看作为对基右乘此变换矩阵时,可以这么理解:这个变换矩阵中的每一列都表示一组新的基中的一个基向量,数值是此向量在原先的基下的坐标。这样,相乘之后的矩阵就是新的基,每一列就是新的基中的基向量。
上面这些,总结一下就是:有两个不同的基,M和N,如果N=MA,那么一个位置在M下的坐标是y,其在N下的坐标就是A-1y。
变换的“相对”
变换是相对的,两个物体,一个物体A相对于另一个物体B变换了位置,可以理解为B相当于A反向变换了位置。
先用个简单的例子来说明。
在图形学中,旋转矩阵是最基本的矩阵之一。对一个在某个基M下的坐标x进行旋转变换T,T的矩阵为A,那么旋转后的坐标为MAx。由于相对变换的相对性,旋转也可以理解为位置不动,而所在的基进行反向的旋转。
根据这个,可以很自然地得到:对一个基的每个坐标反向旋转,相当于A-1M,这样得到的坐标和之前的坐标相同,最终的结果也就是A-1Mx。也就是说这里A-1Mx和MAx是相等的。
等等,这两个不相等啊,哪里错了……
哪里错了?
错在,如果要把基进行反向旋转的话,要保持位置不动,而不是坐标不动。或者说,这种理解变换的方式中,需要的是最终在某个基下变换后的坐标,而不是空间中最终的位置。对于基M,M下变换T来说,MAx中的Ax是最终的坐标;对于把基M的每一个坐标反向旋转之后,A-1Mx,x是最终的坐标。而用A-1左乘基矩阵这样改变基M的话,由于x是在M下表示的,这样x的位置就和随着矩阵A-1的左乘一起变了。为了保持x在M变换前后位置不变,在对基M进行反向旋转之后,应该再把坐标x进行正向旋转,保持位置不变,也就是A-1MAx。而在这个变换过的基下的坐标,依然是Ax。(这里很绕。形象地说就是,一张纸上面有个点,你把这张纸逆时针旋转了一定角度之后,要保持上面的那个点位置不变,就得把这个点擦掉,在之前点所在空间中的位置上再画上。)
因为把基反向旋转来得到旋转变换的相对效果,我们要的是坐标在最终的那个基下相等。这个不能按照在空间中的最终位置来理解,而这种用相对变换来理解变换效果的方法,在最终的空间坐标没有意义,当然数值上也不一样。
不过旋转矩阵由于其特殊性(正交矩阵,所以可逆),不能推广到一般情况。比如投影矩阵等这种不可逆的变换,就不能这样相对了。
不同基下的变换矩阵
和上面相同,还是M,以及另一个基N=MA,还有一个位置,其M下的坐标x,N下坐标y。这时候又来了一个变换F,其在M下的变换矩阵为P。如果在M下的坐标x经过这个变换为MPx,那这个变换F在N下的坐标y,有一个什么变换矩阵呢Q?最基本的想法嘛,我们可以先把基变换回去,再进行变换,然后再把基变换回来。用矩阵表示就是:先变换回去NA-1y,进行变换NA-1Py,再变换回来NA-1PAy。从这个过程可以知道,NA-1PAy和MPx拥有相同的位置。那么F在N下的变换矩阵Q也就是A-1PA啦。
再反过来看,为什么NA-1PAy和MPx相等。这个就很简单了,首先,NA-1是M;再根据上面的推导来看,x=Ay,那么NA-1PAy=MPx。
上面这些,总结一下就是:有两个不同的基M和N,一个变换F,在M下的变换矩阵为P,如果N=MA,那么其在N下的变换矩阵就是A-1PA。
回到坐标基的矩阵定义
根本上来讲,最后的坐标无论如何都是要在标准正交基I下进行表达的,也就是说,不管一个坐标x在哪个基下,最底层的,都要在I下进行表示。这样,在基M下的坐标x写成Mx,但M中的每个基向量还是在I下定义的,所以M每个向量m都是Im,整体上M就是IM。所以,Mx可以写成IMx,Iy可以写成IIy。我们再推广一下,如果M中的基向量m不是在I下定义的,是N下,那么x在M下定义的坐标其实就是NMx,也就是INMx,可以这么一直定义下去:MnM(n-1)...M1M0x。这里也可以说,每个基矩阵都可以理解成一个变换矩阵。
上面这种矩阵套矩阵的定义坐标的方法,我们不妨叫其矩阵栈,最左边的矩阵是栈底,最右边的矩阵是栈顶,那么x最终在I中的坐标就是从这一坨东西的乘积,但为什么要搞个矩阵栈这么个概念出来呢?下一节说局部坐标系与全局坐标系的时候要用到。
由于矩阵乘是符合结合律的,从谁开始乘都没有关系,这样就可以从两个方面去理解。这里以3个矩阵M2M1M0x连乘为例。
一个是从左向右看:先有一个M2定义了一个基;再来一个M1,其每个基向量在M2的基础上定义,这时基就变了,被右乘了一个M1,这样就相当于基直接是一个矩阵(M2M1);然后又是M0,再次把基改变,成为(M2M1M0);最后这个基就是x所在的坐标系,相乘就是x在I下的坐标。
另一个是从右向左看:有一个坐标x;先有一个M0左乘这个坐标,相当于改变了x的基,从默认的I变为M0,这时x在I中的位置就变了一次,相当于把x从坐标系I下移动到了M0下;再来了一个M1,再次把已经变换了一次坐标的x——M0x,变换到了M1M0x;最后是变换到M2M1M0x,这还是x最终在I下的坐标。
坐标变换与局部坐标系
说一说矩阵的一种用法吧。
在对空间物体的坐标描述中,为了描述一个物体模型,其每个顶点都会有一个坐标,这个坐标是相对于这个物体的自定义的“中心”,那这些坐标就是物体的局部坐标系下的物体描述。一个场景中,会有很多物体,每个物体的位置不同,放置方向不同,大小也不同。那怎么表达空间中这些物体呢?
最直接的方法是保存下来空间中每个物体的每个顶点。不过如果有很多物体,只是在空间的位置与方向不同,那这样保存明显太浪费地方了。再或者场景中的一个物体需要移动/转动等,保存这个物体每个顶点的运动轨迹也明显太浪费。所以一个很显然的解决方式就是,分别保存不同的物体模型,然后把它们在空间中放置。重复的物体只需要两个引用指向相同的模型即可,而不需要分别保存它们在空间中的定点。
为了方便的达到这个方法,我们有了一个个的物体模型,然后需要把它们放置在场景内。这个“放置”的过程,就是保存一个物体用的是哪个模型,在空间的位置是什么,朝向哪里,放大缩小的倍率等。而这些,都是线性变换,都可以用一个个的矩阵来表示(平移不是线性变换,但可以用高一个维度的线性变换表示,所以都可以用矩阵表示)。(具体的矩阵是什么样的是图形学最基本的内容,这里就不再说明了,可搜索查看相关资料。)
按照这个想法,就可以方便的在空间中放置物品了:
不妨假设最终的场景坐标系为单位矩阵I,我们有个物品,把其每个顶点作为矩阵O的列向量,那O就可以描述这个模型。显然,如果只考虑物品O,则矩阵O的每个坐标就是需要的顶点坐标。下来往场景中放这个物体。假设物体原点在p处,这个位移可用矩阵T表示,那么让整个物体移动到场景p就可以表示为TO,这样就把局部坐标系中的每一个点移动到了场景坐标系中。
目前这个物品的朝向和局部坐标系中还是相同的,我们想让物品绕着自身坐标系的x轴旋转一下,再放到场景中。假设这个旋转可用矩阵R表示,那么这个物品O在场景中的最终坐标就是TRO。这个顺序很重要,不同的顺序含义不同,应该按照从后向前的顺序去理解每一个矩阵和每一个变换的对应。如果搞成RTO的话,就变成先移动到p处,再绕场景原点旋转了。
再稍微复杂些。假设有个坦克的模型,每个可活动的部分都分别是独立的模型,每个部分都有自己的坐标系。车身所在的坐标系是基坐标系;炮塔要在车身上旋转,所以炮塔坐标系的基坐标系是车身;而对于炮管来说,炮塔所在的坐标系是基坐标系,炮管要绕炮塔上的固定点旋转;同理,车顶机枪的基坐标系也是炮塔。再假设炮塔顶点坐标矩阵T,位置的位移矩阵TT,旋转矩阵RT;炮管顶点坐标矩阵B,位置位移矩阵TB,旋转矩阵RB;车顶机枪顶点坐标矩阵M,位置位移矩阵TM,旋转矩阵RM。那炮塔在坦克坐标系中就是TTRTT;炮管是相对于炮塔的,所以炮管在炮塔的坐标系中就是TBRBB,在车身的坐标系中就是TTRTTBRBB;同理,由于车顶机枪也是相对于炮塔的,所以在炮塔的坐标系中就是TMRMM,在车身的坐标系中就是TBRBTMRMM。如果坦克在场景中位置位移矩阵T,转向矩阵R,缩放矩阵S,那么可知其炮管在场景中最终的位置是TRSTTRTTBRBB,好长是吧……这个例子里,坦克可以看作是一个模型节点,炮塔是其一个子节点,炮管和车顶机枪分别是炮塔的两个子节点,这样也就形成了一个模型关系的树形结构。
每个顶点要是都这么算一次矩阵乘法显然太那个了。首先,同一个模型节点中的顶点的各种变换肯定是相同的,所以把对这个模型的变换矩阵可以先乘出来,然后每次只需要用这个矩阵左乘模型中的顶点。再者,处理不同的子节点时,只需要在父节点的变换矩阵上再乘以这个节点的变换矩阵即可。这就明显是一个递归模型了:根节点是场景,每个子节点有其对应的变换矩阵,子节点的子节点也有其对应的变换矩阵……这样便可以用递归的方式处理场景中的每一个物体,或者,我们手动控制这个递归过程,这就需要我们自己把每层节点的变换矩阵保存在栈里,处理新的子节点时对其变换矩阵压栈,处理完后出栈。
以上面坦克的例子来说明一下,当处理到这个坦克的节点时,栈顶的矩阵是此坦克所在的节点的变换矩阵M0,这时计算出坦克的变换矩阵M=TRS,用M0左乘M得到M1,压栈,用M1处理坦克车身的每个顶点;然后处理炮塔,这是栈顶矩阵是M1,算出炮塔的变换矩阵M=TBRB,用M1左乘M得到M2,压栈,用M2处理炮塔的每个顶点……当炮塔自己与所有子节点处理完后,可以让M2出栈了,然后用M1继续处理坦克车身的其他子节点。
结束的分割线
费了不少劲写了点这种基础内容,写的过程中让自己清晰化了一些东西。写完自己读起来感觉也就那么回事……希望没白写吧,从各种意义上来说。