概述

OpenGL管线中,在光栅化操作之前,包括顶点位置与法线向量的几何数据经顶点操作与图元装配操作进行变换。

  

模型坐标

它是模型对象的局部坐标系,同时也是任何变换之前模型对象的初始位置与朝向。为了变换模型对象,可以使用glRotatef()、glTranslatef()、glScalef()。

观察坐标

它由模型坐标乘以GL_MODELVIEW矩阵产生。在OpenGL中,可以使用GL_MODELVIEW矩阵将模型对象从模型空间转换到观察空间。GL_MODELVIEW矩阵由模型矩阵与视图矩阵相结合组成(Mview*Mmodel)。模型变换从模型空间变换到世界空间。视图变换从世界空间变换到观察空间。

注意,OpenGL中并没有独立的摄像机(视图)矩阵。因此,为了模仿摄像机变换或视图变换,场景(3D对象与光照)必须按视图变换的逆方向变换。换句话所,OpenGL定义定义摄像机总是位于观察空间坐标的(0,0,0)点且朝向Z轴负方向,且不能够发生变换。关于GL_MODELVIEW矩阵的更详细信息,请参考视图变换矩阵

为了计算光照,法线向量也将从模型坐标变换到观察坐标。注意,法线向量的变换与顶点变换不同。它将法线向量与GL_MODELVIEW矩阵的逆矩阵的转置矩阵相乘。更详细信息,请参考法线向量变换

裁剪坐标

此时,观察坐标乘以GL_PROJECTION矩阵变换为裁剪坐标。GL_PROJECTION矩阵定义视锥体(平头截面),定义顶点数据如何投影到屏幕(透视或正视)。之所以被称为裁剪坐标,是因为变换后的顶点(x,y,z)通过与±w比较进行裁剪。关于GL_PROJECTION矩阵的更详细信息,请参考投影矩阵

规范化设备坐标(MDC)

它由裁剪坐标除以w产生。这被称为透视除法。它更接近窗口(屏幕)坐标,不过它并没有经过移动与按屏幕像素缩放。此时,值得范围在3个坐标轴上都规范化为-1到1。

窗口坐标(屏幕坐标)

它由将规范化的设备坐标(NDC)进行视口变换产生。NDC被缩放与平移以适应渲染屏幕。窗口坐标最后经过OpenGL管线的光栅化过程变为片段。glViewport()用于定义最终图像映射的渲染区域矩形。glDepthRange()用于决定窗口坐标的z值。窗口坐标由上述两个函数的指定参数计算而来:

glViewport(x,y,w,h);

glDepthRange(n,f);

视口变换公式需要再NDC与窗口坐标之间进行线性关系映射:

OpenGL变换矩阵

OpenGL使用4×4矩阵。注意,矩阵中的16个元素以列优先顺序存储在1维数组中。如果你希望将它转换为标准习惯的行优先格式,你需要进行转置操作。

OpenGL具有4种不同类别的矩阵:GL_MODELVIEW、GL_PROJECTION、GL_TEXTIRE与GL_COLOR。你可以在代码中通过使用glMatrixMode()切换当前类型。例如,为了选择GL_MODELVIRE矩阵,需要使用glMatrixMode(GL_MODELVIEW)。

模型视图矩阵(GL_MODELVIEW)

GL_MODELVIEW矩阵将视图矩阵与模型矩阵组合成一个矩阵。为了变换视图矩阵,需要以逆变换方式移动整个场景。gluLookAt()主要用于设置视图变换。

最右面3列矩阵元素(m12,m13,m14)为平移变换,glTranslatef()。元素m15为其次坐标。它主要用于投影变换。

3元素集合(m0,m1,m2)、(m4,m5,m6)与(m8,m9,m10)是便于欧几里得与仿射变换,如旋转glRotatef()与缩放glScalef()。注意,这3个集合实际表示3个正交坐标轴:

  • (m0,m1,m2):+X轴,左向量,默认(1,0,0)
  • (m4,m5,m6):+Y轴,上向量,默认(0,1,0)
  • (m8,m9,m10):+Z轴,前向量,默认(0,0,1)

我们可以直接通过角度或观察向量构建GL_MODELVIEW矩阵,而不是变换函数。下面是构造GL_MODELVIEW矩阵的有用代码:

注意,当多个变换作用于一个顶点上时,OpenGL以相反顺序执行矩阵乘法。例如,当顶点首先通过MA进行变换,其次通过MB进行变换,OpenGL先执行MB×MA,然后再乘以该顶点。因此,在代码中,后一次变换先出现,第一次变换后出现。

