分享

矩阵及变换,以及矩阵在DirectX和OpenGL中的运用问题:左乘/右乘,行优先/列优...

 imelee 2017-08-08
核心提示:如d3d中,D3D是行向量,行优先存储,OpenGL是列向量,列优先存储。同一个矩阵用D3D存储还是用opengl存储虽然不同,但是变换的结果却是相同,因为opengl 变换向量是把向量视作列向量,并同矩阵的每一列相乘,用来实...
矩阵及变换,以及矩阵在DirectX和OpenGL中的运用问题:左乘/右乘,行优先/列优先,… /.html   2013年09月08日  

(一)首先,无论dx还是opengl,所表示的矢量和矩阵都是依据线性代数中的标准定义的:
“矩阵A与B的乘积矩阵C的第i行第j列的元素c(ij)等于A的第i行于B的第j列的对应元素乘积的和。”(实用数学手册,科学出版社,第二版)
例如c12 = a11*b11+a12*b21+a12*b13...

(二)在明确了这一点后,然后我们再看“矩阵的存储方式”,矩阵存储方式有两种,一种是“行主序(row-majororder)/行优先”,另一种就是“列主序(column-major order)/列优先”
1)Direct3D 采用行主序存储

“Effect matrix parameters and HLSL matrix variables can definewhether the value is a row-major or column-major matrix; however,the DirectX APIs always treat D3DMATRIX and D3DXMATRIX asrow-major.”(见d3d9 document/Casting and Conversion 一节)
2)OpenGL 采用列主序存储
“The m parameter points to a 4x4 matrix of single- ordouble-precision floating-point values stored in column-majororder. 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 sourcecode,让我们一窥顶点变换的“庐山真面目”

void FASTCALL __glXForm3(__GLcoord *res, const __GLfloat v,const __GLmatrix *m)
{
    __GLfloat x= v[0];
    __GLfloat y= v;
    __GLfloat z= v;

    res->x= x*m->matrix[0][0] + y*m->matrix[0] +z*m->matrix[0]
 + m->matrix[0];
    res->y =x*m->matrix[0] + y*m->matrix +z*m->matrix
 + m->matrix;
    res->z =x*m->matrix[0] + y*m->matrix +z*m->matrix
 + m->matrix;
    res->w =x*m->matrix[0] + y*m->matrix +z*m->matrix
 + m->matrix;
}

可见确实如上所述,“OPENGL列向量和矩阵的每一列相乘,仍然表示线性代数行向量和矩阵的每一行相乘”
再来看一下opengl 矩阵相乘,“用a的每一列去乘b的每一行”。


void FASTCALL __glMultMatrix(__GLmatrix *r, const __GLmatrix *a,const __GLmatrix *b)
{
    __GLfloatb00, b01, b02, b03;
    __GLfloatb10, b11, b12, b13;
    __GLfloatb20, b21, b22, b23;
    __GLfloatb30, b31, b32, b33;
    GLint i;

    b00 =b->matrix[0][0]; b01 = b->matrix[0];
       b02 = b->matrix[0]; b03 = b->matrix[0];
    b10 =b->matrix[0]; b11 = b->matrix;
       b12 = b->matrix; b13 = b->matrix;
    b20 =b->matrix[0]; b21 = b->matrix;
       b22 = b->matrix; b23 = b->matrix;
    b30 =b->matrix[0]; b31 = b->matrix;
       b32 = b->matrix; b33 = b->matrix;

    for (i =0; i < 4; i++) {
 r->matrix[0] = a->matrix[0]*b00 +a->matrix*b10
    + a->matrix*b20 + a->matrix*b30;
 r->matrix = a->matrix[0]*b01 +a->matrix*b11
    + a->matrix*b21 + a->matrix*b31;
 r->matrix = a->matrix[0]*b02 +a->matrix*b12
    + a->matrix*b22 + a->matrix*b32;
 r->matrix = a->matrix[0]*b03 +a->matrix*b13
    + a->matrix*b23 + a->matrix*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维的columnvector构成的column matrix。
其中,X[n*1]/X [m*1]就等价于1个n/m维的columnvector。X[1*n]/X [1*m]就等价于1个n/m维的row vector。
Row matrix和Columnmatrix只是两种不同的表示法,前者表示把一个向量映射到矩阵的一行,后者表示把一个向量映射到矩阵的一列。
本质上体现的是同一线性变换。矩阵运算规定了它们可以通过转置运算来改变这个映射关系。

3。矩阵的相乘顺序:前乘或左乘 vs. 后乘或右乘

需要注意的是两种不同的表示法对应不同的运算顺序:
如果对一个column vector做变换,则变换矩阵(rowmatrix/vectors)必须出现在乘号的左边,即pre-multiply,又叫前乘或左乘。
如果对一个row vector做变换,则变换矩阵(columnmatrix/vectors)必须出现在乘号的右边,即post-multiply,又叫后乘或右乘。
一般不会弄错,因为矩阵乘法性质决定了相同的内维数的矩阵才能相乘。至于为什么是这个规律,为什么要row vector乘以columnvector或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来表示的,所以对应的变换矩阵是ColumnMatrix/Vectors,而OGL中,点/向量是用Column Vector来表示的,所以对应的变换矩阵是RowMatrix/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:

/Matrix.html

/community/forums/topic.asp?topic_id=

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多