OpenGL基础仿射变换原理解析

仿射变换(Affine Transformation)

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

OpenGL基础仿射变化原理

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


基础仿射变换

几种典型的仿射变换如下:

平移变换 Translation

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

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

缩放变换(Scale)

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

变换效果如下:

剪切变换(Shear)

变换矩阵为:

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

效果:

旋转变换(Rotation)

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

效果:

组合

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

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

先移动到中心节点,然后旋转,然后再移动回去。

仿射变化原理:

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

1. 视觉坐标

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

2. 视图变换

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

3. 模型变换

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

4. 模型视图的二次元

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

5. 投影变换

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

6. 视口变换

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

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;
}
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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353