// 注意,模型对象将先平移再旋转
glRotatef(angle, 1, 0, 0); // 绕X轴旋转模型对象angle角度
glTranslatef(x, y, z); // 移动模型对象到(x,y,z
drawObject();

投影矩阵(GL_PROJECTION)

GL_PROJECTION矩阵用于定义视锥体。视锥体决定哪些模型对象或部分模型对象将被裁剪掉。同时,它也决定3D场景如何投影到屏幕。(请阅读更详尽的如何构造投影矩阵。)

OpenGL提供两个GL_PROJECTION变换函数。glFrustum()用于产生一个透视投影,glOrtho()用于产生一个正交(平行)投影。这两个函数都需要6个参数以指定6个裁剪面:左裁剪面、右裁剪面、下裁剪面、上裁剪面、近裁剪面与远裁剪面。视锥体的8个顶点如下列图片。


OpenGL透视椎体

远(后)平面的顶点可以简单滴通过简单三角形比率计算出来,如,左远平面为:


OpenGL正交椎体

对于正交投影,该比率为1,因此远平面的左、右、下与上顶点值与近平面上的对应值相同。

你也可以使用更少参数的gluPerspective()与gluOrtho2D()函数。gluPerspective()仅仅需要4个参数:垂直视场(FOV)、宽高纵横比与远近裁剪面的距离。从gluPerspective()到glFrustum()的等价变换描述如下的代码:

// 创建对称截面体
// 从给定4个参数(fovy, aspect, near, far)
// 转换到glFrustum()的六个参数(l, r, b, t, n, f)
void makeFrustum(double fovY, double aspectRatio, double front, double back)
{
const double DEG2RAD = 3.14159265 / 180; double tangent = tan(fovY/2 * DEG2RAD); // 半fovY的正切值
double height = front * tangent; // 近平面半高度
double width = height * aspectRatio; // 近平面半宽度 // 参数: left, right, bottom, top, near, far
glFrustum(-width, width, -height, height, front, back);
}

对称视锥体示例

然而,如果你需要建立一个非对称视锥体,你必须直接使用glFrustum()。例如,如果你希望渲染宽场景到2个邻接屏幕上,你可以将截面体分成两个对称截面体(左与右)。然后,使用每一个截面体渲染场景。

纹理矩阵(GL_TEXTURE)

纹理坐标(s,t,r,q)在纹理映射之前乘以GL_TEXTURE矩阵。默认情况下,该纹理矩阵为单位矩阵,因此纹理会完全按照你赋值纹理坐标的方式映射到模型对象。通过改变GL_TEXTURE矩阵,你可以滑动、旋转、伸展与收缩纹理。

// 绕X轴旋转纹理
glMatrixMode(GL_TEXTURE);
glRotatef(angle, 1, 0, 0);

颜色矩阵(GL_COLOR)

颜色分量(r,g,b,a)与GL_COLOR矩阵相乘。这可用于颜色空间转换与颜色分量交换。GL_COLOR矩阵并不常用且需要GL_ARB_imaging扩展。

其他矩阵函数

glPushMatrix():将当前矩阵压入当前矩阵栈

glPopMatrix():将当前矩阵弹出当前矩阵栈

glLoadMatrix():设置当前矩阵为单位矩阵

glLoadMatrix{fd}(m):用矩阵m替换当前矩阵

glLoadTransposeMatrix{fd}(m):用行优先矩阵m替换当前矩阵

glMultMatrix{fd}(m):将当前矩阵乘以矩阵m,并更新当前矩阵值

glMultTransposeMatrix{fd}(m):将当前矩阵乘以行优先矩阵m,并更新当前矩阵值

glGetFloatv(GL_MODELVIEW_MATRIX, m):返回GL_MODELVIEW矩阵的16个值到m

实例:模型视图矩阵

这个例子程序展示如何使用glTranslatef()与glRotatef()操作GL_MODELVIEW矩阵。

下载源程序与二进制程序:matrixModelView.zip

注意,所有OpenGL函数调用在ModelGL.h与ModelGL.cpp中实现。

为了制定模型与相机变换,该实例程序使用自定义的4×4矩阵类与默认OpenGL矩阵函数。在ModelGL.cpp中定义3个矩阵对象:matrixModel、matrixView与matrixModelView。每个矩阵保存先前计算的矩阵,并且使用glLoadMatrix()传递这些矩阵元素。实际绘制程序像这样:

