从零开始学习OpenGL ES之七 – 变换和矩阵
?
今天的主题是我一度谈之色变的。概念上讲,它是3D编程中最为困难的部分。
既然我们有将矩阵相乘的能力,我们就可以合并多个矩阵。由于我们的矩阵相乘的方法是硬件加速的,而OpenGL ES不支持矩阵与矩阵相乘的硬件加速,所以我们的方法应该比内嵌的变换方法要快一些3?。我们现在开始进行转移变换。
我们可以将其转换为函数:
static inline void Matrix3DSetTranslation(Matrix3D matrix, GLfloat xTranslate,
??? GLfloat yTranslate, GLfloat zTranslate)
{
??? matrix[0] = matrix[5] =? matrix[10] = matrix[15] = 1.0;
??? matrix[1] = matrix[2] = matrix[3] = matrix[4] = 0.0;
??? matrix[6] = matrix[7] = matrix[8] = matrix[9] = 0.0;
??? matrix[11] = 0.0;
??? matrix[12] = xTranslate;
??? matrix[13] = yTranslate;
??? matrix[14] = zTranslate;
}
现在,我们怎样将其引入到 drawView: 方法中? 我们可以删除 glTranslatef(),替换为定义另一个矩阵并赋予适当的转移值,然后将其与当前矩阵相乘最后将结果加载到OpenGL的代码,对吗?
??? static Matrix3D??? identityMatrix;
??? Matrix3DSetIdentity(identityMatrix);
??? static Matrix3D??? translateMatrix;
??? Matrix3DSetTranslation(translateMatrix, 0.0, yPos, -3.0);
??? static Matrix3D??? resultMatrix;
??? Matrix3DMultiply(identityMatrix, translateMatrix, resultMatrix);
??? glLoadMatrixf(resultMatrix);
是的,这可以工作,但它做了一些不必要的工作。记住,如果你将矩阵与单元矩阵相乘,其结果一定是矩阵本身。所以当使用自定义矩阵时,如果要进行任何变换,我们不再需要首先加载单元矩阵。我们只需要创建变换矩阵并加载它:
??? static Matrix3D??? translateMatrix;
??? Matrix3DSetTranslation(translateMatrix, 0.0, yPos, -3.0);
??? glLoadMatrixf(translateMatrix);
由于不需要加载单元矩阵,此方法可以省去一些工作。另外,注意我将Matrix3D定义为static。我们不希望经常分配和解除内存分配。我们知道当程序运行时每一秒钟都需要用到此矩阵许多次,所以将其定义为static可以省去分配和解除内存分配的开销。

x, y, 或 z 为1.0表示在相应方向上尺寸无变化。3个值都为1.0代表单元矩阵。如果你赋值为2.0,则物在相应轴上尺寸加倍。我们可以将尺寸变换矩阵转化为OpenGL ES矩阵,像这样:
static inline void Matrix3DSetScaling(Matrix3D matrix, GLfloat xScale,
??? GLfloat yScale, GLfloat zScale)
{
??? matrix[1] = matrix[2] = matrix[3] = matrix[4] = 0.0;
??? matrix[6] = matrix[7] = matrix[8] = matrix[9] = 0.0;
??? matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
??? matrix[0] = xScale;
??? matrix[5] = yScale;
??? matrix[10] = zScale;
??? matrix[15] = 1.0;
}
现在,因为我们需要使用超过一种变换,我们将使用矩阵乘法。要进行尺寸变换和旋转,我们需要将这两个矩阵相乘。删除先前代码中的 glScalef() 替换为下列代码:
??? static Matrix3D??? translateMatrix;
??? Matrix3DSetTranslation(translateMatrix, 0.0, yPos, -3.0);
??? static Matrix3D??? scaleMatrix;
??? Matrix3DSetScaling(scaleMatrix, scale, scale, scale);
??? static Matrix3D???? resultMatrix;
??? Matrix3DMultiply(translateMatrix, scaleMatrix, resultMatrix);
??? glLoadMatrixf(resultMatrix);
我们创建一个矩阵并赋予其适当的转移值。然后创建尺寸变换矩阵并赋值。然后将这两个矩阵相乘将结果加载到模型视口矩阵中。
?

绕X轴像这样:

绕 Y轴旋转:

