矩陣及變換,以及矩陣在DirectX和OpenGL中的運用問題:左乘/右乘,行優先/列優先,...

(一)首先,無論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

發佈了15 篇原創文章 · 獲贊 8 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章