1、第二章 图形学(OpenGL)编程11/24/20221一.基本内容OpenGL API的发展历史OpenGL 体现结构OpenGL 作为一个状态机(state machine)函数 Functions 类型 Types格式Formats简单程序11/24/20222API的早期历史IFIPS(1973)组织了两个委员会建立图形 API标准图形核心系统(Graphical Kernel System GKS)二维,但包含很好的工作站模型Core:同时应用于二维和三维GKS 成为 IS0标准,后来成为 ANSI 标准(1980s)GKS 很难推广到 3D(GKS-3D)远远落后于硬件的发展11/
2、24/20223PHIGS and X程序员层次交互式图形系统PHIGS(Programmers Hierarchical Graphics System)来自于 CAD业界保存图形的数据库模型X Window 系统DEC/MIT 的成果提出了应用图形系统的客户-服务器体现(Client-server)PEX 把两者结合在一起不易应用(也是两者的缺陷)11/24/20224SGI 和 GLSilicon Graphics(SGI)通过实现流水线体系改良了图形工作站(1982)源程序通过一个图形库(GL)与系统通讯借助于 GL,可以非常简单地设计出三维交互图形应用程序 11/24/20225Op
3、enGLGL 的成功导致了 OpenGL的出现(1992),这是一个与平台无关的图形 API:使用方便与硬件非常贴近,从而能充分发挥其功能注重渲染和绘制(rendering)没有提供窗口和输入接口,从而避免依赖与窗口系统11/24/20226OpenGL 的演化由 Architectural Review Board(ARB)控制成员包括 SGI,Microsoft,Nvidia,HP,3DLabs,IBM,.相对稳定(目前版本 version 2.0)演化反映了新的硬件能力3D 纹理映射和纹理对象纹理映射和纹理对象基于顶点的编程基于顶点的编程通过扩展可以指定具体平台相应的功能11/24/20
4、227OpenGL 库OpenGL 核心库Windows:OpenGL32大多数 unix/linux 系统:GL库OpenGL 实用库(GLU)利用 OpenGL核心库提供一些功能,从而避免重复编写代码 与窗口系统的连接GLX for X window systemsWGL for WindowsAGL for Macintosh11/24/20228GLUTOpenGL 实用工具库(GLUT:OpenGL Utility Toolkit Library)提供所有窗口系统的共同功能打开窗口 Open a window从鼠标和键盘获得输入Get input from mouse and key
5、board菜单 Menus事件驱动 Event-driven代码可以在平台之间移植,但是GLUT 缺乏在确定平台上优秀工具包所具有的功能没有滚动条11/24/20229软件组织GLUTGLUGLGLX,AGLor WGLX,Win32,Mac O/S软软 件件or 硬硬 件件 应用程序应用程序OpenGL 图形图形工具条等工具条等11/24/202210OpenGL 体系结构快速模式显示列表显示列表多项式求值器多项式求值器逐顶点操作逐顶点操作&基本图元集成基本图元集成光栅化光栅化逐片操作逐片操作纹理内纹理内存存CPU像素操作像素操作帧缓存帧缓存geometry pipeline11/24/20
6、2211OpenGL 函数类型 基本图形元素(Primitives)定义图形系统可以显示的低级对象或原子实体,典型的如:点 Points线段 Line Segments多边形Polygons像素 Pixel字符 Character 属性函数(Attributes)线段颜色的设置多边形内部团填充图形标题文字的字体选择 变换(Transformations)视图 Viewing:定义了各种不同的视域建模 Modeling:虚拟照相机模型等 控制(GLUT):多窗口环境下的多进程之间的通信 输入(GLUT):处理键盘、鼠标等设备的输入响应 查询Query:获得诸如照相机参数、帧缓存里的数据等内在信息
7、11/24/202212OpenGL 状态OpenGL 是一个状态机(state machine)OpenGL 函数有两种基本类型基本图元的生成(Primitive generating)如果图元可见,可以得到输出顶点如何处理,基本形状的外观由状态控制改变状态(State changing)变换函数属性函数11/24/202213面向对象方面的缺憾OpenGL 不是面向对象的,因此逻辑上的一个函数却对应着多个OpenGL函数glVertex3f glVertex2i glVertex3dv内在存储模式是相同的在C+可以很容易地创建重载函数,但是效率却成为主要问题11/24/202214Open
8、GL 接口核心库GL:OpenGL 中所有函数的名字都以gl开头Windows:OpenGL32大多数 unix/linux 系统:GL库实用库 GLU只引用 OpenGL核心库GL中的函数,但还包括了球体等这些常用对象的建模代码以及其他的一些功能,从而避免重复编写代码 实用工具性GLUT提供了任何窗口操作系统所需要的最小功能集GLX:把:OpenGL与 X Window操作系统“粘合”起来11/24/202215OpenGL 函数名称格式glVertex3f(x,y,z)属于 GL 库函数名x,y,z 为 float数据类型glVertex3fv(p)p 为指向float的指针维数(参数个数
9、)注意每一部分的大小写11/24/202216OpenGL 中的#defines大多数常数采用预定义方式#defines在头文件 gl.h,glu.h 和 glut.h定义定义注意#include 会自动包含另外两个头文件例如:glBegin(GL_POLYGON)glClear(GL_COLOR_BUFFER_BIT)包含文件也定义 OpenGL 的数据类型:GLfloat,GLdouble,.常数11/24/202217一个简单的程序在黑色背景上画一个矩形11/24/202218simple.c#include /包含包含GL头文件头文件void mydisplay()glClear(GL
10、_COLOR_BUFFER_BIT);/清除屏幕及深度缓存清除屏幕及深度缓存 设置 glBegin(GL_POLYGON);/设置画多边形设置画多边形 glVertex2f(-0.5,-0.5);glVertex2f(-0.5,0.5);glVertex2f(0.5,0.5);glVertex2f(0.5,-0.5);glEnd();glFlush();/强制系统立刻在屏幕上显示输出图形强制系统立刻在屏幕上显示输出图形int main(int argc,char*argv)/主函数主函数glutInit(&argc,argv);/初始化初始化OPENGLglutCreateWindow(“si
11、mple”);/窗口的标题窗口的标题 glutDisplayFunc(mydisplay);/调用显示函数调用显示函数 glutMainLoop();/主函数循环主函数循环11/24/202219事件循环在程序中定义了一个显示回调函数(display callback function):mydisplay每个 glut 程序必须有一个显示回调函数只要OpenGL确定显示内容要被刷新时,显示回调函数就会被调用:例如,当窗口被打开的时候 main()函数以程序进入事件循环作为结束11/24/202220默认值simple.c 是个很简单的程序大量使用了状态变量的默认值视图模式 Viewing色彩
12、设置 Colors窗口参数 Window parameters后续的程序将改变一下默认值11/24/202221如何在VC+6.0环境下使用进行OPENGL环境设置 下载 有关的GLUT软件库:如glutdlls37beta 压缩包(1)将下载的压缩包解开,将得到5个文件:glut.h,glut.lib,glut32.lib,glut.dll和glut32.dll,并按如下要求进行设置(1)“找到 盘符(d:)Program FilesMicrosoft Visual StudioVC98includeGL文件夹”。把解压得到的glut.h放到这个GL文件夹里。没有GL文件夹可以自己建一个,一
13、般都有的。(2)把解压得到的glut.lib和glut32.lib放到静态函数库所在文件夹,即lib文件夹。如“盘符(d:)Program FilesMicrosoft Visual StudioVC98lib文件夹”)。(3)把解压得到的glut.dll和glut32.dll放到操作系统目录下面的system32文件夹内。(典型的位置为:C:WindowsSystem32)这是非常重要的动态链接库设置!11/24/202222如何在VC+6.0环境编辑和运行OpenGL程序WINDOWS环境设置创建一个Win32 console application 类型的workspace 文件创建一个
14、C/C+文件,包含simple.c的代码,并把这个文件插入到创建好的workspace 文件中直接进行编译 CTRL+F7,进行函数库的链接 F7执行 编译好的可执行文件F5(080311讲完)11/24/202223图元与属性 OpenGL基本库:很小的基本图元集 在GLU中包含从基本库里推导而来的其他类型的基本对象 OpenGL支持两类基本图元几何型图元在问题(用户)域里定义,具有空间和几何属性:点、线段、多边形、曲线、曲面等经过几何处理流水线,经过光栅化后变成帧缓存里的像素几何处理处理:决定图元是否可见、计算在屏幕上的显示位置等在问题域进行几何对象的建模和几何变换旋转、平移等光栅型图元把
15、对象看作是像素的集合,缺少几何属性,不能在问题空间中对其进行处理,经过另一条平行流水线到达帧缓存11/24/202224图元和属性的定义与设置OpenGL中的基本图元都是用空间中的点或顶点定义的,采用 glBigin()glEnd()之间来定义glBing(type);/type参数决定定义的几何对象类型glVertex*();glVertex*();glEnd();11/24/202225OpenGL基本图元-点、线段(P33)点(GL_POINT):每个顶点至少是一个像素大小线段(GL_LINE):线段类型把两个相邻的顶点当作线段的两个端点多段线(GL_LINE_STRIP,GL_LINE
16、_LOOP):封闭和不封闭11/24/202226OpenGL 基本图形元素11/24/202227OpenGL基本图元-多边形 多边形:带边界的、封闭的、具有内部区域的对象(GL_POLYGON)多边形的边界与采用GL_LINE_LOOP的类型一样,相邻两个顶点定义边界线,其中那个一条线段连接最后一个顶点和第一个顶点 三角形和四边形是多边形的特例(P35,图2.13)(GL_TRIANGLES)(GL_QUADS)带状和扇形:由一组三角形或四边形组成,共享部分顶点和边界线(P35,图2.14)(GL_TRIANGLE_STRIP)(GL_QUAD_STRIP)(GL_TRIANGLE_FAN
17、)11/24/202228多边形的限制条件OpenGL 只能显示满足下述条件的多边形简单多边形:边除顶点外不相交凸多边形:对于多边形中的任意两点,连接这两点的线段完全在多边形中平面多边形:所有顶点都在同一个平面内用户自己确保上述条件的满足OpenGL 将报错,如果上述条件不满足三角形满足上述所有限制条件nonsimple polygonnonconvex polygon11/24/202229曲线和曲面用图元来逼近曲线或曲面可以用一个正N多边形来逼近一个圆弧可以用正多面体来逼近球体用数学的方法来定义曲线和曲面,推导出这些对象的图形函数,再利用这些函数来创建对象四次曲面参数多项式曲线和曲面11/
18、24/202230属性属性是 OpenGL状态的一部分确定对象的外观颜色 (点,线,多边形)大小和宽度(points,lines)宽度和虚实形式(lines,polygons)多边形填充显示:颜色和模式显示边界显示顶点11/24/202231RGB 色彩每个颜色组件分别存储在帧缓存中一般在帧缓存中每个组件为8位注意在 glColor3f中,颜色值的范围从0.0到1.0,而在 glColor3ub中,值的范围在中,值的范围在 0 25511/24/202232索引颜色(颜色查找表)颜色值指向颜色查找表需要更少的存储量一般为8位现在不是很重要了存储器很便宜了需要更多的颜色做渲染11/24/2022
19、33颜色和状态颜色通过 glColor 来设置,成为状态机的一部分,直到被改变为止,一直保持这种状态颜色与其他属性不是对象的一部分,但是在渲染时要把这些属性赋给对象可以按下述方法创建顶点的颜色 glColor glVertex glColor glVertex11/24/202234颜色的光滑过渡默认状态是光滑过渡OpenGL 根据多边形顶点的颜色插值出来内部的颜色函数:函数:glShadeModel(GL_SMOOTH)11/24/202235颜色的平坦过渡另外一种状态是平坦过渡第一个顶点的颜色确定填充颜色函数:函数:glShadeModel(GL_FLAT)11/24/202236Open
20、GL编程第二节 完整的程序(080318)11/24/202237主要内容修改第一个程序改变默认值定义标准程序结构简单视图二维视图:作为三维视图的特例基本的OpenGL 图形元素属性11/24/202238程序结构大多数 OpenGL 程序有类似的结构,包含以下函数:main():主函数定义回调函数 打开一个或多个具有指定属性的窗口进入事件循环(最后一条可执行语句)init():设置状态变量视图:Viewing属性:AttributesCallbacks 回调函数显示函数输入和窗口函数11/24/202239对simple.c 的修改在这一版本的程序中,会得到同样的输出,但是所有具有默认值的相
21、应状态值都通过函数调用显式指定特别地,设置了颜色 Colors视图条件 Viewing conditions窗口属性 Window properties11/24/202240main.c#include int main(int argc,char*argv)glutInit(&argc,argv);glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB);glutInitWindowSize(500,500);glutInitWindowPosition(0,0);glutCreateWindow(simple);glutDisplayFunc(mydisplay
22、);init();glutMainLoop();includes gl.hdefine window propertiesset OpenGL stateenter event loopdisplay callback自动包含了gl.h定义窗口属性显示回调函数设置OpenGL状态进入事件循环(500,500)像素大小11/24/202241GLUT 函数glutInit 使得应用程序可以获得命令行参数并初始化系统gluInitDisplayMode 设置窗口的属性(渲染上下文the rendering context)RGB 颜色单缓冲区 Single buffering属性按逻辑“或”组合在
23、一起glutWindowSize 以像素为单位定义窗口的尺寸glutWindowPosition 定义窗口左上角在显示器的位置glutCreateWindow 创建窗口,标题为“simple”,来自参数glutDisplayFunc 定义显示回调函数glutMainLoop 进入无穷的事件循环11/24/202242Init()void init()glClearColor(0.0,0.0,0.0,1.0);glColor3f(1.0,1.0,1.0);glMatrixMode(GL_PROJECTION);glLoadIdentity();glOrtho(-1.0,1.0,-1.0,1.0,
24、-1.0,1.0);清除色(背景色)为黑色不透明窗口矩形填充色彩值设置白色矩阵模式设置为投影模式视景体设置(物体各个顶点的坐标值经过坐标变换后必须介于调用glOrtho()函数中提供参数的范围内)将当前的矩阵清零,不可再恢复 11/24/202243坐标系在 glVertex 中的单位是由应用程序确定的,称为世界坐标系(也叫对象或问题坐标系object or problem coordinates)视景体(viewing volume)也是相对于世界坐标系指定的,视景体确定出现在图像空间中的对象 在 OpenGL内部,会把世界坐标系转化为照相机坐标系(camera(eye)coordinate
25、s),稍后再转化为屏幕坐标系(screen coordinates)OpenGL 也常用某些内部表示法,这些内部表示法对应用程序并不可见11/24/202244OpenGL 照相机OpenGL 照相机被放置在坐标原点,指向Z轴负方向默认的视景体是一个中心在原点,边长为2的立方体11/24/202245正交视图(Orthographic Viewing)z=0z=0在默认的正交视图中,点沿着Z轴投影到Z=0的平面上plane z=011/24/202246变换与视图在 OpenGL中,投影是利用投影矩阵乘法进行的 由于只存在一个变换函数系列,因此必须先设置矩阵模式 glMatrixMode(GL
26、_PROJECTION)变换函数是累加在一起的所以需要从单位矩阵开始,然后把它改变为一个投影矩阵以定义视景体 glLoadIdentity();11/24/202247二维与三维视图在 glOrtho(left,right,bottom,top,near,far)中,近与远是相对于与照相机的距离而言的glOrtho(-1.0,1.0,-1.0,1.0,-1.0,1.0);二维顶点命令把所有的顶点放在z=0的盘面上如果应用程序处于二维状态,那么可以使用下述函数设置正交视景体:gluOrtho2D(left,right,bottom,top)对二维情形,视景体或裁剪体退化为裁剪窗口(clippin
27、g window)11/24/202248mydisplay.cvoid mydisplay()glClear(GL_COLOR_BUFFER_BIT);glBegin(GL_POLYGON);glVertex2f(-0.5,-0.5);glVertex2f(-0.5,0.5);glVertex2f(0.5,0.5);glVertex2f(0.5,-0.5);glEnd();glFlush();11/24/202249视窗并不需要把整个当前窗口用来显示图像:可以在窗口中设置视窗,函数为:glViewport(x,y,w,h)/x,y,w,h的的含义见下图含义见下图参数值以像素为单位(屏幕坐标系
28、)11/24/202250OpenGL 编程Part 3:三维图形11/24/202251主要内容提供一个更复杂的三维图形实例分形:Sierpinski 鏤垫l隐藏面消除11/24/202252三维图形应用程序在OpenGL中,二维应用程序是三维应用程序的特殊情形要得到三维图形应用程序只对前面程序做很小的修改使用 glVertex3*()必须考虑多边形绘制的顺序或者启用隐藏面消除功能只考虑平面的简单图多边形11/24/202253Sierpinski Gasket(2D)从一个三角形开始连接三边的中点并去掉中间的三角形Repeat11/24/202254示例 五次细分后的结果11/24/202
29、255作为分形的鏤垫考虑黑色填充区域的面积与周长(即包含填充区域的所有线段总长)当持续细分时面积趋向于零但周长趋向于无穷因此无穷细分后的结果不是通常的几何形状它的维数既不是一维的,也不是二维的称之为分形(fractional dimension)对象11/24/202256Gasket Program#include/initial triangle typedef GLfloat point22;/initial trianglepoint2 v=-1.0,-0.58,1.0,-0.58,0.0,1.15;int n;/递归步数递归步数11/24/202257绘制三角形void triang
30、le(point2 a,point2 b,point2 c)/display one triangleglBegin(GL_TRIANGLES);glVertex2fv(a);glVertex2fv(b);glVertex2fv(c);glEnd();11/24/202258三角形细分void divide_triangle(point2 a,point2 b,point2 c,int m)/*triangle subdivision using vertex numbers*/point2 v0,v1,v2;int j;if(m0)for(j=0;j2;j+)v0j=(aj+bj)/2;fo
31、r(j=0;j2;j+)v1j=(aj+cj)/2;for(j=0;j0)for(j=0;j3;j+)v1j=(aj+bj)/2;for(j=0;j3;j+)v2j=(aj+cj)/2;for(j=0;j3;j+)v3j=(bj+cj)/2;divide_triangle(a,v1,v2,m-1);divide_triangle(c,v2,v3,m-1);divide_triangle(b,v3,v1,m-1);else(triangle(a,b,c);11/24/202266生成四面体的代码void tetrahedron(int m)glColor3f(1.0,0.0,0.0);divid
32、e_triangle(v0,v1,v2,m);glColor3f(0.0,1.0,0.0);divide_triangle(v3,v2,v1,m);glColor3f(0.0,0.0,1.0);divide_triangle(v0,v3,v1,m);glColor3f(0.0,0.0,0.0);divide_triangle(v0,v2,v3,m);11/24/202267问题由于三角形是按照在程序中定义的顺序画出的,本来在前面的三角形并不是显示在位于它后面的三角形的前面get thiswant this11/24/202268隐藏面的消除只想见到那些位于其它面前面的曲面或平面片OpenGL采
33、用称为z缓冲区的算法进行隐藏面消除,在z缓冲区中存贮着对象的深度信息,从而只有前面的对象出现在图像中11/24/202269Z缓冲区算法在该算法中创建专门的缓冲区(称为z缓冲区),当几何体经过流水线各步骤时,存贮着该几何体的深度信息启用该算法的要素在 main()中()中 glutInitDisplayMode (GLUT_SINGLE|GLUT_RGB|GLUT_DEPTH)在init()中启用glEnable(GL_DEPTH_TEST)在显示回调函数中glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)11/24/202270二维细分 vs 三维细分在我们的例子中,对每个二维面进行细分我们也可以使用相同的中点来细分三维的图形中点定义了四个小一些的四面体,每一个四面体对应一个顶点在中点移除一个体,仅保持这些四面体11/24/202271体的细分形式11/24/202272