1. 基础变换
1.1 平移
1.2 旋转
1.3 缩放
1.4 组合变换
平移和旋转
对比上面2个变换,我们可以发现:在组合变换中,变换的顺序是不可以随意修改的。
- 数学分析:这里分析2D变换。
//先旋转再平移
┏ cosθ sinθ 0 ┓┏ 1 0 0 ┓
[X, Y, 1] = [x, y, 1]┃ -sinθ cosθ 0 ┃┃ 0 1 0 ┃
┗ 0 0 1 ┛┗ dx dy 1 ┛
//先平移再旋转
┏ 1 0 0 ┓┏ cosθ sinθ 0 ┓
[X, Y, 1] = [x, y, 1]┃ 0 1 0 ┃┃ -sinθ cosθ 0 ┃
┗ dx dy 1 ┛┗ 0 0 1 ┛
这个的问题本质原因是矩阵的乘法不满足交换律。
1.5 代码示例
- 先移动再旋转
GLfloat xPos = 0.0f;
GLfloat yPos = 0.0f;
//红色
GLfloat vRed[] = {1.0,0.0,0,1};
void RenderScene(void)
{
//清除屏幕、深度缓存区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//1.建立基于时间变化的动画
static CStopWatch rotTimer;
//当前时间 * 60s
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
//2.矩阵变量
/*
mView: 平移
mModel: 旋转
mModelView: 模型视图
mModelViewProjection: 模型视图投影MVP
*/
M3DMatrix44f mView, mModel, mModelView, mModelViewProjection;
//mModel旋转矩阵,绕y轴旋转yRot度
m3dRotationMatrix44(mModel, m3dDegToRad(yRot), 0.0f, 1.0f, 0.0f);
//mView平移矩阵,沿z轴移动-2.5
m3dTranslationMatrix44(mView, 0.0f, 0.0f, -2.5f);
//mModelview = mView * mModel
m3dMatrixMultiply44(mModelview, mView, mModel);
//mModelViewProjection = ProjectionMatrix * mView * mModel
m3dMatrixMultiply44(mModelViewProjection,
viewFrustum.GetProjectionMatrix(),
mModelview);
GLfloat vBlack[] = { 0.0f, 0.0f, 0.0f, 1.0f };
shaderManager.UseStockShader(GLT_SHADER_FLAT,
mModelViewProjection,
vBlack);
torusBatch.Draw();
glutSwapBuffers();
glutPostRedisplay();
}
2. 矩阵栈
我们在矩阵储存时可能用到以下实例:
GLMatrixStack modelViewMatrix;
GLMatrixStack projectionMatrix;
GLFrame cameraFrame;
GLFrame objectFrame;
其中,cameraFrame
用于存储观察者矩阵,objectFrame
用于存储模型矩阵。projectionMatrix
只用于存储投影矩阵,我们操作最多的是modelViewMatrix
。
- 模型矩阵绕世界坐标系y轴旋转-5.0度
objectFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
- 观察者矩阵向后退15.0
//GLFrame中默认的朝向是z轴的负方向,即(0.0, 0.0, -1.0)
//向前走-15.0,即(0.0, 0.0, -1.0 * -15.0) = (0.0, 0.0, 15.0)
cameraFrame.MoveForward(-15.0f);
- 渲染过程中矩阵栈操作,获取MVP矩阵的计算结果
//压栈
modelViewMatrix.PushMatrix();
//获取观察者矩阵
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
//栈顶矩阵乘以传入矩阵,相乘的结果简存储在栈顶
modelViewMatrix.MultMatrix(mCamera);
//获取模型矩阵
M3DMatrix44f mObjectFrame;
objectFrame.GetMatrix(mObjectFrame);
//由于先乘的观察者矩阵,现在再乘以模型矩阵
//栈顶 = M_view * M_model
modelViewMatrix.MultMatrix(mObjectFrame);
...
//由于初始化时传入了,modelViewMatrix和projectionMatrix的引用
//transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
//下面的代码会计算projectionMatrix * modelViewMatrix
//结合上面的代码,等价于M_projection * M_view * M_model
transformPipeline.GetModelViewProjectionMatrix()
...
//出栈
modelViewMatrix.PopMatrix();
2.1 优化矩阵栈操作
之前我们用cameraFrame
和objectFrame
来记录相机和模型的变化,用到了两个对象。但我们可以固定一个对象,变化另一个对象。
例如,之前的做法需要观察者向后退,同时物体旋转,把变化作用到了两个物体上,所以用到了两个矩阵分别记录两个物体的变化,最后再使用矩阵相乘把两个变化合并起来。
如果固定一个物体,那么根据相对运动,就是把之前两个物体的变化,相对地作用到另一个物体上,就可以少用一个矩阵了。
下面我们看看代码如何实现:
-
目前的做法
void SetupRC() { ... // 相机向z轴正方向移动2.5 cameraFrame.MoveForward(-2.5f); } void RenderScene(void) { ... // 模型每次刷新时多转-1.0度 objectFrame.RotateWorld(m3dDegToRad(-1.0), 0.0f, 1.0f, 0.0f); // 入栈 modelViewMatrix.PushMatrix(); // 获取观察者矩阵 M3DMatrix44f mCamera; cameraFrame.GetCameraMatrix(mCamera); modelViewMatrix.MultMatrix(mCamera); // 获取模型矩阵 M3DMatrix44f mModelview; objectFrame.GetMatrix(mModelview); modelViewMatrix.MultMatrix(mModelview); ... // 出栈 modelViewMatrix.PopMatrix(); // 交换缓冲区,并立即刷新 glutSwapBuffers(); glutPostRedisplay(); }
-
只使用
objectFrame
void SetupRC() { ... // 相对运动 // 模型向z轴负方向移动2.5 objectFrame.MoveForward(2.5f); } void RenderScene(void) { ... // 模型每次刷新时多转-1.0度 objectFrame.RotateWorld(m3dDegToRad(-1.0), 0.0f, 1.0f, 0.0f); // 入栈 modelViewMatrix.PushMatrix(); // 获取模型矩阵 M3DMatrix44f mModelview; objectFrame.GetMatrix(mModelview); modelViewMatrix.MultMatrix(mModelview); ... // 出栈 modelViewMatrix.PopMatrix(); // 交换缓冲区,并立即刷新 glutSwapBuffers(); glutPostRedisplay(); }
又因为
GLMatrixStack
的 PushMatrix(GLFrame& frame) 方法,可以一次解决多行代码:void PushMatrix(GLFrame& frame) { M3DMatrix44f m; frame.GetMatrix(m); PushMatrix(m); }
使用上面的API可以让代码更简洁:
void SetupRC() { ... // 相对运动 // 模型向z轴负方向移动2.5 objectFrame.MoveForward(2.5f); } void RenderScene(void) { ... // 模型每次刷新时多转-1.0度 objectFrame.RotateWorld(m3dDegToRad(-1.0), 0.0f, 1.0f, 0.0f); // 入栈模型矩阵 modelViewMatrix.MultMatrix(objectFrame); ... // 出栈 modelViewMatrix.PopMatrix(); // 交换缓冲区,并立即刷新 glutSwapBuffers(); glutPostRedisplay(); }
2.1 矩阵栈过程
上面就是矩阵栈的操作过程,其中:
- 入栈,是为了保存当前的矩阵栈状态
- 出栈,是为了恢复入栈前的矩阵栈状态
这个操作类似于iOS
的Core Graphic
中context
的保存与恢复。
//保存
void CGContextSaveGState(CGContextRef c);
//恢复
void CGContextRestoreGState(CGContextRef c);