小谈矩阵和坐标变换

来源:互联网 发布:thinkphp D 源码 编辑:程序博客网 时间:2024/05/16 15:49

很多朋友学习3D游戏编程,但往往一些基本概念含混不清,这会对以后的学习带来很大阻碍。最近看到有朋友问关于矩阵和坐标变换如何理解,故在此略作讨论,如有谬误,请不吝赐教!

首先,谈到矩阵,就离不开坐标变换,而3D坐标变换的基础是来源于线性代数。以下摘抄自孟岩的blog:
1 线性空间中的任何一个对象,通过选取基和坐标的办法,都可以表达为向量的形式。这里只要把基看成是线性空间里的坐标系就可以了。在线性空间中选定基之后,向量刻画对象,矩阵刻画对象的运动,用矩阵与向量的乘法施加运动。所以,矩阵的本质是运动的描述。
2 矩阵是线性空间中的线性变换的一个描述。在一个线性空间中,只要我们选定一组基,那么对于任何一个线性变换,都能够用一个确定的矩阵来加以描述。
3 矩阵不仅可以作为线性变换的描述,而且可以作为一组基的描述。而作为变换的矩阵,不但可以把线性空间中的一个点给变换到另一个点去,而且也能够把线性空间中的一个坐标系(基)表换到另一个坐标系(基)去。而且,变换点与变换坐标系,具有异曲同工的效果。线性代数里最有趣的奥妙,就蕴含在其中。

我们先讨论3*3矩阵:
1 矩阵的行序和列序(也称行优先或列优先)仅仅是指矩阵的存储方式,即我们如果用一个4*4数组m存储矩阵,如果m[0][0]-m[0][3]连续存储了矩阵的第一行,那么就是行优先,反之就是列优先。无论是行优先还是列优先,它们代表的数学意义是相同的。
2 如果矩阵是行序,那么它的第一列(m[0][0],m[1][0],m[2][0])就代表X变换,第二列就是Y变换,第三列就是Z变换。
  我们来看看为什么:
刚才提到矩阵把线性空间中的一个点给变换到另一个点,不妨称变换前的点为P1(x1,y1,z1),变换后的点为P2(x2,y2,z2):
那么根据线代中的矩阵乘法,P1 * M = P2展开就成了:
x2 = x1*m[0][0] + y1*m[1][0] + z1*m[2][0];
y2 = x1*m[0][1] + y1*m[1][1] + z1*m[2][1];
z2 = x1*m[0][2] + y1*m[1][2] + z1*m[2][2];
看出什么了吗?
如果把一个行序矩阵(接下来讨论的都是行序矩阵,就省略行序二字了)的每一列分别用X,Y,Z三个矢量来表示,那么M就表示为:XYZ三根轴。
现在,矩阵M看起来是不是很像一个坐标系?而P1到P2的变换就是P1分别与X,Y,Z的点积!
也就是矩阵M是把P1从老坐标系变换到新坐标系,而矩阵的三列(三根轴)就分别代表了新坐标系的三根轴在老坐标系中的坐标!
而点积的几何意义其实就是求取投影!所以坐标变换的几何本质(刚才已经讨论过其代数本质),就是把一个点分别投影到三根轴上去而已!
再仔细想一想,P1在XYZ三根轴上的投影,不正是它在新坐标系下的坐标吗?这样一来,坐标变换是不是就太容易理解了呢?
3 所以缩放矩阵M的三根轴,XYZ的模就不等于1,如果某一根轴的模不等于1,那么就有了对应的缩放。所以纯旋转矩阵,前三列的模必然等于1。
4 DX的矩阵是行序,OpenGL的矩阵是列序。我写的引擎,S3D的矩阵也是列序的。

最后说说4*4矩阵:
1 刚才讨论了旋转和缩放,平移如何表示呢?
  x2 = x1*m[0][0] + y1*m[1][0] + z1*m[2][0] + Xt;
这里Xt就是表示x方向上的平移。Xt,Yt,Zt共同构成了一个4*4矩阵的第4行的前3列。
2 4*4矩阵的第4列初始情况下一般设置为0,0,0,1,这些值也被称为W,它代表了近大远小的比例关系。此时需要被变换的点(矢量),也必须增加一个元素,成为x,y,z,w,而w的初始值设置为1。注意这里的xyzw并不是四元数的概念,这里的w仅仅是为了配合4*4矩阵运算而存在的。
3 4*4矩阵实际上是为了把多步坐标变换并置而产生的,矩阵并置后可以用一个4*4矩阵代表多次坐标变换,从而显著提高顶点变换的效率。而且参与变换的W最终还可以用于透视校正和深度测试。
4 对于4*4矩阵的上三角阵(3*3矩阵)的理解,可以完全沿用刚才3*3矩阵的讨论。

注1:几何流水线坐标变换基本流程:  Local->World->View->Projection->Homo->Viewport->Screen
注2:如果一个矩阵变换是线性的,那么它的转置与求逆等价。我们经常在一些逆变换(比如ShadowMap算法)中看到转置,不必疑惑,它的意义仍然是求逆。
注3:矩阵运算不满足交换律,所以会出现一些有趣的结果,比如M1代表一个旋转操作,M2代表一个平移操作,那么 P*M1*M2 就是先旋转再平移,而P*M2*M1就成了先平移在旋转。我们在实际3D编码中经常遇到这样的问题,就是一个物体是以自己为参照物旋转,还是以世界为参照物旋转,那么按照刚才的描述,假如Mw表示世界变换矩阵,如果Mw左乘M1,再作为最终变换矩阵,那么就是以自己为参照物旋转(很容易理解,因为是先旋转,才变换到世界坐标系嘛,所以这个旋转是定义在本地的),而如果Mw右乘M1,就是先变换到世界坐标系,再旋转了,这时当然就是以以世界为参照物旋转了。
注4:几个最基本的矩阵表达式,感兴趣的可以自己推导一下:
// | x 0 0 0 |
// | 0 y 0 0 |
// | 0 0 z 0 |
// | 0 0 0 1 | 缩放

// | 1 0 0 0 |
// | 0 1 0 0 |
// | 0 0 1 0 |
// | x y z 1 | 平移

// | 1  0 0 0 |
// | 0  c s 0 |
// | 0 -s c 0 |
// | 0  0 0 1 | X旋转 s,c表示旋转角的sin,cos值,下同

// | c 0 -s 0 |
// | 0 1  0 0 |
// | s 0  c 0 |
// | 0 0  0 1 | Y旋转

// |  c s 0 0 |
// | -s c 0 0 |
// |  0 0 1 0 |
// |  0 0 0 1 | Z旋转

// | Sx*Sy*Sz + Cy*Cz Cx*Sz Sx*Cy*Sz - Sy*Cz 0 |
// | Sx*Sy*Cz - Cy*Sz Cx*Cz Sx*Cy*Cz + Sy*Sz 0 |
// |            Cx*Sy   -Sx            Cx*Cy 0 |
// |                0     0                0 1 | ZXY旋转的并置