關於OpenGL和OSG的矩陣

 

矩陣真的是一個很神奇的數學工具, 雖然單純從數學上看, 它並沒有什麼特別的意義, 但一旦用到空間中的座標變換,它就“一遇風雲便成龍”, 大顯神威了。簡單的工具實現了複雜的功能,便預示着要理解它我們還是要花上點功夫的。下面就簡單介紹一下OpenGL中的轉換矩陣。

1 轉換矩陣的原理
OpenGL中的轉換矩陣是這樣定義的:
              [Xx, Yx, Zx, Tx]
             [Xy, Yy, Zy, Ty]
M   =      [Xz, Yz, Zz, Tz]
              [0, 0, 0, 1 ]

其實我們可以這麼理解這個變換矩陣, 它表示了一個局部座標系, 這個局部座標系,是把世界座標系的原點移到(Tx, Ty, Tz),把X軸轉到(Xx, Xy, Xz), Y軸轉到(Yx, Yy, Yz),Z軸轉到(Zx, Zy, Zz)而形成的。用它來變換一個世界座標系中的點V, 就是得到這個局部座標系中的點。
要證明這一點很容易, 我們從可以從更通用的方面來考慮,假設我們用矩陣Ma來表示座標系a, Mb來表示座標系b, Mt表示從a到b的轉換, 那麼:
Mt * Ma = Mb
Mt * Ma * (Ma)^-1 = Mb * (Ma)^-1
矩陣雖然不符合乘法交換律,但其符合乘法結合律, 於是:
Mt* (Ma * (Ma)^-1) = Mb * (Ma)^-1
Mt = Mb * (Ma)^-1
這就是a到b轉換矩陣的表達式,現在我們從世界座標系轉換到局部座標系,a表示的世界座標系是個單位矩陣,所以:
Mt = Mb
即局部座標系的矩陣表示就是從世界座標系到局部座標系的轉換矩陣。

我們再進一步分析,如果我們用這個矩陣來變換一個點V(Vx, Vy, Vz, 1),需要把這個點右乘變換矩陣

                        [Xx, Yx, Zx, Tx]   [Vx]
                       [Xy, Yy, Zy, Ty]   [Vy]
V' = M*T =       [Xz, Yz, Zz, Tz] * [Vz]
                       [0, 0, 0, 1   ]   [1 ]

對於V變換後的x分量,Vx' = Xx*Vx + Yx*Vy + Zx*Vz + Tx,我們可以發現影響V的x分量的只有X,Y,Z軸旋轉的x分量和平移的x分量,對於V的y, z分量也是同樣道理。

2 行主序, 列主序
OpenGL中推薦用一維數組來表示此轉換矩陣 : typedef GLfloat Matrix16[16];
爲了能快速的訪問X軸, Y軸, Z軸, 該數組是按列主序來表示這個矩陣的:
[m0, m4, m8, m12]
[m1, m5, m9, m13]
[m2, m6, m10,m14]
[m3, m7, m11,m15]
這樣, 爲了訪問X軸, 即訪問m0, m1, m2,因爲他們是連續的存儲空間,所以速度比較快, 相反, 如果我們數組按行主序來表示這個矩陣:
[m0, m1, m2, m3 ]
[m4, m5, m6, m7 ]
[m8, m9, m10, m11]
[m12, m13, m14, m15]
我們發現爲了訪問X軸, 即m0, m4, m8, 是不連續的地址, 因此速度就慢了下來。
所以我們可以知道, OpenGL爲什麼採用列主序的矩陣, 那是因爲其所定義的轉換矩陣如果按列主序存入數組, 我們對X,Y,Z軸就可以有較快的訪問速度。也就是說, 如果我非要把這個矩陣按列主序的方式存入數組也可以, 只不過速度慢了點而已。(當然, 我們要告訴OpenGL我們是按行主序表示的)。

其實, 如果我們換一種方式來表示轉換矩陣:
                [Xx, Xy, Xz, 0]
                [Yx, Yy, Yz, 0]
M' =         [Zx, Zy, Zz, 0]
                [Tx, Ty, Tz, 1]

這個矩陣是是前一個轉換矩陣的轉置,我們把這個矩陣按行主序存入數組就比較划算了。原因很明顯, 爲了快速訪問X軸,我們希望Xx, Xy, Xz是連續存儲的, 那麼自然要按行存儲了。

