【一个提示】该方法虽然可行但是已经淘汰很多年,建议自行尝试,后面也许会写论文最好的方式是:
1. 使用Kd-tree组织场景中的物体,以便于快速查找。
2. 使用屏幕坐标->空间三维坐标的逆矩阵变换,实现选取。
在介绍开始,首先给出工程和可执行程序的下载链接:
或者http://download.csdn.net/detail/mahabharata_/9709959
程序的执行结果如下:
首先,我们在OpenGL的绘制方式有两种:GL_RENDER和GL_SELECT。顾名思义,GL_RENDER是渲染模式,也就是默认的绘制方式,通俗的讲,就是绘制操作都会被绘制在屏幕上;GL_SELECT则是选择模式,这种方式不会被绘制在屏幕上,之后的矩阵变化为选取矩阵的变化。
这里对GL_RENDER不做过多说明,因为这是默认方式,就是操纵显示在屏幕上时的矩阵变换。
对于GL_SELECT:在用OpenGL进行图形编程的时候,通常要用鼠标进行交互操作,比如用鼠标点选择画面中的物体,我们称之为拾取(Picking),这里我们介绍一下:在OpenGL中如何拾取,如何利用OpenGL提供的一系列函数来完成拾取,再简单介绍下OpenGL的名字栈(Name stack),拾取矩阵(Picking Matrix)等等。
1. 首先获得一系列参数信息:
const int BUFSIZE = 1024; //缓冲区selectBuf
GLuint selectBuf[BUFSIZE];
glSelectBuffer (BUFSIZE, selectBuf);
GLint viewport[4]; //获取视口viewport的信息
glGetIntegerv (GL_VIEWPORT, viewport);
2. 进入GL_SELECT模式,并初始化名字栈
glRenderMode(GL_SELECT);
glInitNames();
glPushName(-1);
3. 保存一下矩阵信息:
glPushMatrix();
4. 在GL_SELECT模式下,指定拾取窗口,并进行投影变换(GL_PROJECTION)
投影变换事实上在GL_RENDER模式下已经进行过一次,但是需要在GL_SELECT模式下,再进行一次。
glMatrixMode (GL_PROJECTION); // 投影变换
glLoadIdentity ();
gluPickMatrix((GLdouble)x, (GLdouble) (viewport[3]-y), 5.0, 5.0, viewport); // 指定选取窗口(x,y为鼠标点击位置)
gluPerspective(45.0, (GLfloat)width()/(GLfloat)height(), 0.1, 10000.0); // 指定视景体
5. 在GL_SELECT模式下,进行模型视图变换GL_MODELVIEW
glMatrixMode(GL_MODELVIEW); // 2: GL_SELECT下模型视图变换
glLoadIdentity();
gluLookAt(0.0,0.0,10.0, 0.0,0.0,0.0, 0.0,1.0,0.0); //指定摄像机位置
/*
为了保证选取结果的正确性,在GL_RENDER下进行的所有缩放、平移、旋转等操作,这里也要进行一次。
*/
6. 在GL_SELECT重新绘制物体,这里使用我上传的程序中的例子。
调用绘制函数,总共有3个人物,绘制函数为renderNPC(GLenum mode);
for(int i=0; i< 3 ;i++)
{
_characters[i]->renderNPC(GL_SELECT); // 使用GL_SELECT方式渲染一次(并不绘制在屏幕上)
}
关于renderNPC(GLenum mode)的具体实现,在后面给出。
7. 出栈,让之前进行的矩阵操作释放掉。
glPopMatrix();
8. 对选取的名字栈进行处理(选取结果已经储存在名字栈中了)
// 切换回GL_RENDER模式
GLint hits = glRenderMode (GL_RENDER); // 返回的hit:表示名字栈中结果的个数。
glMatrixMode (GL_PROJECTION); /// 此处需要重新进行一次GL_RENDER模式下的投影变换。
glLoadIdentity ();
gluPerspective(45.0, (GLfloat)width()/(GLfloat)height(), 0.1, 10000.0);
if(hits) ///如果选中物体,设置为"选中"状态
{
_characters[selectBuf[3]]->_isSelected=!( _characters[selectBuf[3]]->_isSelected );
}
updateGL(); // 在屏幕上重绘,因为有的物体已经被选中。
关于在上面出现的renderNPC(GLenum mode)函数,下面给出其内部细节(为了简单表述,以画方块为例)。
void renderNPC(GLenum model)
{
glPushMatrix();
/*
这里进行属于本物体的旋转、平移、缩放变换
*/
if(model == GL_SELECT)
{
glLoadName(_name); //这里的_name为预先设计的该物体的名字,为一个GLuint值,比如1、2、3、.....
}
if(_isSelected == true) ///如果被选中
{
/*
对于选取的物体,进行诸如修改颜色啊啥啥啥的操作。
*/
}
glutSolidCube(3.0); //绘制出物体,这里以一个“方块”举例。
glPopMatrix();
}
此外,我们还需要了解的最后一点,名字栈的机制,名字栈最终保存在selectBuffer[]中。当某物件被“拾取”(被光束射中)的时候,对应的名字和相关信息便会被提交给HIT Record,存储在selectBuffer里面。相关信息包括该物件离光束发射处(相机/眼睛)最近的点的深度值和最远的点的深度值等等,以下反映了当一个物件被拾取后,名字栈机制向HIT Record发送的信息(然后HIT Record把此信息存入selectBuffer):
击中的物件的名字的数目
这个物件中最近的点的深度值
这个物件中最远的点的深度值
击中的物件的名字之一
击中的物件的名字之二
(若有多个名字,则如此类推...)