《计算机图形学》PPT课件.ppt
OpenGL,OpenGL,什么是OpenGL?OpenGL是一个功能强大的开放图形库(Open Graphics Library)。其前身是SGI公司为其图形工作站开发的IRIS GL。为使其能够更加容易地移植到不同的硬件和操作系统,SGI开发了OpenGL。OpenGL被打造为开放性标准,任何软硬件厂商均可自由使用,这让它受到广泛的欢迎。,2,背景1992年7月,SGI正式发布OpenGL 1.0标准。OpenGL 1.0完全实现了SGI的预期设计目标:功能强大、移植性良好并能自由使用。SGI和微软进行首次合作、联手将OpenGL 1.0移植到Windows NT平台。OpenGL规范由ARB(Architecture Review Broad)管理,成员为SGI、MS、intel、IBM、Sun等公司。2003年的7月,ARB公布OpenGL 1.5规范迄今为止最新的OpenGL版本。支持各种操作系统和编程环境。,3,从程序开发人员的角度来看,OpenGL是一组绘图命令的API集合。利用这些API能够方便地描述二维和三维几何物体,并控制这些物体按某种方式绘制到显示缓冲区中。主要功能:绘制模型:点线多边形、球、锥、多面体、茶壶、贝塞尔曲线各种变换:基本变换(平移、缩放、旋转、镜像)、平行投影、透视投影着色模式:RGBA(alpha),颜色索引,4,主要功能:光照处理:材质,辐射光、环境光、漫反射光等,5,主要功能:纹理映射:纹理数据包括颜色、亮度和alpha,真实感纹理,6,主要功能:位图和图像制作动画:双缓存,后台缓存用于计算场景和生成画面,前台缓存用于显示画面,产生平滑动画选择和反馈,7,OpenGL的绘制流程和原理:,评价器:顶点转换、光照设置基片:一系列图像的帧缓存描述值,演示程序,8,OpenGL能做什么?,OpenGL怎样学?,函数很多,先以模仿为主,熟能生巧,1顶点坐标 OpenGL采用有序排列的顶点集合来构造几何图元。如:glVertex2s(2,5);/整数定义的二维坐标 glVertex3f(2,5,7);/浮点定义的三维坐标 顶点关系在OpenGL中,同一个几何图元的所有被定义的顶点一起放在glBegin()和glEnd()函数之间,同时定义这些顶点之间的关系。如:glBegin(GL_POLYGON);glVertex2s(0,0);glVertex2s(0,11);glVertex2s(11,14);glVertex2s(14,7);glEnd();,11,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连续填充四边形串,12,1、用OpenGL生成点 glPointSize(2.0f);glBegin(GL_POINTS);glVertex2f(0.0,0.0);glVertex3f(0.0f,0.0f,0.3f);glEnd();,13,2、用OpenGL生成直线glLineWidth(2.0f)glLineStipple,线形用16位二进制描述,如表示点线,编程时转为十六进制 glEnable(GL_LINE_STIPPLE);glLineStipple(2,0 x00FF);,14,3、用OpenGL生成区域填充区域图形绘制GL_POLYGON实模式填充点画模式(图案32*32)填充glEnable(GL_POLYGON_STIPPLE);glPolygonStipple(fly);glRectf(-0.3,-0.6,0.0,0.0);glDisable(GL_POLYGON_STIPPLE);,15,3、用OpenGL生成区域填充多边形面的绘制多边形是三维的,被认为由正反两个面组成多边形面的控制 void glPolygonMode(GLenum face,GLenum mode)face控制是否绘制正反面:GL_FRONT_AND_BACKGL_FRONT;GL_BACKmode控制绘制方式:GL_POINT用一定间隔的点填充;GL_LINE只画多边形的边框;GL_FILL填充多边形例:glPolygonMode(GL_FRONT,GL_FILL)glPolygonMode(GL_BACK,GL_LINE),16,3、用OpenGL生成区域填充多边形面的绘制多边形面的定义 void glFrontFace(GLenum mode)mode:GL_CW;GL_CCW(逆时针,默认)多边形面的剔除void glCullFace(GLenum mode)mode:GL_FRONT,剔除正面GL_BACK,剔除背面GL_FRONT_AND_BACK,剔除全部需要glEnable(GL_CULL_FACE)激活和glDisable(GL_CULL_FACE)去活,17,3、用OpenGL生成区域填充多边形面的绘制多边形面的法向量 面在空间中的方向,决定可以接收多少光照glNormal*()设置当前法向量。void glNormal3bsidf(TYPE nx,TYPE ny,TYPE nz);void glNormal3bsidfv(const TYPE*v);法向量需要由用户计算并显式指定。,18,4、用OpenGL生成字符英文字体位图字体速度快定位glRasterPos()轮廓字体多边形填充有利于拉伸、缩放等变换纹理映射字体街道、墙面的标记wglUseFontBitmaps(wglGetCurrentDC(),0,256,1000);glListBase(1000);glRasterPos3f(-0.5f,0.0f,0.0f);glCallLists(20,GL_UNSIGNED_BYTE,”Draw with List Text.”),19,4、用OpenGL生成字符中文字体(1)用wglUseFontBitmaps或wglUseOutlines为每个字生成一个List。(2)为每个字调用glCallList或为每个字串调用glCallLists问题一:汉字字体个数太多,不能一次全部读入建立清单,相当于字体缓存,用到的字体才加进来问题二:汉字为双字节组合为DWORD传给wglUseFont*(),20,OpenGL的缓存(Buffer)1 颜色缓存(Color Buffer):颜色索引或者RGBA数据,前、后缓存;2 深度缓存(Depth Buffer):保存象素Z方向的数值,深度大的被深度小的代替,用以实现消隐;3 模板缓存(Stencil Buffer):用以保持屏幕上某些位置图形不变,而其他部分重绘。例如大家熟悉的开飞机和赛车的游戏的驾驶舱视角,只有挡风外面的景物变化,舱内仪表等等并不变化。4 累计缓存(Accumulation Buffer)只保存RGBA数据,用于合成图象,例如有某缓存中的位图调入这里合成一幅新图。,21,安装GLUT工具包GLUT不是OpenGL所必须的,但它会给我们的学习带来一定的方便,推荐安装。Windows环境下安装GLUT的步骤:1、将下载的压缩包解开,将得到5个文件2、在“我的电脑”中搜索“gl.h”,并找到其所在文件夹(如果是VisualStudio6.0,则应该是其安装目录下面的“VC98IncludeGL文件夹”)。把解压得到的glut.h放到这个文件夹。3、把解压得到的glut.lib和glut32.lib放到静态函数库所在文件夹(如果是VisualStudio6.0,则应该是其安装目录下面的“VC98Lib”文件夹)。4、把解压得到的glut.dll和glut32.dll放到操作系统目录下面的system32文件夹内。(典型的位置为:C:WindowsSystem32),使用VC开发OpenGL程序的基本方法,22,建立一个OpenGL工程这里以VisualStudio为例。选择File-New-Project,然后选择Win32 Console Application,选择一个名字,然后按OK。在谈出的对话框左边点Application Settings,找到A Hello,World!application并勾上,选择Finish。,23,第一个OpenGL程序#include void myDisplay(void)glClear(GL_COLOR_BUFFER_BIT);glRectf(-0.5f,-0.5f,0.5f,0.5f);glFlush();int main(int argc,char*argv)glutInit(该程序的作用是在一个黑色的窗口中央画一个白色的矩形。,24,首先,需要包含头文件#include,这是GLUT的头文件。本来OpenGL程序一般还要包含和,但GLUT的头文件中已经自动将这两个文件包含了,不必再次包含。int main(int argc,char*argv),是带命令行参数的main函数注意main函数中的各语句,除了最后的return之外,其余全部以glut开头。这种以glut开头的函数都是GLUT工具包所提供的函数,下面对用到的几个函数进行介绍。1、glutInit,对GLUT进行初始化,这个函数必须在其它的GLUT使用之前调用一次。其格式比较死板,一般照抄这句glutInit(&argc,argv)就可以了。2、glutInitDisplayMode,设置显示方式,其中GLUT_RGB表示使用RGB颜色,与之对应的还有GLUT_INDEX(表示使用索引颜色)。GLUT_SINGLE表示使用单缓冲,与之对应的还有GLUT_DOUBLE(使用双缓冲)。更多信息,请Google一下。3、glutInitWindowPosition,设置窗口在屏幕中的位置。4、glutInitWindowSize,设置窗口的大小。,25,5、glutCreateWindow,根据前面设置的信息创建窗口。参数将被作为窗口的标题。注意:窗口被创建后,并不立即显示到屏幕上。需要调用glutMainLoop才能看到窗口。6、glutDisplayFunc,设置一个函数,当需要进行画图时,这个函数就会被调用。7、glutMainLoop,进行一个消息循环。这个函数可以显示窗口,并且等待窗口关闭后才会返回。在glutDisplayFunc函数中,设置了“当需要画图时,请调用myDisplay函数”。于是myDisplay函数就用来画图。观察myDisplay中的三个函数调用,发现它们都以gl开头。这种以gl开头的函数都是OpenGL的标准函数:1、glClear,清除。GL_COLOR_BUFFER_BIT表示清除颜色,glClear函数还可以清除其它的东西。2、glRectf,画一个矩形。四个参数分别表示了位于对角线上的两个点的横、纵坐标。3、glFlush,保证前面的OpenGL命令立即执行(而不是让它们在缓冲区中等待)。,26,点、直线和多边形数学中有点、直线和多边形的概念,但这些概念在计算机中会有所不同。数学上的点,只有位置,没有大小。但在计算机中,无论计算精度如何提高,始终不能表示一个无穷小的点。另一方面,无论图形输出设备(例如显示器)如何精确,始终不能输出一个无穷小的点。一般情况下,OpenGL中的点将被画成单个的像素,虽然它可能足够小,但并不会是无穷 小。同一像素上,OpenGL可以绘制许多坐标只有稍微不同的点,但该像素的具体颜色将取决于OpenGL的实现。同样的,数学上的直线没有宽度,但OpenGL的直线则是有宽度的。同时,OpenGL的直线必须是有限长度,而不是像数学概念那样是无限的。可以认为,OpenGL的“直线”概念与数学上的“线段”接近,它可以由两个端点来确定。,27,多边形是由多条线段首尾相连而形成的闭合区域。OpenGL规定,一个多边形必须是一个“凸多边形”(其定义为:多边形内任意两点所确定的线段都在多边形内)。多边形可以由其边的端点(这里可称为顶点)来确定。(注意:如果使用的多边形不是凸多边形,则最后输出的 效果是未定义的OpenGL为了效率,放宽了检查,这可能导致显示错误。要避免这个错误,尽量使用三角形,因为三角形都是凸多边形)可以想象,通过点、直线和多边形,就可以组合成各种几何图形。甚至于,可以把一段弧看成是很多短的直线段相连,这些直线段足够短,以至于其长度小于一个像素的宽度。这样一来弧和圆也可以表示出来了。通过位于不同平面的相连的小多边形,还可以组成一个“曲面”。,在OpenGL中指定顶点由以上的讨论可以知道,“点”是一切的基础。如何指定一个点呢?OpenGL提供了一系列函数。它们都以glVertex开头,后面跟一个数字和12个字母。例如:glVertex2d glVertex2f glVertex3f glVertex3fv数字表示参数的个数,2表示有两个参数,3表示三个字母表示参数的类型s表示16位整数(OpenGL中将这个类型定义为GLshort)i表示32位整数(OpenGL中将这个类型定义为GLint和GLsize)f表示32位浮点数(OpenGL中将这个类型定义为GLfloat和GLclampf)d表示64位浮点数(OpenGL中将这个类型定义为GLdouble和GLclampd)v表示传递的几个参数将使用指针的方式,29,这些函数除了参数的类型和个数不同以外,功能是相同的。例如,以下五个代码段的功能是等效的:(一)glVertex2i(1,3);(二)glVertex2f(1.0f,3.0f);(三)glVertex3f(1.0f,3.0f,0.0f);(四)glVertex4f(1.0f,3.0f,0.0f,1.0f);(五)GLfloat VertexArr3=1.0f,3.0f,0.0f;glVertex3fv(VertexArr3);注意:OpenGL的很多函数都是采用这样的形式,一个相同的前缀再加上参数说明标记,这一点会随着学习的深入而有更多的体会。,假设现在已经指定了若干顶点,那么OpenGL是如何知道拿这些顶点来干什么呢?是一个一个的画出来,还是连成线?或者构成一个多边形?或者做其它什么事情?为了解决这一问题,OpenGL要求:指定顶点的命令必须包含在glBegin函数之后,glEnd函数之前(否则指定的顶点将被忽略)。并由glBegin来指明如何使用这些点。例如:glBegin(GL_POINTS);glVertex2f(0.0f,0.0f);glVertex2f(0.5f,0.0f);glEnd();则这两个点将分别被画出来。如果将GL_POINTS替换成GL_LINES,则两个点将被认为是直线的两个端点,OpenGL将会画出一条直线。我们还可以指定更多的顶点,然后画出更复杂的图形。,31,另一方面,glBegin支持的方式除了GL_POINTS和GL_LINES,还有GL_LINE_STRIP,GL_LINE_LOOP,GL_TRIANGLES,GL_TRIANGLE_STRIP,GL_TRIANGLE_FAN等,每种方式的大致效果:,程序代码:void myDisplay(void)glClear(GL_COLOR_BUFFER_BIT);glBegin(/*在这里填上你所希望的模式*/);/*在这里使用glVertex*系列函数*/*指定你所希望的顶点位置*/glEnd();glFlush();,32,例一、画一个圆正四边形,正五边形,正六边形,直到正n边形,当n越大时,这个图形就越接近圆,当n大到一定程度后,人眼将无法把它跟真正的圆相区别,这时我们已经成功的画出了一个“圆”#include const int n=20;const GLfloat R=0.5f;const GLfloat Pi=3.1415926536f;void myDisplay(void)int i;glClear(GL_COLOR_BUFFER_BIT);glBegin(GL_POLYGON);for(i=0;in;+i)glVertex2f(R*cos(2*Pi/n*i),R*sin(2*Pi/n*i);glEnd();glFlush();试修改下面的const int n的值,观察当n=3,4,5,8,10,15,20,30,50等不同数值时输出的变化情况,将GL_POLYGON改为GL_LINE_LOOP、GL_POINTS等其它方式,观察输出的变化情况,33,例二、画一个五角星#include const GLfloat Pi=3.1415926536f;void myDisplay(void)GLfloat a=1/(2-2*cos(72*Pi/180);GLfloat bx=a*cos(18*Pi/180);GLfloat by=a*sin(18*Pi/180);GLfloat cy=-a*cos(18*Pi/180);GLfloat PointA2=0,a,PointB2=bx,by,PointC2=0.5,cy,PointD2=-0.5,cy,PointE2=-bx,by;,34,glClear(GL_COLOR_BUFFER_BIT);glBegin(GL_LINE_LOOP);glVertex2fv(PointA);glVertex2fv(PointC);glVertex2fv(PointE);glVertex2fv(PointB);glVertex2fv(PointD);glEnd();glFlush();,例三、画出正弦函数的图形#include const GLfloat factor=0.1f;void myDisplay(void)GLfloat x;glClear(GL_COLOR_BUFFER_BIT);glBegin(GL_LINES);glVertex2f(-1.0f,0.0f);glVertex2f(1.0f,0.0f);,35,/以上两个点可以画x轴 glVertex2f(0.0f,-1.0f);glVertex2f(0.0f,1.0f);/以上两个点可以画y轴 glEnd();glBegin(GL_LINE_STRIP);for(x=-1.0f/factor;x1.0f/factor;x+=0.01f)glVertex2f(x*factor,sin(x)*factor);glEnd();glFlush();,点太小,难以看清楚;直线也太细,不舒服;或者想画虚线,但不知道方法只能用许多短直线,甚至用点组合而成。1、关于点点的大小默认为1个像素,但也可以改变。改变的命令为glPointSize,其函数原型如下:void glPointSize(GLfloat size);size必须大于0.0f,默认值为1.0f,单位为“像素”。例子:void myDisplay(void)glClear(GL_COLOR_BUFFER_BIT);glPointSize(5.0f);glBegin(GL_POINTS);glVertex2f(0.0f,0.0f);glVertex2f(0.5f,0.5f);glEnd();glFlush();,36,2、关于直线(1)直线可以指定宽度:void glLineWidth(GLfloat width);其用法跟glPointSize类似。(2)画虚线。首先,使用glEnable(GL_LINE_STIPPLE);来启动虚线模式(使用glDisable(GL_LINE_STIPPLE)可以关闭)。然后,使用glLineStipple来设置虚线的样式。void glLineStipple(GLint factor,GLushort pattern);pattern是由1和0组成的长度为16的序列,从最低位开始看,如果为1,则直线上接下来应该画的factor个点将被画为实的;如果为0,则直线上接下来应该画的factor个点将被画为虚的。,37,示例代码:void myDisplay(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();,38,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,顺时针,39,void myDisplay(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();将glFrontFace(GL_CCW)修改为glFrontFace(GL_CW),观察结果变化,40,OpenGL支持两种颜色模式:一种是RGBA,一种是颜色索引模式。无论哪种颜色模式,计算机都必须为每一个像素保存一些数据。不同的是,RGBA模式中,数据直接就代表了颜色;而颜色索引模式中,数据代表的是一个索引,要得到真正的颜色,还必须去查索引表。1.RGBA颜色RGBA模式中,每一个像素会保存以下数据:R值(红色分量)、G值(绿色分量)、B值(蓝色分量)和A值(alpha分量)。其中红、绿、蓝三种颜色相组合,就可以得到我们所需要的各种颜色,而alpha不直接影响颜色,它将留待以后介绍。在RGBA模式下选择颜色是十分简单的事情,只需要一个函数就可以搞定。glColor*系列函数可以用于设置颜色,其中三个参数的版本可以指定R、G、B的值,而A值采用默认;四个参数的版本可以分别指定R、G、B、A的值。例如:void glColor3f(GLfloat red,GLfloat green,GLfloat blue);void glColor4f(GLfloat red,GLfloat green,GLfloat blue,GLfloat alpha);,41,将浮点数作为参数,其中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找不到精确的颜色,会进行类似“四舍五入”的处理。,可以通过改变下面代码中glColor3f的参数值,绘制不同颜色的矩形。void myDisplay(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表示最大的使用。,43,2、索引颜色在索引颜色模式中,OpenGL需要一个颜色表。这个表就相当于画家的调色板:虽然可以调出很多种颜色,但同时存在于调色板上的颜色种数将不会超过调色板的格数。试将颜色表的每一项想象成调色板上的一个格子:它保存了一种颜色。在使用索引颜色模式画图时,“把第i种颜色设置为某某”,其实就相当于将调色板的第i格调为某某颜色。“需要第k种颜色来画图”,那么就用画笔去蘸一下第k格调色板。颜色表的大小是很有限的,一般在2564096之间,且总是2的整数次幂。在使用索引颜色方式进行绘图时,总是先设置颜色表,然后选择颜色。,44,2.1、选择颜色使用glIndex*系列函数可以在颜色表中选择颜色。其中最常用的是glIndexi,它的参数是一个整形。void glIndexi(GLint c);2.2、设置颜色表OpenGL 并直接没有提供设置颜色表的方法,因此设置颜色表需要使用操作系统的支持。我们所用的Windows和其他大多数图形操作系统都具有这个功能,但所使用的函数却不相同。另一个OpenGL工具包:aux,是VisualStudio自带的,不必另外安装,但它已经过时。,45,#include#include#include#pragma comment(lib,opengl32.lib)#pragma comment(lib,glaux.lib)#include const GLdouble Pi=3.1415926536;void myDisplay(void)int i;for(i=0;i8;+i)auxSetOneColor(i,(float)(i,首先,使用auxSetOneColor设置颜色表中的一格。循环八次就可以设置八格。然后在循环中用glVertex设置顶点,同时用glIndexi改变顶点代表的颜色。最终得到的效果是八个相同形状、不同颜色的三角形。,46,索引颜色的主要优势是占用空间小(每个像素不必单独保存自己的颜色,只用很少的二进制位就可以代表其颜色在颜色表中的位置),花费系统资源少,图形运算速度快,但它编程稍稍显得不是那么方便,并且画面效果也会比RGB颜色差一些。目前的PC机性能已经足够在各种场合下使用RGB颜色,因此PC程序开发中,使用索引颜色已经不是主流。当然,一些小型设备例如GBA、手机等,索引颜色还是有它的用武之地。,47,三维变换如果要观察一个物体,我们可以:1、从不同的位置去观察它。(视图变换)2、移动或者旋转它,当然了,如果它只是计算机里面的物体,我们还可以放大或缩小它。(模型变换)3、如果把物体画下来,我们可以选择:是否需要一种“近大远小”的透视效果。另外,我们可能只希望看到物体的一部分,而不是全部(剪裁)。(投影变换)4、我们可能希望把整个看到的图形画下来,但它只占据纸张的一部分,而不是全部。(视口变换)这些,都可以在OpenGL中实现。OpenGL变换实际上是通过矩阵乘法来实现。无论是移动、旋转还是缩放大小,都是通过在当前矩阵的基础上乘以一个新的矩阵来达到目的。,48,1、模型变换和视图变换从“相对移动”的观点来看,改变观察点的位置与方向和改变物体本身的位置与方向具有等效性。在OpenGL中,实现这两种功能甚至使用的是同样的函数。由于模型和视图的变换都通过矩阵运算来实现,在进行变换前,应先设置当前操作的矩阵为“模型视图矩阵”。设置的方法是以GL_MODELVIEW为参数调用glMatrixMode函数,像这样:glMatrixMode(GL_MODELVIEW);通常,我们需要在进行变换前把当前矩阵设置为单位矩阵。这也只需要一行代码:glLoadIdentity();,49,进行模型和视图变换,主要涉及到三个函数:glTranslate*,把当前矩阵和一个表示移动物体的矩阵相乘。三个参数分别表示了在三个坐标上的位移值。glRotate*,把当前矩阵和一个表示旋转物体的矩阵相乘。物体将绕着(0,0,0)到(x,y,z)的直线以逆时针旋转,参数angle表示旋转的角度。glScale*,把当前矩阵和一个表示缩放物体的矩阵相乘。x,y,z分别表示在该方向上的缩放比例。,50,假设当前矩阵为单位矩阵,然后先乘以一个表示旋转的矩阵R,再乘以一个表示移动的矩阵T,最后得到的矩阵再乘上每一个顶点的坐标矩阵v。所以,经过变换得到的顶点坐标就是(RT)v)。由于矩阵乘法的结合率,(RT)v)=(R(Tv),换句话说,实际上是先进行移动,然后进行旋转。即:实际变换的顺序与代码中写的顺序是相反的。由于“先移动后旋转”和“先旋转后移动”得到的结果很可能不同,初学的时候需要特别注意这一点。个人理解:平移、旋转都是针对坐标系而言的!,51,如果要改变观察点的位置,除了配合使用glRotate*和glTranslate*函数以外,还可以使用这个函数:gluLookAt。前三个参数表示了观察点的位置,中间三个参数表示了观察目标的位置,最后三个参数代表从(0,0,0)到(x,y,z)的直线,它表示了观察者认为的“上”方向。,52,2、投影变换投影变换就是定义一个可视空间,可视空间以外的物体不会被绘制到屏幕上。OpenGL支持两种类型的投影变换,即透视投影和正投影。投影也是使用矩阵来实现的。如果需要操作投影矩阵,需要以GL_PROJECTION为参数调用glMatrixMode函数。glMatrixMode(GL_PROJECTION);通常,需要在进行变换前把当前矩阵设置为单位矩阵。glLoadIdentity();,53,透视投影所产生的结果类似于照片,有近大远小的效果,比如在火车头内向前照一个铁轨的照片,两条铁轨似乎在远处相交了。使用glFrustum函数可以将当前的可视空间设置为透视投影空间。其参数的意义如下图:,54,也可以使用更常用的gluPerspective函数。其参数的意义如下图:,55,正投影相当于在无限远处观察得到的结果,它只是一种理想状态。但对于计算机来说,使用正投影有可能获得更好的运行速度。使用glOrtho函数可以将当前的可视空间设置为正投影空间。其参数的意义如下图:如果绘制的图形空间本身就是二维的,可以使用gluOrtho2D。他的使用类似于glOrgho,56,3、视口变换当一切工作已经就绪,只需要把像素绘制到屏幕上了。这时候还剩最后一个问题:应该把像素绘制到窗口的哪个区域呢?通常情况下,默认是完整的填充整个窗口,但我们完全可以只填充一半。(即:把整个图象填充到一半的窗口内)使用glViewport来定义视口。其中前两个参数定义了视口的左下脚(0,0表示最左下方),后两个参数分别是宽度和高度。,57,4、操作矩阵堆栈可以把堆栈想象成一叠盘子。开始的时候一个盘子也没有,可以一个一个往上放,也可以一个一个取下来。每次取下的,都是最后一次被放上去的盘子。通常,在计算机实现堆栈时,堆栈的容量是有限的,如果盘子过多,就会出错。当然,如果没有盘子了,再要求取一个盘子,也会出错。在进行矩阵操作时,有可能需要先保存某个矩阵,过一段时间再恢复它。当需要保存时,调用glPushMatrix函数,它相当于把矩阵(相当于盘子)放到堆栈上。当需要恢复最近一次的保存时,调用glPopMatrix函数,它相当于把矩阵从堆栈上取下。OpenGL规定堆栈的容量至少可以容纳32个矩阵,某些OpenGL实现中,堆栈的容量实际上超过了32个。因此不必过于担心矩阵的容量问题。用这种先保存后恢复的措施,比先变换再逆变换要更方便,更快速。注意:模型视图矩阵和投影矩阵都有相应的堆栈。使用glMatrixMode来指定当前操作的究竟是模型视图矩阵还是投影矩阵。,58,5、综合举例要制作的是一个三维场景,包括了太阳、地球和月亮。假定一年有12个月,每个月30天。每年,地球绕着太阳转一圈。每个月,月亮围着地球转一圈。即一年有360天,要求绘制出太阳、地球、月亮的相对位置示意图。首先,认定这三个天体都是球形,且他们的运动轨迹处于同一水平面,建立以下坐标系:太阳的中心为原点,天体轨迹所在的平面表示了X轴与Y轴决定的平面,且每年第一天,地球在X轴正方向上,月亮在地球的正X轴方向。为了得到透视效果,使用gluPerspective来设置可视空间。假定可视角为60度,高宽比为1.0。最近可视距离为1.0,最远可视距离为200000000*2=400000000。即:gluPerspective(60,1,1,400000000);,59,把三个天体都想象成规则的球体。而所使用的glut实用工具中,正好就有一个绘制球体的现成函数:glutSolidSphere,这个函数在“原点”绘制出一个球体。由于坐标是可以通过glTranslate*和glRotate*两个函数进行随意变换的,所以就可以在任意位置绘制球体了。函数有三个参数:第一个参数表示球体的半径,后两个参数代表了“面”的数目,简单点说就是球体的精确程度,数值越大越精确,当然代价就是速度越缓慢。太阳在坐标原点,所以不需要经过任何变换,直接绘制就可以了。地球则要复杂一点,需要变换坐标。由于今年已经经过的天数已知为day,则地球转过的角度为day/一年的天数*360度。前面已经假定每年都是360天,因此地球转过的角度恰好为day。所以可以通过下面的代码来解决:glRotatef(day,0,0,-1);/*注意地球公转是“自西向东”的,因此是饶着Z轴负方向进行逆时针旋转*/glTranslatef(地球轨道半径,0,0);glutSolidSphere(地球半径,20,20);,60,月亮是最复杂的。因为它不仅要绕地球转,还要随着地球绕太阳转。但如果选择地球作为参考,则月亮进行的运动就是一个简单的圆周运动了。如果先绘制地球,再绘制月亮,则只需要进行与地球类似的变换:glRotatef(月亮旋转的角度,0,0,-1);glTranslatef(月亮轨道半径,0,0);glutSolidSphere(月亮半径,20,20);但这个“月亮旋转的角度”,并不能简单的理解为day/一个月的天数30*360度。因为在绘制地球时,这个坐标已经是旋转过的。现在的旋转是在以前的基础上进行旋转,因此还需要处理这个“差值”。可以写成:day/30*360-day,即减去原来已经转过的角度。这只是一种简单的处理,当然也可以在绘制地球前用glPushMatrix保存矩阵,绘制地球后用glPopMatrix恢复矩阵。再设计一个跟地球位置无关的月亮位置公式,来绘制月亮。通常后