一、简介(Introduction)
OpenGL是一种比较“纯粹”的3D图形API,一般仅用于三维图形的渲染,对于特定领域的开发者(如游戏开发者)而言,如果选择使用 OpenGL进行开发,类似碰撞检测的机制就都需要自行编写了。但是由于鼠标在图形程序中的应用非常非常之广泛(例如现在已经很少有PC游戏能完全地脱离鼠标),OpenGL在图形库的基础上添加了选择与反馈机制(Select & Feedback)来满足用户使用鼠标实时操作三维图形的需要。但由于种种原因,我们需要更为特殊的选择机制以满足特定需求,在这里我们提出了一种简单迅速的RIP(Ray-Intersection-Penetration)方法,可以满足绝大多数典型应用的需要。
二、相关研究(Related Work)
用过OpenGL选择与反馈机制的开发者,或多或少可能都会觉得它难以令人满意。大致表现在下面几个方面:
一、编写程序比较繁琐。
想要使用选择反馈机制就需要切换渲染模式,操作命名堆栈,计算拣选矩阵,检查选中记录,这些繁琐的步骤很容易出错,而且非常不便于调试,只会降低工作效率和热情。
二、只能做基于图元的选定。
如下图(1 - a),使用GL_TRIANGLES绘制了一个三角形,三个顶点分别为 P1、P2和P3。若使用该机制,你将只能判断是否在三维场景中选中了这个三角形(用户点击处是否在P1、P2和P3的范围内),而无法判断用户是点击了这个三角形哪一部分(是左边的m区域内还是右边的n区域内),因为所绘制的P1、P2和P3本身构成的三角形就是一个基本图元,对于拣选机制而言是不可分的。当然,把这个三角形拆成两个三角形再分别进行测试也是一个可行的方案,可是看看图(1 - b),这可怎么拆呢?还有图(1 – c)呢?另外,如果n和m两个平面不共面呢?对于使用者而言,OpenGL提供的拣选机制功能的确有限。
三、降低了渲染效率。
OpenGL中的选择和反馈是与普通渲染方式不同的一种特殊的渲染方式。我们使用时一般是先在帧缓存中渲染普通场景,然后进入选择模式重绘场景,此时帧缓存的内容并无变化。也就是说,为了选择某些物体,我们需要在一帧中使用不同的渲染方式将其渲染两遍。我们知道对对象进行渲染是比较耗时的操作,当场景中需要选择的对象多而杂的时候,采用这个机制是非常影响速度的。
另外在OpenGL红宝书中介绍了一种简便易行的办法:在后缓冲中使用不同的颜色重绘所有对象,每个对象用一个单色来标示其颜色,这样画好之后我们读取鼠标所在点的颜色,就能够确定我们拣选了哪个物体。这种方法有一个缺陷,当场景中需要选择的对象的数目超出一定限度时,可能会出现标识数的溢出。对于这个问题,红宝书给出的解决办法就是多次扫描。实践证明这种方法的确简便易行,但仍有不少局限性,而且做起来并不比第一种机制方便多少。限于篇幅,不再赘述。
三、具体描述(Related Work)
看过了上面两种方法,我们会发现这两种方法都不是十分的方便,而且使用者不能对其进行完全的控制,不能精确地判定鼠标定位与实际的世界空间中三维坐标的关系。那么有什么更好的办法能够更简单更精确地对其加以控制呢?
实际上此处给出的解决方案十分简单,就是一个很普通也很有用的 GLU 函数 gluUnProject()。
此函数的具体用途是将一个OpenGL视区内的二维点转换为与其对应的场景中的三维坐标。
转换过程如下图所示(由点P在窗口中的XY坐标得到其在三维空间中的世界坐标):
这个函数在glu.h中的原型定义如下:
int APIENTRY gluUnProject (
GLdouble winx,
GLdouble winy,
GLdouble winz,
const GLdouble modelMatrix[16],
const GLdouble projMatrix[16],
const GLint viewport[4],
GLdouble *objx,
GLdouble *objy,
GLdouble *objz);
其中前三个值表示窗口坐标,中间三个分别为模型视图矩阵(Model/View Matrix),投影矩阵(Projection Matrix)和视区(ViewPort),最后三个为输出的世界坐标值。
可能你会问:窗口坐标不是只有X轴和Y轴两个值么,怎么这里还有Z值?这就要从二维空间与三维空间的关系说起了。
众所周知,我们通过一个放置在三维世界中的摄像机,来观察当前场景中的对象。通过使用诸如gluPerspective() 这样的OpenGL函数,我们可以设置这个摄像机所能看到的视野的大小范围。这个视野的边界所围成的几何体是一个标准的平截头体(Frustum),可以看做是金字塔状的几何体削去金字塔的上半部分后形成的一个台状物,如果还原成金字塔状,就得到了通常我们所说的视锥(View Frustum)这个视锥的锥顶就是视点(View Point)也就是摄像机所在的位置。平截头体,视锥以及视点之间的关系,如下图所示:
在上面的图中,远裁剪面ABCD和近裁剪面A’B’C’D’构成了平截头体,加上虚线部分就是视锥,顶点O就是摄像机所在的视点。我们在窗口中所能看到的东东,全部都在此平截头体内。这跟前面的窗口坐标Z值有什么关系呢?看下图:
如此图所示,点P和点P’分别在远裁剪面ABCD和近裁剪面A’B’C’D’上。我们点击屏幕上的点P,反映到视锥中,就是选中了所有的从点P到点P’的点。举个形象的例子,这就像是我们挽弓放箭,如果射出去的箭近乎笔直地飞出(假设力量非常之大近乎无穷),从挽弓的地点直至击中目标,在这条直线的轨迹上任何物体都将被一穿而过。对应这里的情况,用户单击鼠标获得屏幕上的某一点,即是指定了从视点指向屏幕深处的某一方向,也就确定了屏幕上某条从O点出发的射线(在图中即为OP)。在这里,我们称呼其为拣选射线。
因此,从窗口的XY坐标,我们仅仅只能获得一条出发自O点的拣选射线,并不能得到用户想要的点在这条射线上的确切位置。
这时候窗口坐标的Z值就能派上用场了。我们通过Z值,来指定我们想要的点在射线上的位置。假如用户点击了屏幕上的点(100,100)得到了这条射线OP,那么我们传入值1.0f就表示近裁剪面上的P点,而值0.0f则对应远裁剪面上的P’点。
这样,我们通过引入一个窗口坐标的Z值,就能指定视锥内任意点的三维坐标。与此同时,我们还解决了前面红宝书给出的方法中存在的缺陷——同一位置上重叠物体的选择问题。解决办法是:从屏幕坐标得到射线之后,分别让重叠的物体与该射线求交,得到的交点,然后根据这些与视点的远近确定选择的对象。如此我们就不必受“仅仅只能选取屏幕中离观察者最近的物体”的限制了。这样一来,如果需要的话,我们甚至可以用代码来作一定的限定,通过判断交点与视点的距离,使得与该拣选射线相交的物体中,离视点远的对象才能被选取,这样就能够对那些暂时被其他对象遮住的物体进行选取。
至于如何求拣选射线与对象的交点,在各种图形学的书中的数学部分均有讲述,在此不再赘述。
四、例程(Sample Code Fragment)
前面讲述了RIP方法,现在我们来看如何编写代码以实现之,以及一些需要注意的问题。
由于拣选射线以线段形式存储更加便于后面的计算,况且我们可以直接得到纵跨整个平截头体的线段(即前面图中的线段PP’),故我们直接计算出这条连接远近裁剪面的线段。我们将拣选射线的线段形式称之为拣选线段。
在下面的代码前方声明有两个类Point3f和LineSegment这分别表示由三个浮点数构成的三维空间中的点,以及由两个点构成的空间中的一条线段。
应注意代码中用到了类Point3f的一个需要三个浮点参数的构造函数,以及类LineSegment的一个需要两个点参数的构造函数。
获取拣选射线的例程如下所示(使用C++语言编写):
class Point3f;
class LineSegment;
LineSegment GetSelectionRay(int mouse_x, int mouse_y) {
// 获取 Model-View、Projection 矩阵 & 获取Viewport视区
GLdouble modelview[16];
GLdouble projection[16];
GLint viewport[4];
glGetDoublev (GL_MODELVIEW_MATRIX, modelview);
glGetDoublev (GL_PROJECTION_MATRIX, projection);
glGetIntegerv (GL_VIEWPORT, viewport);
GLdouble world_x, world_y, world_z;
// 获取近裁剪面上的交点
gluUnProject( (GLdouble) mouse_x, (GLdouble) mouse_y, 0.0,
modelview, projection, viewport,
&world_x, &world_y, &world_z);
Point3f near_point(world_x, world_y, world_z);
// 获取远裁剪面上的交点
gluUnProject( (GLdouble) mouse_x, (GLdouble) mouse_y, 1.0,
modelview, projection, viewport,
&world_x, &world_y, &world_z);
Point3f far_point(world_x, world_y, world_z);
return LineSegment(near_point, far_point);
}
如果你是使用Win32平台进行开发,那么应当注意传入正确的参数。因为无论是使用Win32 API 还是DirectInput 来获取鼠标坐标,得到的Y值都应取反后再传入。因为OpenGL默认的原点在视区的左下角,Y轴从左下角指向左上角,而Windows默认的原点在窗口的左上角,而Y轴方向与OpenGL相反,从左上角指向左下角。如下图所示:
0
X
Win32 默认窗口坐标样式
OpenGL 默认窗口坐标样式
0
X
Y
Y
我们可以看到代码被注释分为了三个部分:获取当前矩阵及视区,获取近裁剪面的交点,获取远裁剪面的交点。
我们通过OpenGL提供的查询函数轻松得到当前的ModelView和Projection矩阵,以及当前的Viewport(视区,也就是窗口的客户端区域,如果整个窗口区域用于OpenGL渲染的话)。
获得两个裁剪面上的交点的代码基本上是一样的,唯一的不同点是我们前面曾经详细地讨论过的窗口的Z坐标。不错,这个坐标表示的就是“深浅”的概念。它的值从点P’到点P的变化是从0.0f逐渐增至1.0f。此处类似于OpenGL的深度测试机制。
在得到两个交点之后,我们使用它们通过返回语句直接构建一条线段。在这里仅仅作为实例代码,故简捷清晰地直接返回线段对象,而没有通过引用参数来提高效率。
此时用户可以使用这个函数来判断所选择的对象了。只需在需要的地方判断对象是否与此线段相交即可判断对象是否被选中,还可以通过进一步计算其交点位置来得到详细的交点信息。这些计算均是常见的计算机图形学与三维数学计算,比如线段与三角形求交,线段与面求交,线段与球体求交,线段与柱体或锥体求交,等等。请参考所列出的计算机图形学书籍。
五、结论(Conclusion)
在本文中,我们介绍了一种行之有效的三维坐标拾取方法,主要使用GLU库中的实用工具实现。这种方法速度快,效率高,能在不必重新绘制对象的前提下完成拣选工作。对比OpenGL自带的拣选机制来看,RIP的确在各种方面均有一定的优势。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/Y___Y/archive/2007/04/14/1564461.aspx
分享到:
相关推荐
使用OpenGL实现三维坐标的鼠标拣选_-_Y___Y的专栏_-_CSDN博客
本文介绍了一种基于OpenGL的三维坐标鼠标拣选方法——RIP(Ray-Intersection-Penetration),旨在提高拣选的精度和算法的执行效率。 #### 二、RIP方法概述 RIP方法的核心在于通过计算鼠标点击位置与三维场景中物体...
在本项目中,“OpenGL绘制三维坐标图”是利用OpenGL库和Microsoft Foundation Classes (MFC)框架开发的一个自定义程序,它能够在Visual Studio 2008环境下成功编译和运行。这个程序的主要目标是展示如何利用OpenGL来...
OpenGL三维坐标系绘图程序是计算机图形学领域中的一个经典应用,它利用OpenGL库来创建和展示复杂的3D模型和图像。在这个程序中,我们主要关注的是如何在三维空间中定位和绘制图形,并且如何通过串行端口(COM口)...
在这个名为"MyMDOpenGL.rar"的压缩包中,我们重点探讨的是如何利用OpenGL实现三维坐标系,并且通过鼠标来控制三维对象的旋转。 首先,让我们深入了解OpenGL的三维坐标系统。在OpenGL中,坐标系分为四个主要区域:...
在"OpenGL实现三维场景运动"这个主题中,我们将探讨如何使用OpenGL来创建动态的三维场景,并实现物体和相机的运动。 首先,理解基本的OpenGL坐标系统至关重要。OpenGL使用右手坐标系,其中X轴正方向向右,Y轴正方向...
在本场景中,我们关注的是使用OpenGL绘制三维模型,并通过鼠标来控制模型的旋转。这个项目是在Visual Studio 2010环境下编写的,这表明代码可能是用C++和OpenGL的GLUT库实现的,因为GLUT是VS2010中常见的用于快速...
本文将深入探讨如何使用OpenGL实现三维图形的变换,包括旋转、平移和缩放,并结合MFC(Microsoft Foundation Classes)库来创建交互式用户界面。 首先,我们需要了解OpenGL的基本概念。OpenGL是一个跨语言、跨平台...
用OpenGL实现鼠标控制三维坐标及其上曲面的空间旋转,可以作为编程人员的参考
利用OpenGL实现了三维点云的显示,添加了鼠标控制事件,可以用鼠标对三维点云模型进行移动、缩放等功能。很不错的源代码,本代码运行环境是VS2010,需要添加OpenGL的配置。很不错的源代码分享给大家。
OpenGL是一种广泛使用的三维图形接口标准,其最大的特点是与硬件无关,可在不同的硬件平台上实现,并且被多种操作系统支持。这使得开发者能够更专注于图形内容的设计与实现,而无需过多考虑底层硬件差异带来的问题。...
本教程将详细介绍如何使用C#和CSGL库在.NET环境中绘制一个80面的三维球体。 首先,我们需要理解OpenGL的基础概念。OpenGL是一个跨语言、跨平台的编程接口,用于渲染2D、3D矢量图形。它提供了大量的函数和状态机来...
在本项目“OpenGL.rar_OpenGL实现三维”中,我们将探讨如何利用OpenGL在单文档界面(SDI)应用程序中实现三维坐标系的旋转,并将其集成到Microsoft Foundation Classes(MFC)框架中。 1. **OpenGL基础**: OpenGL...
OpenGL是用于创建交互式2D和3D图形的开源跨语言绘图API,而MFC(Microsoft Foundation Classes)是微软提供的...通过调试和分析这些代码,你可以更深入地理解MFC和OpenGL的结合使用,以及如何实现三维图形的动态交互。
Qt + OpenGL 点云 通过Z-Buffer获取鼠标位置的深度值。然后转世界坐标。如果pointsize过大,精度会有一定损失。为1时精度在小数点后两位。当鼠标move到点上时,控制台输出原始点 x y z的坐标信息。窗口不要改动,...
在本项目"OpenGL 三维茶壶鼠标事件"中,我们将在VC++6.0集成开发环境中利用OpenGL库来实现一个交互式的3D茶壶模型。这个模型不仅能够静态展示,还能通过鼠标操作进行动态旋转,改变茶壶的显示位置和角度,从而提供...
本文介绍一种改进的三维拾取方法,该方法基于OpenGL这一广泛使用的图形库,能够有效地处理三维地形图上的点拾取,并返回点的精确三维坐标。 #### 三维拾取方法概述 三维拾取是指在三维图形场景中选取特定位置的过程...
在这个项目中,我们将探讨如何使用OpenGL在Visual Studio 2008环境下实现三维物体的旋转和缩放功能,同时读取存储在TXT文件中的点云数据。 首先,我们需要设置开发环境。在Visual Studio 2008中,创建一个新的MFC...