OpenGL学习笔记:拾取与选择

转自:OpenGL学习笔记:拾取与选择

在开发OpenGL程序时,一个重要的问题就是互动,假设一个场景里面有很多元素,当用鼠标点击不同元素时,期待作出不同的反应,那么在OpenGL里面,是怎么知道我当前鼠标的位置是哪一个物体呢?

OpenGL有一套机制,叫做Picking, 里面涉及到几个核心概念:

1. selection mode. 选择模式

2. name stack. 名字栈

3. hit record。 命中记录

4. viewing volume。 视角范围

在OpenGL的picking中,选择物体不是选择一个单独的物体,而是选择一片范围内的所有物体。这种设计思路是有点奇怪,但是OpenGL就是这麽设计的。假如鼠标当前的位置是(200,200),普通的应用就是选择在点(200,200)处的物体ID, 但OpenGL不然,它是选择以(200,200)为中心的,比如长宽都为20的这个范围内可见的物体,也就是说,前面一种选择,是以点为选择依据,OpenGL的选择,是以中心为(200,200),长宽各位10的面为依据,好像举着一个画框,只要在这个画框内的物体,都会当作返回结果。

我不是很理解为什么OpenGL要这麽设计,但肯定有它的道理。以上的几个核心概念,就是配合这种设计思路的,不难看出:

1. Viewing volume。就是指用来选择的画框

2. hit record。就是所返回的物体数据

3. name stack。是用来分配并保存物体ID的堆栈

4. selection mode. OpenGL有三种模式,Render mode。就是普通的绘图模式,Select mode。在picking时的选择模式,Feedback mode。不画图,不选择,而是把所有最终的渲染完之后的绘画指令返回给用户。在绘图仪上作画等方面特别有用

当需要相应鼠标选择事件的时候,要首先进入selection mode, 然后才能执行相应的操作步骤

以下是详细的操作步骤:

1. 获取鼠标位置

2. 进入选择模式selection mode

3. 设置画框大小view volume,当然,是根据鼠标位置来的

4. 像往常一样绘制场景

5. 退出选择模式,得到选择结果

进入选择模式:

1. glSelectBuffer,设置选择缓冲区,hit record会被OpenGL保存在里面

2. glRenderMode(GL_SELECT),正式进入选择模式

设置画框大小:

1. gluPickMatrix。设置画框近平面大小

2. gluPerspective。影响画框的容量

实例代码:

glSelectBuffer(BUFSIZE,selectBuf);
glRenderMode(GL_SELECT);

glMatrixMode(GL_PROJECTION); //保存投影矩阵
glPushMatrix();
glLoadIdentity();

glGetIntegerv(GL_VIEWPORT,viewport);
gluPickMatrix(cursorX,viewport[3]-cursorY,5,5,viewport);
gluPerspective(45,ratio,0.1,1000);

注意,上面的代码中在执行gluPickMatrix值钱先保存了投影矩阵,然后再重新创造一个新的供自己使用。原因是gluPickMatrix会操作投影举证,该函数和后续的gluPerspective共同作用,最终会生成一个全新的投影矩阵,该矩阵中的所有物体,都会被视为选中。为了不影响旧有的投影矩阵,因此需要保存先

绘制场景:

绘制场景的时候有一点比较重要,那就是对每个需要绘制的单独元素,起一个名字(ID),这样OpenGL才能在推出选择模式的时候告知调用者,哪个物体被选择了。当然如果非不取,那OpenGL也会返回被选择的物体,只是不带名字。因此绘制场景的步骤一般如下:

1. glInitNames, 初始化name stack。 OpenGL的中对元素名字的操作需要通过一个栈,叫做名字栈,为啥这麽墨迹,不直接指定名字?具体原因是OpenGL总要有个地方去读取名字,而且,一个名字下包括哪些需要绘画的内容?OpenGL怎么知道当前画的这个人头是属于上个人的还是上上个人的?只能通过名字栈的变化才能知道。而且一个元素可以有多个内容,怎么搞?于是乎名字栈就应运而生,OpenGL认为这个东东可以解决这一系列问题

2. glPushName。 创建一个名字。

3. 开始画啊画啊画

4. glPopName。一个元素绘制结束

