高级计算机图形学中国科学技术大学计算机学院黄章进zhuang@ustc.edu.cn上次内容输入设备•物理设备:指向设备、键盘设备•逻辑设备:定位、拾取、键盘、笔划、定值、选择•输入模式:请求模式、事件模式GLUT事件驱动编程•鼠标事件:glutMouseFunc,glutMotionFunc•键盘事件:glutKeyboardFunc,glutSpecialFunc•窗口事件:glutReshapeFunc第三章之第二节更复杂的交互程序基本内容子窗口菜单拾取•从显示结果中选择对象橡皮条显示列表子窗口与多窗口intglutCreateWindow(char*name)•创建一个顶层窗口name,并为其返回一个整数标识符voidglutDestroyWindow(intid)•销毁标识符为id的窗口voidglutSetWindow(intid)•把当前窗口设为标识符为id的窗口intglutCreateSubWindow(intparent,intx,inty,intwidth,intheight)•为parent窗口创建一个子窗口,返回子窗口的标识符。子窗口原点位于(x,y),宽度为width,高度为heightvoidglutPostWindowRedisplay(intid)•通知标识符为id的窗口重新显示构件(widgets)许多窗口系统提供了一个工具包或者一组库函数用来建立用户界面,界面中用到了一些特殊类型的窗口,称为构件(widgets)构件集中包含下述工具:•菜单•滑动条•对话框•文本输入框但上述构件通常都是与平台相关的GLUT只提供了包含菜单在内的很少几个构件菜单GLUT支持弹出式菜单•可以有子菜单创建弹出式菜单的三个步骤•定义菜单内各菜单项•定义每个菜单项的行为•如果菜单项被选择执行的操作•把菜单关接到鼠标按钮上菜单intglutCreateMenu(void(*f)(intvalue))•创建一个使用回调函数f()的菜单,并返回菜单的整数标识符voidglutAddMenuEntry(char*name,intvalue)•为当前菜单增加一个名为name的菜单项;value值在选中时返回给菜单回调函数voidglutAttachMenu(intbutton)•将当前菜单关联到鼠标按钮button上(GLUT_RIGHT_BUTTON、GLUT_MIDDLE_BUTTON、GLUT_LEFT_BUTTON)定义一个简单菜单在main()中menu_id=glutCreateMenu(mymenu);glutAddMenuEntry(“ClearScreen”,1);glutAddMenuEntry(“Exit”,2);glutAttachMenu(GLUT_RIGHT_BUTTON);ClearScreenExit当按下右按钮时,就会出现的条目标识符菜单的行为菜单回调函数voidmymenu(intvalue){if(value==1)glClear(GL_COLOR_BUFFER_BIT);if(value==2)exit(0);}添加子菜单voidglutAddSubMenu(char*submenu_name,intsubmenu_id)增加一个子菜单项submenu_name作为当前菜单的一项,子菜单创建时返回的标识符为submenu_id必须先创建子菜单父菜单中一项子菜单在main()函数中创建一个层级菜单intsub_menu;sub_menu=glutCreateMenu(processSizeMenu);glutAddMenuEntry(“increasesquaresize“,2);glutAddMenuEntry(“decreasesquaresize“,3);glutCreateMenu(processTopMenu);glutAddMenuEntry(“quit“,1);glutAddSubMenu(“resize,sub_menu);glutAttachMenu(GLUT_RIGHT_BUTTON);旋转的正方形将正方形的4个顶点定义为圆周上的4个等间隔点GLfloattheta=0.0;//全局变量voiddisplay(){glclear(GL_COLOR_BUFFER_BIT);Glfloatt1=theta/(3.14159/180.0);//度转换为弧度glBegin(GL_POLYGON);glVertex2f(cos(t1),sin(t1));glVertex2f(-sin(t1),cos(t1));glVertex2f(-cos(t1),-sin(t1));glVertex2f(sin(t1),-cos(t1));glEnd();glFlush();}旋转的正方形空闲回调函数:当事件队列为空时被执行voidglutIdleFunc(void(*f)(void))利用空闲回调函数改变theta值,使正方形绕原点旋转•在main()函数里,注册空闲回调函数:glutIdleFunc(idle);•空闲回调函数的定义:voididle(){theta+=2.0;if(theta360.0)theta-=360.0;glutPostRedisplay();//请求重绘}单缓冲区的问题当通过显示回调函数重新绘制显示结果时,我们通常会首先清除整个窗口:glClear()然后再绘制已发生了变化的显示结果问题:帧缓冲区中的信息在显示器上的显示结果出现了错位•图形系统可以同时向显存中写入内容和从中读出内容从而我们会看到部分显示内容•显卡若启动了硬件加速功能,不会发现上述问题双缓冲区不只用一个颜色缓冲区,而是应用两个缓冲区•前缓冲区:显示它的内容,但不向它写入内容•后缓冲区:写入内容,不显示程序在main()中请求使用双缓存•glutInitDisplayMode(GLUT_RGB|GLUT_DOUBLE)•在显示回调函数结束之前,交换两个缓冲区voiddisplay(){glClear(GL_COLOR_BUFFER_BIT|…);…//绘制图形…glutSwapBuffers();}GLUT中其它的函数动态窗口•在执行期间创建或关闭窗口在执行期间改变回调函数计时器字体•glutBitmapCharacter•glutStrokeCharacterGLUT教程拾取(picking)从屏幕上显示结果中识别用户定义的对象原则上说要达到这个目标是很简单的,因为鼠标可以提供位置信息,我们应该能够根据位置确定对应的是哪个对象实际操作中的困难•流水线图形体系结构是单向的,很难从二维屏幕返回到三维世界•二维屏幕,三维世界,也使问题变得复杂•离位置多近可以认为选择了对象?绘制模式OpenGL使用三种模式进行绘制GLintglRenderMode(GLenummode)•GL_RENDER正常绘制模式:正常绘制到帧缓冲区中(缺省模式)•GL_FEEDBACK反馈模式:提供已绘制的图元列表,但并不输出到帧缓冲区中•GL_SELECTION选择模式:视景体中的每个图元产生一个命中记录,这个记录放到名称堆栈中,稍后要被检测选择模式中用到的函数glSelectBuffer():指定名称缓冲区glInitNames():初始化名称堆栈glPushName(id):把id压入名称堆栈中glPopName():把名称堆栈顶部的名称弹出glLoadName(id):取代在名称堆栈中的栈顶名称•id是由应用程序设置,用以识别对象绘制对象前,把对象的名称加载(取代栈顶名称)到名称堆栈。初始化时,将一个未使用的名称压栈:glInitNames();glPushName(0);利用选择模式进行拾取初始化名称缓冲区进入选择模式(例如:应用鼠标)绘制场景,场景中对象有用户指定的标识重新进入正常绘制模式•该操作返回命中次数检查名称缓冲区的内容(命中记录)•命中记录包括id与深度信息选择模式与拾取在选择模式中不能进行选取,因为这时在视景体中的每个对象都会生成一个命中记录在选择模式中改变视图参数,使得只有靠近鼠标指针的对象位于新的视景体中•利用gluPickMatrix(…)利用屏幕上的区域进行拾取在许多应用程序中,屏幕采用简单的矩形分划•例如:这时,相比于应用选择模式进行拾取而言,通过查看鼠标位置,确定屏幕的区域就更简单了绘图区域工具条菜单通过另外的缓冲区和颜色进行拾取对于很少数目的对象,可以给每个对象赋以唯一的颜色(有时是在颜色索引模式中)然后把场景输入到不是前缓冲区的一个颜色缓冲区中,这样就不会看到显示出来的结果然后获到鼠标位置,利用glReadPixels()读取缓冲区中的颜色根据返回的颜色确定是哪个对象像素的写入模式读像素帧缓冲区逐位逻辑操作目标像素源像素写像素d’sdXOR写入模式缺省的写入模式是直接用源像素取代目标像素,即d’=s•用这种方法不能绘制一条临时直线,因为我们不能用快速简单的方法恢复在临时直线下面的内容异或操作(XOR):d’=d⊕s•相同值为0,不同值为1•(d⊕s)⊕s=d•因此如果应用XOR模式画一条直线,那么只要在原地再画一遍这条直线就可以删除这条直线橡皮条式绘图切换到XOR写入模式绘制对象•利用第一次单击鼠标固定线段的一个端点,然后再利用鼠标移动的回调函数连续更新第二个端点•每次鼠标移动的时候,重新画一遍原来的直线从而删除它,然后再从固定的第一个端点到新的第二个端点之间画一条直线•最后切换回到正常的绘图模式并绘制直线•也适用于其它对象:矩形、圆橡皮条型直线初始显示结果第二个点第一个点利用鼠标在XOR模式中画一条直线把鼠标移到新的位置原来的直线利用XOR重新画一遍新的直线利用XOR画出来OpenGL中的逻辑操作在两位之间有16种可能的逻辑操作OpenGL支持所有的这16种操作•必须首先启动逻辑操作•glEnable(GL_COLOR_LOGIC_OP)•然后选择逻辑操作类型•glLogicOp(GL_XOR)•glLogicOp(GL_COPY)(默认值)即时模式与保留模式注意:在标准的OpenGL程序中,一旦某个对象被显示输出,对它没有任何记忆;为了重新显示它,需要再执行一次相应的代码•这就是即时模式(immediatemode)图形•如果对象相当复杂,而且通过网络传输,那么这就可能导致系统非常慢另外一种模式就是首先定义对象,然后以某种形式把它记忆下来,从而更容易重新显示•这就是保留模式(retainedmode)图形•在OpenGL中是通过显示列表实现的显示列表从概念上类似于图形文件•必须首先定义列表的名称并创建它•向列表中添加内容•关闭列表在客户-服务的体系环境中,显示列表是放在服务器(显示服务器)一方•这样不必通过网络每次发送原来的图元定义就可以重新显示显示列表中用到的函数voidglNewList(GLuintname,GLenummode)•创建一个新的显示列表,其标识符为name。•mode:GL_COMPILE把显示列表放到服务器,但不显示;GL_COMPILE_AND_EXECUTE创建时即被显示voidglEndList()•结束显示列表的定义voidglCallList(GLuintname)•执行显示列表namevoidglDeleteLists(GLuintfirst,GLsizeinumber)•删除自first开始的number个显示列表•显示列表一经创建便不可修改,只能删除显示列表创建显示列表调用已创建的显示列表GLuintid;voidinit(void){id=glGenLists(1);glNewList(id,GL_COMPILE);//其它OpenGL绘图子程序或者绘图命令glEndList();}voiddi