OpenGL 仿射变换原理解析

1. 引言

查阅相关资料对OpenGL仿射变换进行整理

2. 仿射变换概念

Affine Transformation是一种二维坐标到二维坐标之间的线性变换,保持二维图形的“平直性”(译注:straightness,即变换后直线还是直线不会打弯,圆弧还是圆弧)和“平行性”(译注:parallelness,其实是指保二维图形间的相对位置关系不变,平行线还是平行线,相交直线的交角不变)。

3. 仿射变换原理

仿射变换可以通过一系列的原子变换的复合来实现,包括:平移(Translation)、缩放(Scale)、翻转(Flip)、旋转(Rotation)和剪切(Shear)。

3.1 平移变换(Translation)

将每一点移动到(x+tx, y+ty),变换矩阵为:

平移变换是一种“刚体变换”,rigid-body transformation,并不会对物体产生形变。
效果:

3.2 缩放变换(Scale)

将每一点的横坐标放大(缩小)至sx倍,纵坐标放大(缩小)至sy倍,变换矩阵为:

效果:

3.3 剪切变换(Shear)

变换矩阵为:


相当于一个横向剪切与一个纵向剪切的复合:


效果:

3.4 旋转变换(Rotation)

目标图形围绕原点顺时针旋转theta弧度,变换矩阵为:


效果:

3.5 组合

旋转变换,目标图形以(x, y)为轴心顺时针旋转theta弧度,变换矩阵为:

相当于两次平移变换与一次原点旋转变换的复合:

基本流程为:先将物体移动到中心节点,然后旋转,然后再移动回去。

4. OpenGL仿射与透视变换流程

物体变化从最终显示效果的实现基本有两种实现方式:

  1. 改变物体所在位置
  2. 反向改变观察者的视角
    与之前提到的物体从对象坐标系到屏幕坐标系的计算流程相结合,我们可以理解物体仿射透视变换的具体过程。

4.1 视觉坐标

视觉可以理解为屏幕坐标。是一个虚拟的固定的坐标系。

4.2 视图变换

视图变化是当观察者移动位置时视图相应的发生角度的变换,默认观察者是以z轴负方向观察,当观察沿x轴负方向观察时,实际观察模型的侧面。
一般都是先进行视觉变化,已确定观察的视角,然后进行其它变化。这样更容易理解变化的过程。

4.3 模型变换

用于操纵模型或某一对象,通过变换改变了其位置,大小或者角度。

4.4 模型视图的二次元

模型视图二次元是模型视图两种变换在线性变换管线中的进行组合,成为一个单独的变化矩阵。即模型视图矩阵。

4.5 投影变换

将模型视图变换之后应用到顶点上,这种变换后实际上是确认了视景体和剪裁平面。
对于平行投影而言,视景体是一个四边平行于投影方向,长度无限的四棱柱。
对于透视投影而言,视景体是以投影中心为顶点的四棱锥。

4.6 视口变换

当以上变换结束后,我们得到了一个场景的二维投影,这时候我们需要将其映射到窗口的某片区域进行显示,还包括颜色缓冲区与窗口像素间的转换。

4.7 模型视图矩阵

模型视图矩阵是一个4x4的矩阵,所表示一个变换后的坐标系,模型视图变换就是通过将要变换的顶点和模型变换矩阵相乘得到一个变换后相对于视觉坐标系新的坐标系。以下是一个模型视图变换的例子:

GLBatch      squareBatch;
GLShaderManager  shaderManager;
GLfloat       blockSize = 0.1;
GLfloat      vVerts[] = { -blockSize, -blockSize, 0.0f,//一个3x4矩阵忽略了w缩放值
                           blockSize, -blockSize, 0.0f,
                           blockSize,  blockSize, 0.0f,
                           -blockSize,  blockSize, 0.0f};

GLfloat xPos = 0.0;
GLfloat yPos = 0.0;

void SetupRC()
{
    //设置背景色
    glClearColor(0.0f, 0.0f, 1.0f, 1.0f );
    //初始化存储着色器
    shaderManager.InitializeStockShaders();
    //设置着色器的图片类型和定点数
    squareBatch.Begin(GL_TRIANGLE_FAN, 4);
    //存储管理顶点着色器的坐标
    squareBatch.CopyVertexData3f(vVerts);
   //当前管理容器结束标识
    squareBatch.End();
}

// 通过键盘的上下左右改变 xPos,yPos的值
void SpecialKeys(int key, int x, int y)
{
    GLfloat stepSize = 0.025;
    if (key == GLUT_KEY_UP){
        yPos += stepSize;
    }
    if (key == GLUT_KEY_DOWN){
        yPos -= stepSize;
    }
    if (key == GLUT_KEY_LEFT){
        xPos -= stepSize;
    }
    if(key == GLUT_KEY_RIGHT){
        xPos += stepSize;
    }
    if(xPos < (-1.0f + blockSize)){
        xPos = -1.0f + blockSize;
    }
    if(xPos > (1.0f - blockSize)){
        xPos = 1.0f - blockSize;
    }
    if(yPos < (-1.0f + blockSize)){
        yPos = -1.0f + blockSize;
    }
    if(yPos > (1.0f - blockSize)) {
        yPos = 1.0f - blockSize;
    }
    glutPostRedisplay();
}

