OpenGL基础图形编程

1、OpenGL与3D图形世界
1.一、OpenGL令人们进入三维图形世界
  咱们生活在一个充满三维物体的三维世界中,为了使计算机能精确地再现这些物体,咱们必须能在三维空间描绘这些物体。咱们又生活在一个充满信息的世界中,可否尽快地理解并运用这些信息将直接影响事业的成败,因此咱们须要用一种最直接的形式来表示这些信息。   最近几年计算机图形学的发展使得三维表现技术得以造成,这些三维表现技术使咱们可以再现三维世界中的物体,可以用三维形体来表示复杂的信息,这种技术就是可视化(Visualization)技术。可视化技术令人可以在三维图形世界中直接对具备形体的信息进行操做,和计算机直接交流。这种技术已经把人和机器的力量以一种直觉而天然的方式加以统一,这种革命性的变化无疑将极大地提升人们的工做效率。可视化技术赋予人们一种仿真的、三维的而且具备实时交互的能力,这样人们能够在三维图形世界中用之前不可想象的手段来获取信息或发挥本身创造性的思惟。机械工程师能够从二维平面图中得以解放直接进入三维世界,从而很快获得本身设计的三维机械零件模型。医生能够从病人的三维扫描图象分析病人的病灶。军事指挥员能够面对用三维图形技术生成的战场地形,指挥具备真实感的三维飞机、军舰、坦克向目标开进并分析战斗方案的效果。   更使人惊奇的是目前正在发展的虚拟现实技术,它能令人们进入一个三维的、多媒体的虚拟世界,人们能够游历远古时代的城堡,也能够遨游浩翰的太空。全部这些都依赖于计算机图形学、计算机可视化技术的发展。人们对计算机可视化技术的研究已经历了一个很长的历程,并且造成了许多可视化工具,其中SGI公司推出的GL三维图形库表现突出,易于使用并且功能强大。利用GL开发出来的三维应用软件颇受许多专业技术人员的喜好,这些三维应用软件已涉及建筑、产品设计、医学、地球科学、流体力学等领域。随着计算机技术的继续发展,GL已经进一步发展成为OpenGL,OpenGL已被认为是高性能图形和交互式视景处理的标准,目前包括ATT公司UNIX软件实验室、IBM公司、DEC公司、SUN公司、HP公司、Microsoft公司和 SGI公司在内的几家在计算机市场占领导地位的大公司都采用了OpenGL图形标准。   值得一提的是,因为Microsoft公司在 Windows NT中提供OpenGL图形标准,OpenGL将在微机中普遍应用,尤为是OpenGL三维图形加速卡和微机图形工做站的推出,人们能够在微机上实现三维图形应用,如CAD设计、仿真模拟、三维游戏等,从而更有机会、更方便地使用OpenGL及其应用软件来创建本身的三维图形世界。 1.二、OpenGL提供直观的三维图形开发环境   OpenGL其实是一种图形与硬件的接口。它包括了120个图形函数,开发者能够用这些函数来创建三维模型和进行三维实时交互。与其余图形程序设计接口不一样,OpenGL提供了十分清晰明了的图形函数,所以初学的程序设计员也能利用OpenGL的图形处理能力和1670万种色彩的调色板很快地设计出三维图形以及三维交互软件。   OpenGL强有力的图形函数不要求开发者把三维物体模型的数据写成固定的数据格式,这样开发者不但能够直接使用本身的数据,并且能够利用其余不一样格式的数据源。这种灵活性极大地节省了开发者的时间,提升了软件开发效益。   长期以来,从事三维图形开发的技术人员都不得不在本身的程序中编写矩阵变换、外部设备访问等函数,这样为调制这些与本身的软件开发目标关系并不十分密切的函数费脑筋,而OpenGL正是提供一种直观的编程环境,它提供的一系列函数大大地简化了三维图形程序。例如:程序员

  • OpenGL提供一系列的三维图形单元供开发者调用。
  • OpenGL提供一系列的图形变换函数。
  • OpenGL提供一系列的外部设备访问函数,使开发者能够方便地访问鼠标、键盘、空间球、数据手套等这种直观的三维图形开发环境体现了OpenGL的技术优点,这也是许多三维图形开发者热衷于OpenGL的原因所在。

1.三、OpenGL成为目前三维图形开发标准   OpenGL成为目前三维图形开发标准在计算机发展初期,人们就开始从事计算机图形的开发。直到计算机硬软件和计算机图形学高度发达的九十年代,人们发现复杂的数据以视觉的形式表现时是最易理解的,于是三维图形得以迅猛发展,因而各类三维图形工具软件包相继推出,如PHIGS、PEX、 RenderMan等。这些三维图形工具软件包有些侧重于使用方便,有些侧重于渲染效果或与应用软件的链接,但没有一种三维工具软件包在交互式三维图形建模能力、外部设备管理以及编程方便程度上可以OpenGL相比拟。   OpenGL通过对GL的进一步发展,实现二维和三维的高级图形技术,在性能上表现得异常优越,它包括建模、变换、光线处理、色彩处理、动画以及更先进的能力,如纹理影射、物体运动模糊等。OpenGL的这些能力为实现逼真的三维渲染效果、创建交互的三维景观提供了优秀的软件工具。OpenGL在硬件、窗口、操做系统方面是相互独立的。   许多计算机公司已经把 OpenGL集成到各类窗口和操做系统中,其中操做系统包括UNIX、Windows NT、DOS等,窗口系统有X窗口、Windows等。为了实现一个完整功能的图形处理系统,设计一个与OpenGL相关的系统结构为:其最底层是图形硬件,第二层为操做系统,第三层为窗口系统,第四层为OpenGL,第五层为应用软件。OpenGL是网络透明的,在客户 — 服务器(Client-Server)体系结构中,OpenGL容许本地和远程绘图。因此在网络系统中,OpenGL在X窗口、Windows或其它窗口系统下均可以以一个独立的图形窗口出现。   OpenGL做为一个性能优越的图形应用程序设计界面(API)而适合于普遍的计算环境,从我的计算机到工做站和超级计算机,OpenGL都能实现高性能的三维图形功能。因为许多在计算机界具备领导地位的计算机公司纷纷采用OpenGL做为三维图形应用程序设计界面,OpenGL应用程序具备普遍的移植性。所以,OpenGL已成为目前的三维图形开发标准,是从事三维图形开发工做的技术人员所必须掌握的开发工具。

2、OpenGL概念创建
算法

 

<>function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus(); 2.一、OpenGL基本理解   OpenGL是一个与硬件图形发生器的软件接口,它包括了100多个图形操做函数,开发者能够利用这些函数来构造景物模型、进行三维图形交互软件的开发。正如上一章所述,OpenGL是一个高性能的图形开发软件包。OpenGL支持网络,在网络系统中用户能够在不一样的图形终端上运行程序显示图形。 OpenGL做为一个与硬件独立的图形接口,它不提供与硬件密切相关的设备操做函数,同时,它也不提供描述相似于飞机、汽车、分子形状等复杂形体的图形操做函数。用户必须从点、线、面等最基本的图形单元开始构造本身的三维模型。固然,象OpenInventor那样更高一级的基于OpenGL的三维图形建模开发软件包将提供方便的工具。所以OpenGL的图形操做函数十分基本、灵活。例如OpenGL中的模型绘制过程就多种多样,内容十分丰富,OpenGL提供了如下的对三维物体的绘制方式:
  • 网格线绘图方式wireframe) 这种方式仅绘制三维物体的网格轮廓线。
  • 深度优先网格线绘图方式depth_cued) 用网格线方式绘图,增长模拟人眼看物体同样,远处的物体比近处的物体要暗些。
  • 反走样网格线绘图方式antialiased) 用网格线方式绘图,绘图时采用反走样技术以减小图形线条的良莠不齐。
  • 平面消隐绘图方式flat_shade) 对模型的隐藏面进行消隐,对模型的平面单元按光照程度进行着色但不进行光滑处理。
  • 光滑消隐绘图方式smooth_shade) 对模型进行消隐按光照渲染着色的过程当中再进行光滑处理,这种方式更接近于现实。
  • 加阴影和纹理的绘图方式shadows、textures) 在模型表面贴上纹理甚至于加上光照阴影,使得三维景观象照片同样。
  • 运动模糊的绘图方式motion-blured) 模拟物体运动时人眼观察所感受的动感现象。
  • 大气环境效果atmosphere-effects) 在三维景观中加入如雾等大气环境效果,令人身临其境。
  • 深度域效果depth-of-effects) 相似于照相机镜头效果,模型在聚焦点处清晰,反之则模糊。
  这些三维物体绘图和特殊效果处理方式,说明OpenGL已经可以模拟比较复杂的三维物体或天然景观,这就是咱们所面对的OpenGL。 2.二、OpenGL工做流程   整个OpenGL的基本工做流程以下图:   其中几何顶点数据包括模型的顶点集、线集、多边形集,这些数据通过流程图的上部,包括运算器、逐个顶点操做等;图像数据包括象素集、影像集、位图集等,图像象素数据的处理方式与几何顶点数据的处理方式是不一样的,但它们都通过光栅化、逐个片元( Fragment)处理直至把最后的光栅数据写入帧缓冲器。在OpenGL中的全部数据包括几何顶点数据和象素数据均可以被存储在显示列表中或者当即能够获得处理。OpenGL中,显示列表技术是一项重要的技术。   OpenGL要求把全部的几何图形单元都用顶点来描述,这样运算器和逐个顶点计算操做均可以针对每一个顶点进行计算和操做,而后进行光栅化造成图形碎片;对于象素数据,象素操做结果被存储在纹理组装用的内存中,再象几何顶点操做同样光栅化造成图形片元。   整个流程操做的最后,图形片元都要进行一系列的逐个片元操做,这样最后的象素值BZ送入帧缓冲器实现图形的显示。 2.三、OpenGL图形操做步骤   在上一节中说明了OpenGL的基本工做流程,根据这个流程能够概括出在OpenGL中进行主要的图形操做直至在计算机屏幕上渲染绘制出三维图形景观的基本步骤:   1)根据基本图形单元创建景物模型,而且对所创建的模型进行数学描述(OpenGL中把:点、线、多边形、图像和位图都做为基本图形单元)。   2)把景物模型放在三维空间中的合适的位置,而且设置视点(viewpoint)以观察所感兴趣的景观。   3)计算模型中全部物体的色彩,其中的色彩根据应用要求来肯定,同时肯定光照条件、纹理粘贴方式等。   4)把景物模型的数学描述及其色彩信息转换至计算机屏幕上的象素,这个过程也就是光栅化(rasterization)。   在这些步骤的执行过程当中,OpenGL可能执行其余的一些操做,例如自动消隐处理等。另外,景物光栅化以后被送入帧缓冲器以前还能够根据须要对象素数据进行操做。

