概述

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. java语法基本知识

    java中,变量分为局部和成员变量.局部变量在程序运行的过程中在栈stack中分配存储空间. 从上到下是:heap, stack, data segment, code segment.

  2. 完整的分页存储过程以及c#调用方法

    高效分页存储过程 USE [db] GO /****** 对象: StoredProcedure [dbo].[p_Page2005] 脚本日期: // :: ******/ SET ANSI_NUL ...

  3. fushioncharts 使用教程要点---使用JSON数据方式

    1.建立图表步骤: A.下载fushionChart,引入FusionCharts.js和FusionChartsExportComponent.js文件 B.建立图表对象 var chart1 =  ...

  4. GnuRadio Hacking②:使用SDR嗅探北欧芯片无线键盘鼠标数据包

    0×00 前言 上半年的时候安全公司Bastille Networks(巴士底狱)安全研究员发现大多数无线鼠标和接收器之间的通信信号是不加密的,黑客可对一两百米范围内存在漏洞的无线键鼠进行嗅探甚至劫持 ...

  5. oracle 将多字段数据合成一个

    1.系统默认 WMSYS.WM_CONCAT(A.ID), 2.再进行替换 REPLACE(WMSYS.WM_CONCAT(A.ID),',','|'), (张)

  6. OpenCV阶段总结扩充。

    Mat类型简单介绍 /* cv::Mat类是用于保存图像以及其他矩阵的数据结构.默认情况下,其尺寸为0,我们也可以设置其初始尺寸,比如定义一个Mat类的对象,就要写cv::Mat pic(320,64 ...

  7. JAVAWEB学习总结 HttpServletResponse对象(一)

    Web服务器收到客户端(浏览器)的http请求,会针对每一次请求,分别创建一个用于代表请求的request对象,和代表响应的response对象. request和response对象既然代表请求和响 ...

  8. C语言复杂声明-void (*signal(int sig, void (*handler)(int)))(int);

    问题提出 请分析此声明:void (*signal(int sig, void (*handler)(int)))(int); 求解过程 在对上面的例子作分析之前,我们需要了解C语言的声明优先级,&l ...

  9. NOIP 考前DP 复习

    POJ 2533 最长不降子序列 #include <cstdio> ; int a[Maxn],Pos[Maxn],F[Maxn],n,Ans; inline int Max(int x ...

  10. 关于JAVA中对字符串与数组求长度的问题

    我在学习中发现在求数组或者字符串的长度的时候,用到length的时候,有时候是length,有时候是length(),很是奇怪,于是上API查了一下,发现一些小细节. 首先看看这段代码 public ...