...
glPushMatrix(); // 为相机变换设置视图矩阵
glLoadMatrixf(matrixView.getTranspose()); // 在模型变换之前绘制网格
drawGrid(); // 为模型与视图变换设置模型视图矩阵
// 从模型对象空间变换到观察空间
glLoadMatrixf(matrixModelView.getTranspose()); // 在视图与模型变换之后绘制一个茶壶
drawTeapot(); glPopMatrix();
...

使用模型OpenGL矩阵函数的等价代码为:

...
glPushMatrix(); //初始化模型视图矩阵
glLoadIdentity(); // 首先,变换相机(视图矩阵):从世界空间到观察空间
// 注意所有变量都为负,因为我们将整个场景超相机变换相反方向移动
glRotatef(-cameraAngle[2], 0, 0, 1); // roll
glRotatef(-cameraAngle[1], 0, 1, 0); // heading
glRotatef(-cameraAngle[0], 1, 0, 0); // pitch
glTranslatef(-cameraPosition[0], -cameraPosition[1], -cameraPosition[2]); // 在模型变换之前在原点绘制网格
drawGrid(); // 变换模型(模型矩阵)
// GL_MODELVIEW矩阵的结果为:ModelView_M = View_M * Model_M
glTranslatef(modelPosition[0], modelPosition[1], modelPosition[2]);
glRotatef(modelAngle[0], 1, 0, 0);
glRotatef(modelAngle[1], 0, 1, 0);
glRotatef(modelAngle[2], 0, 0, 1); // 使用模型与视图变换绘制一个茶壶
drawTeapot(); glPopMatrix();
...

实例:投影矩阵

这个例子程序展示如何使用glFrustum()与glOrtho()操作投影变换。

下载源程序与二进制程序:matrixProjection.zip

同样,ModelGL.h与ModelGL.cpp是OpenGL函数调用放置的地方。

ModelGL类使用自定义矩阵对象,matrixProjection与2个成员函数:setFrustum()与setOrthoFrustum(),这两个函数与glFrustum()与glOrtho()等价。

///////////////////////////////////////////////////////////////////////////
// 使用与glFrustum()相同的6个参数(left,right,bottom,top,near,far)设置透视截面体// 注意: 此处为行优先标记法。OpenGL需要对它进行转置
///////////////////////////////////////////////////////////////////////////
void ModelGL::setFrustum(float l, float r, float b, float t, float n, float f)
{
matrixProjection.identity();
matrixProjection[0] = 2 * n / (r - l);
matrixProjection[2] = (r + l) / (r - l);
matrixProjection[5] = 2 * n / (t - b);
matrixProjection[6] = (t + b) / (t - b);
matrixProjection[10] = -(f + n) / (f - n);
matrixProjection[11] = -(2 * f * n) / (f - n);
matrixProjection[14] = -1;
matrixProjection[15] = 0;
} //////////////////////////////////////////////////////////////////////////////
// 使用与glOrtho()相类似的6个参数(left, right, bottom, top, near, far)设置正交投影
// 注意: 此处为行优先标记法。OpenGL需要对它进行转置
//////////////////////////////////////////////////////////////////////////////
void ModelGL::setOrthoFrustum(float l, float r, float b, float t, float n,
float f)
{
matrixProjection.identity();
matrixProjection[0] = 2 / (r - l);
matrixProjection[3] = -(r + l) / (r - l);
matrixProjection[5] = 2 / (t - b);
matrixProjection[7] = -(t + b) / (t - b);
matrixProjection[10] = -2 / (f - n);
matrixProjection[11] = -(f + n) / (f - n);
}
... // pass projection matrx to OpenGL before draw
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(matrixProjection.getTranspose());
...

英文原文:http://www.songho.ca/opengl/gl_normaltransform.html