3、WindowsNT下的OpenGL
3.一、Windows NT下的OpenGL函数   如前面的章节所述,Windows NT下的OpenGL一样包含100多个库函数,这些函数都按必定的格式来命名,即每一个函数都以gl开头。Windows NT下的OpenGL除了具备基本的OpenGL函数外,还支持其余四类函数:
相应函数 具体说明
OpenGL实用库 43个函数,每一个函数以glu开头。
OpenGL辅助库 31个函数,每一个函数以aux开头。
Windows专用库函数(WGL) 6个函数,每一个函数以wgl开头。
Win32 API函数 5个函数,函数前面没有专用前缀。
  在OpenGL中有115个核心函数,这些函数是最基本的,它们能够在任何OpenGL的工做平台上应用。这些函数用于创建各类各样的形体,产生光照效果,进行反走样以及进行纹理映射,进行投影变换等等。因为这些核心函数有许多种形式并可以接受不一样类型的参数,实际上这些函数能够派生出300 多个函数。   OpenGL的实用函数是比OpenGL核心函数更高一层的函数,这些函数是经过调用核心函数来起做用的。这些函数提供了十分简单的用法,从而减轻了开发者的编程负担。OpenGL的实用函数包括纹理映射、坐标变换、多边形分化、绘制一些如椭球、圆柱、茶壶等简单多边形实体(本指南将详细讲述这些函数的具体用法)等。这部分函数象核心函数同样在任何OpenGL平台均可以应用。   OpenGL的辅助库是一些特殊的函数,这些函数原本是用于初学者作简单的练习之用,所以这些函数不能在全部的OpenGL平台上使用,在Windows NT环境下能够使用这些函数。这些函数使用简单,它们能够用于窗口管理、输入输出处理以及绘制一些简单的三维形体。为了使OpenGL的应用程序具备良好的移植性,在使用OpenGL辅助库的时候应谨慎。   6个WGL函数是用于链接OpenGL与Windows NT的,这些函数用于在Windows NT环境下的OpenGL窗口可以进行渲染着色,在窗口内绘制位图字体以及把文本放在窗口的某一位置等。这些函数把Windows与OpenGL揉合在一块儿。最后的5个Win32函数用于处理象素存储格式和双缓冲区,显然这些函数仅仅可以用于Win32系统而不能用于其它OpenGL平台。 3.二、OpenGL基本功能   OpenGL可以对整个三维模型进行渲染着色,从而绘制出与客观世界十分相似的三维景象。另外OpenGL还能够进行三维交互、动做模拟等。具体的功能主要有如下这些内容。
  • 模型绘制 OpenGL可以绘制点、线和多边形。应用这些基本的形体,咱们能够构造出几乎全部的三维模型。OpenGL一般用模型的多边形的顶点来描述三维模型。如何经过多边形及其顶点来描述三维模型,在指南的在后续章节会有详细的介绍。
  • 模型观察 在创建了三维景物模型后,就须要用OpenGL描述如何观察所创建的三维模型。观察三维模型是经过一系列的坐标变换进行的。模型的坐标变换在使观察者可以在视点位置观察与视点相适应的三维模型景观。在整个三维模型的观察过程当中,投影变换的类型决定观察三维模型的观察方式,不一样的投影变换获得的三维模型的景象也是不一样的。最后的视窗变换则对模型的景象进行裁剪缩放,即决定整个三维模型在屏幕上的图象。
  • 颜色模式的指定 OpenGL 应用了一些专门的函数来指定三维模型的颜色。程序员能够选择二个颜色模式,即RGBA模式和颜色表模式。在RGBA模式中,颜色直接由RGB值来指定;在颜色表模式中,颜色值则由颜色表中的一个颜色索引值来指定。程序员还能够选择平面着色和光滑着色二种着色方式对整个三维景观进行着色。
  • 光照应用 用OpenGL绘制的三维模型必须加上光照才能更加与客观物体类似。OpenGL提供了管理四种光(辐射光、环境光、镜面光和漫反射光)的方法,另外还能够指定模型表面的反射特性。
  • 图象效果加强 OpenGL提供了一系列的加强三维景观的图象效果的函数,这些函数经过反走样、混合和雾化来加强图象的效果。反走样用于改善图象中线段图形的锯齿而更平滑,混合用于处理模型的半透明效果,雾使得影像从视点到远处逐渐褪色,更接近于真实。
  • 位图和图象处理 OpenGL还提供了专门对位图和图象进行操做的函数。
  • 纹理映射 三维景物因缺乏景物的具体细节而显得不够真实,为了更加逼真地表现三维景物,OpenGL提供了纹理映射的功能。OpenGL提供的一系列纹理映射函数使得开发者能够十分方便地把真实图象贴到景物的多边形上,从而能够在视窗内绘制逼真的三维景观。
  • 实时动画 为了得到平滑的动画效果,须要先在内存中生成下一幅图象,而后把已经生成的图象从内存拷贝到屏幕上,这就是OpenGL的双缓存技术(double buffer)。OpenGL提供了双缓存技术的一系列函数。
  • 交互技术 目前有许多图形应用须要人机交互,OpenGL提供了方便的三维图形人机交互接口,用户能够选择修改三维景观中的物体。
3.三、Windows NT下OpenGL的结构   OpenGL的做用机制是客户(client)/服务器(sever)机制,即客户(用OpenGL绘制景物的应用程序)向服务器(即OpenGL内核)发布OpenGL命令,服务器则解释这些命令。大多数状况下,客户和服务器在同一机器上运行。正是OpenGL的这种客户/服务器机制,OpenGL能够十分方便地在网络环境下使用。所以Windows NT下的OpenGL是网络透明的。正象Windows的图形设备接口(GDI)把图形函数库封装在一个动态连接库(Windows NT下的GDI32.DLL)内同样,OpenGL图形库也被封装在一个动态连接库内(OPENGL32.DLL)。受客户应用程序调用的OpenGL函数都先在OPENGL32.DLL中处理,而后传给服务器WINSRV.DLL。OpenGL的命令再次获得处理而且直接传给Win32的设备驱动接口(Device Drive Interface,DDI),这样就把通过处理的图形命令送给视频显示驱动程序。下图简要说明这个过程: 图3-1 OpenGL在Windows NT下运行机制
  在三维图形加速卡的GLINT图形加速芯片的加速支持下,二个附加的驱动程序被加入这个过程当中。一个OpenGL可安装客户驱动程序(Installable Client Driver,ICD)被加在客户这一边,一个硬件指定DDI(Hardware-specific DDI)被加在服务器这边,这个驱动程序与Wind32 DDI是同一级别的。
图3-2 在三维图形加速下OpenGL运行机制