5. 从步骤2循环到4的一个个元素绘制

实例代码:

	glInitNames();

	glPushName(BODY);
	drawBody();
	glPopName();

	glPushName(HEAD);
	drawHead();
	drawEyes();
	glPopName();

	drawGround();  //不带名字的元素

还有另外一个有用的函数,glLoadName。它的作用是用一个新的名字替代栈顶当前的名字。说白了,就等同于:

glPopName()

glPushName(newName)

OpenGL里面,一个物体还可以有多个名字,这个貌似有点荒唐,确实也比较荒唐,但OpenGL也有它的道理,比如一个元素,它的名字叫“head",那么,如果我想知道这个头是谁的头,怎么搞?OpenGL告诉你压力不大,再给他取一个名字叫”Aka",那么你就知道这是Aka的头。也就是说,我绘制了一个元素,然后给它两个名字,一个叫head, 一个叫aka,那么当你选择了该物体的时候,你就知道了,这个物体是aka的头。

示例代码如下:

glPushName(Aka)

glPushName(Head)

draw()

glPopName(Head)

glPopName(Aka)

于是,draw出来的物体就有两个名字了.

处理选择结果:

处理选择接过之前,要先推出selection模式,然后OpenGl会返回一个数目,被选中物体的数目,跟据该数目,就知道在选择缓冲区里面保存了多少个物体,每个物体都有固定的数据结构,一个个的读取该数据结构,就知道哪些物体被选取了。

示例代码:

void stopPicking() {

	int hits;

	// restoring the original projection matrix
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glFlush();

	// returning to normal rendering mode
	hits = glRenderMode(GL_RENDER);//得到选中物体的数目

	// if there are hits process them
	if (hits != 0)
		processHits(hits,selectBuf);
}

重要的是选择缓冲区中hit record的数据结构,该结构是一个GLuinit的类型,也就说里面所有的数据都是uinit,等等,名字不是也在里面吗,这可是string啊,no, 名字其实不是string,准确的说应该叫ID,普通的1,2,3而已

每一个元素的结构提如下:

1. 该物体的名字的数目。一个物体可以有多个名字,你懂的

2. 该物体被选中区域的最小Z值。我们往往根据这个值得知哪一个才是鼠标点所在的物体

3. 该物体被选中区域的最大Z值。

4. 名字

5. 名字。。

由于物体可以有多个名字,甚至可以木有名字,因此元素的结构不是固定大小的,以下为例:

Hit Record Contents Description
0 表示这个元素没名字
4.2822e+009 最小Z值
4.28436e+009 最大Z值
1 这个元素有1个名字
4.2732e+009 最小Z值
4.27334e+009 最大Z值
6 名字叫6
2 这个元素有2个名字
4.27138e+009 最小Z值
4.27155e+009 最大Z值
2 第一个名字叫2
5 第二个名字叫5

所以呢,只需要对着选择缓冲区,一个个的读下去就对了,示例代码:

void processHits2 (GLint hits, GLuint buffer[])
{
   unsigned int i, j;
   GLuint names, *ptr, minZ,*ptrNames, numberOfNames;

   printf ("hits = %d\n", hits);
   ptr = (GLuint *) buffer;
   minZ = 0xffffffff;
   for (i = 0; i < hits; i++) {
      names = *ptr;
	  ptr++;
	  if (*ptr < minZ) {
		  numberOfNames = names;
		  minZ = *ptr;
		  ptrNames = ptr+2;
	  }

	  ptr += names+2;
	}
  printf ("The closest hit names are ");
  ptr = ptrNames;
  for (j = 0; j < numberOfNames; j++,ptr++) {
     printf ("%d ", *ptr);
  }
  printf ("\n");

}

注意:以上所有的概念和函数,都只在selection mode下面有效。如果转到了Render mode,系统怎么处理这些函数呢?很简单,华丽的无视掉。

时间: 2024-12-26 10:19:00

OpenGL学习笔记:拾取与选择的相关文章

【OpenGL 学习笔记02】宽点画线

