概述
OpenGL固定功能管線提供4個不同類型的矩陣(GL_MODELVIEW、GL_PROJECTION、GL_TEXTURE與GL_COLOR),並且為這些矩陣提供變換函數:glLoadIdentity()、glTranslatef()、glRotatef()、glScalef()、glMultiMatrixf()、glFrustum()與glOrtho()。
這些內置矩陣與函數對於開發簡單的OpenGL程序與理解矩陣變換很有幫助。不過,一旦你的應用程序變得復雜起來,更好的選擇是為每一個可移動模型對象管理自己的矩陣實現。此外,在OpenGL可編程管線(GLSL)中,你不能夠使用這些內置矩陣函數。如OpenGL v3.0+、OpenGL ES v2.0+與WebGL v1.0+。你必須擁有屬於自己的矩陣實現,並且向OpenGL着色語言傳遞該矩陣數據。
該篇文章提供一個獨立運行的、通用4×4矩陣類----C++寫的Matrix4,並且描述如何將該矩陣類整合到OpenGL程序中。該矩陣類只依賴於相似的矩陣:定義於Vector.h的Vector3與Vector4。這些向量也包含在matrix.zip。
Matrix4創建&初始化

Matrix4使用行優先

OpenGL使用列優先
Matrix4類包含用於存放4×4方陣的16個元素的浮點數據類型數組,同時擁有3個構造函數用以實例化Matrix4類對象。
注意,Matrix4類使用行優先標記法,而不是OpenGL使用的列優先順序。不過,行優先與列優先順序只是將多維數組保存在線性(1D)內存中的不同方式,矩陣算法與操作並沒有不同。
使用默認構造函數(不帶任何參數的),Matrix4對象創建一個單位矩陣。另外3個構造函數使用16個參數或一個具有16個元素的數組。你也可以使用拷貝構造函數與賦值操作符(=)初始化Matrix4對象。
順便說一下,Matrix4對象的拷貝構造函數與賦值構造函數將由C++編譯器自動為你產生。
下面為以多種方式構造Matrix4對象的示例代碼。首先,在使用Matrix4.h類的代碼中包含Matrices.h文件。
#include "Matrices.h" // 為了使用 Matrix2, Matrix3, Matrix4 .. // 使用默認構造函數創建一個單位矩陣 Matrix4 m1; // 使用16個元素創建一個矩陣 Matrix4 m2(1, 1, 1, 1, // 第一行 1, 1, 1, 1, // 第二行 1, 1, 1, 1, // 第三行 1, 1, 1, 1); // 第四行 // 使用數組創建一個矩陣 float a[16] = {2,2,2,2, 2,2,2,2, 2,2,2,2, 2,2,2,2}; Matrix4 m3(a); // 使用構造函數創建一個矩陣 Matrix4 m4(m3); Matrix4 m5 = m3; ...
Matrix4存取器(賦值與取值)
賦值
Matrix4類提供set()方法賦值所有16個元素。
Matrix4 m1; // 使用16個浮點數給矩陣賦值 m1.set(1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1); // 使用數組給矩陣賦值 float a1[] = {2,2,2,2, 2,2,2,2, 2,2,2,2, 2,2,2,2}; m1.set(a1);
你也可以每次使用setRow()或setColumn()設置行或列元素。setRow()與setColumn()的第一個參數以0為基准的(0,1,2或3)。第二個參數為數組指針。
Matrix4 m2; float a2[4] = {2,2,2,2}; // 使用行索引與數組設置一行 m2.setRow(0, a2); // 第一行 // 使用列索引與數組設置一列 m2.setColumn(2, a2); // 第三列
單位矩陣
Matrix4類擁有一個特殊的賦值函數identity()以創建一個單位矩陣。
// 設置單位矩陣 Matrix4 m3; m3.identity();
獲取
Matrix4::get()方法返回16個元素數組的指針。getTranspose()返回轉置后的矩陣元素。它可用於向OpenGL傳遞矩陣數據。更詳細信息請參考實例。
// 以數組指針方式獲取矩陣元素 Matrix4 m4; const float* a = m4.get(); // 向OpenGL傳遞矩陣 glLoadMatrixf(m4.getTranspose());
訪問單獨的元素
矩陣的獨立元素也可以通過下標操作符[]訪問。
Matrix4 m5; float f = m5[0]; // 獲取第一個元素 m5[1] = 2.0f; // 設置第2個元素
打印Matrix4
為了調試,Matrix4也提供一個使用std::ostream操作符<<的便捷打印函數。
// 打印矩陣 Matrix4 m6; std::cout << m6 << std::endl;
矩陣運算
Matrix4提供在2個矩陣間的基本算術運算。
加法&減法
你可以進行兩個矩陣相見與相加。
Matrix4 m1, m2, m3; // 加法 m3 = m1 + m2; m3 += m1; // m3 = m3 + m2的簡潔操作 // 減法 m3 = m1 - m2; m3 -= m1; // m3 = m3 - m1的簡潔操作
乘法
你可以將2個矩陣乘在一起,也可以進行3D/4D向量的乘法,用於使用矩陣對向量進行變換。注意,矩陣乘法不可交換。
Matrix4 m1, m2, m3; // 矩陣乘法 m3 = m1 * m2; m3 *= m1; // m3 = m3 * m1的簡潔寫法 // 標量乘法 m3 = 2 * m1; // 縮放所有元素 // 向量乘法 Vector3 v1, v2; v2 = m1 * v1; v2 = v1 * m1; // 前向乘
比較
Matrix4類提供比較2個矩陣所有元素的操作。
Matrix4 m1, m2; // 絕對比較 if(m1 == m2) std::cout << "equal" << std::endl; if(m1 != m2) std::cout << "not equal" << std::endl;
矩陣變換函數
OpenGL為矩陣變換提供許多函數:glTranslatef()、glRotatef()與glScalef()。Matrix4類提供變換矩陣的等價函數:translate()、rotate()與scale()。此外,Matrix4提供invert()用於計算逆矩陣。
Matrix4::translate(x,y,z)
translate()將當前矩陣平移(x,y,z)。首先,它生成一個平移矩陣M,接着將它與當前矩陣對象相乘產生最終變換矩陣:M=M•M
注意,該函數等價於OpenGL的glTranslatef(),不過OpenGL使用右乘而不是左乘(平移矩陣又乘回當前矩陣。)M=M•MT。如果你進行多次變換,你會發現結果會有很明顯的不同,因為矩陣乘法是不可互換的。
對於應用多個變換,請參考“ModelView矩陣的更多實例”。
// M1 = Mt * M1 Matrix4 m1; m1.translate(1, 2, 3); // move to (x, y, z)
Matrix4::rotate(angle, x, y, z)
rotate()用於將3D模型繞軸(x、y、z)旋轉任意度數(角度)。該函數生成一旋轉矩陣MR,然后將它與當前矩陣對象相乘,生成最終旋轉變換矩陣:M=MR•M。
它等價於glRotatef(),使用右乘生成最終變換矩陣:M=M•MR
rotate()可以在任意軸上旋轉。Matrix4類提供額外的3個函數進行基准軸旋轉操作:rotateX()、rotateY()與rotateZ()。
// M1 = Mr * M1 Matrix4 m1; m1.rotate(45, 1,0,0); // 繞X軸旋轉45度 m1.rotateX(45); // 與rotate(45, 1, 0, 0)相同
Matrix4::scale(x,y,z)
scale()通過將當前矩陣對象與縮放矩陣相乘:M=Ms•M,在軸(x,y,z)上生成一個不均勻的縮放變換矩陣。
再次注意,OpenGL的glScalef()執行右乘:M=M•Ms。
Matrix4類也提供均勻縮放函數。
// M1 = Ms * M1 Matrix4 m1; m1.scale(1,2,3); // 非均勻縮放 m1.scale(4); // 均勻縮放 (所有軸都一樣)
Matrix::invert()
invert()函數計算當前矩陣的逆矩陣。該你矩陣通常用於將法向量從模型對象空間變換到觀察空間。法向量與頂點的變換不同。法向量通過乘以GL_MODELVIEW矩陣的逆矩陣變換到觀察空間:n'=nTM-1 = (M-1)Tn。詳細解釋參考這里。
如果矩陣僅僅為歐氏變換(旋轉與平移)或仿射變換(再加上縮放與剪切),逆矩陣的變換就非常簡單。Matrix4::invert()將判斷合適的求逆方法,不過你可以調用更具體的求逆函數:invertEuclidean()、invertAffine()、invertProjective()或invertGeneral()。請參考Matrices.cpp中的詳細描述。
Matrix4 m1; m1.invert(); // 矩陣求逆
實例:模型視圖矩陣
下載源文件與二進制文件:matrix.zip。
該實例展示如何將Matrix4類應用到OpenGL中。GL_MODELVIEW矩陣是視圖矩陣與模型矩陣的組合,不過我們單獨保存它們,並且在需要時向OpenGL的GL_MODELVIEW傳遞二者的乘積。
Matrix4 matModel, matView, matModelView; glMatrixMode(GL_MODELVIEW); ... // orbital camera (view) matView.identity(); // 變換順序 matView.rotate(-camAngleY, 0,1,0); // 1: 繞Y軸旋轉 matView.rotate(-camAngleX, 1,0,0); // 2: 繞X軸旋轉 matView.translate(0, 0, -camDist); // 3: 沿Z軸移動 // 模型變換:繞Y軸旋轉45度,然后向上移動2個單位 matModel.identity(); matModel.rotate(45, 0,1,0); // 第一次變換 matModel.translate(0, 2, 0); // 第二次變換 // 生成模型視圖矩陣: Mmv = Mv * Mm matModelView = matView * matModel; // 在繪制之前傳給OpenGL // 注意: 需要進行轉置操作 glLoadMatrixf(matModelView.getTranspose()); // 繪制 ...
等價OpenGL實現如下。結果與上面的相同
//注意: 變換順序相反,因為OpenGL使用右乘 glMatrixMode(GL_MODELVIEW); glLoadIdentity(); // 相機軌跡(視圖) glTranslatef(0, 0, -camDist); // 3: 沿Z軸移動 glRotatef(-camAngleX, 1,0,0); // 2: 繞X軸旋轉 glRotatef(-camAngleY, 0,1,0); // 1: 繞Y軸旋轉 // 模型變換: 繞Y軸旋轉45度,然后向上移動2個單位 glTranslatef(0, 2, 0); // 第二次變換 glRotatef(45, 0,1,0); // 第一次變換 // 繪制 ...
模型視圖矩陣的逆矩陣用於從模型對象空間到觀察空間的法向量變換。在可編程渲染管線中,你需要向GLSL着色器傳遞它。
// 為法向量構建逆矩陣: (M^-1)^T Matrix4 matNormal = matModelView; // 獲取模型視圖矩陣 matNormal.invert(); // 獲取法向量變換的逆矩陣 matNormal.transpose(); // 轉置矩陣
實例:投影矩陣
該實例展示如何產生投影矩陣,等價於glFrustum()與glOrtho()。更詳細描述請參考源代碼。
// 設置投影矩陣並傳遞到OpenGL Matrix4 matProject = setFrustum(-1, 1, -1, 1, 1, 100); glMatrixMode(GL_PROJECTION); glLoadMatrixf(matProject.getTranspose()); ... /////////////////////////////////////////////////////////////////////////////// // glFrustum() /////////////////////////////////////////////////////////////////////////////// Matrix4 setFrustum(float l, float r, float b, float t, float n, float f) { Matrix4 mat; mat[0] = 2 * n / (r - l); mat[2] = (r + l) / (r - l); mat[5] = 2 * n / (t - b); mat[6] = (t + b) / (t - b); mat[10] = -(f + n) / (f - n); mat[11] = -(2 * f * n) / (f - n); mat[14] = -1; mat[15] = 0; return mat; } /////////////////////////////////////////////////////////////////////////////// // gluPerspective() /////////////////////////////////////////////////////////////////////////////// Matrix4 setFrustum(float fovY, float aspect, float front, float back) { float tangent = tanf(fovY/2 * DEG2RAD); // fovY的半角 float height = front * tangent; // 近平面半高 float width = height * aspect; // 近平面半寬 // 參數: left, right, bottom, top, near, far return setFrustum(-width, width, -height, height, front, back); } /////////////////////////////////////////////////////////////////////////////// // glOrtho() /////////////////////////////////////////////////////////////////////////////// Matrix4 setOrthoFrustum(float l, float r, float b, float t, float n, float f) { Matrix4 mat; mat[0] = 2 / (r - l); mat[3] = -(r + l) / (r - l); mat[5] = 2 / (t - b); mat[7] = -(t + b) / (t - b); mat[10] = -2 / (f - n); mat[11] = -(f + n) / (f - n); return mat; } ...