4、OpenGL基础程序结构

  用OpenGL编写的程序结构相似于用其余语言编写的程序。实际上,OpenGL是一个丰富的三维图形函数库,编写OpenGL程序并不是难事,只需在基本C语言中调用这些函数,用法同Turbo C、Microsoft C等相似,但也有许多不一样之处。
  本指南全部的程序都是在Windows NT的Microsoft Visual C++集成环境下编译链接的,其中有部分头文件和函数是为这个环境所用的,例如判别操做系统的头文件“glos.h”。此外,为便于各种读者同时快速入门,在短期内掌握OpenGL编程的基本方法和技巧,指南中例子尽可能采用标准ANSI C调用OpenGL函数来编写,并且全部例程都只采用OpenGL附带的辅助库中的窗口系统。此外,这样也便于程序在各平台间移植,尤为往工做站UNIX 操做系统移植时,也只需改动头文件等不多不多的部分。下面列出一个简单的OpenGL程序:

  例4-1 OpenGL简单例程Simple.c

  #include <GL/gl.h>   #include <GL/glaux.h>   #include "glos.h"
  void main(void)   {     auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);     auxInitPosition(0,0,500,500);     auxInitWindow("simple");
    glClearColor(0.0,0.0,0.0,0.0);     glClear(GL_COLOR_BUFFER_BIT);
     glColor3f(1.0,0.0,0.0);     glRectf(-0.5,-0.5,0.5,0.5);
     glFlush();     _sleep(1000);   }
  这个程序运行结果是在屏幕窗口内画一个红色的方块。
  下面具体分析整个程序结构:首先,在程序最开始处是OpenGL头文件:<GL/gl.h>、<GL/glaux.h>。前一个是gl库的头文件,后一个是辅助库的头文件。此外,在之后的几章中还将说明OpenGL的另外两个头文件,一个是<GL/glu.h>实用库的头文件,另外一个是<GL/glx.h>X窗口扩充库的头文件(这个经常使用在工做站上)。接下来是主函数main()的定义:通常的程序结构是先定义一个窗口:

  auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);   auxInitPosition(0,0,500,500);   auxInitWindow("simple");
  auxInitDisplayMode(AUX_SINGLE|AUX_RGBA)设置窗口显示模式为RGBA方式,即彩色方式,而且图形缓存为单缓存(SINGLE BUFFER)。 auxInitPosition(0, 0, 500, 500)定义窗口的初始位置,前两个参数(0, 0)为窗口的左上角点的屏幕坐标,后两个参数(500,500)为窗口的宽度和高度。auxInitWindow("simple")是窗口初始化,字符参数是窗口名称。
  而后是窗口内清屏:

  glClearColor(0.0,0.0,0.0,0.0); glClear(GL_COLOR_BUFFER_BIT);
  第一句将窗口清为黑色,第二句将颜色缓冲区清为glClearColor(0.0, 0.0, 0.0, 0.0)命令所设置的颜色,即同学口背景颜色一致。
  再接着是在窗口内画一个物体:

  glColor3f(1.0,0.0,0.0);   glRectf(-0.5,-0.5,0.5,0.5);
  很明显,第一句设置物体颜色,函数中前三个参数分别为R、G、B值,最后一个参数是Alpha值,范围都从0至1;第二句绘制一个二维矩形。注意:OpenGL是针对三维图形而言,所以用做OpenGL编程绘制物体必须意识到任何一个物体都是三维的,具备空间性,而显示于屏幕上的物体都是三维物体在二维平面上的投影。
  从表面上看,上述程序代码很简单,实际上已经用到了缺省的投影形式(正射投影)。再看glFlush()函数,表示强制绘图完成。最后一句_sleep(1000),参数单位为毫秒,整句意思是保持现有情况一秒钟,而后结束程序运行。这个函数是VC++的库函数。
  总而言之,OpenGL程序基本结构为定义窗口、清理窗口、绘制物体、结束运行。



5、OpenGL的数据类型和函数名

  OpenGL的数据类型定义能够与其它语言一致,但建议在ANSI C下最好使用如下定义的数据类型,例如GLint、GLfloat等。具体类型见表5-1。 前缀    数据类型                    相应C语言类型        OpenGL类型 ================================================================ b       8-bit integer            signed char         GLbyte s       16-bit integer           short               GLshort i       32-bit integer           long                        GLint,GLsizei f       32-bit floating-point    float                       GLfloat,GLclampf d       64-bit floating-point    double                      GLdouble,GLclampd ub      8-bit unsigned integer   unsigned char               GLubyte,GLboolean us      16-bit unsigned integer  unsigned short      GLushort ui      32-bit unsigned integer  unsigned long       GLuint,GLenum,GLbitfield
编程

表5-1 命令前缀和参数数据类型
  OpenGL的库函数命名方式颇有规律,了解这种规律后阅读和编写程序都比较容易方便。   首先,每一个库函数有前缀gl、glu、glx或aux,表示此函数分属于基本库、实用库、X窗口扩充库或辅助库,其后的函数名头字母大写,后缀是参数类型的简写,取i、f,参见表5-1。例:
  glVertex2i(2,4);   glVertex3f(2.0,4.0,5.0);
  注意:有的函数参数类型后缀前带有数字二、三、4。2表明二维,3表明三维,4表明alpha值(之后介绍)。   有些OpenGL函数最后带一个字母v,表示函数参数可用一个指针指向一个向量(或数组)来替代一系列单个参数值。下面两种格式都表示设置当前颜色为红色,两者等价。
  glColor3f(1.0,0.0,0.0);   float color_array[]={1.0,0.0,0.0};   glColor3fv(color_array);
  除了以上基本命名方式外,还有一种带“*”星号的表示方法,例如glColor*(),它表示能够用函数的各类方式来设置当前颜色。同理,glVertex*v()表示用一个指针指向全部类型的向量来定义一系列顶点坐标值。   最后,OpenGL也定义GLvoid类型,若是用C语言编写,能够用它替代void类型。




6、OpenGL辅组库的基本使用

  OpenGL是一个开放的系统,它是独立于任何窗口系统或操做系统的。尽管它包含了许多图形函数,但它却没有窗口函数,也没有从键盘和鼠标读取事件的函数,因此要初学者写出一个完整的图形程序是至关困难的。另外,OpenGL图形函数中只提供基本的几何原形:点、线、多边形,所以要建立基本的三维几何体如球、锥体等,也很不容易。而OpenGL辅助库就是为解决这些基本问题专门设计的,它提供了一些基本的窗口管理函数和三维图形绘制函数,能帮助初学者尽快进入OpenGL世界,掌握关键的三维图形技术,体会其中奇妙的乐趣。可是,对于复杂的应用,这些函数远远不够,只能做为参考。

6.一、辅助库函数分类
  这一节内容能够做为手册查阅,初学者没必要深究。
  辅助库函数大体分为六类:

  6.1.1 窗口初始化和退出   相关函数有三个,它们在第一章已提到,这里将详细介绍:

  void auxInitWindow(GLbyte *titleString)

  打开一个由auxInitDisplayMode()和auxInitPosition()指定的窗口。函数参数是窗口标题,窗口背景缺省颜色是RGBA下的黑色或颜色表(color_index)下的0号调色板的颜色。按下Escape键能够完成关掉窗口、结束程序、所有清屏三项功能。

  void auxInitDisplayMode(GLbitfield mask)

  设置窗口显示模式。基本模式有RGBA或颜色表、单或双缓存,也可指定其余附加模式:深度、模板或累积缓存(depth,stencil,and/or accumulation buffer)。参数mask是一组位标志的联合(取或),AUX_RGBA或AUX_INDEX、AUX_SINGLE或AUX_DOUBLE,以及其它有效标志AUX_DEPTH、AUX_STENCIL或AUX_ACCUM。

  void auxInitPosition(GLint x,GLint y,GLsizei width,GLsizei height)

  设置窗口位置及大小。参数(x, y)为窗口的左上角点的屏幕坐标,参数(width, height)为窗口的宽度和高度,单位为象素,缺省值为(0, 0, 100, 100)。

  6.1.2 窗口处理和事件输入
  当窗口建立后,且在进入主函数循环以前,应当登记如下列出的回调函数(callback function):

  void auxReshapeFunc(void(*function)(GLsizei,GLsizei))

  定义窗口改变时形状重定函数。参数function是一个函数指针,这个函数带有两个参数,即窗口改变后的新宽度和新高度。一般,function是 glViewport(),显示裁减后的新尺寸,重定义投影矩阵,以便使投影后图像的比例与视点匹配,避免比例失调。若不调用 auxReshapeFunc(),缺省重定物体形状的函数功能是调用一个二维的正射投影矩阵。运用辅助库,窗口将在每一个事件改变后自动从新绘制。

  void auxKeyFunction(GLint key,void(*function)(void))

  定义键盘响应函数。参数function就是当按下key键时所调用的函数指针,辅助库为参数key定义了几个常量:AUX_0至AUX_九、 AUX_A至AUX_Z、AUX_a至AUX_z、AUX_LEFT、AUX_RIGHT、AUX_UP、AUX_DOWN(方向键)、 AUX_ESCAPE、AUX_SPACE或AUX_RETURN。

  void auxMouseFunc(GLint button,Glint mode,void(*function)(AUX_EVENTREC *))

  定义鼠标响应函数。参数function就是当鼠标以mode方式做用于button时所调用的函数。参数button有 AUX_LEFTBUTTON、AUX_MIDDLEBUTTON或AUX_RIGHTBUTTON(以右手为标准)。参数mode表明鼠标触击状态,击中时为AUX_MOUSEDOWN,释放时为AUX_MOUSEUP。参数function必须带一个参数,它是指向结构AUX_EVENNTREC的指针。当函数auxMouseFunc()被调用时将为这个结构分配相应的内存。一般用法相似以下:

  void function(AUX_EVENTREC *event)   {     GLint x,y;     x=event->data[AUX_MOUSEX];     y=event->data[AUX_MOUSEY];     ...   }
  6.1.3 颜色表装入
  由于OpenGL自己没有窗口系统,因此依赖于窗口系统的颜色映射就无法装入颜色查找表。若是采用颜色表模式,就要用到辅助库提供的用RGB值定义的单个颜色索引函数:

  void auxSetOneColor(GLint index,GLfloat red,GLfloat green,GLfloat blue)
  设置自定义颜色的索引。参数index即索引号,参数red、green、blue分别为红、绿、蓝值,范围在(0~1)内。

  6.1.4 三维物体绘制
  每组三维物体包括两种形式:网状体(wire)和实心体(solid)。网状体没有平面法向,而实心体有,能进行光影计算,有光照时采用实心体模型。下面这些函数的 参数都是定义物体大小的,能够改变。