我们要知道,有三种绘图操作是最基本的:清除窗口,绘制几何图形,绘制光栅化对象. 光栅化对象后面再解释. 1.清除窗口 比如我们可以同时清除颜色缓冲区和深度缓冲区 glClearColor (0.0, 0.0, 0.0, 0.0);//指定颜色缓冲区清除为黑色 glClearDepth(1.0);//指定深度缓冲区的清除值为1.0 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//指定要清除的缓冲区并清除 2.绘制几何图形 先要设置绘制颜色,

【opengl 学习笔记01】HelloWorld示例

<<OpenGL Programming Guide>>这本书是看了忘,忘了又看,赶脚还是把笔记做一做心里比较踏实,哈哈. 我的主题是,好记性不如烂笔头. ================================================================ 1. 下载glut库 glut库地址为:www.opengl.org/resources/libraries/glut/glutdlls37beta.zip glut全称为:OpenGL Utilit

OpenGL学习笔记3 —— 绘制3D物体、鼠标交互、反向变换

/* reference http://nehe.gamedev.net/article/using_gluunproject/16013/ */ #include <windows.h> // windows系统要加这个.因为下面2个头文件的一些宏是在这个文件中定义的 #include <gl/Gl.h> #include <gl/glut.h> //这两个头文件在OpenGL程序中几乎必加. #include <cstdio> //标准输入输出,用来打印

【OpenGL 学习笔记04】顶点数组

通过之前的学习,我们知道,如果要绘制一个几何图形,那就要不断的调用绘制函数,比如绘制一个20条边的多边形,起码要调用22条函数(包含glBegin和glEnd). 所以OpenGL提供了一系列的顶点数组函数减少函数调用的次数来提高性能.而且使用顶点还可以避免顶点共享的冗余处理. 1.简单示例 先来回顾一下之前我们是怎么画直线的: void drawOneLine(GLfloat x1,GLfloat y1,GLfloat x2,GLfloat y2) { glBegin(GL_LINES); g

【OpenGL 学习笔记03】点画多边形

1.点画多边形 //定义填充多边形的点画模式.mask为32 x 32 的位图指针,1画0不画,使用前必须启用多边形点画功能 void glPloygonStipple(const GLubyte* mask); //绘制一个矩形 void glRectf(GLfloat x1,GLfloat y1,GLfloat x2,GLfloat y2); 2.示例 #include <GL/glut.h> #include <stdlib.h> void display(void) { G

OpenGL学习笔记1 —— 画点

#include <windows.h> // windows系统要加这个.因为下面2个头文件的一些宏是在这个文件中定义的 #include <gl/Gl.h> #include <gl/glut.h> //这两个头文件在OpenGL程序中几乎必加. //<<<<<<<<<<<<<<<<<<<<<<< myInit >>&

OpenGL学习笔记2 —— 画立方体

#include <windows.h> // windows系统要加这个.因为下面2个头文件的一些宏是在这个文件中定义的 #include <gl/Gl.h> #include <gl/glut.h> //这两个头文件在OpenGL程序中几乎必加. //<<<<<<<<<<<<<<<<<<<<<<< myInit >>&

OpenGL学习笔记4:纹理

原始图像数据 像素包装 图像数据在内存中很少以紧密包装的形式存在.在许多硬件平台上,处于性能上的考虑,一幅图像的每一行都应该从一种特定字节对齐地址开始.绝大多数编译器会自动把变量和缓冲区放置在一个针对该架构对齐优化的地址上. 例如一个包含3个分量的rgb图像,每个分量存储在一个字节中,如果图像有199个像素,那么一行需要597个像素.如果硬件本身的体系结构是4字节排列,那么图像每一行的末尾将由额外的3个空字符进行填充达到600字节. 我们可以使用下列函数改变或者回复像素的存储方式. void g

OpenGL学习笔记2017/8/29

OpenGL学习日志: 感谢doing5552 的OpenGL入门学习:http://www.cppblog.com/doing5552/archive/2009/01/08/71532.html 相信有部分人还在使用pascal学习OpenGL(像我一样)说不定也有人经常会遇到莫名其妙的编译错误(即使只是一个模板) 经过十个多小时的研究,我终于找到了OpenGL的使用方法 首先,在百度上(也可以是别的搜索引擎)上找到叫做glutdlls37beta的压缩包,下载解压后全部放入bin->i386