这三种旋转写成函数:
static inline void Matrix3DSetXRotationUsingRadians(Matrix3D matrix, GLfloat degrees){ matrix[0] = matrix[15] = 1.0; matrix[1] = matrix[2] = matrix[3] = matrix[4] = 0.0; matrix[7] = matrix[8] = 0.0; matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0; matrix[5] = cosf(degrees); matrix[6] = -fastSinf(degrees); matrix[9] = -matrix[6]; matrix[10] = matrix[5];}static inline void Matrix3DSetXRotationUsingDegrees(Matrix3D matrix, GLfloat degrees){ Matrix3DSetXRotationUsingRadians(matrix, degrees * M_PI / 180.0);}static inline void Matrix3DSetYRotationUsingRadians(Matrix3D matrix, GLfloat degrees){ matrix[0] = cosf(degrees); matrix[2] = fastSinf(degrees); matrix[8] = -matrix[2]; matrix[10] = matrix[0]; matrix[1] = matrix[3] = matrix[4] = matrix[6] = matrix[7] = 0.0; matrix[9] = matrix[11] = matrix[13] = matrix[12] = matrix[14] = 0.0; matrix[5] = matrix[15] = 1.0;}static inline void Matrix3DSetYRotationUsingDegrees(Matrix3D matrix, GLfloat degrees){ Matrix3DSetYRotationUsingRadians(matrix, degrees * M_PI / 180.0);}static inline void Matrix3DSetZRotationUsingRadians(Matrix3D matrix, GLfloat degrees){ matrix[0] = cosf(degrees); matrix[1] = fastSinf(degrees); matrix[4] = -matrix[1]; matrix[5] = matrix[0]; matrix[2] = matrix[3] = matrix[6] = matrix[7] = matrix[8] = 0.0; matrix[9] = matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0; matrix[10] = matrix[15] = 1.0;}static inline void Matrix3DSetZRotationUsingDegrees(Matrix3D matrix, GLfloat degrees){ Matrix3DSetZRotationUsingRadians(matrix, degrees * M_PI / 180.0);}对于各轴的旋转有两种方法,一种是使用弧度,另一种是使用角度。这三个矩阵被称为Eular angle?。有一个问题是我们必须按顺序对多个轴进行旋转,当我们对这三个角度进行旋转时,我们可能会遇到一种被称作gimbal lock(框架自锁)的现像,它导致其中一个轴的旋转被锁定。为防止这种现象的发生,我们必须创建一个可以处理多轴旋转的矩阵。除了可以消除自锁的问题,它还能在物体需要绕多轴旋转时节省处理器的开销。
此矩阵要求向量是以单元向量(即法线)方式被传递的,所以我们在为矩阵赋值前必须保证这点。表示为OpenGL矩阵,像这样:
static inline void Matrix3DSetRotationByRadians(Matrix3D matrix, GLfloat angle, GLfloat x, GLfloat y, GLfloat z){ GLfloat mag = sqrtf((x*x) + (y*y) + (z*z)); if (mag == 0.0) { x = 1.0; y = 0.0; z = 0.0; } else if (mag != 1.0) { x /= mag; y /= mag; z /= mag; } GLfloat c = cosf(angle); GLfloat s = fastSinf(angle); matrix[3] = matrix[7] = matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0; matrix[15] = 1.0; matrix[0] = (x*x)*(1-c) + c; matrix[1] = (y*x)*(1-c) + (z*s); matrix[2] = (x*z)*(1-c) - (y*s); matrix[4] = (x*y)*(1-c)-(z*s); matrix[5] = (y*y)*(1-c)+c; matrix[6] = (y*z)*(1-c)+(x*s); matrix[8] = (x*z)*(1-c)+(y*s); matrix[9] = (y*z)*(1-c)-(x*s); matrix[10] = (z*z)*(1-c)+c;}static inline void Matrix3DSetRotationByDegrees(Matrix3D matrix, GLfloat angle, GLfloat x, GLfloat y, GLfloat z){ Matrix3DSetRotationByRadians(matrix, angle * M_PI / 180.0, x, y, z);}下面是代码:
static inline void Matrix3DSetShear(Matrix3D matrix, GLfloat xShear, GLfloat yShear){ matrix[0] = matrix[5] = matrix[10] = matrix[15] = 1.0; matrix[1] = matrix[2] = matrix[3] = 0.0; matrix[6] = matrix[7] = matrix[8] = matrix[9] = 0.0; matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0; matrix[1] = xShear; matrix[4] = yShear;}如果我们应用剪切矩阵,我们将得到:

尝试一下用内嵌函数实现。你可以组合矩阵调用。例如,创建一个函数不需要任何矩阵乘法生成转移和尺寸变换。
矩阵是一个很大而且经常让人误解的课题,许多人(包括我自己)都在殚精竭虑去理解它。希望本文能给你足够的帮助,而且还提供了一个矩阵相关的库,你可以用在自己的项目中。如果你希望下载自己尝试一下,请下载feePart6Projectl。我定义了两个常量,一个用来打开/关闭自定义变换,另一个用来打开/关闭剪切变换。它们在GLViewController.h?中:
#define USE_CUSTOM_MATRICES 1#define USE_SHEAR_TRANSFORM 1
1为打开,0为关闭。另外我还更新了?OpenGL ES Xcode TemEmpty%20OpenGL%20ES%20Application?项目使其包括了新的矩阵函数,包括向量化矩阵乘法函数。如果你无法完全消化,你也无需担心。对于开发OpenGL ES程序的99%的人来说,你不需要完全理解透视空间,同质坐标或线性变换,只需大体上的了解即可。
注脚