数组

功能
函数
绘制球
void auxWireSphere(GLdouble radius) void auxSolidSphere(GLdouble radius)
绘制立方体
void auxWireCube(GLdouble size) void auxSolidCube(GLdouble size)
绘制长方体
void auxWireBox(GLdouble width,GLdouble height,GLdouble depth) void auxSolidBox(GLdouble width,GLdouble height,GLdouble depth)
绘制环形圆纹面
void auxWireTorus(GLdouble innerRadius,GLdouble outerRadius) void auxSolidTorus(GLdouble innerRadius,GLdouble outerRadius)
绘制圆柱
void auxWireCylinder(GLdouble radius,GLdouble height) void auxSolidCylinder(GLdouble radius,GLdouble height)
绘制二十面体
void auxWireIcosahedron(GLdouble radius) void auxSolidIcosahedron(GLdouble radius)
绘制八面体
void auxWireOctahedron(GLdouble radius) void auxSolidOctahedron(GLdouble radius)
绘制四面体
void auxWireTetrahedron(GLdouble radius) void auxSolidTetrahedron(GLdouble radius)
绘制十二面体
void auxWireDodecahedron(GLdouble radius) void auxSolidDodecahedron(GLdouble radius)
绘制圆锥
void auxWireCone(GLdouble radius,GLdouble height) void auxSolidCone(GLdouble radius,GLdouble height)
绘制茶壶
void auxWireTeapot(GLdouble size) void aucSolidTeapot(GLdouble size)
表6-1


  以上物体均以各自中心为原点绘制,全部坐标都已单位化,能够缩放。

  6.1.5 背景过程管理

  void auxIdleFunc(void *func)
  定义空闲状态执行函数。参数func是一个指针,指向所要执行的函数功能。当它为零时,func执行无效。

  6.1.6 程序运行

  void auxMainLoop(void(*displayFunc)(void))
  定义场景绘制循环函数。displayFunc指针指向场景绘制函数。当窗口须要更新或场景发生改变时,程序便调用它所指的函数,从新绘制场景。

6.二、辅助库应用示例
  下面举一个辅助库的应用例子,testaux.c:

  例6-1 辅助库应用例程 testaux.c

  #include "glos.h"   #include <GL/gl.h>   #include <GL/glaux.h>
  void myinit(void);   void CALLBACK myReshape(GLsizei w,GLsizei h);   void CALLBACK display(void);
  void myinit(void)   {     glClearColor(0.0,0.0,0.0,0.0);     glClear(GL_COLOR_BUFFER_BIT);   }
  void CALLBACK myReshape(GLsizei w,GLsizei h)   {     glViewport(0,0,w,h);     glMatrixMode(GL_PROJECTION);     glLoadIdentity();     if(w<=h)       glOrtho(-1.5,1.5,-1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w,-10.0,10.0);     else       glOrtho(-1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w,-1.5,1.5,-10.0,10.0);     glMatrixMode(GL_MODELVIEW);     glLoadIdentity();   }
  void CALLBACK display(void)   {     glColor3f(1.0,1.0,0.0);     auxWireSphere(1.0);     glFlush();   }
  void main(void)   {     auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);     auxInitPosition(0,0,500,500);     auxInitWindow("AUX_SAMPLE");     myinit();     auxReshapeFunc(myReshape);     auxMainLoop(display);   }

缓存

图6-1 网状球体


  以上程序运行结果是在屏幕窗口内绘制一个黄色的网状球体,这个程序充分体现了辅助库的基本应用方法。
  首先,在主函数中用辅助库函数定义一个窗口auxInitWindow(),而后初始化颜色myinit(),这些在第一章中已说明。接下来是两个十分重要的函数 auxReshapeFunc()和auxMainLoop(),参数都是一个函数指针,指向的都是回调函数(回调函数定义用CALLBACK说明)。
  前者是窗口形状重定函数,参数指针指向函数myReshape(),它的两个参数就是窗口的新宽度和新高度。而后用glViewport(0, 0, w, h)重定视口,而且在新视口内从新定义投影矩阵,

  glMatrixMode(GL_PROJECTION);   glLoadIdentity();   if(w<=h)     glOrtho(-1.5,1.5,-1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w,-10.0,10.0);   else     glOrtho(-1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w,-1.5,1.5,-10.0,10.0);

  即先用glMatrixMode()说明当前矩阵操做与投影有关GL_PROJECTION,再用glLoadIdentity()将矩阵清为单位矩阵,避免受其它矩阵操做的干扰;而后调用glOrtho()对物体进行正射投影,而且用判断语句给出了两种状况,使投影后图像的比例与视点匹配,避免比例失调。
  再下来调用glMatrixMode()将矩阵操做改成对观察物体有关的方式GL_MODELVIEW,一样用 glLoadIdentity()清矩阵。后者是主函数循环函数,参数指针指向函数display(),即绘制物体。当窗口须要更新或物体发生改变时,程序便调用它从新绘制。以上例子是辅助库的最基本应用,复杂的应用将在后续的章节中详细介绍。



7、OpenGL建模
  OpenGL基本库提供了大量绘制各类类型图元的方法,辅助库也提供了很多描述复杂三维图形的函数。这一章主要介绍基本图元,如点、线、多边形,有了这些图元,就能够创建比较复杂的模型了。

7.一、描述图元
  OpenGL是三维图形的函数库,它所定义的点、线、多边形等图元与通常的定义不太同样,存在必定的差异。对编程者来讲,可否理解两者之间的差异十分重要。一种差异源于基于计算机计算的限制。OpenGL中全部浮点计算精度有限,故点、线、多边形的坐标值存在必定的偏差。另外一种差异源于位图显示的限制。以这种方式显示图形,最小的显示图元是一个象素,尽管每一个象素宽度很小,但它们仍然比数学上所定义的点或线宽要大得多。当用OpenGL 进行计算时,虽然是用一系列浮点值定义点串,但每一个点仍然是用单个象素显示,只是近似拟合。
  OpenGL图元是抽象的几何概念,不是真实世界中的物体,所以须用相关的数学模型来描述。

  7.1.1 齐次坐标Homogeneous Coordinate
  在空间直角坐标系中,任意一点可用一个三维坐标矩阵[x y z]表示。若是将该点用一个四维坐标的矩阵[Hx Hy Hz H]表示时,则称为齐次坐标表示方法。在齐次坐标中,最后一维坐标H称为比例因子。
  在OpenGL中,二维坐标点全看做三维坐标点,全部的点都用齐次坐标来描述,统一做为三维齐次点来处理。每一个齐次点用一个向量(x, y, z, w)表示,其中四个元素全不为零。齐次点具备下列几个性质:
  1)若是实数a非零,则(x, y, x, w)和(ax, ay, az, aw)表示同一个点,相似于x/y = (ax)/( ay)。
  2)三维空间点(x, y, z)的齐次点坐标为(x, y, z, 1.0),二维平面点(x,y)的齐次坐标为(x, y, 0.0, 1.0)。
  3)当w不为零时,齐次点坐标(x, y, z, w)即三维空间点坐标(x/w, y/w, z/w);当w为零时,齐次点(x, y, z, 0.0)表示此点位于某方向的无穷远处。
  注意:OpenGL中指定w大于或等于0.0。

  7.1.2 点(Point)
  用浮点值表示的点称为顶点(Vertex)。全部顶点在OpenGL内部计算时都做为三维点处理,用二维坐标(x, y)定义的点在OpenGL中默认z值为0。全部顶点坐标用齐次坐标(x, y, z, w) 表示,若是w不为0.0,这些齐次坐标表示的顶点即为三维空间点(x/w, y/w, z/w)。编程者能够本身指定w值,但不多这样作。通常来讲,w缺省为1.0。

  7.1.3 线(Line)
  在OpenGL中,线表明线段(Line Segment),不是数学意义上的那种沿轴两个方向无限延伸的线。这里的线由一系列顶点顺次连结而成,有闭合和不闭合两种。见图7-1所示。

服务器

图7-1 线段的两种连结方式


  7.1.4 多边形(Polygon)
  OpenGL中定义的多边形是由一系列线段依次连结而成的封闭区域。这些线段不能交叉,区域内不能有空洞,多边形必须在凸多边形,不然不能被OpenGL函数接受。合法和非法多边形图示见图7-2。

网络

图7-2 合法和非法多边形


  OpenGL多边形能够是平面多边形,即全部顶点在一个平面上,也能够是空间多边形。更复杂的多边形将在提升篇中介绍。

