美文网首页OpenGL/ES、Metal
NO.8 - OpenGL基础变换及矩阵栈

NO.8 - OpenGL基础变换及矩阵栈

作者: z夜流星 | 来源:发表于2020-07-12 22:35 被阅读0次

1、基础变换

1.1视图变换

应用:指定观察者或照相机的位置
在默认情况下,透视投影中的观察点位于原点(0,0,0),并沿着z轴的负方向进行观察(向显示器内部“看进去”)。观察点相对于视觉坐标系进行移动,来提供有利位置。当观察点位于原点(0,0,0)时,就像在透视投影中一样,绘制在z坐标为正的位置的对象则位于观察者背后。

1.2模型变换

应用:在场景中移动物体
模型变换用于操纵模型和其中的特定对象。这些变换将对象移动到需要的位置,然后再对它们进行旋转和缩放。

1.3投影变换

应用:改变视景体的大小或重新设置它的形状
投影变换指定一个完成的场景(所有模型变换都已完成)是如何投影到屏幕上的最终图像。有正投影和透视投影两种变换方式

1.4视口变换

应用:这是一种伪变换,只是对窗口上的最终输出进行缩放
当所有变换都完成之后就得到了一个场景的二维投影,它将映射到屏幕上某处的窗口上。这种到物理窗口坐标的映射是我们最后要做的变换,称为视口变换。视口变换会将“规范化”设备坐标重新映射到窗口坐标上

2、模型视图矩阵

模型视图矩阵是一个4*4矩阵,它表示一个变换后的坐标系,我们可以用来放置对象和确定对象的方向。我们为图元提供的顶点将作为一个单列矩阵(也就是一个向量)的形式来使用,并乘以一个模型视图矩阵来获得一个相对于视觉坐标系的经过变换的新坐标。

┏ X ┓              ┏ X1 ┓
┃ Y ┃ ┏ 4 x 4 ┓ =  ┃ Y1 ┃
┃ Z ┃ ┗   M   ┛    ┃ Z1 ┃
┗ W ┛              ┗ W1 ┛

一个包含顶点数据的矩阵乘以模型视图矩阵后得到新的视觉坐标.

矩阵操作
  • 平移


    平移

    平移矩阵仅仅是将我们的顶点沿着3个坐标轴中的一个或多个进行平移。
    我们可以调用math3d库中的m3dTranslationMatrix44函数来使用变换矩阵

M3DMatrix44f m;
void m3dTranslationMatrix44(M3DMatrix44f m, float x, float y, float z);
  • 旋转


    旋转

    调用math3d库中的m3dRotationMatrix44函数来使用变换矩阵。

M3DMatrix44f m;
void m3dRotationMatrix44(M3DMatrix44f m, float angle, float x, float y, float z);
  • 缩放


    缩放

    缩放矩阵可以沿着3个坐标轴方向,按照指定因子放大或缩小所有顶点,以改变对象大小。使用math3d库创建一个缩放矩阵,方法与创建平移和旋转矩阵的方法类似

M3DMatrix44f m;
void m3dScaleMatrix44(M3DMatrix44f m, float xScale, float yScale, float zScale);
  • 组合变换
    math3d库函数m3dMatrixMultiply44用来将两个矩阵相乘并返回运算结果
void m3dMatrixMultiply44(M3DMatrix44f product, const M3DMatrix44f a, const M3DMatrix44f b);
先旋转在平移
先平移再旋转

通过上面2个变换,我们可以发现:在组合变换中,变换的顺序是不可以随意修改的。

//先旋转再平移
                     ┏ 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 ┛

通过分析2D变换得知本质原因是矩阵的乘法不满足交换律。

代码示例 - 先移动再旋转
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();

对上面代码做下解释:

1) 压栈 PushMatrix();

modelViewMatrix.PushMatrix(); 这句代码的意思是压栈,如果 PushMatix() 括号里是空的,就代表是把栈顶的阵复制一份,再压栈到它的顶部。如果不是空的,比如是括号里是单元矩阵,那么就代表压入一个单元矩阵到栈顶了

2) 矩阵相乘 MultMatrix(mObjectFrame)

将modelViewMatrix 的堆栈中的矩阵 与 mOjbectFrame 矩阵相乘,存储到modelViewMatrix矩阵堆栈中modelViewMatrix.MultMatrix(mObjectFrame);
意思是把模型视图矩阵堆栈 的 栈顶 的矩阵copy出一份来和新矩阵进行矩阵相乘,然后再将相乘的结果赋值给栈顶的矩阵

3)出栈PopMatrix()

modelViewMatrix.PopMatrix(); 把栈顶的矩阵出栈,恢复为原始的矩阵堆栈,这样就不会影响后续的操作了。

4)cameraFrame.GetCameraMatrix(mCamera)

意思是从 cameraFrame 这个 观察者坐标系 中获取矩阵,然后赋值给 mCamera。同理的还有获取 世界坐标位置 的矩阵。

矩阵栈流程图
流程图 出处简书:凡几多
  • 入栈,是为了保存当前的矩阵栈状态
  • 出栈,是为了恢复入栈前的矩阵栈状态

相关文章

网友评论

    本文标题:NO.8 - OpenGL基础变换及矩阵栈

    本文链接:https://www.haomeiwen.com/subject/tcmncktx.html