OpenGL变换的更多相关文章

  1. OpenGL变换【转】

    http://www.cnblogs.com/hefee/p/3811099.html OpenGL变换 概述 OpenGL变换矩阵 实例:GL_MODELVIEW矩阵 实例:GL_PROJECTIO ...

  2. openGL 变换06

    变换 使用(多个)矩阵(Matrix) 对象可以更好的变换(Transform)一个物体. 向量 向量最基本的定义就是一个方向. 或者说 向量有一个方向(Direction)和大小(Magnitude ...

  3. OpenGL学习进程(12)第九课:矩阵乘法实现3D变换

    本节是OpenGL学习的第九个课时,下面将详细介绍OpenGL的多种3D变换和如何操作矩阵堆栈.     (1)3D变换: OpenGL中绘制3D世界的空间变换包括:模型变换.视图变换.投影变换和视口 ...

  4. SharpGL学习笔记(七) OpenGL的变换总结

    笔者接触OpenGL最大的困难是: 经常调试一份代码时, 屏幕漆黑一片, 也不知道结果对不对,不知道如何是好! 这其实就是关于OpenGL"变换"的基础概念没有掌握好, 以至于对& ...

  5. OpenGL chapter4 基础变换

    math3d库有两个数据类型,能够表示一个三维或四维向量: M3DVector3f M3DVector4f 4.3 理解投影 正投影 : 正交变换 透视投影 : 透视变换 表4.1 OpenGL变换术 ...

  6. SCARA——OpenGL入门学习五六(三维变换、动画)

    OpenGL入门学习(五) 此课为三维变换的内容,比较枯燥.主要是因为很多函数在单独使用时都不好描述其效果, 在前面绘制几何图形的时候,大家是否觉得我们绘图的范围太狭隘了呢?坐标只能从-1到1,还只能 ...

  7. OpenGL基础图形编程(八)变换

    八.OpenGL变换 OpenGL变换是本篇的重点内容,它包含计算机图形学中最主要的三维变换,即几何变换.投影变换.裁剪变换.视口变换,以及针对OpenGL的特殊变换概念理解和使用方法,如相机模拟.矩 ...

  8. OpenGL入门学习 教程 (五)三维的空间变换

    http://oulehui.blog.163.com/blog/static/796146982011924428755/ 在前面绘制几何图形的时候,大家是否觉得我们绘图的范围太狭隘了呢?坐标只能从 ...

  9. Opengl中矩阵和perspective/ortho的相互转换

    Opengl中矩阵和perspective/ortho的相互转换 定义矩阵 Opengl变换需要用四维矩阵.我们来定义这样的矩阵. +BIT祝威+悄悄在此留下版了个权的信息说: 四维向量 首先,我们定 ...

随机推荐

  1. 使用Myeclipse插件将wsdl生成java客户端代码

    使用环境:MyEclipse9.0 本教程使用Myeclipse内置插件生成java代码,网上说这是xfire插件,不管怎样,生成和调用客户端代码都十分简单. 1.在项目上右键,选择New->O ...

  2. AudioUnit 用法

    1.描述音频单元 AudioComponentDescription desc; desc.componentType = kAudioUnitType_Output; desc.componentS ...

  3. ViewPager实现自动翻页功能 --转载出处找不到了,根据自己的理解写个随笔方便以后的记忆以及代码的共享,感谢给我启发的那位高手--第一次写博客哈

    xml文件 textview 用于显示图片的标题 viewpager 用于实现翻页效果 <LinearLayout xmlns:android="http://schemas.andr ...

  4. [Python]命令行进度条

    关键点是输出'\r'这个字符可以使光标回到一行的开头,这时输出其它内容就会将原内容覆盖. import time import sys def progress_test(): bar_length= ...

  5. 入住cnblogs第一篇随笔 Hello, world!

    在网上搜索计算机参考资料时经常看到各位大神的博客,甚是神往.今天我也在这里安家,记录自己的学习过程,也同各位共勉. 第一篇随笔,就用来测试一下这里的文本编辑器吧. //The C language # ...

  6. sudo: /etc/sudoers is world writable

    错误信息: sudo: /etc/sudoers is world writable sudo: no valid sudoers sources found, quitting 解决办法: 修复磁盘 ...

  7. 关于jetty项目中的问题.

    在某台虚拟机上部署的项目出现的问题: 我想要更改定义的owl文件,重启服务器,却打不开网页. 1.couldnot found owl ,然后我拷贝一份owl到work/config目录下,继续更改配 ...

  8. zynq中uboot的qspi启动报错及解决办法

    问题描述: 用u-boot-xlnx-v2016.3版本编译的uboot通过qspi flash启动出现如下错误: 尝试在uboot命令行输入"sf probe 0 0 0"挂载q ...

  9. nodejs入门 SSH服务器远程部署nodejs2

    服务器安装nodejs昨天好像出了点问题 今天参考的链接是http://nodejs.cn/download/package-manager/#debian-and-ubuntu-based-linu ...

  10. php语言的几种循环语句的使用格式,及其区别

    while 只要指定的条件成立,则循环执行代码块 do...while 首先执行一次代码块,然后在指定的条件成立时重复这个循环 for 循环执行代码块指定的次数 foreach 根据数组中每个元素来循 ...