7.二、绘制图元

  7.2.1 定义顶点
  在OpenGL中,全部几何物体最终都由有必定顺序的顶点集来描述。
  函数glVertex{234}{sifd}[v](TYPE coords)能够用二维、三维或齐次坐标定义顶点。举例以下:

  glVertex2s(2,3);   glVertex3d(0.0,1.0,3.1414926535);   glVertex4f(2.4,1.0,-2.2,2.0);   GLfloat pp[3]={5.0,2.0,10.2};   glVertex3fv(pp);
  第一例子表示一个空间顶点(2, 3, 0),第二个例子表示用双精度浮点数定义一个顶点,第三个例子表示用齐次坐标定义一个顶点,其真实坐标为(1.2, 0.5, -1.1),最后一个例子表示用一个指针(或数组)定义顶点。

  7.2.2 构造几何图元
  在实际应用中,一般用一组相关的顶点序列以必定的方式组织起来定义某个几何图元,而不采用单独定义多个顶点来构造几何图元。在OpenGL中,全部被定义的顶点必须放在glBegain()和glEnd()两个函数之间才能正确表达一个几何图元或物体,不然,glVertex*()不完成任何操做。如:

  glBegin(GL_POLYGON);     glVertex2f(0.0,0.0);     glVertex2f(0.0,3.0);     glVertex2f(3.0,3.0);     glVertex2f(4.0,1.5);     glVertex2f(3.0,0.0);   glEnd();
  以上这段程序定义了一个多边形,若是将glBegin()中的参数GL_POLYGON改成GL_POINTS,则图形变为一组顶点(5个),见图7-3所示。
  
app

图7-3 绘制多边形或一组顶点


  点函数glBegin(GLenum mode)标志描述一个几何图元的顶点列表的开始,其参数mode表示几何图元的描述类型。全部类型及说明见表7-1所示,相应的图示见图7-4。

函数

类型 说明
GL_POINTS 单个顶点集
GL_LINES 多组双顶点线段
GL_POLYGON 单个简单填充凸多边形
GL_TRAINGLES 多组独立填充三角形
GL_QUADS 多组独立填充四边形
GL_LINE_STRIP 不闭合折线
GL_LINE_LOOP 闭合折线
GL_TRAINGLE_STRIP 线型连续填充三角形串
GL_TRAINGLE_FAN 扇形连续填充三角形串
GL_QUAD_STRIP 连续填充四边形串
表7-1 几何图元类型和说明


图7-4 几何图元类型


  函数glEnd()标志顶点列表的结束。
  从图7-4中可看出,能够采用许多方法构造几何图元,这些方法仅仅依赖于所给的顶点数据。
  在glBegin()和glEnd()之间最重要的信息就是由函数glVertex*()定义的顶点,必要时也可为每一个顶点指定颜色、法向、纹理坐标或其余,即调用相关的函数,见表7-2所示,具体用法之后会逐步介绍。

工具

函数 函数意义
glVertex*() 设置顶点坐标
glColor*() 设置当前颜色
glIndex*() 设置当前颜色表
glNormal*() 设置法向坐标
glEvalCoord*() 产生坐标
glCallList(),glCallLists() 执行显示列表
glTexCoord*() 设置纹理坐标
glEdgeFlag*() 控制边界绘制
glMaterial*() 设置材质
表7-2 在glBegin()和glEnd()之间可调用的函数


  看以下几句:

  glBegin(GL_POINTS);     glColor3f(1.0,0.0,0.0); /* red color */     glVertex(...);     glColor3f(0.0,1.0,0.0); /* green color */     glColor3f(0.0,0.0,1.0); /* blue color */     glVertex(...);     glVertex(...);   glEnd();
  颜色等的设置只对当前点或后续点有效。上一例中第一个点是红色,第二个点和第三个点都是蓝色。其中设置绿色时,以后没有顶点操做,而是设置蓝色,故只有当前蓝色对紧跟其后的两个顶点有效。
  为了更好地理解构造几何图元函数的用法,下面举一个简单的例子:

  例7-3 几何图元构造例程drawgeom.c

  #include "glos.h"   #include<GL/gl.h>   #include<GL/glaux.h>
  void myinit(void);   void DrawMyObjects(void);   void CALLBACK myReshape(GLsizei w,GLsizei h);   void CALLBACK display(void);
  void myinit(void)   {     glClearColor(0.0,0.0,0.0,0.0);     glClear(GL_COLOR_BUFFER_BIT);     glShadeModel(GL_FLAT);   }
  void CALLBACK myReshape(GLsizei w,GLsizei h)   {     glViewport(0,0,w,h);     glMatrixMode(GL_PROJECTION);     glLoadIdentity();
    if(w<=h)       glOrtho(-20.0,20.0,-20.0*(GLfloat)h/(GLfloat)w, 20.0*(GLfloat)h/(GLfloat)w,-50.0,50.0);     else       glOrtho(-20.0*(GLfloat)h/(GLfloat)w, 20.0*(GLfloat)h/(GLfloat)w,-20.0,20.0,-50.0,50.0);     glMatrixMode(GL_MODELVIEW);     glLoadIdentity();   }
  void CALLBACK display(void)   {     glColor3f(1.0,1.0,0.0);     DrawMyObjects();     glFlush();   }
  void DrawMyObjects(void)   {     /* draw some points */     glBegin(GL_POINTS);       glColor3f(1.0,0.0,0.0);       glVertex2f(-10.0,11.0);       glColor3f(1.0,1.0,0.0);       glVertex2f(-9.0,10.0);       glColor3f(0.0,1.0,1.0);       glVertex2f(-8.0,12.0);     glEnd();
    /* draw some line_segments */     glBegin(GL_LINES);       glColor3f(1.0,1.0,0.0);       glVertex2f(-11.0,8.0);       glVertex2f(-7.0,7.0);       glColor3f(1.0,0.0,1.0);       glVertex2f(-11.0,9.0);       glVertex2f(-8.0,6.0);     glEnd();
    /* draw one opened_line */     glBegin(GL_LINE_STRIP);       glColor3f(0.0,1.0,0.0);       glVertex2f(-3.0,9.0);       glVertex2f(2.0,6.0);       glVertex2f(3.0,8.0);       glVertex2f(-2.5,6.5);     glEnd();
    /* draw one closed_line */     glBegin(GL_LINE_LOOP);       glColor3f(0.0,1.0,1.0);       glVertex2f(7.0,7.0);       glVertex2f(8.0,8.0);       glVertex2f(9.0,6.5);       glVertex2f(10.3,7.5);       glVertex2f(11.5,6.0);       glVertex2f(7.5,6.0);     glEnd();
    /* draw one filled_polygon */     glBegin(GL_POLYGON);       glColor3f(0.5,0.3,0.7);       glVertex2f(-7.0,2.0);       glVertex2f(-8.0,3.0);       glVertex2f(-10.3,0.5);       glVertex2f(-7.5,-2.0);       glVertex2f(-6.0,-1.0);     glEnd();
    /* draw some filled_quandrangles */     glBegin(GL_QUADS);       glColor3f(0.7,0.5,0.2);       glVertex2f(0.0,2.0);       glVertex2f(-1.0,3.0);       glVertex2f(-3.3,0.5);       glVertex2f(-0.5,-1.0);       glColor3f(0.5,0.7,0.2);       glVertex2f(3.0,2.0);       glVertex2f(2.0,3.0);       glVertex2f(0.0,0.5);       glVertex2f(2.5,-1.0);     glEnd();
    /* draw some filled_strip_quandrangles */     glBegin(GL_QUAD_STRIP);       glVertex2f(6.0,-2.0);       glVertex2f(5.5,1.0);       glVertex2f(8.0,-1.0);       glColor3f(0.8,0.0,0.0);       glVertex2f(9.0,2.0);       glVertex2f(11.0,-2.0);       glColor3f(0.0,0.0,0.8);       glVertex2f(11.0,2.0);       glVertex2f(13.0,-1.0);       glColor3f(0.0,0.8,0.0);       glVertex2f(14.0,1.0);     glEnd();
    /* draw some filled_triangles */     glBegin(GL_TRIANGLES);       glColor3f(0.2,0.5,0.7);       glVertex2f(-10.0,-5.0);       glVertex2f(-12.3,-7.5);       glVertex2f(-8.5,-6.0);       glColor3f(0.2,0.7,0.5);       glVertex2f(-8.0,-7.0);       glVertex2f(-7.0,-4.5);       glVertex2f(-5.5,-9.0);     glEnd();
    /* draw some filled_strip_triangles */     glBegin(GL_TRIANGLE_STRIP);       glVertex2f(-1.0,-8.0);       glVertex2f(-2.5,-5.0);       glColor3f(0.8,0.8,0.0);       glVertex2f(1.0,-7.0);       glColor3f(0.0,0.8,0.8);       glVertex2f(2.0,-4.0);       glColor3f(0.8,0.0,0.8);       glVertex2f(4.0,-6.0);     glEnd();
    /* draw some filled_fan_triangles */     glBegin(GL_TRIANGLE_FAN);       glVertex2f(8.0,-6.0);       glVertex2f(10.0,-3.0);       glColor3f(0.8,0.2,0.5);       glVertex2f(12.5,-4.5);       glColor3f(0.2,0.5,0.8);       glVertex2f(13.0,-7.5);       glColor3f(0.8,0.5,0.2);       glVertex2f(10.5,-9.0);     glEnd();   }
  void main(void)   {     auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);     auxInitPosition(0,0,500,500);     auxInitWindow("Geometric Primitive Types");     myinit();     auxReshapeFunc(myReshape);     auxMainLoop(display);   }
  以上程序运行结果就是图7-4所示的内容,这个例子很好地说明了几何图元的类型及颜色等函数的用法。但愿读者本身仔细分析每一个物体的绘制方法,体会其中的关键之处,达到触类旁通的效果。固然,还可利用上一章辅助库中提供的基本三维图元构造比较复杂的物体,你不妨也试一试。