void RenderScene(void)
{
    //清理缓冲区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    //颜色矩阵
    GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };
    //4x4矩阵
    M3DMatrix44f mFinalTransform, mTranslationMatrix, mRotationMatrix;
    //平移矩阵,平移后后相对于视觉系新的坐标集合会存储在mTranslationMatrix中
    //原理是坐标系的每个顶点坐标与平移矩阵进行相乘会得到一个相对与视觉系的新的顶点,所有坐标相乘后就会得到一个相对于视觉的新的坐标系。
    //例如将顶点(1,1,1)沿着x正方向移动到(2,1,1)乘以 1,0,0,1即可
   //                                            0,1,0,0
   //                                            0,0,1,0
   //                                            0,0,0,1
    m3dTranslationMatrix44(mTranslationMatrix, xPos, yPos, 0.0f);
    static float yRot = 0.0f;
    yRot += 5.0f;
    // 旋转矩阵 转换后相对于视觉系新的坐标集合会存储在mRotationMatrix中
    // 其中yRot为旋转的角度,0.0f, 0.0f, 1.0f为旋转的方向;
    // 这里是沿着z轴旋转乘以 cos角,-sin角,0,1即可
    //                   sin角, cos角,0,0
    //                   0,    0,    1,0
    //                   0,    0,    0,1
    m3dRotationMatrix44(mRotationMatrix, m3dDegToRad(yRot), 0.0f, 0.0f, 1.0f);
    // 最终会把平移和旋转的结果应用到mFinalTransform中
    m3dMatrixMultiply44(mFinalTransform, mTranslationMatrix, mRotationMatrix);
    // 通过平面着色器将3d坐标转换成2d坐标
    shaderManager.UseStockShader(GLT_SHADER_FLAT, mFinalTransform, vRed);
    // 绘制
    squareBatch.Draw();
    // 后台缓冲区和前台缓冲区切换函数
    glutSwapBuffers();
    
}

void ChangeSize(int w, int h)
{
    // 确认视口大小
    glViewport(0, 0, w, h);
}

int main(int argc, char* argv[])
{
    gltSetWorkingDirectory(argv[0]);
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH);
    glutInitWindowSize(600, 600);
    glutCreateWindow("MOVE");
    GLenum err = glewInit();
    if (GLEW_OK != err)
    {
        fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
        return 1;
    }
    glutReshapeFunc(ChangeSize);
    glutDisplayFunc(RenderScene);
    glutSpecialFunc(SpecialKeys);//监听键盘按键的回调
    SetupRC();
    glutMainLoop();
    return 0;
}

4.8 模型视图投影矩阵

因为计算机只识别单元立方的坐标系,有时候我们需要跳出坐标系进行变换,比如说当我们采用投影变换后得到的非单元立方的坐标系,这时候就要通过模型视图矩阵转型成单位立方矩阵。
以下是一个通过透视投影然后经过模型视图投影矩阵变换的例子:


GLFrustum           viewFrustum;
GLShaderManager     shaderManager;
GLTriangleBatch     torusBatch;
void ChangeSize(int w, int h)
{
    if(h == 0)
        h = 1;
    glViewport(0, 0, w, h);
    //进行透视投影得到视景图
    viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 1000.0f);
}
void RenderScene(void)
{
    static CStopWatch rotTimer;
    //时间监听,得到当前时间
    float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    M3DMatrix44f mTranslate, mRotate, mModelview, mModelViewProjection;
    //平移变换矩阵, 向z轴平移-2.5f
    m3dTranslationMatrix44(mTranslate, 0.0f, 0.0f, -2.5f);
    //旋转变换矩阵, 沿y轴逆时针旋转
    m3dRotationMatrix44(mRotate, m3dDegToRad(yRot), 0.0f, 1.0f, 0.0f);
    //得到视图模型变换后矩阵
    m3dMatrixMultiply44(mModelview, mTranslate, mRotate);
    //其实视图变换矩阵就可以绘制显示了,但是计算机只识别单元立方体坐标系,而经过透视投影之后为非单元立方体坐标系,所以需要模型视图投影矩阵进行转换成单元立方坐标系,这里首先先从透镜矩阵实例viewFrustum,取到投影矩阵坐标,然后和模型视图矩阵通过m3dMatrixMultiply44方法转换换成单元立方坐标系放在mModelViewProjection中.
    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();
}

void SetupRC()
{
    glClearColor(0.8f, 0.8f, 0.8f, 1.0f );
    //开启深度测试,防止图元重叠
    glEnable(GL_DEPTH_TEST);
    shaderManager.InitializeStockShaders();
    gltMakeTorus(torusBatch, 0.4f, 0.15f, 30, 30);
    //多边形模式,多边形图元以线段的形式前后都显示
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}

int main(int argc, char* argv[])
{
    gltSetWorkingDirectory(argv[0]);
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    glutInitWindowSize(800, 600);
    glutCreateWindow("ModelViewProjection");
    glutReshapeFunc(ChangeSize);
    glutDisplayFunc(RenderScene);
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
        return 1;
    }
    SetupRC();
    glutMainLoop();
    return 0;
}

5. 参考

https://baike.baidu.com/item/仿射变换/4289056
OpenGL基础仿射变换原理解析 - 简书
OpenGL学习脚印: 投影矩阵和视口变换矩阵(math-projection and viewport matrix)_The fool的博客-CSDN博客
OpenGL中投影矩阵(Projection Matrix)详解_opengl中projection_0小龙虾0的博客-CSDN博客
OpenGL坐标系解析(顶点从对象坐标系到屏幕坐标系的计算流程) - 简书

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值