资源描述
论文题目: 基于OpenGL的屏幕对象拾取 27 / 31毕业论文(设计)原创性声明本人所呈交的毕业论文(设计)是我在导师的指导下进行的研究工作与取得的研究成果。据我所知,除文中已经注明引用的容外,本论文(设计)不包含其他个人已经发表或撰写过的研究成果。对本论文(设计)的研究做出重要贡献的个人和集体,均已在文中作了明确说明并表示意。 作者签名: 日期:毕业论文(设计)授权使用说明本论文(设计)作者完全了解*学院有关保留、使用毕业论文(设计)的规定,学校有权保留论文(设计)并向相关部门送交论文(设计)的电子版和纸质版。有权将论文(设计)用于非赢利目的的少量复制并允许论文(设计)进入学校图书馆被查阅。学校可以公布论文(设计)的全部或部分容。的论文(设计)在解密后适用本规定。 作者签名: 指导教师签名:日期: 日期:注意事项1.设计(论文)的容包括:1)封面(按教务处制定的标准封面格式制作)2)原创性声明3)中文摘要(300字左右)、关键词4)外文摘要、关键词5)目次页(附件不统一编入)6)论文主体部分:引言(或绪论)、正文、结论7)参考文献8)致9)附录(对论文支持必要时)2.论文字数要求:理工类设计(论文)正文字数不少于1万字(不包括图纸、程序清单等),文科类论文正文字数不少于1.2万字。3.附件包括:任务书、开题报告、外文译文、译文原文(复印件)。4.文字、图表要求:1)文字通顺,语言流畅,书写字迹工整,打印字体与大小符合要求,无错别字,不准请他人代写2)工程设计类题目的图纸,要求部分用尺规绘制,部分用计算机绘制,所有图纸应符合国家技术标准规。图表整洁,布局合理,文字注释必须使用工程字书写,不准用徒手画3)毕业论文须用A4单面打印,论文50页以上的双面打印4)图表应绘制于无格子的页面上5)软件工程类课题应有程序清单,并提供电子文档5.装订顺序1)设计(论文)2)附件:按照任务书、开题报告、外文译文、译文原文(复印件)次序装订3)其它目录摘要1ABSTRACT21 绪论31.1 课题的目的和意义31.2 拾取技术国外研究31.3 本论文研究主要容32 基于OPENGLMFC的建模基础52.1 OpenGL概括52.2 OpenGL渲染管线过程52.2.1顶点变换62.2.2图元组装62.2.3图元处理62.2.4片元处理62.2.5光栅化操作62.3 MFC概述62.4 MFC特点72.4.1封装72.4.2继承82.4.3虚拟函数和动态约束82.5 应用程序的构成82.6 基于OpenGL+MFC的三维模拟的编程环境配置93 拾取技术123.1 基于射线求交拾取技术123.1.1判断线段和包围盒的相对位置123.2 基于GPU的重绘式拾取技术143.3 各种拾取技术比较164 系统的设计与实现184.1 系统的选择机制184.1.1进入选择模式之前184.1.2获取当前选择模式184.1.3退出选择模式204.1.4拾取204.2 拾取结果截图215 结论与展望255.1 结论255.2 展望25参考文献26致28摘要屏幕对象的拾取是计算机图形处理系统中一个重要的功能,在许多情况下,计算机图形处理系统不仅要绘制图形,而且要允许操作者能够通过输入设备(通常是鼠标)操纵屏幕上的物体(标识、移动和修改)。有时还需要获取物体上点的空间坐标或测量物体的几何特性如距离、角度、半径等,这些操作都需要以拾取作为实现的基础。OpenGL为了解决拾取问题,提供了一种基于名字堆栈和命中记录的选择机制。在OpenGL中,拾取物体是利用拾取矩阵和投影变换,将拾取的围限制在鼠标热点的有效区中,一旦触发鼠标事件就进入选择模式并将有效区初始化,最后利用拾取矩阵拾取有效区的物体。有效区的定义由glPick2Matix()函数来完成。一旦拾取成功,就以记录的形式返回与拾取物体相关的信息,并生成一个记录表示一个物体被命中。这种物体拾取方法非常简单,不需要写很多代码。在使用OpenGL工具包开发图形处理系统时,物体的拾取有多种方法,其中包括OpenGL提供的选择机制、射线拾取法、重绘式拾取法等。本文采用OpenGL本身提供的选择机制来拾取对象,突出OpenGL工具包在屏幕对象的拾取方面的优势。关 键 词:OpenGL;计算机应用;拾取算法AbstractIn most cases, one important feature of graphics processing system is picking, which allow users to select objects by mouse, and to modify the their attributes, such as gemetry mesh or angle and so on. In order to solve the problem, pick OpenGL provides a name on the stack and hit record choice mechanism. In the OpenGL and pickobject is using the loot matrix and projection transformation, and will gather in the limits of the mouse, effective once entered the mouse event trigger mode choice and will be effective area, finally using initialization gleaned matrix pick objects in the area of effective. The definition of effective glPick2Matix () function to finish. Once pick success, to return to pick the record object relevant information, and to create a record, says an object to be hit. This object pick method is very simple, need not write many code.In the use of graphics processing system development OpenGL toolkit objects, pick a variety of methods, including OpenGL provide choice mechanism, ray pick, redrawn type gather method, etc. Based on the mechanism of OpenGL itself to provide choice pickup, outstanding OpenGL toolkit objects in the screen in the object.Key words: OpenGL; computer application;picking1 绪论目前许多优秀的图形工具能为我们绘制惟妙惟肖的虚拟现实场景,在面对这些场景时候我们除了欣赏之外更多的是希望能与之互动,做为编程人员可以使用代码轻松的重新构造场景,但对于终端用户而言,他们也希望自己也能对造场景进行一些操作,如添加,删除等等。对于这些操作而言,首先我们要做的是能让用户使用鼠标来选择他所希望操作的对象,这就是本文要讨论的一个重点:拾取,这是一种在许多交互性程序中有基础地位的操作,是对屏幕中对象进行定位,并确定所选择的是哪个物体。然而该操作给我们提出了一些难题,首先,需要对对象进行届定。其次,必须对“拾取目标”进行定义。这就需要终端用户确定单击的位置是构成对象的图元上,还是对象附近的位置以与考虑如果选取点落在两个以上物体交集部分如何处理等等问题,本文利用了OpenGL中的选择模式进行有效的对象判别和拾取。1.1 课题的目的和意义图形对象的拾取是计算机图形处理系统中一个重要的功能,很多图形系统需要用户通过输入设备与系统交互,如移动、旋转某个物体,或查询某个物体的状态信息,需要通过拾取来确定景中的操作对象。快速可靠的拾取被广泛地运用于各种系统中,如实时图形系统、虚拟现实、游戏和CAD系统等方面,拾取操作己成为这些系统的重要部分,它在计算机建模软件尤其重要,能够通过拾取操作对模型进行局部修改和编辑,提高建模系统的灵活性和适用性。随着计算机软硬件的快速发展,人们对实时系统的交互的实时性要求也越发苛刻,而且三维场景复杂度也日益提高,这就要求系统提供快速的拾取操作。因此,高效的拾取算法能够决定拾取操作快与慢的关键所在,从而开发高效的拾取算法已成为当今一个的课题研究。1.2 拾取技术国外研究随着网络科技越来越发达,拾取操作也越来越方便了,有的只要鼠标点点就可以。而拾取操作的关键是拾取算法。到目前为止,拾取算法大致上可以分三种。第一种是基于射线求交的拾取技术,1992年,Mark Segul,Carl Korobkin,Rolf van Widenfelt等人首次采用了基于射线求交的拾取技术原理实现了对衣服的拾取1;1998年Michael Deering,Step hanie Winner and Bic Schediwy等也才采用了同样的技术成功做到了人物的拾取2;2005年,龚堰珏,颜敏等人采用了基于射线求交的拾取技术实现对几个简单物体的拾取3;同年,韦宇炜也是用此技术实现对游戏中各类的拾取,如对技能的释放,对地上物品的捡起来,对别的玩家人物属性的查看等等4;2006年,继权,晓豁等人也采用了这种拾取技术成功地做到了对3D网游游戏中的各种各样的拾取5,等等。第二种是基于GPU的重绘式拾取技术,这种方法对硬件的依赖性大,不过拾取速度快。1997年,MasaakiOka,Kyoya Tsutsui,Akio Ohba等人第一次采用了基于GPU的重绘式拾取技术在房子中拾取到了房子主人与小孩子6;2006年,力强,周明全等多人采用基于GPU的重绘式拾取技术,在大规模室外地形中拾取到了地面某个区域7。第三种是OpenGL自带的拾取机制,1992年7月,SGI公司发布了OpenGL的1.0版本,随后又与微软公司共同开发了Windows NT版本的OpenGL,从而使一些原来必须在高档图形工作站上运行的大型3D图形处理软件也可以在微机上运用。1995年OpenGL的1.1版本面市,该版本较1.0性能提高许多,并加入了一些新的功能,如本身自带的拾取机制,这项功能使OpenGL在各个领域都得到了应用8。随后出现的各个版本,使自带的拾取机制越来越完善,操作越来越方便,应用围越来越广泛。1.3 本文研究的主要容(一):综述本课题研究的意义以与国外研究现状;(二):介绍了OpenGL与OpenGL渲染管道,分析MFC编程框架和基于OpenGL+MFC的三维模拟的编程环境配置;(三):各种拾取技术的实现原理,优缺点;(四):采用了OpenGL本身自带的拾取机制设计并实现简单的拾取操作;(五):总结本文并进一步给出了展望。2 基于OpenGLMFC的建模基础2.1 OpenGL概括OpenGL(全写Open Graphics Library)是个定义了一个跨编程语言、跨平台的编程软件接口的规格,该接口包括了大约250个不同的函数(其中核心OpenGL大约包括200个函数,另外还有50个左右位于OpenGL工具函数库),可以用这些函数指定物体和操作,创建交互性三维应用程序。它是个专业的图形程序接口,是一个功能强大,调用方便的底层图形库,Opengl用于三维图象(二维的亦可)。OpenGL的设计目标就是作为一种流线型的、独立于硬件的接口,可以在许多不同的硬件平台上实现。为了实现这个目标,OpenGL并未包含用于执行窗口任务或者获取用户输入之类的函数。反之,必须通过窗口系统控制所使用的特定硬件。类似地,OpenGL并没有提供用于描述三维物体模型的高层函数。这类函数可能允许你指定相对较为复杂的形状,例如汽车、身体的某 个部位、飞机或分子等。在OpenGL中,必须根据少数几个基本几何图元(geometric primitive)(如点、直线和多边形)来创建你所需要的模型。当然,我们可以在OpenGL上创建能够提供这些特性的高级函数库。OpenGL 实用函数库(GLU)提供了许多建模特性,例如二次曲面以与NURBS曲线和表面。GLU是所有OpenGL实现的一个标准组成部分。1992年7月,SGI公司发布了OpenGL的1.0版本,随后又与微软公司共同开发了Windows NT版本的OpenGL,从而使一些原来必须在高档图形工作站上运行的大型3D图形处理软件也可以在微机上运用。1995年OpenGL的1.1版本面市,该版本较1.0性能提高许多,并加入了一些新的功能。1997年,Windows 95下3D游戏的大量涌现,游戏开发公司迫切需要一个功能强大、兼容性好的3D图形接口,而当时微软公司自己的3D图形接口DirectX 3.0功能却是很糟糕。因而以制作雷神之锤等经典3D射击游戏而著名的id公司同其它一些游戏开发公司一同强烈要求微软在Windows95中加入对OpenGL的支持。微软公司最终在Windows95的OSR2版和后来的Windows版本中加入了对OpenGL的支持。这样,不但许多支持OpenGL的电脑3D游戏得到广泛应用,而且许多在3D图形设计软件也可以运用支持OpenGL标准的3D加速卡,大大提高其3D图形的处理速度。2003年的7月28日,SGI和ARB公布了OpenGL 1.5。OpenGL 1.5中包括OpenGL ARB的正式扩展规格绘制语言“OpenGL Shading Language”。2004年8月,OpenGL2.0版本发布OpenGL 2.0标准的主要制订者并非原来的SGI,而是逐渐在ARB中占据主动地位的3Dlabs。2008年8月初Khronos工作组在Siggraph 2008大会上宣布了OpenGL 3.0图形接口规,GLSL1.30 shader语言和其他新增功能将再次未来开放3D接口发展指明方向。2009年3月又公布了升级版新规OpenGL 3.1,也是这套跨平台免费API有史以来的第九次更新。2009年8月Khronos小组发布了OpenGL 3.2,这是一年以来OpenGL进行的第三次重要升级。Khronos旗下的OpenGL ARB(Architecture Review Board)工作组推出了GLSL 1.5OpenGLShading Language(OpenGL着色语言)的升级版,以与在OpenGL3.2框架下推出了两个新功能,可以让开发者在开发新程序时能够在使用流水线核特性或兼容性特性之间做出选择,其中兼容性特性会提供与旧版OpenGL之间的兼容性。2.2 OpenGL渲染管线过程管线这个术语用于描述一种过程,它涉与两个或更多个独特的阶段或步骤。当应用程序进行OpenGL API 调用时,这些命令被放置在一个命令缓冲区中。这个缓冲区最终填满了命令、顶点数据、纹理数据等。当缓冲区被刷新时,命令和数据就被传递给管线的下一个阶段。通常,顶点数据首先进行转换和光照。在转换阶段,描述物体几何形状的点被重新计算,以确定这个物体的位置和方向。同时所进行的光照计算将确定每个顶点应该具有的颜色亮度。当这个阶段完成之后,数据就被输入到管线的光栅化部分。光栅阶段根据几何图形、颜色和纹理数据实际创建彩色图像。然后,图像被放入帧缓冲区中。帧缓冲区就是图形显示设备的存,这意味着这幅图像将会在屏幕上显示。图1显示了OpenGL工作流程图顺序,虽然并没有严格规定OpenGL必须采用这样的实现,但它却提供了一个可靠的指南方向,可以预测OpenGL将以什么样的顺序来执行这些操作。图 1 Opengl工作渲染流程图OpenGL渲染管线的操作过程主要包括以下几部分:1)顶点变换2)图元组装3)图元处理4)片元处理5)光栅化操作2.2.1顶点变换这个阶段主要是对输入的顶点进行逐个处理,这些顶点都包括很多属性(如位置、颜色、法线和纹理坐标等),经过处理后,输出是经过变换后的顶点属性与关联信息。主要过程:顶点变换(几何变换和投影变换)、光照计算、纹理坐标变换和生成。2.2.2图元组装这个阶段主要是按照输入的变换后的顶点属性和关联信息,组装形成图元。2.2.3图元处理这个阶段主要是对输入的图元进行处理,输出片元(帧缓存中更新象素属性的数据)信息,该片元信息是对顶点变换阶段得出的属性进行插值处理得到的。主要过程:视景裁剪、背面剔除。2.2.4片元处理这个阶段的输入为经过插值计算后的最终片元信息,经过处理后,输出信息为片元的深度和颜色值。 主要过程:纹理、雾化、颜色汇总(包括纹理颜色,光照颜色,主颜色等)。2.2.5光栅化操作这个阶段的输入为像素位置和片元的深度、颜色值等信息,经过一系列的测试(剪切测试、Alpha测试、模板测试和深度测试)后形成像素的颜色。主要过程:剪切测试、Alpha测试、模板测试和深度测试、写入帧缓存。2.3 MFC概述MFC(Microsoft Foundation Class Library)中的各种类结合起来构成了一个应用程序框架,它的目的就是让程序员在此基础上来建立Windows下的应用程序,这是一种相对SDK来说更为简单的方法。因为总体上,MFC框架定义了应用程序的轮廓,并提供了用户接口的标准实现方法,程序员所要做的就是通过预定义的接口把具体应用程序特有的东西填入这个轮廓。Microsoft Visual C+提供了相应的工具来完成这个工作:AppWizard可以用来生成初步的框架文件(代码和资源等);资源编辑器用于帮助直观地设计用户接口;ClassWizard用来协助添加代码到框架文件;最后,编译,则通过类库实现了应用程序特定的逻辑。 MFC是在1992年随微软的Microsoft C/C+ 7.0编译器发布的,用于面向16位Windows的软件开发。起初,MFC是作为一个应用程序框架开发的,所以定名为Application FrameworkX (AFX)。 Borland几乎同时发布了面向Turbo C编译器的OWL,并且在一开始比MFC更具有市场占有率,但是在Borland发布了一个不向下兼容的应用程序框架之后,它丧失了很多市场份额;在Borland从微软获得发布MFC的授权之后它的市场份额进一步减少。Borland最终用Visual Component Library来作为OWL的后继者。 随着Visual Basic和Visual Studio .NET的发布,曾经一度被微软重点推荐的MFC被Visual Basic、C#、Windows Forms抢走了不少市场份额,但是MFC继续在非托管软件开发中占据重要地位。在托管开发方面,MFC中也包括对Windows Forms和托管非托管互操作的封装。微软在Windows Vista和Windows 7发布之后在MFC中增加了对新的Windows API支持。 很多商用类库在MFC的基础上进一步实现了皮肤、渐变风格、多顶层窗口程序、属性列表等较受欢迎的功能;同时,在C+在线社区中,很大一部分开放的源代码也是基于MFC的。VC+产品版本与MFC版本如下:表格 1 VC+产品版本与MFC版本2.4 MFC 特点2.4.1封装构成MFC框架的是MFC类库。MFC类库是C+类库。这些类或者封装了Win32应用程序编程接口,或者封装了应用程序的概念,或者封装了OLE特性,或者封装了ODBC和DAO数据访问的功能,等等,分述如下:(1)对Win32应用程序编程接口的封装;(2)对应用程序概念的封装;(3)对COM/OLE特性的封装;(4)对ODBC功能的封装。2.4.2继承首先,MFC抽象出众多类的共同特性,设计出一些基类作为实现其他类的基础。这些类中,最重要的类是CObject和CCmdTarget。CObject是MFC的根类,绝大多数MFC类是其派生的,包括CCmdTarget。CObject 实现了一些重要的特性,包括动态类信息、动态创建、对象序列化、对程序调试的支持,等等。所有从CObject派生的类都将具备或者可以具备CObject所拥有的特性。CCmdTarget通过封装一些属性和方法,提供了消息处理的架构。MFC中,任何可以处理消息的类都从CCmdTarget派生。针对每种不同的对象,MFC都设计了一组类对这些对象进行封装,每一组类都有一个基类,从基类派生出众多更具体的类。这些对象包括以下种类:窗口对象,基类是CWnd;应用程序对象,基类是CwinThread;文档对象,基类是Cdocument,等等。2.4.3虚拟函数和动态约束MFC以“C+”为基础,自然支持虚拟函数和动态约束。但是作为一个编程框架,有一个问题必须解决:如果仅仅通过虚拟函数来支持动态约束,必然导致虚拟函数表过于臃肿,消耗存,效率低下。例如,CWnd封装 Windows窗口对象时,每一条Windows消息对应一个成员函数,这些成员函数为派生类所继承。如果这些函数都设计成虚拟函数,由于数量太多,实现起来不现实。于是,MFC建立了消息映射机制,以一种富有效率、便于使用的手段解决消息处理函数的动态约束问题。2.5 应用程序的构成图2解释了该应用程序的结构与对象,箭头表示信息流向。图 2 应用程序的结构从CWinApp、CDocument、CView、CMDIFrameWnd、CMDIChildWnd类对应地派生出CTApp、CTDoc、CTView、CMainFrame、CChildFrame五个类,这五个类的实例分别是应用程序对象、文档对象、视对象、主框架窗口对象和文档边框窗口对象。主框架窗口包含了视窗口、工具条和状态栏。对这些类或者对象解释如下:(1)应用程序(CWinApp) 应用程序类派生于CWinApp。基于框架的应用程序必须有且只有一个应用程序对象,它负责应用程序的初始化、运行和结束。(2)边框窗口(CMDIFrameWnd) 如果是SDI应用程序,从CFrameWnd类派生边框窗口类,边框窗口的客户子窗口(MDIClient)直接包含视窗口;如果是MDI应用程序,从CMDIFrameWnd类派生边框窗口类,边框窗口的客户子窗口(MDIClient)直接包含文档边框窗口。如果要支持工具条、状态栏,则派生的边框窗口类还要添加CToolBar和CStatusBar类型的成员变量,以与在一个OnCreate消息处理函数中初始化这两个控制窗口。边框窗口用来管理文档边框窗口、视窗口、工具条、菜单、加速键等,协调半模式状态(如上下文的帮助(SHIFT+F1模式)和打印预览)。(3)文档边框窗口(CMDIChildWnd) 文档边框窗口类从CMDIChildWnd类派生,MDI应用程序使用文档边框窗口来包含视窗口。(4)文档(CDocument) 文档类从CDocument类派生,用来管理数据,数据的变化、存取都是通过文档实现的。视窗口通过文档对象来访问和更新数据。(5)视(CView) 视类从CView或它的派生类派生。视和文档联系在一起,在文档和用户之间起中介作用,即视在屏幕上显示文档的容,并把用户输入转换成对文档的操作。用图的形式可直观地表示所涉与的MFC类的继承或者派生关系,如下:图 3 MFC的层次2.6 基于OpenGL+MFC的三维模拟的编程环境配置用MFC调用OpenGL函数来进行三维模拟的编程环境配置:(一)创建MFC项目(1) 创建项目文件:选择File/New 菜单选项,建立一个名为MyTest 的单文档 (SDI) 应用程序;(二)配置OpenGL开发环境(1)将91h,gluh,glauxh和gluth拷贝到(即盘符+路径)Microsoft Visual StudioVC98IncludeGL目录中;(2)将opengl321ib,glu321ib,glaux1ib和glut321ib拷贝到(即盘符+路径)Microsoft Visual StudioVc98Lib目录中;(3)将opengl32.dll,glu.dll,glu32.dll,glut.dll,glut32.dll文件拷贝到操作系统安装目录C:WINDOWSsystem32目录下;(4)选择Project/Setting 菜单选项。在Link栏的Lib输入域中添加openg132.lib、glu32.lib,若需使用OpenGL的辅助库函数,则还需添加glaux.lib。到此,基于OpenGLMFC的开发环境就建立好。(三)初始化OpenGL具体编程步骤:第一步:修改窗口风格设置。需要在函数CGLView:PreCreateWindow中增加对Windows窗口风格的设置,以防止在窗口重叠时把图形绘制到子窗口和兄弟窗口。实现代码如下:csstyle l=WS_CLIPCHILDREN WS CLIPSIBLINGS;第二步:设置像素格式。首先需要在视图类CGLView中添加一个成员函数,函数原型如下:BOOL CGLView:SetupPixelFormat(CDC*pDC);设置像素格式并向视图类CGLView中添加两个成员变量:CDC* m_pDC;OpenGL设备场境HGLRC m_hRC;OpenGL渲染场境在Windows中,使用结构PIXELFO腿ATDEscRIPTOR来设置像素格式,并提供ChoosePixelFormat()函数来选择最为匹配的像素格式以与SetPixelFormat()函数来为设备场境设置像素格式。设置像素格式的步骤如下:a.填写结构PIXELFORMATDESCRIPTOR,像素格式是用这个结构来描述的。b.调用函数ChoosePixelFormat(),将填写好的像素格式结构传递给该函数,函数ChoosePixelFormat()返回一个整型的序号。这个序号标识一个当前设备场境中所能提供的,且与所要求的像素格式最为匹配的像素格式。c将这个返回的像素格式序号传递给函数SetPixelFormat(),使之设置成为当前设备场境的像素格式。第三步:创建渲染场境。用wglreatecontext()函数来创建OpenGL的一个渲染场境;用wglMakeCurrent()函数使给定的渲染场境设置成为当前调用线程的渲染场境,而线程中随后调用的OpenGL命令都将通过与该渲染场境相关联的设备场境来实现窗口的场景绘制。具体实现代码如下:HDC m hDC;HGLRC nl hglRC:m_hglRC=wglCreateContext(m_hDC);创建一个渲染场境wglMakecurrent(m_hDC,m hglRC);使成为当前调用线程的渲染场境第四步:添加消息处理函数。利用MFC ClassWizard为CGLView类添加消息:帐_CREATE、wM_DESTROY、m,t_SIZE和删_TIMER的响应函数,消息处理函数名依次为:OnCreate()、onDestroy()、OnSize()和OnTimer()。ACGLView:OnCreate()函数该函数完成的功能是:在视图窗口创建完成后,进行OpenGLWindows的初始化工作。(四)清理工作CGLView:OnDestroy()函数该函数完成的功能是:在视图窗口被释放时,用于清除当前的渲染场境,并释放设备场境。具体实现代码如下:wglMakeCurrent(NULL,NULL);wglDeleteContext(m_hglRC);清除渲染场境:ReleaseDC(m_hWnd,m_hDC);释放设备场境CCGLView:OnSize()函数DCGLView:OnTimer()函数该函数完成的功能是:通过定时器每隔一定时间的消息驱动,来实现动态的场景更新。3 拾取技术拾取算法的研究可大致分为两类:一类是基于三维空间的射线拾取算法,代表算法就是基于CPU的射线求交拾取技术;另一类是基于图像空间的拾取算法,例如:基于GPU的重绘式拾取技术。3.1 基于射线求交拾取技术其基本原理是:获取屏幕坐标并转换成图形系统的视口坐标,根据不同图形API(应用程序编程接口)的实现给该点加上适当的深度值(如OpenGL标准的深度值介于0-1),反算出该拾取点的世界空间坐标,将相机焦点坐标转换为屏幕坐标,过相机位置点向鼠标选中点作一条射线在三维空间中对射线和物体进行求交,离相机位置点最近的实体就是被选中的实体。算法的具体实现步骤如下:(1)初始化,获取鼠标点的屏幕坐标(x,y),并将相机焦点坐标转换为屏幕坐标;(2)相机焦点深度值(z值)作为鼠标点的深度值(z值),并将鼠标点坐标转化为世界坐标(XW ,YW ,ZW);(3)过相机位置点向点(XW,YW,ZW)作一射线m,并分别求射线m 和投影空间近截面和远截面的交点A,B,得到线段AB。如果射线m垂直于视线,则射线m和投影空间不相交;(4)依次取出场景实体列表中的每个实体,获取该实体的转换矩阵,并利用该矩阵将A、B点的坐标转换为局部坐标;(5)计算实体的包围盒,并判断AB和包围盒的相对位置。如果AB和包围盒相交,则求该实体和AB交点的参数值,并将该实体标记为选中对象。如不相交,则进行下一个实体的处理。在上述算法中,判断线段AB和实体包围盒的相对位置与线段AB和实体的求交是算法实现的关键。为了提高拾取速度,对传统的包围盒算法和求交算法进行了改进。3.1.1判断线段和包围盒的相对位置实体的最大包围盒是一个各表面平行于坐标平面的六面体,其左下顶点的x、y、z坐标值为实体所有顶点相应坐标值的最小值,其右上顶点的坐标值为实体所有顶点相应坐标值的最大值。采用向量法来判断线段和包围盒相对位置,该算法的基本思想为:将线段看成一个由起点指向终点的向量,求向量各分量和离起点最近的三个包围盒侧面所在平面的交点,如果各分量与平面的交点都在包围盒侧面上或者在包围盒且在各分量上,则该线段和包围盒相交(本文中我们把线段与包围盒相交和线段在包围盒这两种情况统称为相交)。判断线段与包围盒相对位置的具体步骤如下:(1)判断起点A是否在包围盒,如果在盒,则线段AB和包围盒相交,判断结束;如果不在盒,则记下该包围盒离起点A最近的三个侧面,这三个侧面必定共点且相互垂直;(2)过起点A向各侧面做垂线,得到三个垂足,连接点A和各垂足,得到三个向量a1、a2、a3(如图4所示),并求a1、a2、a3和向量AB各相应分量的比值ti=ai/AB(i=1,2,3)。如果AB的某个分量为零,则比值取-1。取t1、t2、t3中的最大值为tmax,如果tmax1,则AB和包围盒不相交,判断结束;(3)根据tmax值求出交点坐标,判断交点是否在包围盒上。(此处最好给出交点坐标的计算式,以与交点是否在包围盒上的判断算式。)如果交点在盒上,则AB和包围盒相交,否则,AB和包围盒相离。图 4 线段AB 和包围盒的位置关系图判断线段与包围盒相对位置的算法流程如图5所示。图 5 向量法判断线段和包围盒相对位置算法流程图根据拾取对象的不同,线段和相关对象求交的方法和过程存在差别。设要和拾取对象求交的线段为AB,针对不同的情况,下面对两种求交过程分别加以描述。(1)拾取对象为点当拾取对象为点时,求交的实质就是以AB为轴线,以一个很小的浮点数tolerance(tolerance一般小于0.001)为半径做一个圆柱,在这个圆柱离视点最近的点就是所求的交点。具体做法是将实体的每一个顶点向AB投影,并求出投影点在线段AB上的参数坐标。在0到1的围,参数坐标的值越小,说明该点离视点越近。如果我们发现了某个实体顶点比当前的最近点离视点更近且该点到AB所在直线的距离小于tolerance,则把该点记录为当前的最近点。当所有的点处理完毕,当前的最近点就是该实体和线段AB的交点。(2)拾取对象为直线或复合线复合线一般指由多个直线段相连接所构成的单元体,在本文所述的三维图形浏览器系统中,曲线如NURBS曲线、圆弧等都存储为复合线的形式。对于线状图元,比较常用的方法是通过计算点线距离来进行拾取,这种方法简单易懂,易于实现。但当拾取曲线、复杂折线等图元时,计算量会非常大。为此提出一种新的改进算法,其具体操作步骤如下:以拾取点为中心、两倍拾取精度(在本文中拾取精度一般为象素宽度的25倍)为边长,绘制一个以背景色为填充色的正方形作为“测试窗口”。 如图6所示图 6 测试窗口重绘图形对象。如果图元进入拾取围,则必然在“测试区”中留下轨迹,重绘图形对象的目的是为了恢复可能被“测试窗口”遮蔽的图形对象。循环读取“测试窗口”中象素点颜色。当发现象素点颜色有别于背景色时,终止读点,标识拾取成功。若未发现有别于背景色的象素点时,则继续进行下一个图元的检测。如图7所示图 7 测试区中的轨迹3.2 基于GPU的重绘式拾取技术基本思路:重绘式拾取法是在OpenGL绘图模式下,通过2次绘图来拾取并显示物体。第1次绘图为虚拟绘图,通过在OpenGL帧缓存中绘制一些辅助图形来帮助选取物体。这些辅助图形对于系统使用者是不可见的,因此在确定所选物体之后要将辅助图形从帧缓存中清除掉才能进行第2次绘图。第2次绘图为实际绘图,所绘图形为系统使用者看到的真实图形。在GPU上实现三维图元拾取的方法有两种,第一种方法与场景几何无关,第二种方法是场景几何依赖。下面介绍第一种方法:场景几何无关方法的应用一直受到限制:它需要GPU的可编程能力,需要ShaderModel2.0和浮点型纹理支持;其次是因为每次出发拾取程序的时候都要在后台绘制一表面,等同于重复绘制了两次场景,不适合频繁连续的拾取动作。该方法的核心是把图元的几何信息、指针等相关信息作为它的颜色渲染到后台的一Render Target型表面上对应帧缓冲做一次特殊的“渲染”。在渲染后只要读取表面上相应像素坐标的颜色值就可以得知它相对应图元的信息。整个算法分为在CPU上和在GPU上的两部分。在CPU上C+程序的任务是建立一个后台的表面,然后调用GPU上的程序对物体进行特殊的RTT(Rallderto Texture,渲染到纹理),再根据渲染结果读取表面某坐标的颜色并还原信息。具体实现时考虑到纹理有pow of two和nonpow of two之别,屏幕分辨率一般不为pow of two,因此可以考虑采用渲染到表面(RRS),用Direct3D9中的CreateRenderTarget()创建一个Render Target型表面,CPU上的主要流程如下:鼠标点击拾取事件触发下面流程:(1)建立一新的临时纹理;(2)将当前设备屏幕的容存入缓冲中,将设备的渲染对象设为该临时纹理;(3)做好渲染前的准备,包括从fx文件中读入effect;(4)对于每个几何对象在GPU通过Shader进行特殊渲染;(5)还原设备信息,设备的渲染对象指向帧缓冲;(6)获取临时纹理上拾取区域的像素;(7)将获得的像素按定义的格式解码。图8说明了上述整个流程。图 8 GPU拾取流程下面定义Vertex Shader的输出容,定义了一个包括坐标和颜色的结构(struct)。其中,pos用于顶点转换,color用于顶点信息存储。Struct VSResultFloat4 pos:POSITION;Float4 color:TEXCOORDO;在Vertex Shader中,输入某点的局部坐标,把坐标的值编码成一个颜色的RGB值后作为输出颜色的RGB值,同时将输入坐标向量左乘世界矩阵、观察矩阵、投影矩阵后得到最终输出的坐标值。在Pixel Shader中,将物体指针信息作为颜色的Alpha值,加上Vertex Shader输出的颜色,输出为最终的渲染颜色。在单个几何图元中,不同顶点有不同的坐标,在Vertex Shader中,根据坐标信息进行顶点转换,同时将坐标信息作为颜色值存入VSResult中。主要如下:VSResult Vs_main float3 pos:POSITION VSResult ret;ret.color=float4(pos,1.of);float4 worldpos=mul(float4(pos,1),worldmatrix);ret.pos=mul(worldpos,ViewProjection);return ret;对于ret.color,前3个RGB值表示输入的位置坐标;而第4个Alpha值用作存储其他信息,暂时填入1.Of,后文将填充图元的指针。3.3 各种拾取技术比较各种拾取技术比较表如下:表 2 各种拾取技术比较原理优点局限性OpenGL的选择机制先把场景画进帧缓冲,然后进入选择模式并重新绘制这个场景。当退出选择模式时,OpenGL返回一个图元(premitives)清单,图元可能被视见体(viewing volume)分割。每个被视见体图元引出一资选择命中(hit)。确切的说,图元清单是作为一个取整数值的名字(integer-valued names)数组和相关的数据命中记录(hit record)对应名字栈(name stack)的当前容。当在选择模式下发布图元绘制命令时向名字栈中加入名字就可建立起名字栈。当名字清单被返回后,可以用它来确定屏幕上的哪个图元可能被用户选中了。此算法简单名字堆栈容量有限,不能处理大规模场景中的拾取。射线拾取方法获取屏幕坐标并转换成图形系统的视口坐标,根据不同图形API(应用程序编程接口)的实现给该点加上适当的深度值(如OpenGL标准的深度值介于0-1),反算出该拾取点的世界空间坐标,并由视点向此点引一条射线,在三维空间中对射线和物体进行求交。对拾取对象为点或简单的选择体较为方便,速度快。(1)因它假设所有的图形变换矩阵必须可逆,经过一系列的逆运算可得到图像空间某点的三维坐标,但实际上这些矩阵并非总是可逆;(2)对于复杂的选择体,判断物体是否在选择体部的算法极其复杂。从而使整个处理过程耗时巨大,且如此复杂的算法也会给整个系统的实现带来一定难度。重绘式拾取法在OpenGL绘图模式下,通过2次绘图来拾取并显示物体。第1次绘图为虚拟绘图,通过在OpenGL帧缓存中绘制一些辅助图形来帮助选取物体。这些辅助图形对于系统使用者是不可见的,因此在确定所选物体之后要将辅助图形从帧缓存中清除掉才能进行第2次绘图。第2次绘图为实际绘图,所绘图形为系统使用者看到的真实图形。(1)没有采用OpenGL选择机制。整个拾取过程都在OpenGL的绘图模式下完成,因此不必要为每个物体命名。同时,避免了在绘图模式与选择模式之间的切换;(2)灵活的拾取围,可以定义各种形态的框选,如圆形,方形;(3)算法简单,容易实现,且容易迁移到GPU上,利用硬件加速拾取算法。依赖于硬件,与GPU的性能息息相关4 系统的设计与实现有些图形应用程序只绘制两维和三维物体构成的静态图形,另一些允许用户识别屏幕上的物体并移动、修改、删除或用其它方法操纵这些物体。OpenGL正是设计用于支持这些交互式应用程序的。因为绘制在屏幕上的物体通常经过多次旋转、移动和透视变换,所以确定用户选中了三维场景中的哪个物体会很困难。为了解决这个问题,OpenGL提供了一个选择机制可惟自动告诉你哪个物体被绘制在窗口的提定区域里。本文的拾取考虑到简单地拾取三个不同的物体,分别是一个一个拾取,而不是同时拾取的过程且拾取物体是简单的选择体,这些正好都不是OpenGL的选择机制的各种缺点且OpenGL的选择机制是OpenGL本身自带的因素,所以采用了OpenGL的选择机制的拾取方法,来实现对三个不同的物体拾取。拾取过程如下:使用OpenGL的选择机制时,首先把场景画进帧缓冲,然后进入选择模式并重新绘制这个场景。然而,一旦进入了选择模式,帧缓冲的容将保存不变,直到退出选择模式。当退出选择时,OpenGL返回一个图元(premitives)清单,图元可能被视见体(viewing volume)分割。每个被视见体图元引出一资选择命中(hit)。确切的说,图元清单是作为一个取整数值的名字(integer-valued names)数组和相关的数据命中记录(hit record)对应名字栈(name stack)的当前容。当在选择模式下发布图元绘制命令时向名字栈中加入名字就可建立起名字栈。这样,当名字清单被返回后,就可以用它来确定屏幕上的哪个图元可能被用户选中了。除了这个选择机制之外,OpenGL提供了一个工具例程,以便在某些情况下通过限定在视口(viewport)一个小区域绘制来简化选择。可以用这个例程决定哪个物体被画在光标附近了,这样就能识别用户拾取了哪个物体,也可以通过指定附加的裁剪面来界定一个选择区域。4.1系统的选择机制选择机制分以下几个部分来进行:(1)进入选择模式之前;(2)获取当前选择模式;(3)退出当前选择模式;(4)拾取。4.1.1进入选择模式之前用glSelectBuffer()指定用于返回命中记录的数组;下面描述glSelectBuffer()和glRenderMode()。void glSelectBuffer(GLsizei size,GLuint *buffer);指定用于返回选择数据的数组。参数buffer是指向无符号整数(unsigned integer)数组的指针,数据就存在这个数组中,size参数说明数组中最多能够保存的值的个数。要在进入选择模式之前调用glSelectBuffer()4.1.2获取当前选择模式以GL_SELECT为参数调用glRenderMode()进入选择模式;GLint glRenderMode(GLenum mode);控制应用程序是否进入绚染(rendering)、选择或反馈模式。mode参数可以是GL_RENDER(默认)、GL_SELECT或GL_FEEDBACK之一。应用程序将保持处于给定模式,直到再次以不同的参数调用glRenderMode()。在进入选择模式之前必须调用glSelectBuffer()指定选择数组。类似的,进入反馈模式之前要调用glFeedbackBuffer()指定反馈数组。如果当前模式是GL_SELECT或GL_FEEDBACK之一,那么glRenderMode()的返回值有意义。返回值是当前退出当前模式时,选择命中数或放在反馈数组中的值的个数。负值意味着选择或反馈数组溢出(overflowed)。用GL_RENDER_MODE调用glGetIntegerv()获取当前模式。定义用于选择的视见体通常它与原来用于绘制场景的视见体不同,也可以用glPushMatrix()和glPopMatrix()来保存和恢复当前的变换矩阵;在选择模式下,被视见体裁剪的每个图元引起一个选择命中。当前一个名字栈控制命令被执行或glRenderMode()被调用后,OpenGL将一个命中记录写进选择数组,如果从上一次名字栈操纵或glRenderMode()调用以来有了一个命中记录的话。这个过程中,共用同样名字的物体例如:由多个图元组成的物体不生成多个命中记录。当然,命中记录不保证会被写进数组中直到glRenderMode()被调用。除图元之外,glRasterPos()产生的有效坐标也可以引起选择命中。在多边形的情况下,如果它已经被消隐掉的话不会有命中记录出现。每个命中记录由四项组成,依次是:(1)当命中出现时名字栈中的名字数;(2)至上次记录的命中以来,被视见体裁剪后的图元的所有顶点的窗口Z坐标的最大;(3)被视见体裁剪后的图元的所有顶点的窗口Z坐标最小值;(4)本次命中时名字栈的容,最底元素最前。进入选择模式时,OpenGL初始化一个指针指向选择数组的起点。每写入一个命中记录,指针相应更新。如果写入一个命中记录会使数组中值的个数超过glSelectBuffer()的size参数时,OpenGL会写入尽可能多的记录并设置一个溢出标志。当用glRenderMode()退出选择模式时,这条命令返回被写入的记录的个数(包括一条部分记录如果有的话),清除名字栈,复位溢出标识,重置栈指针。如设定溢了出标识则返回值是-1。用glInitName()和glPushName()初始化名字栈名字栈是返回给你的选择信息的基础,要建立名字栈,首先用glInitNames()初始化它,这将简单地清空栈。然后当发布相应的绘制命令时向其中加入整数名字。栈控制命令允许你压入名字(glPushName()),弹出名字(glPopName()),替换栈顶的名字(glLoadName())。举个例子:Example 12-1: Creating a Name StackglInitNames();glPushName(-1);glPushMatrix(); /*保存当前的转型的状态*/glLoadName(1);drawSomeObject();glLoadName(2);drawAnotherObject();glLoadName(3); drawYetAnotherObject();drawJustOneMoreObject();glPopMatrix (); /*恢复以前的转型的状态*/在这个例子中,前两个被绘制的物体有自己的名字,第三和第四个共用一个名字。这样,如果第三或第四个物体中的一个或全部引起一个选择命中,只有一个命中记录返回给你。如果处理命中记录时不想区分各个物体的话,可以让多个物体共享一个名字。void glInitNames(void);清空名字
展开阅读全文