8、OpenGL变换
  OpenGL变换是本篇的重点内容,它包括计算机图形学中最基本的三维变换,即几何变换、投影变换、裁剪变换、视口变换,以及针对OpenGL的特殊变换概念理解和用法,如相机模拟、矩阵堆栈等。学好了这章,才开始真正走进三维世界。

8.一、从三维空间到二维平面

  8.1.1 相机模拟
  在真实世界里,全部的物体都是三维的。可是,这些三维物体在计算机世界中却必须以二维平面物体的形式表现出来。那么,这些物体是怎样从三维变换到二维的呢?下面咱们采用相机(Camera)模拟的方式来说述这个概念,如图8-1所示。

图8-1 相机模拟


  实际上,从三维空间到二维平面,就如同用相机拍照同样,一般都要经历如下几个步骤 (括号内表示的是相应的图形学概念):
  第一步,将相机置于三角架上,让它对准三维景物(视点变换,Viewing Transformation)。
  第二步,将三维物体放在适当的位置(模型变换,Modeling Transformation)。
  第三步,选择相机镜头并调焦,使三维物体投影在二维胶片上(投影变换,Projection Transformation)。
  第四步,决定二维像片的大小(视口变换,Viewport Transformation)。
  这样,一个三维空间里的物体就能够用相应的二维平面物体表示了,也就能在二维的电脑屏幕上正确显示了。

  8.1.2 三维图形显示流程
  运用相机模拟的方式比较通俗地讲解了三维图形显示的基本过程,但在具体应用OpenGL函数库编程时,还必须了解三维图形世界中的几个特殊坐标系的概念,以及用这些概念表达的三维图形显示流程。
  计算机自己只能处理数字,图形在计算机内也是以数字的形式进行加工和处理的。你们都知道,坐标创建了图形和数字之间的联系。为了使被显示的物体数字化,要在被显示的物体所在的空间中定义一个坐标系。这个坐标系的长度单位和坐标轴的方向要适合对被显示物体的描述,这个坐标系称为世界坐标系。
  计算机对数字化的显示物体做了加工处理后,要在图形显示器上显示,这就要在图形显示器屏幕上定义一个二维直角坐标系,这个坐标系称为屏幕坐标系。这个坐标系坐标轴的方向一般取成平行于屏幕的边缘,坐标原点取在左下角,长度单位常取成一个象素的长度,大小能够是整型数。
  为了使显示的物体能以合适的位置、大小和方向显示出来,必需要经过投影。投影的方法有两种,即正射投影和透视投影。
  有时为了突出图形的一部分,只把图形的某一部分显示出来,这时能够定义一个三维视景体(Viewing Volume)。正射投影时通常是一个长方体的视景体,透视投影时通常是一个棱台似的视景体。只有视景体内的物体能被投影在显示平面上,其余部分则不能。在屏幕窗口内能够定义一个矩形,称为视口(Viewport),视景体投影后的图形就在视口内显示。
  为了适应物理设备坐标和视口所在坐标的差异,还要做一适应物理坐标的变换。这个坐标系称为物理设备坐标系。根据上面所述,三维图形的显示流程应如图8-2所示。

图8-2 三维图形的显示流程


  8.1.3 基本变换简单分析
  下面举一个简单的变换例子,cube.c:

  例8-4 简单变换例程cube.c

  #include "glos.h"   #include <GL/gl.h>   #include <GL/glu.h>   #include <GL/glaux.h>
  void myinit(void);   void CALLBACK myReshape(GLsizei w, GLsizei h);   void CALLBACK display(void);
  void CALLBACK display (void)   {     glClear(GL_COLOR_BUFFER_BIT);     glColor3f (1.0, 1.0, 1.0);     glLoadIdentity (); /* clear the matrix */     glTranslatef (0.0, 0.0, -5.0); /* viewing transformation */     glScalef (1.0, 2.0, 1.0); /* modeling transformation */     auxWireCube(1.0); /* draw the cube */     glFlush();   }
  void myinit (void)   {     glShadeModel (GL_FLAT);   }
  void CALLBACK myReshape(GLsizei w, GLsizei h)   {     glMatrixMode (GL_PROJECTION); /* prepare for and then */     glLoadIdentity (); /* define the projection */     glFrustum (-1.0, 1.0, -1.0, 1.0, 1.5, 20.0); /* transformation */     glMatrixMode (GL_MODELVIEW); /* back to modelview matrix */     glViewport (0, 0, w, h); /* define the viewport */   }
  void main(void)   {     auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);     auxInitPosition (0, 0, 500, 500);     auxInitWindow ("Perspective 3-D Cube");     myinit ();     auxReshapeFunc (myReshape);     auxMainLoop(display);   }
  以上程序运行结果就是绘制一个三维的正面透视立方体。其中已经用到了相机模拟中提到的四种基本变换,即视点变换、模型变换、投影变换和视口变换。

图8-3 三维的正面透视立方体


  下面简单分析一下整个程序过程:
  1)视点变换。视点变换是在视点坐标系中进行的。视点坐标系于通常的物体所在的世界坐标系不一样,它遵循左手法则,即左手大拇指指向Z正轴,与之垂直的四个手指指向X正轴,四指弯曲90度的方向是Y正轴。而世界坐标系遵循右手法则的。如图8-4所示。当矩阵初始化glLoadIdentity()后,调用glTranslatef()做视点变换。函数参数(x, y, z)表示视点或相机在视点坐标系中移动的位置,这里z=-5.0,意思是将相机沿Z负轴移动5个单位。
  一般相机位置缺省值同场景中的物体同样,都在原点处,并且相机初始方向都指向Z负轴。
  这里相机移走后,仍然对准立方体。若是相机须要指向另外一方向,则调用glRotatef()能够改变。

图8-4 视点坐标系与世界坐标系


  2)模型变换。模型变换是在世界坐标系中进行的。在这个坐标系中,能够对物体实施平移 glTranslatef()、旋转glRotatef()和放大缩小glScalef()。例子里只对物体进行比例变换,glScalef(sx, sy, sz)的三个参数分别是X、Y、Z轴向的比例变换因子。缺省时都为1.0,即物体没变化。程序中物体Y轴比例为2.0,其他都为1.0,就是说将立方体变成长方体。
  3)投影变换。投影变换相似于选择相机的镜头。本例中调用了一个透视投影函数 glFrustum(),在调用它以前先要用glMatrixMode()说明当前矩阵方式是投影GL_PROJECTION。这个投影函数一共有六个参数,由它们能够定义一个棱台似的视景体。即视景体内的部分可见,视景体外的部分不可见,这也就包含了三维裁剪变换。
  4)视口变换。视口变换就是将视景体内投影的物体显示在二维的视口平面上。一般,都调用函数glViewport()来定义一个视口,这个过程相似于将照片放大或缩小。
  总而言之,一旦全部必要的变换矩阵被指定后,场景中物体的每个顶点都要按照被指定的变换矩阵序列逐一进行变换。注意:OpenGL 中的物体坐标一概采用齐次坐标,即(x, y, z, w),故全部变换矩阵都采用4X4矩阵。通常说来,每一个顶点先要通过视点变换和模型变换,而后进行指定的投影,若是它位于视景体外,则被裁剪掉。最后,余下的已经变换过的顶点x、y、z坐标值都用比例因子w除,即x/w、y/w、z/w,再映射到视口区域内,这样才能显示在屏幕上。

8.二、几何变换
  实际上,上述所说的视点变换和模型变换本质上都是一回事,即图形学中的几何变换。
  只是视点变换通常只有平移和旋转,没有比例变换。当视点进行平移或旋转时,视点坐标系中的物体就至关于在世界坐标系中做反方向的平移或旋转。所以,从某种意义上讲,两者能够统一,只是各自出发点不同而已。读者能够根据具体状况,选择其中一个角度去考虑,这样便于理解。

  8.2.1 两个矩阵函数解释
  这里先解释两个基本OpenGL矩阵操做函数,便于之后章节的讲述。函数解释以下:

  void glLoadMatrix{fd}(const TYPE *m)
  设置当前矩阵中的元素值。函数参数*m是一个指向16个元素(m0, m1, ..., m15)的指针,这16个元素就是当前矩阵M中的元素,其排列方式以下:

