五、OpenGL基础变换与矩阵栈

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 优化矩阵栈操作

不同的实现方式

之前我们用cameraFrameobjectFrame来记录相机和模型的变化,用到了两个对象。但我们可以固定一个对象,变化另一个对象。

例如,之前的做法需要观察者向后退,同时物体旋转,把变化作用到了两个物体上,所以用到了两个矩阵分别记录两个物体的变化,最后再使用矩阵相乘把两个变化合并起来。

如果固定一个物体,那么根据相对运动,就是把之前两个物体的变化,相对地作用到另一个物体上,就可以少用一个矩阵了。

下面我们看看代码如何实现:

  • 目前的做法

    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();
    }
    

    又因为GLMatrixStackPushMatrix(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 矩阵栈过程

矩阵栈过程

上面就是矩阵栈的操作过程,其中:

  • 入栈,是为了保存当前的矩阵栈状态
  • 出栈,是为了恢复入栈前的矩阵栈状态

这个操作类似于iOSCore Graphiccontext的保存与恢复。

//保存
void CGContextSaveGState(CGContextRef c);
//恢复
void CGContextRestoreGState(CGContextRef c);

你可能感兴趣的:(五、OpenGL基础变换与矩阵栈)