其實, 如果讓我設計OpenGL,我會選擇用第二種方式來表示轉換矩陣,原因如下:
如果我要轉換一個點V, 依次經過三個轉換矩陣L, M, N的轉換, 那麼對於第一種方式:
V' = N*(M*(L*V)) = (N*M*L) * V
我們的組合轉換矩陣是N*M*L, 與我們定義的轉換過程剛好相反, 但是, 如果我們是第二種方式表示的話,我轉換一個點是左乘轉換矩陣而不是右乘了:
V' = ((V*L)*M)*N = V * (L*M*N)
組合轉換矩陣是按我們變換的順序組合起來的, 就比較直觀了, 然後我們按行主序存儲此矩陣, 速度依然。

3 二維數組存儲矩陣
很多人有這樣錯誤的認識, 就是在OpenGL中如果用二維數組來表示轉換矩陣, 速度就比較慢, 而這種認識或多或少源於<<OpenGL超級寶典>>中的闡述。但是, 事實是這樣嗎?
二維數組如下:
typedef GLfloat Matrix44[4][4];

按我們理解的,邏輯上的二維數組, 其表示爲:
[m00, m01, m02, m03]
[m10, m11, m12, m13]
[m20, m21, m22, m23]
[m30, m31, m32, m33]
因爲這個邏輯模型, 導致我們產生那種錯誤的認識:
X軸是用m00, m10, m20表示的, 而他們是不連續的, 所以比較慢, 但是, 這只是其邏輯模型, 如果按邏輯模型去理解的話, 一維數組的邏輯模型是:
[m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15]
那我們是不是可以說, 一維數組根本不能用來表示矩陣? 當然不是。
其實, 不論是一維數組還是二維數組, 其在內存中的物理模型都是連續的16個float型的內存單元:
一維數組:[m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15]
二維數組:[m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33]
看到這裏, 既然一維數組可以用列主序表示並很快, 爲什麼二維數組就不快了呢?他們除了訪問時的名字不一樣, 本質上並沒有區別啊:
[m00, m10, m20, m30]
[m01, m11, m21, m31]
[m02, m12, m22, m32]
[m03, m13, m23, m33]
我們可以看到,二維數組按列主序表示的轉換矩陣是這樣的, 訪問X軸即訪問m00, m01, m02, 連續的, 一樣快。
只不過, 這種表示方式和我們所理解的二維數組的邏輯模型不太統一, 有些不直觀罷了。這一點在OpenGL紅寶書的說的比較正確:二維數組的元素m[i][j]將位於OpenGL變換矩陣的第i列, 第j行, 因此容易產生行列混淆,爲了避免行列混淆, 推薦用一維數組表示。 真正的原因是爲了避免行列混淆, 而不是速度。

發現OSG對矩陣的存儲和矩陣變換的使用方式與OpenGL的用法有些不一致:

1. 在OpenGL中使用glMultMatrix/glLoadMatrix 設置矩陣時,參數矩陣需要是列主序存儲的;而OSG中的矩陣(Matrixd)卻是按行主序存儲的(仍然使用glMultMatrix/glLoadMatrix 設置矩陣),二者互爲轉置。

2. 紅寶書中講到,OpenGL中對頂點座標應用矩陣變換,應該是左乘矩陣(v' = M × v );然而,我在OSG中計算頂點座標投影的視口座標的時候,卻需要使用右乘才能得到正確的結果,計算裁剪座標的代碼如下:
       osg::Matrix matMVP = view->getCamera()->getViewMatrix() * view->getCamera()->getProjectionMatrix();
       v = matMVP.preMult(v);       // 右乘矩陣
即 OpenGL中的矩陣變換爲:矩陣×列向量
    OSG中的矩陣變換爲:行向量×矩陣
如果對於同一個向量v(x,y,z,w)(可以作爲行向量也可以作爲列向量),應用同一個矩陣變換M,使用左乘和使用右乘得到的結果顯然是不同的;

單獨考慮以上的兩點,似乎都是不可理解的
但是考慮以下情況的結果:
         [x,y,z,w]×[m0,m1,m2...m15](行主序)   與   [m0,m1,m2...m15](列主序)×[x,y,z,w]
      (或者寫作:   v×M   與   MT×v   (MT爲M的轉置))
二者的結果是相同的(雖然得到的一個是行向量,一個是列向量),這似乎能夠爲解釋以上兩點差異提供一些支持

以上內容是我學習OSG/OPENGL矩陣變換是的一點理解,似乎是收穫了一些東西,但又沒有能夠穿起來的感覺
希望哪位能夠不吝給我點點播,也歡迎大家加入討論,共同學習共同進步

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