M = | m0 m4 m8 m12 | | m1 m5 m9 m13 | | m2 m6 m10 m14 | | m3 m7 m11 M15 |


  void glMultMatrix{fd}(const TYPE *m)
  用当前矩阵去乘*m所指定的矩阵,并将结果存放于*m中。当前矩阵能够是用glLoadMatrix() 指定的矩阵,也能够是其它矩阵变换函数的综合结果。
  当几何变换时,调用OpenGL的三个变换函数glTranslate*()、glRotate*()和glScale*(),实质上至关于产生了一个近似的平移、旋转和比例矩阵,而后调用glMultMatrix()与当前矩阵相乘。可是直接调用这三个函数程序运行得快一些,因OpenGL自动能计算矩阵。

  8.2.2 平移
  平移变换函数以下:

  void glTranslate{fd}(TYPE x,TYPE y,TYPE z)
  三个函数参数就是目标分别沿三个轴向平移的偏移量。这个函数表示用这三个偏移量生成的矩阵乘以当前矩阵。当参数是(0.0,0.0,0.0)时,表示对函数glTranslate*()的操做是单位矩阵,也就是对物体没有影响。平移示意如图8-5所示。

图8-5 平移示意图


  8.2.3 旋转
  旋转变换函数以下:

  void glRotate{fd}(TYPE angle,TYPE x,TYPE y,TYPE z)

  函数中第一个参数是表示目标沿从点(x, y, z)到原点的方向逆时针旋转的角度,后三个参数是旋转的方向点坐标。这个函数表示用这四个参数生成的矩阵乘以当前矩阵。当角度参数是0.0时,表示对物体没有影响。旋转示意如图8-6所示。

图8-6 旋转示意图


  8.2.3 缩放和反射
  缩放和反射变换函数以下:

  void glScale{fd}(TYPE x,TYPE y,TYPE z)
  三个函数参数值就是目标分别沿三个轴向缩放的比例因子。这个函数表示用这三个比例因子生成的矩阵乘以当前矩阵。这个函数能完成沿相应的轴对目标进行拉伸、压缩和反射三项功能。当参数是(1.0, 1.0, 1.0)时,表示对函数glScale*()操做是单位矩阵,也就是对物体没有影响。当其中某个参数为负值时,表示将对目标进行相应轴的反射变换,且这个参数不为1.0,则还要进行相应轴的缩放变换。最好不要令三个参数值都为零,这将致使目标沿三轴都缩为零。缩放和反射示意如图8-7所示。

图8-7 缩放和反射示意图


  8.2.5 几何变换举例
  以上介绍了三个基本几何变换函数,下面举一个简单的例子进一步说明它们的用法。程序以下:

  例 8-5 几何变换例程geomtrsf.c

  #include "glos.h"   #include <GL/gl.h>   #include <GL/glu.h>   #include <GL/glaux.h>
  void myinit(void);   void draw_triangle(void);   void CALLBACK display(void);   void CALLBACK myReshape(GLsizei w, GLsizei h);
  void draw_triangle(void)   {     glBegin(GL_LINE_LOOP);       glVertex2f(0.0, 25.0);       glVertex2f(25.0, -25.0);       glVertex2f(-25.0, -25.0);     glEnd();   }
  void CALLBACK display(void)   {     glClearColor (0.0, 0.0, 0.0, 1.0);     glClear (GL_COLOR_BUFFER_BIT);
    /* draw an original triangle */     glLoadIdentity ();     glColor3f (1.0, 1.0, 1.0);  /* white */     draw_triangle ();
    /* translating a triangle along X_axis */     glLoadIdentity ();     glTranslatef (-20.0, 0.0, 0.0);     glColor3f(1.0,0.0,0.0);   /* red */     draw_triangle ();
    /* scaling a triangle along X_axis by 1.5 and along Y_axis by 0.5 */     glLoadIdentity();     glScalef (1.5, 0.5, 1.0);     glColor3f(0.0,1.0,0.0);   /* green */     draw_triangle ();
    /* rotating a triangle in a counterclockwise direction about Z_axis */     glLoadIdentity ();     glRotatef (90.0, 0.0, 0.0, 1.0);     glColor3f(0.0,0.0,1.0);   /* blue */     draw_triangle ();
    /* scaling a triangle along Y_axis and reflecting it about Y_axis */     glLoadIdentity();     glScalef (1.0, -0.5, 1.0);     glColor3f(1.0,1.0,0.0);   /* yellow */     draw_triangle ();
    glFlush();   }
  void myinit (void)   {     glShadeModel (GL_FLAT);   }
  void CALLBACK myReshape(GLsizei w, GLsizei h)   {     glViewport(0, 0, w, h);     glMatrixMode(GL_PROJECTION);     glLoadIdentity();     if (w <= h)       glOrtho(-50.0, 50.0, -50.0*(GLfloat)h/(GLfloat)w, 50.0*(GLfloat)h/(GLfloat)w,-1.0,1.0);     else       glOrtho(-50.0*(GLfloat)w/(GLfloat)h, 50.0*(GLfloat)w/(GLfloat)h, -50.0, 50.0,-1.0,1.0);     glMatrixMode(GL_MODELVIEW);   }
  void main(void)   {     auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);     auxInitPosition (0, 0, 500, 500);     auxInitWindow ("Geometric Transformations");     myinit ();     auxReshapeFunc (myReshape);     auxMainLoop(display);   }
  以上程序运行结果:第一个白色三角形是原始三角形,第二个红色三角形是白三角沿X 负轴平移后的三角形,第三个绿色三角形是白三角分别沿X轴和Y轴比例变换后的三角形,第四个蓝色三角形是白三角绕Z正轴逆时针转90度后的三角形,第五个黄色三角形是白三角沿Y轴方向缩小一倍且相对于X轴做反射后造成的三角形。

图8-8 三角形的几何变换


8.三、投影变换
  投影变换是一种很关键的图形变换,OpenGL中只提供了两种投影方式,一种是正射投影,另外一种是透视投影。无论是调用哪一种投影函数,为了不没必要要的变换,其前面必须加上如下两句:

  glMAtrixMode(GL_PROJECTION);   glLoadIdentity();
  事实上,投影变换的目的就是定义一个视景体,使得视景体外多余的部分裁剪掉,最终图像只是视景体内的有关部分。本指南将详细讲述投影变换的概念以及用法。

  8.3.1 正射投影Orthographic Projection
  正射投影,又叫平行投影。这种投影的视景体是一个矩形的平行管道,也就是一个长方体,如图8-9所示。正射投影的最大一个特色是不管物体距离相机多远,投影后的物体大小尺寸不变。这种投影一般用在建筑蓝图绘制和计算机辅助设计等方面,这些行业要求投影后的物体尺寸及相互间的角度不变,以便施工或制造时物体比例大小正确。

图8-9 正射投影视景体


  OpenGL正射投影函数共有两个,这在前面几个例子中已用过。一个函数是:

  void glOrtho(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top,       GLdouble near,GLdouble far)
  它建立一个平行视景体。实际上这个函数的操做是建立一个正射投影矩阵,而且用这个矩阵乘以当前矩阵。其中近裁剪平面是一个矩形,矩形左下角点三维空间坐标是(left,bottom,-near),右上角点是(right,top,-near);远裁剪平面也是一个矩形,左下角点空间坐标是(left,bottom,-far),右上角点是(right,top,-far)。全部的near和far值同时为正或同时为负。若是没有其余变换,正射投影的方向平行于Z轴,且视点朝向Z负轴。
  这意味着物体在视点前面时far和near都为负值,物体在视点后面时far和near都为正值。另外一个函数是:

  void gluOrtho2D(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top)
  它是一个特殊的正射投影函数,主要用于二维图像到二维屏幕上的投影。它的near和far缺省值分别为-1.0和1.0,全部二维物体的Z坐标都为0.0。所以它的裁剪面是一个左下角点为(left,bottom)、右上角点为(right,top)的矩形。

  8.3.2 透视投影Perspective Projection
  透视投影符合人们心理习惯,即离视点近的物体大,离视点远的物体小,远到极点即为消失,成为灭点。它的视景体相似于一个顶部和底部都被切除掉的棱椎,也就是棱台。这个投影一般用于动画、视觉仿真以及其它许多具备真实性反映的方面。
  OpenGL透视投影函数也有两个,其中函数glFrustum()在8.1.3节中提到过,它所造成的视景体如图8-10所示。

图8-10 函数glFrustum()透视投影视景体


  这个函数原型为:

  void glFrustum(GLdouble left,GLdouble Right,GLdouble bottom,GLdouble top,       GLdouble near,GLdouble far);

  它建立一个透视视景体。其操做是建立一个透视投影矩阵,而且用这个矩阵乘以当前矩阵。这个函数的参数只定义近裁剪平面的左下角点和右上角点的三维空间坐标,即(left,bottom,-near)和(right,top,-near);最后一个参数far是远裁剪平面的Z负值,其左下角点和右上角点空间坐标由函数根据透视投影原理自动生成。near和far表示离视点的远近,它们总为正值。
  另外一个函数是:

  void gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear, GLdouble zFar);
  它也建立一个对称透视视景体,但它的参数定义于前面的不一样,如图8-11所示。其操做是建立一个对称的透视投影矩阵,而且用这个矩阵乘以当前矩阵。参数 fovy定义视野在X-Z平面的角度,范围是[0.0, 180.0];参数aspect是投影平面宽度与高度的比率;参数zNear和Far分别是远近裁剪面沿Z负轴到视点的距离,它们总为正值。

图8-11 函数gluPerspective()透视投影视景体


  以上两个函数缺省时,视点都在原点,视线沿Z轴指向负方向。两者的应用实例将在后续章节中介绍。

