(一)首先,无论dx还是opengl,所表示的矢量和矩阵都是依据线性代数中的标准定义的:
“矩阵A与B的乘积矩阵C的第i行第j列的元素c(ij)等于A的第i行于B的第j列的对应元素乘积的和。”(实用数学手册,科学出版社,第二版)
例如c12 = a11*b11+a12*b21+a12*b13...
(二)在明确了这一点后,然后我们再看“矩阵的存储方式”,矩阵存储方式有两种,一种是“行主序(row-major order)/行优先”,另一种就是“列主序(column-major order)/列优先”
1)Direct3D 采用行主序存储
“Effect matrix parameters and HLSL matrix variables can define whether the value is a row-major or column-major matrix; however, the DirectX APIs always treat D3DMATRIX and D3DXMATRIX as row-major.”(见d3d9 document/Casting and Conversion 一节)
2)OpenGL 采用列主序存储
“The m parameter points to a 4x4 matrix of single- or double-precision floating-point values stored in column-major order. That is, the matrix is stored as follows”
(见msdn glLoadMatrixf API说明)
存储顺序说明了线性代数中的矩阵如何在线性的内存数组中存储,d3d 将每一行在数组中按行存储,而opengl将每一列存储到数组的每一行中:
线性代数意义的同一个矩阵,在d3d 和 ogl 中却有不同的存储顺序
线代:a11,a12,a13,a14 d3d : a11,a12,a13,a14 gl: a11,a21,a31,a41
a21,a22,a23,a24 a21,a22,a23,a24 a12,a22,a32,a42
a31,a32,a33,a34 a31,a32,a33,a34 a13,a23,a33,a43
a41,a42,a43,a44 a41,a42,a43,a44 a14,a24,a34,a44
(三)矩阵乘法顺序和规则
矩阵乘法在线性代数中的定义是确定的,然而在不同的实现中出现了“左乘”和“右乘”的区别,或者叫做“前乘(pre-multiply),后乘(post-multiply)”
这个规则取决于vector的表示形式,即行向量还是列向量。如果是行向量,其实就是一个行矩阵。那么表示线性代数意义的“行x列”,就是前乘。矩阵乘法也是如此。
如d3d中,
D3D 是行向量,行优先存储,OpenGL是列向量,列优先存储。同一个矩阵用D3D存储还是用opengl存储虽然不同,但是变换的结果却是相同,
因为opengl 变换向量是把向量视作列向量,并同矩阵的每一列相乘,用来实现线性代数中同一个变换。
我们通常很难看到opengl变换坐标的代码,以下代码出自opengl source code,让我们一窥顶点变换的“庐山真面目”
void FASTCALL __glXForm3(__GLcoord *res, const __GLfloat v[3], const __GLmatrix *m)
{
__GLfloat x = v[0];
__GLfloat y = v[1];
__GLfloat z = v[2];
res->x = x*m->matrix[0][0] + y*m->matrix[1][0] + z*m->matrix[2][0]
+ m->matrix[3][0];
res->y = x*m->matrix[0][1] + y*m->matrix[1][1] + z*m->matrix[2][1]
+ m->matrix[3][1];
res->z = x*m->matrix[0][2] + y*m->matrix[1][2] + z*m->matrix[2][2]
+ m->matrix[3][2];
res->w = x*m->matrix[0][3] + y*m->matrix[1][3] + z*m->matrix[2][3]
+ m->matrix[3][3];
}
可见确实如上所述,“OPENGL列向量和矩阵的每一列相乘,仍然表示线性代数行向量和矩阵的每一行相乘”
再来看一下opengl 矩阵相乘,“用a的每一列去乘b的每一行”。
/*
** Compute r = a * b, where r can equal b.
*/
void FASTCALL __glMultMatrix(__GLmatrix *r, const __GLmatrix *a, const __GLmatrix *b)
{
__GLfloat b00, b01, b02, b03;
__GLfloat b10, b11, b12, b13;
__GLfloat b20, b21, b22, b23;
__GLfloat b30, b31, b32, b33;
GLint i;
b00 = b->matrix[0][0]; b01 = b->matrix[0][1];
b02 = b->matrix[0][2]; b03 = b->matrix[0][3];
b10 = b->matrix[1][0]; b11 = b->matrix[1][1];
b12 = b->matrix[1][2]; b13 = b->matrix[1][3];
b20 = b->matrix[2][0]; b21 = b->matrix[2][1];
b22 = b->matrix[2][2]; b23 = b->matrix[2][3];
b30 = b->matrix[3][0]; b31 = b->matrix[3][1];
b32 = b->matrix[3][2]; b33 = b->matrix[3][3];
for (i = 0; i < 4; i++) {
r->matrix[i][0] = a->matrix[i][0]*b00 + a->matrix[i][1]*b10
+ a->matrix[i][2]*b20 + a->matrix[i][3]*b30;
r->matrix[i][1] = a->matrix[i][0]*b01 + a->matrix[i][1]*b11
+ a->matrix[i][2]*b21 + a->matrix[i][3]*b31;
r->matrix[i][2] = a->matrix[i][0]*b02 + a->matrix[i][1]*b12
+ a->matrix[i][2]*b22 + a->matrix[i][3]*b32;
r->matrix[i][3] = a->matrix[i][0]*b03 + a->matrix[i][1]*b13
+ a->matrix[i][2]*b23 + a->matrix[i][3]*b33;
1。矩阵和线性变换:一一对应
矩阵是用来表示线性变换的一种工具,它和线性变换之间是一一对应的。
考虑线性变换:
a11*x1 + a12*x2 + ...+a1n*xn = x1'
a21*x1 + a22*x2 + ...+a2n*xn = x2'
...
am1*x1 + am2*x2 + ...+amn*xn = xm'
对应地,用矩阵来表示就是:
|a11 a12 ... a1n | |x1| |x1'|
|a21 a22 ... a2n | |x2| |x2'|
|... |* |...|= |... |
|am1 am2 ... amn | |xn| |xm'|
也可以如下来表示:
|a11 a21 ... am1|
|a12 a22 ... am2|
|x1 x2...xn|*|... |= |x1' x2'... xm'|
|a1n a2n ... amn|
其中涉及到6个矩阵。分别为A[m*n],X[n*1],X'[m*1]以及X[1*n],A[n*m],X'[1*m]。
可以理解成向量x(x1,x2,...,xn)经过一个变换矩阵A[m*n]或A[n*m]后变成另外一个向量x'(x1',x2',...,xm'))。
2。矩阵的表示法:行矩阵 vs. 列矩阵
行矩阵和列矩阵的叫法是衍生自行向量和列向量。
其实,矩阵A[m*n]可以看成是m个n维的row vector构成的row matrix,也可看成是n个m维的column vector构成的column matrix。
其中,X[n*1]/X'[m*1]就等价于1个n/m维的column vector。X[1*n]/X'[1*m]就等价于1个n/m维的row vector。
Row matrix和Column matrix只是两种不同的表示法,前者表示把一个向量映射到矩阵的一行,后者表示把一个向量映射到矩阵的一列。
本质上体现的是同一线性变换。矩阵运算规定了它们可以通过转置运算来改变这个映射关系。
3。矩阵的相乘顺序:前乘或左乘 vs. 后乘或右乘
需要注意的是两种不同的表示法对应不同的运算顺序:
如果对一个column vector做变换,则变换矩阵(row matrix/vectors)必须出现在乘号的左边,即pre-multiply,又叫前乘或左乘。
如果对一个row vector做变换,则变换矩阵(column matrix/vectors)必须出现在乘号的右边,即post-multiply,又叫后乘或右乘。
一般不会弄错,因为矩阵乘法性质决定了相同的内维数的矩阵才能相乘。至于为什么是这个规律,为什么要row vector乘以column vector或column vector乘以row vector???想想吧。。。
所以左乘还是右乘,跟被变换的vector的表示形式相关,而非存储顺序决定。
4。矩阵的存储顺序:按行优先存储 vs. 按列优先存储
涉及到在计算机中使用矩阵时,首先会碰到存储矩阵的问题。
因为计算机存储空间是先后有序的,如何存储A[m*n]的m*n个元素是个问题,一般有两种:按行优先存储和按列优先存储。
row-major:存成a11,a12,...,amn的顺序。
column-major:存成a11,a21,...,amn的顺序。
这样问题就来了,给你一个存储好的矩阵元素集合,你不知道如何读取元素组成一个矩阵,比如你不知道a12该放在几行几列上。
所以,每个系统都有自己的规定,比如以什么规则存储的就以什么规则读取。DX使用Row-major,OGL使用Column-major.即一个相同的矩阵A[m*n]在DX和OGL中的存储序列是不一样的,这带来了系统间转换的麻烦。
不过,一个巧合的事情是:DX中,点/向量是用Row Vector来表示的,所以对应的变换矩阵是Column Matrix/Vectors,而OGL中,点/向量是用Column Vector来表示的,所以对应的变换矩阵是Row Matrix/Vectors.所以,如果在DX中对一个向量x(x1,x2,x3,1)或点(x(x1,x2,x3,1))应用A[4*4]的矩阵变换,就是x' = x(x1,x2,x3,1) * A[4*4],由于采用Row-major,所以它的存储序列是a11,a12,...,a43,a44。在OGL中,做同样的向量或点的变换,因为其使用Row Matrix/Vectors,其应用的变换矩阵应该是A'[4*4] = A[4*4]( ' 表示Transpose/转置),就是x' = A'[4*4] * x'(x1,x2,x3,1),但是由于采用Column-major,它的存储序列正好也是a11,a12,...,a43,a44!!!
所以实际上,对DX和OGL来讲,同一个变换,存储的矩阵元素序列是一样的.比如:都是第13,14,15个元素存储了平移变化量deltaZ,deltaY,deltaZ.
Refs:
http://mathworld.wolfram.com/Matrix.html
http://www.gamedev.net/community/forums/topic.asp?topic_id=321862