概述
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;
}
...