8.四、裁剪变换
  在OpenGL中,空间物体的三维裁剪变换包括两个部分:视景体裁剪和附加平面裁剪。视景体裁剪已经包含在投影变换里,前面已述,这里再也不重复。下面简单讲一下平面裁剪函数的用法。
  除了视景体定义的六个裁剪平面(上、下、左、右、前、后)外,用户还可本身再定义一个或多个附加裁剪平面,以去掉场景中无关的目标,如图8-12所示。

图8-12 附加裁剪平面和视景体


  附加平面裁剪函数为:

  void glClipPlane(GLenum plane,Const GLdouble *equation);
  函数定义一个附加的裁剪平面。其中参数equation指向一个拥有四个系数值的数组,这四个系数分别是裁剪平面Ax+By+Cz+D=0的A、B、 C、D值。所以,由这四个系数就能肯定一个裁剪平面。参数plane是GL_CLIP_PLANEi(i=0,1,...),指定裁剪面号。
  在调用附加裁剪函数以前,必须先启动glEnable(GL_CLIP_PLANEi),使得当前所定义的裁剪平面有效;当再也不调用某个附加裁剪平面时,可用glDisable(GL_CLIP_PLANEi)关闭相应的附加裁剪功能。
  下面这个例子不只说明了附加裁剪函数的用法,并且调用了gluPerspective()透视投影函数,读者能够细细体会其中的用法。例程以下:

  例8-6 裁剪变换例程clipball.c)

  #include "glos.h"   #include <GL/gl.h>   #include <GL/glu.h>   #include <GL/glaux.h>
  void myinit(void);   void CALLBACK myReshape(GLsizei w, GLsizei h);   void CALLBACK display(void);
  void CALLBACK display(void)   {     GLdouble eqn[4] = {1.0, 0.0, 0.0, 0.0};
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f (1.0, 0.0, 1.0);     glPushMatrix();     glTranslatef (0.0, 0.0, -5.0);
    /* clip the left part of wire_sphere : x<0 */     glClipPlane (GL_CLIP_PLANE0, eqn);     glEnable (GL_CLIP_PLANE0);     glRotatef (-90.0, 1.0, 0.0, 0.0);     auxWireSphere(1.0);     glPopMatrix();     glFlush();   }
  void myinit (void)   {     glShadeModel (GL_FLAT);   }
  void CALLBACK myReshape(GLsizei w, GLsizei h)   {     glViewport(0, 0, w, h);     glMatrixMode(GL_PROJECTION);     glLoadIdentity();     gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);     glMatrixMode(GL_MODELVIEW);   }
  void main(void)   {     auxInitDisplayMode (AUX_SINGLE | AUX_RGB);     auxInitPosition (0, 0, 500, 500);     auxInitWindow ("Arbitrary Clipping Planes");     myinit ();     auxReshapeFunc (myReshape);     auxMainLoop(display);   }

图8-13 剪取后的网状半球体


8.五、视口变换
  在前面几节内容中已相继提到过视口变换,这一节将针对OpenGL来说述视口变换的原理及其相关函数的用法。运用相机模拟方式,咱们很容易理解视口变换就是相似于照片的放大与缩小。在计算机图形学中,它的定义是将通过几何变换、投影变换和裁剪变换后的物体显示于屏幕窗口内指定的区域内,这个区域一般为矩形,称为视口。OpenGL中相关函数是:

  glViewport(GLint x,GLint y,GLsizei width, GLsizei height);
  这个函数定义一个视口。函数参数(x, y)是视口在屏幕窗口坐标系中的左下角点坐标,参数width和height分别是视口的宽度和高度。缺省时,参数值即(0, 0, winWidth, winHeight) 指的是屏幕窗口的实际尺寸大小。全部这些值都是以象素为单位,全为整型数。
  注意:在实际应用中,视口的长宽比率老是等于视景体裁剪面的长宽比率。若是两个比率不相等,那么投影后的图像显示于视口内时会发生变形,如图8-14所示。另外,屏幕窗口的改变通常不明显影响视口的大小。所以,在调用这个函数时,最好实时检测窗口尺寸,及时修正视口的大小,保证视口内的图像能随窗口的变化而变化,且不变形。

图8-14 视景体到视口的映射


8.6 矩阵堆栈
  学过计算机的人也许都知道这个使用频率极高的名词 — “堆栈”。顾名思义,堆栈指的是一个顶部打开底部封闭的柱状物体,一般用来存放经常使用的东西。这些东西从顶部依次放入,但取出时也只能从顶部取出,即“先进后出,后进先出”。在计算机中,它常指在内存中开辟的一块存放某些变量的连续区域。所以,OpenGL的矩阵堆栈指的就是内存中专门用来存放矩阵数据的某块特殊区域。
  实际上,在建立、装入、相乘模型变换和投影变换矩阵时,都已用到堆栈操做。通常说来,矩阵堆栈经常使用于构造具备继承性的模型,即由一些简单目标构成的复杂模型。例如,一辆自行车就是由两个轮子、一个三角架及其它一些零部件构成的。它的继承性表如今当自行车往前走时,首先是前轮旋转,而后整个车身向前平移,接着是后轮旋转,而后整个车身向前平移,如此进行下去,这样自行车就往前走了。
  矩阵堆栈对复杂模型运动过程当中的多个变换操做之间的联系与独立十分有利。由于全部矩阵操做函数如glLoadMatrix()、glMultMatrix()、 glLoadIdentity()等只处理当前矩阵或堆栈顶部矩阵,这样堆栈中下面的其它矩阵就不受影响。堆栈操做函数有如下两个:

  void glPushMatrix(void);   void glPopMatrix(void);
  第一个函数表示将全部矩阵依次压入堆栈中,顶部矩阵是第二个矩阵的备份;压入的矩阵数不能太多,不然出错。第二个函数表示弹出堆栈顶部的矩阵,令原第二个矩阵成为顶部矩阵,接受当前操做,故原顶部矩阵被破坏;当堆栈中仅存一个矩阵时,不能进行弹出操做,不然出错。由此看出,矩阵堆栈操做与压入矩阵的顺序恰好相反,编程时要特别注意矩阵操做的顺序。
  为了更好地理解这两个函数,咱们能够形象地认为glPushMatrix()就是“记住本身在哪”,glPopMatrix()就是“返回本身原来所在地”。请看下面一例:

  例8-7 堆栈操做例程arm.c

  #include "glos.h"   #include <GL/gl.h>   #include <GL/glu.h>   #include <GL/glaux.h>
  void myinit(void);   void drawPlane(void);   void CALLBACK elbowAdd (void);   void CALLBACK elbowSubtract (void);   void CALLBACK shoulderAdd (void);   void CALLBACK shoulderSubtract (void);   void CALLBACK display(void);   void CALLBACK myReshape(GLsizei w, GLsizei h);
  static int shoulder = 0, elbow = 0;
  void CALLBACK elbowAdd (void)   {     elbow = (elbow + 5) % 360;   }
  void CALLBACK elbowSubtract (void)   {     elbow = (elbow - 5) % 360;   }
  void CALLBACK shoulderAdd (void)   {     shoulder = (shoulder + 5) % 360;   }
  void CALLBACK shoulderSubtract (void)   {     shoulder = (shoulder - 5) % 360;   }
  void CALLBACK display(void)   {     glClear(GL_COLOR_BUFFER_BIT);     glColor3f(0.0, 1.0, 1.0);
    glPushMatrix();     glTranslatef (-0.5, 0.0, 0.0);     glRotatef ((GLfloat)     shoulder, 0.0, 0.0, 1.0);     glTranslatef (1.0, 0.0, 0.0);     auxWireBox(2.0, 0.2, 0.5);
    glTranslatef (1.0, 0.0, 0.0);     glRotatef ((GLfloat) elbow, 0.0, 0.0, 1.0);     glTranslatef (0.8, 0.0, 0.0);     auxWireBox(1.6, 0.2, 0.5);
    glPopMatrix();     glFlush();   }
  void myinit (void)   {     glShadeModel (GL_FLAT);   }
  void CALLBACK myReshape(GLsizei w, GLsizei h)   {     glViewport(0, 0, w, h);     glMatrixMode(GL_PROJECTION);     glLoadIdentity();     gluPerspective(65.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);     glMatrixMode(GL_MODELVIEW);     glLoadIdentity(); glTranslatef (0.0, 0.0, -5.0); /* viewing transform */   }
  void main(void)   {     auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);     auxInitPosition (0, 0, 400, 400);     auxInitWindow ("Composite Modeling Transformations");
    myinit ();
    auxKeyFunc (AUX_LEFT, shoulderSubtract);     auxKeyFunc (AUX_RIGHT, shoulderAdd);     auxKeyFunc (AUX_UP, elbowAdd);     auxKeyFunc (AUX_DOWN, elbowSubtract);     auxReshapeFunc (myReshape);     auxMainLoop(display);   }
  从以上例程能够看出,复杂的机械手臂是由两个简单的长方体依据必定的继承关系构成的,而这个继承关系是由矩阵堆栈的顺序决定的。

图8-15 简单机械手臂的符合运动
相关文章
相关标签/搜索