OpenGL游戏程序设计ppt.ppt

上传人:za****8 文档编号:13189934 上传时间:2020-06-07 格式:PPT 页数:144 大小:254.50KB
返回 下载 相关 举报
OpenGL游戏程序设计ppt.ppt_第1页
第1页 / 共144页
OpenGL游戏程序设计ppt.ppt_第2页
第2页 / 共144页
OpenGL游戏程序设计ppt.ppt_第3页
第3页 / 共144页
点击查看更多>>
资源描述
OpenGL游戏程序设计,第一章OpenGL概述,一、OpenGL的特点1、与C语言紧密结合。OpenGL命令最初就是用C语言函数来进行描述的,对于学习过C语言的人来讲,OpenGL是容易理解和学习的。如果你曾经接触过TC的graphics.h,你会发现,使用OpenGL作图甚至比TC更加简单。,2、强大的可移植性微软的Direct3D虽然也是十分优秀的图形API,但它只用于Windows系统(现在还要加上一个XBOX游戏机)。而OpenGL不仅用于Windows,还可以用于Unix/Linux等其它系统,它甚至在大型计算机、各种专业计算机(如:医疗用显示设备)上都有应用。并且,OpenGL的基本命令都做到了硬件无关,甚至是平台无关。,3、高性能的图形渲染。OpenGL是一个工业标准,它的技术紧跟时代,现今各个显卡厂家无一不对OpenGL提供强力支持,激烈的竞争中使得OpenGL性能一直领先,OpenGL程序准备工作,第一步,选择一个编译环境现在Windows系统的主流编译环境有VisualStudio,BrolandC+Builder等都是支持OpenGL的。但这里我们选择VisualC+作为学习OpenGL的环境。,第二步,安装GLUT工具包GLUT不是OpenGL所必须的,但它会给我们的学习带来一定的方便,推荐安装。Windows环境下安装GLUT的步骤:1、将下载的压缩包解开,将得到5个文件2、在“我的电脑”中搜索“gl.h”,并找到其所在文件夹。把解压得到的glut.h放到这个文件夹。3、把解压得到的glut.lib和glut32.lib放到静态函数库所在文件夹(如果是VisualC+,则应该是其安装目录下面的“VClib”文件夹)。4、把解压得到的glut.dll和glut32.dll放到操作系统目录下面的system32文件夹内。(典型的位置为:C:WindowsSystem32),第三步,建立一个OpenGL工程这里以VisualC+6.0为例。选择File-New-Project,然后选择Win32ConsoleApplication,选择一个名字,然后按OK。在谈出的对话框左边点ApplicationSettings,找到Emptyproject并勾上,选择Finish。然后向该工程添加一个代码文件。,第一个OpenGL程序,见文本文件“第一个OpenGL文本文件”,第二章基本图元绘制,OpenGL函数形式OpenGL函数通常以gl开头,中间是函数相关功能单词,后面跟一个数字和12个字母。例如:glVertex2dglVertex2fglVertex3fglVertex3fv数字表示参数的个数,2表示有两个参数,3表示三个,4表示四个,字母表示参数的类型:s表示16位整数(OpenGL中将这个类型定义为GLshort);i表示32位整数(OpenGL中将这个类型定义为GLint和GLsizei);f表示32位浮点数(OpenGL中将这个类型定义为GLfloat和GLclampf);d表示64位浮点数(OpenGL中将这个类型定义为GLdouble和GLclampd)。v表示传递的几个参数将使用指针的方式,见下面的例子。这些函数除了参数的类型和个数不同以外,功能是相同的。,例如,以下五个代码段的功能是等效的:(一)glVertex2i(1,3);(二)glVertex2f(1.0f,3.0f);(三)glVertex3f(1.0f,3.0f,0.0f);(四)glVertex4f(1.0f,3.0f,0.0f,1.0f);(五)GLfloatVertexArr3=1.0f,3.0f,0.0f;glVertex3fv(VertexArr3);,OpenGL要求:指定顶点的命令必须包含在glBegin函数之后,glEnd函数之前(否则指定的顶点将被忽略)。并由glBegin来指明如何使用这些点。,例如:glBegin(GL_POINTS);glVertex2f(0.0f,0.0f);glVertex2f(0.5f,0.0f);glEnd();则这两个点将分别被画出来。如果将GL_POINTS替换成GL_LINES,则两个点将被认为是直线的两个端点,OpenGL将会画出一条直线。,glBegin支持的方式除了GL_POINTS和GL_LINES,还有GL_LINE_STRIP,GL_LINE_LOOP,GL_TRIANGLES,GL_TRIANGLE_STRIP,GL_TRIANGLE_FAN等,每种方式的大致效果见下图:,程序代码:voidmyDisplay(void)glClear(GL_COLOR_BUFFER_BIT);glBegin(/*在这里填上你所希望的模式*/);/*在这里使用glVertex*系列函数*/*指定你所希望的顶点位置*/glEnd();glFlush();,三个课堂练习,见三个相关文本文件,第三章图元绘制的深入,1、关于点点的大小默认为1个像素,但也可以改变之。改变的命令为glPointSize,其函数原型如下:voidglPointSize(GLfloatsize);size必须大于0.0f,默认值为1.0f,单位为“像素”。注意:对于具体的OpenGL实现,点的大小都有个限度的,如果设置的size超过最大值,则设置可能会有问题。,voidmyDisplay(void)glClear(GL_COLOR_BUFFER_BIT);glPointSize(5.0f);glBegin(GL_POINTS);glVertex2f(0.0f,0.0f);glVertex2f(0.5f,0.5f);glEnd();glFlush();,2、关于直线(1)直线可以指定宽度:voidglLineWidth(GLfloatwidth);其用法跟glPointSize类似。(2)画虚线。首先,使用glEnable(GL_LINE_STIPPLE);来启动虚线模式(使用glDisable(GL_LINE_STIPPLE)可以关闭之)。然后,使用glLineStipple来设置虚线的样式。voidglLineStipple(GLintfactor,GLushortpattern);pattern是由1和0组成的长度为16的序列,从最低位开始看,如果为1,则直线上接下来应该画的factor个点将被画为实的;如果为0,则直线上接下来应该画的factor个点将被画为虚的。,voidmyDisplay(void)glClear(GL_COLOR_BUFFER_BIT);glEnable(GL_LINE_STIPPLE);glLineStipple(2,0 x0F0F);glLineWidth(10.0f);glBegin(GL_LINES);glVertex2f(0.0f,0.0f);glVertex2f(0.5f,0.5f);glEnd();glFlush();,3、关于多边形(1)多边形的两面以及绘制方式。虽然我们目前还没有真正的使用三维坐标来画图,但是建立一些三维的概念还是必要的。从三维的角度来看,一个多边形具有两个面。每一个面都可以设置不同的绘制方式:填充、只绘制边缘轮廓线、只绘制顶点,其中“填充”是默认的方式。可以为两个面分别设置不同的方式。glPolygonMode(GL_FRONT,GL_FILL);/设置正面为填充方式glPolygonMode(GL_BACK,GL_LINE);/设置反面为边缘绘制方式glPolygonMode(GL_FRONT_AND_BACK,GL_POINT);/设置两面均为顶点绘制方式,(2)反转一般约定为“顶点以逆时针顺序出现在屏幕上的面”为“正面”,另一个面即成为“反面”。生活中常见的物体表面,通常都可以用这样的“正面”和“反面”,“合理的”被表现出来(请找一个比较透明的矿泉水瓶子,在正对你的一面沿逆时针画一个圆,并标明画的方向,然后将背面转为正面,画一个类似的圆,体会一下“正面”和“反面”。你会发现正对你的方向,瓶的外侧是正面,而背对你的方向,瓶的内侧才是正面。正对你的内侧和背对你的外侧则是反面。这样一来,同样属于“瓶的外侧”这个表面,但某些地方算是正面,某些地方却算是反面了)。可以通过glFrontFace函数来交换“正面”和“反面”的概念。glFrontFace(GL_CCW);/设置CCW方向为“正面”,CCW即CounterClockWise,逆时针glFrontFace(GL_CW);/设置CW方向为“正面”,CW即ClockWise,顺时针,voidmyDisplay(void)glClear(GL_COLOR_BUFFER_BIT);glPolygonMode(GL_FRONT,GL_FILL);/设置正面为填充模式glPolygonMode(GL_BACK,GL_LINE);/设置反面为线形模式glFrontFace(GL_CCW);/设置逆时针方向为正面glBegin(GL_POLYGON);/按逆时针绘制一个正方形,在左下方glVertex2f(-0.5f,-0.5f);glVertex2f(0.0f,-0.5f);glVertex2f(0.0f,0.0f);glVertex2f(-0.5f,0.0f);glEnd();glBegin(GL_POLYGON);/按顺时针绘制一个正方形,在右上方glVertex2f(0.0f,0.0f);glVertex2f(0.0f,0.5f);glVertex2f(0.5f,0.5f);glVertex2f(0.5f,0.0f);glEnd();glFlush();,(3)剔除多边形表面在三维空间中,一个多边形虽然有两个面,但我们无法看见背面的那些多边形,而一些多边形虽然是正面的,但被其他多边形所遮挡。如果将无法看见的多边形和可见的多边形同等对待,无疑会降低我们处理图形的效率。在这种时候,可以将不必要的面剔除。首先,使用glEnable(GL_CULL_FACE);来启动剔除功能(使用glDisable(GL_CULL_FACE)可以关闭之)然后,使用glCullFace来进行剔除。glCullFace的参数可以是GL_FRONT,GL_BACK或者GL_FRONT_AND_BACK,分别表示剔除正面、剔除反面、剔除正反两面的多边形。注意:剔除功能只影响多边形,而对点和直线无影响。例如,使用glCullFace(GL_FRONT_AND_BACK)后,所有的多边形都将被剔除,所以看见的就只有点和直线。,(4)镂空多边形直线可以被画成虚线,而多边形则可以进行镂空。首先,使用glEnable(GL_POLYGON_STIPPLE);来启动镂空模式(使用glDisable(GL_POLYGON_STIPPLE)可以关闭之)。然后,使用glPolygonStipple来设置镂空的样式。voidglPolygonStipple(constGLubyte*mask);其中的参数mask指向一个长度为128字节的空间,它表示了一个32*32的矩形应该如何镂空。其中:第一个字节表示了最左下方的从左到右(也可以是从右到左,这个可以修改)8个像素是否镂空(1表示不镂空,显示该像素;0表示镂空,显示其后面的颜色),最后一个字节表示了最右上方的8个像素是否镂空。,第四章颜色模型,OpenGL支持两种颜色模式:一种是RGBA,一种是颜色索引模式。无论哪种颜色模式,计算机都必须为每一个像素保存一些数据。不同的是,RGBA模式中,数据直接就代表了颜色;而颜色索引模式中,数据代表的是一个索引,要得到真正的颜色,还必须去查索引表。,1.RGBA颜色RGBA模式中,每一个像素会保存以下数据:R值(红色分量)、G值(绿色分量)、B值(蓝色分量)和A值(alpha分量)。其中红、绿、蓝三种颜色相组合,就可以得到我们所需要的各种颜色,而alpha不直接影响颜色,它将留待以后介绍。在RGBA模式下选择颜色是十分简单的事情,只需要一个函数就可以搞定。glColor*系列函数可以用于设置颜色,其中三个参数的版本可以指定R、G、B的值,而A值采用默认;四个参数的版本可以分别指定R、G、B、A的值。例如:voidglColor3f(GLfloatred,GLfloatgreen,GLfloatblue);voidglColor4f(GLfloatred,GLfloatgreen,GLfloatblue,GLfloatalpha);,将浮点数作为参数,其中0.0表示不使用该种颜色,而1.0表示将该种颜色用到最多。例如:glColor3f(1.0f,0.0f,0.0f);表示不使用绿、蓝色,而将红色使用最多,于是得到最纯净的红色。glColor3f(0.0f,1.0f,1.0f);表示使用绿、蓝色到最多,而不使用红色。混合的效果就是浅蓝色。glColor3f(0.5f,0.5f,0.5f);表示各种颜色使用一半,效果为灰色。注意:浮点数可以精确到小数点后若干位,这并不表示计算机就可以显示如此多种颜色。实际上,计算机可以显示的颜色种数将由硬件决定。如果OpenGL找不到精确的颜色,会进行类似“四舍五入”的处理。,voidmyDisplay(void)glClear(GL_COLOR_BUFFER_BIT);glColor3f(0.0f,1.0f,1.0f);glRectf(-0.5f,-0.5f,0.5f,0.5f);glFlush();,注意:glColor系列函数,在参数类型不同时,表示“最大”颜色的值也不同。采用f和d做后缀的函数,以1.0表示最大的使用。采用b做后缀的函数,以127表示最大的使用。采用ub做后缀的函数,以255表示最大的使用。采用s做后缀的函数,以32767表示最大的使用。采用us做后缀的函数,以65535表示最大的使用。,2、索引颜色在索引颜色模式中,OpenGL需要一个颜色表。这个表就相当于画家的调色板:虽然可以调出很多种颜色,但同时存在于调色板上的颜色种数将不会超过调色板的格数。试将颜色表的每一项想象成调色板上的一个格子:它保存了一种颜色。在使用索引颜色模式画图时,我说“我把第i种颜色设置为某某”,其实就相当于将调色板的第i格调为某某颜色。“我需要第k种颜色来画图”,那么就用画笔去蘸一下第k格调色板。颜色表的大小是很有限的,一般在2564096之间,且总是2的整数次幂。在使用索引颜色方式进行绘图时,总是先设置颜色表,然后选择颜色。,2.1、选择颜色使用glIndex*系列函数可以在颜色表中选择颜色。其中最常用的可能是glIndexi,它的参数是一个整形。voidglIndexi(GLintc);,3、指定清除屏幕用的颜色glClear(GL_COLOR_BUFFER_BIT);意思是把屏幕上的颜色清空。使用glClearColor来指定“空”的颜色,它需要四个参数,其参数的意义跟glColor4f相似。在索引颜色模式下,使用glClearIndex来指定“空”的颜色所在的索引,它需要一个参数,其意义跟glIndexi相似。voidmyDisplay(void)glClearColor(1.0f,0.0f,0.0f,0.0f);glClear(GL_COLOR_BUFFER_BIT);glFlush();,4、指定着色模型OpenGL允许为同一多边形的不同顶点指定不同的颜色。例如:#includeconstGLdoublePi=3.1415926536;voidmyDisplay(void)inti;/glShadeModel(GL_FLAT);glClear(GL_COLOR_BUFFER_BIT);glBegin(GL_TRIANGLE_FAN);glColor3f(1.0f,1.0f,1.0f);glVertex2f(0.0f,0.0f);for(i=0;i=8;+i)glColor3f(i,使用glShadeModel函数可以关闭这种计算,如果顶点的颜色不同,则将顶点之间的其它点全部设置为与某一个点相同。(直线以后指定的点的颜色为准,而多边形将以任意顶点的颜色为准,由实现决定。)为了避免这个不确定性,尽量在多边形中使用同一种颜色。,glShadeModel的使用方法:glShadeModel(GL_SMOOTH);/平滑方式,这也是默认方式glShadeModel(GL_FLAT);/单色方式,第五章变换,我们生活在一个三维的世界如果要观察一个物体,我们可以:1、从不同的位置去观察它。(视图变换)2、移动或者旋转它,当然了,如果它只是计算机里面的物体,我们还可以放大或缩小它。(模型变换)3、如果把物体画下来,我们可以选择:是否需要一种“近大远小”的透视效果。另外,我们可能只希望看到物体的一部分,而不是全部(剪裁)。(投影变换)4、我们可能希望把整个看到的图形画下来,但它只占据纸张的一部分,而不是全部。(视口变换),OpenGL变换实际上是通过矩阵乘法来实现。无论是移动、旋转还是缩放大小,都是通过在当前矩阵的基础上乘以一个新的矩阵来达到目的。OpenGL可以在最底层直接操作矩阵,不过作为初学,这样做的意义并不大。,1、模型变换和视图变换从“相对移动”的观点来看,改变观察点的位置与方向和改变物体本身的位置与方向具有等效性。在OpenGL中,实现这两种功能甚至使用的是同样的函数。由于模型和视图的变换都通过矩阵运算来实现,在进行变换前,应先设置当前操作的矩阵为“模型视图矩阵”。设置的方法是以GL_MODELVIEW为参数调用glMatrixMode函数,像这样:glMatrixMode(GL_MODELVIEW);,通常,我们需要在进行变换前把当前矩阵设置为单位矩阵。这也只需要一行代码:glLoadIdentity();,进行模型和视图变换,主要涉及到三个函数:glTranslate*,把当前矩阵和一个表示移动物体的矩阵相乘。三个参数分别表示了在三个坐标上的位移值。glRotate*,把当前矩阵和一个表示旋转物体的矩阵相乘。物体将绕着(0,0,0)到(x,y,z)的直线以逆时针旋转,参数angle表示旋转的角度。glScale*,把当前矩阵和一个表示缩放物体的矩阵相乘。x,y,z分别表示在该方向上的缩放比例。,假设当前矩阵为单位矩阵,然后先乘以一个表示旋转的矩阵R,再乘以一个表示移动的矩阵T,最后得到的矩阵再乘上每一个顶点的坐标矩阵v。所以,经过变换得到的顶点坐标就是(RT)v)。由于矩阵乘法的结合率,(RT)v)=(R(Tv),换句话说,实际上是先进行移动,然后进行旋转。即:实际变换的顺序与代码中写的顺序是相反的。,OpenGL之所以这样设计,是为了得到更高的效率。但在绘制复杂的三维图形时,如果每次都去考虑如何把变换倒过来,也是很痛苦的事情。这里介绍另一种思路,可以让代码看起来更自然(写出的代码其实完全一样,只是考虑问题时用的方法不同了)。让我们想象,坐标并不是固定不变的。旋转的时候,坐标系统随着物体旋转。移动的时候,坐标系统随着物体移动。如此一来,就不需要考虑代码的顺序反转的问题了。,以上都是针对改变物体的位置和方向来介绍的。如果要改变观察点的位置,除了配合使用glRotate*和glTranslate*函数以外,还可以使用这个函数:gluLookAt。它的参数比较多,前三个参数表示了观察点的位置,中间三个参数表示了观察目标的位置,最后三个参数代表从(0,0,0)到(x,y,z)的直线,它表示了观察者认为的“上”方向。,2、投影变换投影变换就是定义一个可视空间,可视空间以外的物体不会被绘制到屏幕上。(注意,从现在起,坐标可以不再是-1.0到1.0了!)OpenGL支持两种类型的投影变换,即透视投影和正投影。投影也是使用矩阵来实现的。如果需要操作投影矩阵,需要以GL_PROJECTION为参数调用glMatrixMode函数。glMatrixMode(GL_PROJECTION);通常,我们需要在进行变换前把当前矩阵设置为单位矩阵。glLoadIdentity();,透视投影所产生的结果类似于照片,有近大远小的效果,比如在火车头内向前照一个铁轨的照片,两条铁轨似乎在远处相交了。使用glFrustum函数可以将当前的可视空间设置为透视投影空间。其参数的意义如下图:,也可以使用更常用的gluPerspective函数。其参数的意义如下图:,正投影相当于在无限远处观察得到的结果,它只是一种理想状态。但对于计算机来说,使用正投影有可能获得更好的运行速度。使用glOrtho函数可以将当前的可视空间设置为正投影空间。其参数的意义如下图:,如果绘制的图形空间本身就是二维的,可以使用gluOrtho2D。他的使用类似于glOrgho。,3、视口变换当一切工作已经就绪,只需要把像素绘制到屏幕上了。这时候还剩最后一个问题:应该把像素绘制到窗口的哪个区域呢?通常情况下,默认是完整的填充整个窗口,但我们完全可以只填充一半。(即:把整个图象填充到一半的窗口内),使用glViewport来定义视口。其中前两个参数定义了视口的左下脚(0,0表示最左下方),后两个参数分别是宽度和高度。,4、操作矩阵堆栈我们在进行矩阵操作时,有可能需要先保存某个矩阵,过一段时间再恢复它。当我们需要保存时,调用glPushMatrix函数,它相当于把矩阵(相当于盘子)放到堆栈上。当需要恢复最近一次的保存时,调用glPopMatrix函数,它相当于把矩阵从堆栈上取下。OpenGL规定堆栈的容量至少可以容纳32个矩阵,某些OpenGL实现中,堆栈的容量实际上超过了32个。因此不必过于担心矩阵的容量问题。通常,用这种先保存后恢复的措施,比先变换再逆变换要更方便,更快速。注意:模型视图矩阵和投影矩阵都有相应的堆栈。使用glMatrixMode来指定当前操作的究竟是模型视图矩阵还是投影矩阵。,综合举例(robot.c),第六章动画,想必大家都知道电影和动画的工作原理吧?是的,快速的把看似连续的画面一幅幅的呈现在人们面前。一旦每秒钟呈现的画面超过24幅,人们就会错以为它是连续的。我们通常观看的电视,每秒播放25或30幅画面。但对于计算机来说,它可以播放更多的画面,以达到更平滑的效果。如果速度过慢,画面不够平滑。如果速度过快,则人眼未必就能反应得过来。对于一个正常人来说,每秒60120幅图画是比较合适的。具体的数值因人而异。,假设某动画一共有n幅画面,则它的工作步骤就是:显示第1幅画面,然后等待一小段时间,直到下一个1/24秒显示第2幅画面,然后等待一小段时间,直到下一个1/24秒显示第n幅画面,然后等待一小段时间,直到下一个1/24秒结束如果用C语言伪代码来描述这一过程,就是:for(i=0;in;+i)DrawScene(i);Wait();,双缓冲技术在计算机上的动画与实际的动画有些不同:实际的动画都是先画好了,播放的时候直接拿出来显示就行。计算机动画则是画一张,就拿出来一张,再画下一张,再拿出来。如果所需要绘制的图形很简单,那么这样也没什么问题。但一旦图形比较复杂,绘制需要的时间较长,问题就会变得突出。让我们把计算机想象成一个画图比较快的人,假如他直接在屏幕上画图,而图形比较复杂,则有可能在他只画了某幅图的一半的时候就被观众看到。而后面虽然他把画补全了,但观众的眼睛却又没有反应过来,还停留在原来那个残缺的画面上。也就是说,有时候观众看到完整的图象,有时却又只看到残缺的图象,这样就造成了屏幕的闪烁。如何解决这一问题呢?我们设想有两块画板,画图的人在旁边画,画好以后把他手里的画板与挂在屏幕上的画板相交换。这样以来,观众就不会看到残缺的画了。这一技术被应用到计算机图形中,称为双缓冲技术。即:在存储器(很有可能是显存)中开辟两块区域,一块作为发送到显示器的数据,一块作为绘画的区域,在适当的时候交换它们。由于交换两块内存区域实际上只需要交换两个指针,这一方法效率非常高,所以被广泛的采用。注意:虽然绝大多数平台都支持双缓冲技术,但这一技术并不是OpenGL标准中的内容。OpenGL为了保证更好的可移植性,允许在实现时不使用双缓冲技术。当然,我们常用的PC都是支持双缓冲技术的。,要启动双缓冲功能,最简单的办法就是使用GLUT工具包。我们以前在main函数里面写:glutInitDisplayMode(GLUT_RGB|GLUT_SINGLE);其中GLUT_SINGLE表示单缓冲,如果改成GLUT_DOUBLE就是双缓冲了。当然还有需要更改的地方每次绘制完成时,我们需要交换两个缓冲区,把绘制好的信息用于屏幕显示(否则无论怎么绘制,还是什么都看不到)。如果使用GLUT工具包,也可以很轻松的完成这一工作,只要在绘制完成时简单的调用glutSwapBuffers函数就可以了。,2、实现连续动画似乎没有任何疑问,我们应该把绘制动画的代码写成下面这个样子:for(i=0;i=360)day=0;myDisplay();intmain(intargc,char*argv)glutInit(,3、关于垂直同步代码是写好了,但相信大家还有疑问。某些朋友可能在运行时发现,虽然CPU几乎都用上了,但运动速度很快,根本看不清楚,另一些朋友在运行时发现CPU使用率很低,根本就没有把空闲时间完全利用起来。但对于上面那段代码来说,这些现象都是合理的。这里就牵涉到关于垂直同步的问题。,大家知道显示器的刷新率是比较有限的,一般为60120Hz,也就是一秒钟刷新60120次。但如果叫计算机绘制一个简单的画面,例如只有一个三角形,则一秒钟可以绘制成千上万次。因此,如果最大限度的利用计算机的处理能力,绘制很多幅画面,但显示器的刷新速度却跟不上,这不仅造成性能的浪费,还可能带来一些负面影响(例如,显示器只刷新到一半时,需要绘制的内容却变化了,由于显示器是逐行刷新的,于是显示器上半部分和下半部分实际上是来自两幅画面)。,采用垂直同步技术可以解决这一问题。即,只有在显示器刷新时,才把绘制好的图象传输出去供显示。这样一来,计算机就不必去绘制大量的根本就用不到的图象了。如果显示器的刷新率为85Hz,则计算机一秒钟只需要绘制85幅图象就足够,如果场景足够简单,就会造成比较多的CPU空闲。几乎所有的显卡都支持“垂直同步”这一功能。,垂直同步也有它的问题。如果刷新频率为60Hz,则在绘制比较简单的场景时,绘制一幅图画需要的时间很段,帧速可以恒定在60FPS(即60帧/秒)。如果场景变得复杂,绘制一幅图画的时间超过了1/60秒,则帧速将急剧下降。如果绘制一幅图画的时间为1/50,则在第一个1/60秒时,显示器需要刷新了,但由于新的图画没有画好,所以只能显示原来的图画,等到下一个1/60秒时才显示新的图画。于是显示一幅图画实际上用了1/30秒,帧速为30FPS。(如果不采用垂直同步,则帧速应该是50FPS),如果绘制一幅图画的时间更长,则下降的趋势就是阶梯状的:60FPS,30FPS,20FPS,(60/1,60/2,60/3,)如果每一幅图画的复杂程度是不一致的,且绘制它们需要的时间都在1/60上下。则在1/60时间内画完时,帧速为60FPS,在1/60时间未完成时,帧速为30FPS,这就造成了帧速的跳动。这是很麻烦的事情,需要避免它要么想办法简化每一画面的绘制时间,要么都延迟一小段时间,以作到统一。,4、计算帧速不知道大家玩过3DMark这个软件没有,它可以运行各种场景,测出帧速,并且为你的系统给出评分。这里我也介绍一个计算帧速的方法。根据定义,帧速就是一秒钟内播放的画面数目(FPS)。我们可以先测量绘制两幅画面之间时间t,然后求它的倒数即可。假如t=0.05s,则FPS的值就是1/0.05=20。理论上是如此了,可是如何得到这个时间呢?通常C语言的time函数精确度一般只到一秒,肯定是不行了。clock函数也就到十毫秒左右,还是有点不够。因为FPS为60和FPS为100的时候,t的值都是十几毫秒。你知道如何测量一张纸的厚度吗?一个粗略的办法就是:用很多张纸叠在一起测厚度,计算平均值就可以了。我们这里也可以这样办。测量绘制50幅画面(包括垂直同步等因素的等待时间)需要的时间t,由t=t*50很容易的得到FPS=1/t=50/t,第七章光照,从生理学的角度上讲,眼睛之所以看见各种物体,是因为光线直接或间接的从它们那里到达了眼睛。人类对于光线强弱的变化的反应,比对于颜色变化的反应来得灵敏。因此对于人类而言,光线很大程度上表现了物体的立体感。请看图1,图中绘制了两个大小相同的白色球体。其中右边的一个是没有使用任何光照效果的,它看起来就像是一个二维的圆盘,没有立体的感觉。左边的一个是使用了简单的光照效果的,我们通过光照的层次,很容易的认为它是一个三维的物体。OpenGL对于光照效果提供了直接的支持,只需要调用某些函数,便可以实现简单的光照效果。,一、建立光照模型在现实生活中,某些物体本身就会发光,例如太阳、电灯等,而其它物体虽然不会发光,但可以反射来自其它物体的光。这些光通过各种方式传播,最后进入我们的眼睛于是一幅画面就在我们的眼中形成了。,就目前的计算机而言,要准确模拟各种光线的传播,这是无法做到的事情。比如一个四面都是粗糙墙壁的房间,一盏电灯所发出的光线在很短的时间内就会经过非常多次的反射,最终几乎布满了房间的每一个角落,这一过程即使使用目前运算速度最快的计算机,也无法精确模拟。不过,我们并不需要精确的模拟各种光线,只需要找到一种近似的计算方式,使它的最终结果让我们的眼睛认为它是真实的,这就可以了。,OpenGL在处理光照时采用这样一种近似:把光照系统分为三部分,分别是光源、材质和光照环境。光源就是光的来源,可以是前面所说的太阳或者电灯等。材质是指接受光照的各种物体的表面,由于物体如何反射光线只由物体表面决定(OpenGL中没有考虑光的折射),材质特点就决定了物体反射光线的特点。光照环境是指一些额外的参数,它们将影响最终的光照画面,比如一些光线经过多次反射后,已经无法分清它究竟是由哪个光源发出,这时,指定一个“环境亮度”参数,可以使最后形成的画面更接近于真实情况。,在物理学中,光线如果射入理想的光滑平面,则反射后的光线是很规则的(这样的反射称为镜面反射)。光线如果射入粗糙的、不光滑的平面,则反射后的光线是杂乱的(这样的反射称为漫反射)。现实生活中的物体在反射光线时,并不是绝对的镜面反射或漫反射,但可以看成是这两种反射的叠加。对于光源发出的光线,可以分别设置其经过镜面反射和漫反射后的光线强度。对于被光线照射的材质,也可以分别设置光线经过镜面反射和漫反射后的光线强度。这些因素综合起来,就形成了最终的光照效果。,二、法线向量根据光的反射定律,由光的入射方向和入射点的法线就可以得到光的出射方向。因此,对于指定的物体,在指定了光源后,即可计算出光的反射方向,进而计算出光照效果的画面。在OpenGL中,法线的方向是用一个向量来表示。OpenGL并不会根据你所指定的多边形各个顶点来计算出这些多边形所构成的物体的表面的每个点的法线,通常,为了实现光照效果,需要在代码中为每一个顶点指定其法线向量。,指定法线向量的方式与指定颜色的方式有雷同之处。在指定颜色时,只需要指定每一个顶点的颜色,OpenGL就可以自行计算顶点之间的其它点的颜色。并且,颜色一旦被指定,除非再指定新的颜色,否则以后指定的所有顶点都将以这一向量作为自己的颜色。在指定法线向量时,只需要指定每一个顶点的法线向量,OpenGL会自行计算顶点之间的其它点的法线向量。并且,法线向量一旦被指定,除非再指定新的法线向量,否则以后指定的所有顶点都将以这一向量作为自己的法线向量。使用glColor*函数可以指定颜色,而使用glNormal*函数则可以指定法线向量。,注意:使用glTranslate*函数或者glRotate*函数可以改变物体的外观,但法线向量并不会随之改变。然而,使用glScale*函数,对每一坐标轴进行不同程度的缩放,很有可能导致法线向量的不正确,虽然OpenGL提供了一些措施来修正这一问题,但由此也带来了各种开销。因此,在使用了法线向量的场合,应尽量避免使用glScale*函数。即使使用,也最好保证各坐标轴进行等比例缩放。,在OpenGL中,仅仅支持有限数量的光源。使用GL_LIGHT0表示第0号光源,GL_LIGHT1表示第1号光源,依次类推,OpenGL至少会支持8个光源,即GL_LIGHT0到GL_LIGHT7。使用glEnable函数可以开启它们。例如,glEnable(GL_LIGHT0);可以开启第0号光源。使用glDisable函数则可以关闭光源。一些OpenGL实现可能支持更多数量的光源,但总的来说,开启过多的光源将会导致程序运行速度的严重下降,一些场景中可能有成百上千的电灯,这时可能需要采取一些近似的手段来进行编程,否则以目前的计算机而言,是无法运行这样的程序的。,每一个光源都可以设置其属性,这一动作是通过glLight*函数完成的。glLight*函数具有三个参数,第一个参数指明是设置哪一个光源的属性,第二个参数指明是设置该光源的哪一个属性,第三个参数则是指明把该属性值设置成多少光源的属性众多,下面将分别介绍。,(1)GL_AMBIENT、GL_DIFFUSE、GL_SPECULAR属性。这三个属性表示了光源所发出的光的反射特性(以及颜色)。每个属性由四个值表示,分别代表了颜色的R,G,B,A值。GL_AMBIENT表示该光源所发出的光,经过非常多次的反射后,最终遗留在整个光照环境中的强度(颜色)。GL_DIFFUSE表示该光源所发出的光,照射到粗糙表面时经过漫反射,所得到的光的强度(颜色)。GL_SPECULAR表示该光源所发出的光,照射到光滑表面时经过镜面反射,所得到的光的强度(颜色)。,(2)GL_POSITION属性。表示光源所在的位置。由四个值(X,Y,Z,W)表示。如果第四个值W为零,则表示该光源位于无限远处,前三个值表示了它所在的方向。这种光源称为方向性光源,通常,太阳可以近似的被认为是方向性光源。如果第四个值W不为零,则X/W,Y/W,Z/W表示了光源的位置。这种光源称为位置性光源。对于位置性光源,设置其位置与设置多边形顶点的方式相似,各种矩阵变换函数例如:glTranslate*、glRotate*等在这里也同样有效。方向性光源在计算时比位置性光源快了不少,因此,在视觉效果允许的情况下,应该尽可能的使用方向性光源。,(3)GL_SPOT_DIRECTION、GL_SPOT_EXPONENT、GL_SPOT_CUTOFF属性。表示将光源作为聚光灯使用(这些属性只对位置性光源有效)。很多光源都是向四面八方发射光线,但有时候一些光源则是只向某个方向发射,比如手电筒,只向一个较小的角度发射光线。GL_SPOT_DIRECTION属性有三个值,表示一个向量,即光源发射的方向。GL_SPOT_EXPONENT属性只有一个值,表示聚光的程度,为零时表示光照范围内向各方向发射的光线强度相同,为正数时表示光照向中央集中,正对发射方向的位置受到更多光照,其它位置受到较少光照。数值越大,聚光效果就越明显。GL_SPOT_CUTOFF属性也只有一个值,表示一个角度,它是光源发射光线所覆盖角度的一半(见图2),其取值范围在0到90之间,也可以取180这个特殊值。取值为180时表示光源发射光线覆盖360度,即不使用聚光灯,向全周围发射。,四、控制材质材质与光源相似,也需要设置众多的属性。不同的是,光源是通过glLight*函数来设置的,而材质则是通过glMaterial*函数来设置的。glMaterial*函数有三个参数。第一个参数表示指定哪一面的属性。可以是GL_FRONT、GL_BACK或者GL_FRONT_AND_BACK。分别表示设置“正面”“背面”的材质,或者两面同时设置。(关于“正面”“背面”的内容需要参看前些课程的内容)第二、第三个参数与glLight*函数的第二、三个参数作用类似。下面分别说明glMaterial*函数可以指定的材质属性。,(1)GL_AMBIENT、GL_DIFFUSE、GL_SPECULAR属性。这三个属性与光源的三个对应属性类似,每一属性都由四个值组成。GL_AMBIENT表示各种光线照射到该材质上,经过很多次反射后最终遗留在环境中的光线强度(颜色)。GL_DIFFUSE表示光线照射到该材质上,经过漫反射后形成的光线强度(颜色)。GL_SPECULAR表示光线照射到该材质上,经过镜面反射后形成的光线强度(颜色)。通常,GL_AMBIENT和GL_DIFFUSE都取相同的值,可以达到比较真实的效果。使用GL_AMBIENT_AND_DIFFUSE可以同时设置GL_AMBIENT和GL_DIFFUSE属性。,(2)GL_SHININESS属性。该属性只有一个值,称为“镜面指数”,取值范围是0到128。该值越小,表示材质越粗糙,点光源发射的光线照射到上面,也可以产生较大的亮点。该值越大,表示材质越类似于镜面,光源照射到上面后,产生较小的亮点。3)GL_EMISSION属性。该属性由四个值组成,表示一种颜色。OpenGL认为该材质本身就微微的向外发射光线,以至于眼睛感觉到它有这样的颜色,但这光线又比较微弱,以至于不会影响到其它物体的颜色。(4)GL_COLOR_INDEXES属性。该属性仅在颜色索引模式下使用,由于颜色索引模式下的光照比RGBA模式要复杂,并且使用范围较小,这里不做讨论。,五、选择光照模型这里所说的“光照模型”是OpenGL的术语,它相当于我们在前面提到的“光照环境”。在OpenGL中,光照模型包括四个部分的内容:全局环境光线(即那些充分散射,无法分清究竟来自哪个光源的光线)的强度、观察点位置是在较近位置还是在无限远处、物体正面与背面是否分别计算光照、镜面颜色(即GL_SPECULAR属性所指定的颜色)的计算是否从其它光照计算中分离出来,并在纹理操作以后在进行应用。,以上四方面的内容都通过同一个函数glLightModel*来进行设置。该函数有两个参数,第一个表示要设置的项目,第二个参数表示要设置成的值。GL_LIGHT_MODEL_AMBIENT表示全局环境光线强度,由四个值组成。GL_LIGHT_MODEL_LOCAL_VIEWER表示是否在近处观看,若是则设置为GL_TRUE,否则(即在无限远处观看)设置为GL_FALSE。GL_LIGHT_MODEL_TWO_SIDE表示是否执行双面光照计算。如果设置为GL_TRUE,则OpenGL不仅将根据法线向量计算正面的光照,也会将法线向量反转并计算背面的光照。GL_LIGHT_MODEL_COLOR_CONTROL表示颜色计算方式。如果设置为GL_SINGLE_COLOR,表示按通常顺序操作,先计算光照,再计算纹理。如果设置为GL_SEPARATE_SPECULAR_COLOR,表示将GL_SPECULAR属性分离出来,先计算光照的其它部分,待纹理操作完成后再计算GL_SPECULAR。后者通常可以使画面效果更为逼真(当然,如果本身就没有执行任何纹理操作,这样的分离就没有任何意义)。,六、最后的准备到现在可以说是完事俱备了。不过,OpenGL默认是关闭光照处理的。要打开光照处理功能,使用下面的语句:glEnable(GL_LIGHTING);要关闭光照处理功能,使用glDisable(GL_LIGHTING);即可。示例,第八章纹理映射,1、启用纹理和载入纹理就像我们曾经学习过的OpenGL光照一样。在使用纹理前,必须启用它。OpenGL支持一维纹理、二维纹理和三维纹理,这里我们仅介绍二维纹理。可以使用以下语句来启用和禁用二维纹理:glEnable(GL_TEXTURE_2D);/启用二维纹理glDisable(GL_TEXTURE_2D);/禁用二维纹理,使用纹理前,还必须载入纹理。利用glTexImage2D函数可以载入一个二维的纹理,该函数有多达九个参数(虽然某些参数我们可以暂时不去了解),现在分别说明如下:第一个参数为指定的目标,对于二维纹理这个参数使用GL_TEXTURE_2D。第二个参数为“多重细节层次”,现在我们并不考虑多重纹理细节,因此这个参数设置为零。,第三个参数有两种用法。在OpenGL1.0,即最初的版本中,使用整数来表示颜色分量数目,例如:像素数据用RGB颜色表示,总共有红、绿、蓝三个值,因此参数设置为3,而如果像素数据是用RGBA颜色表示,总共有红、绿、蓝、alpha四个值,因此参数设置为4。而在后来的版本中,可以直接使用GL_RGB或GL_RGBA来表示以上情况,显得更直观。,第四、五个参数是二维纹理像素的宽度和高度。使用纹理时要特别注意其大小。尽量使用大小为2的整数次方的纹理,当这个要求无法满足时,使用gluScaleImage函数把图象缩放至所指定的大小无论旧版本还是新版本,都限制了纹理大小的最大值,例如,某OpenGL实现可能要求纹理最大不能超过1024*1024。可以使用如下的代码来获得OpenGL所支持的最大纹理:GLintmax;glGetIntegerv(GL_MAX_TEXTURE_SIZE,第六个参数是纹理边框的大小,我们没有使用纹理边框,因此这里设置为零。第七个参数表示读取的内容,例如:GL_RGB就会依次读取像素的红、绿、蓝三种数据,GL_RGBA则会依次读取像素的红、绿、蓝、alpha四种数据,GL_RED则只读取像素的红色数据(类似的还有GL_GREEN,GL_BLUE,以及GL_ALPHA)。第八个参数表示读取的内容保存到内存时所使用的格式,例如:GL_UNSIGNED_BYTE会把各种数据保存为GLubyte,GL_FLOAT会把各种数据保存为GLfloat等。第九个参数表示一个指针,像素数据被读取后,将被保存到这个指针所表示的地址。,举个例子,如果有一幅大小为width*height,格式为Windows系统中使用最普遍的24位BGR,保存在pixels中的像素图象。则把这样一幅图象载入为纹理可使用以下代码:glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,width,height,0,GL_BGR_EXT,GL_UNSIGNED_BYTE,pixels);,注意,载入纹理的过程可能比较慢,原因是纹理数据通常比较大,例如一幅512*512的BGR格式的图象,大小为0.75M。把这些像素数据从主内存传送到专门的图形硬件,这个过程中还可能需要把程序中所指定的像素格式转化为图形硬件所能识别的格式(或最能发挥图形硬件性能的格式),这些操作都需要较多时间。,2、纹理坐标指定每一个顶点在纹理图象中所对应的像素位置,OpenGL就会自动计算顶点以外的其它点在纹理图象中所对应的像素位置。我们可以这样类比一下:在绘制一条线段时,我们设置其中一个端点为红色,另一个端点为绿色,则OpenGL会自动计算线段中其它各像素的颜色,如果是使用glShadeMode(GL_SMOOTH);,则最终会形成一种渐变的效果(例如线段中点,就是红色和绿色的中间色)。类似的,在绘制一条线段时,我们设置其中一个端点使用“纹理图象中最左下角的颜色”作为它的颜色,另一个端点使用“纹理图象中最右上角的颜色”作为它的颜色,则OpenGL会自动在纹理图象中选择合适位置的颜色,填充到线段的各个像素(例如线段中点,可能就是选择纹理图象中央的那个像素的颜色)。,在类比时,使用了“纹理图象中最左下角的颜色”这种说法。但这种说法在很多时候不够精确,我们需要一种精确的方式来表示我们究竟使用纹理中的哪个像素。纹理坐标也就是因为这样的要求而产生的。以二维纹理为例,规定纹理最左下角的坐标为(0,0),最右上角的坐标为(1,1),于是纹理中的每一个像素的位置都可以用两个浮点数来表示(三维纹理会用三个浮点数表示,一维纹理则只用一个即可)。,使用glTexCoord*系列函数来指定纹理坐标。这些函数的用法与使用glVertex*系列函数来指定顶点坐标十分相似。例如:glTexCoord2f(0.0f,0.0f);指定使用(0,0)纹理坐标。通常,每个顶点使用不同的纹理,于是下面这样形式的代码是比较常见的。glBegin(/*.*/);glTexCoord2f(/*.*/);glVertex3f(/*.*/);glTexCoord2f(/*.*/);glVertex3f(/*.*/);/*.*/glEnd();,3、纹理参数在使用纹理前还有某些参数是必须设置的。使用glTexParameter*系列函数来设置纹理参数。通常需要设置下面四个参数:GL_TEXTURE_MAG_FILTER:指当纹理图象被使用到一个大于它的形状上时(即:有可能纹理图象中的一个像素会被应用到实际绘制时的多个像素。例如将一幅256*256的纹理图象应用到一个512*512的正方形),应该如何处理。可选择的设置有GL_NEAREST和GL_LINEAR,前者表示“使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色”,后者表示“使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色”。,GL_TEXTURE_MIN_FILTER:指当纹理图象被使用到一个小于(或等于)它的形状上时(即有可能纹理图象中的多个像素被应用到实际绘制时的一个像素。例如将一幅256*256的纹理图象应用到一个128*128的正方形),应该如何处理。可选择的设置有GL_NEAREST,GL_LINEAR,GL_NEAREST_MIPMAP_NEAREST,GL_NEAREST_MIPMAP_LINEAR,GL_LINEAR_MIPMAP_NEAREST和GL_LINEAR_MIPMAP_LINEAR。其中后四个涉及到mipmap,现在暂时不需要了解。前两个选项则和GL_TEXTURE_MAG_FILTER中的类似。,GL_TEXTURE_WRAP_S:指当纹理坐标的第一维坐标值大于1.0或小于0.0时,应该如何处理。基本的选项有GL_CLAMP和GL_REPEAT,前者表示“截断”,即超过1.0的按1.0处理,不足0.0的按0.0处理。后者表示“重复”,即对坐标值加上一个合适的整数(可以是正数或负数),得到一个在0.0,1.0范围内的值,然后用这个值作为新的纹理坐标。例如:某二维纹理,在绘制某形状时,一像素需要得到纹理中坐标为(3.5,0.5)的像素的颜色,其中第一维的坐标值3.5超过了1.0,则在GL_CLAMP方式中将被转化为(1.0,0.5),在GL_REPEAT方式中将被转化为(0.5,0.5)。,GL_TEXTURE_WRAP_T:指当纹理坐标的第二维坐标值大于1.0或小于0.0时,应该如何处理。选项与GL_TEXTURE_WRAP_S类似,不再重复。如果不指定这个参数,则默认为GL_REPEAT。glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);,4、纹理对象前面已经提到过,载入一幅纹理所需要的时间是比较多的。因此应该尽量减少载入纹理的次数。如果只有一幅纹理,则应该在第一次绘制前就载入它,以后就不需要再次载入了。但是,在每次绘制时要使用两幅或更多幅的纹理时,这个办法就行不通了。你可能会编写下面的代码:,glTexImage2D(/*.*/);/载入第一幅纹理/使用第一幅纹理glTexImage2D(/*.*/);/载入第二幅纹理/使用第二幅纹理/当纹理的数量增加时,这段代码会变得更加复杂。在绘制动画时,由于每秒钟需要将画面绘制数十次,因此如果使用上面的代码,就会反复载入纹理,这对计算机是非常大的负担,以目前的个人计算机配置来说,根本就无法让动画能够流畅的运行。因此,需要有一种机制,能够在不同的纹理之间进行快速的切换。,纹理对象正是这样一种机制
展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 图纸专区 > 课件教案


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!