基本上,拾取操作是通过一个修正观察体来实现的,而这个观察体根据一个指定的拾取窗口形成。我们队一个场景内的对象用整数进行标识。所有与观察体相交的对象的标识符都保存在一个拾取缓冲区数组中。因此,为了使用OpenGL的拾取功能,我们必须在程序中包含以下过程:
1.创建并显示一个场景
2.拾取一个屏幕位置,并且在鼠标回调函数内部进行如下操作:
设置一个拾取缓冲区
激活拾取操作(选择模式)
为对象标识符初始化一个ID名称栈
保存当前的观察和几何变换矩阵
指定鼠标输入的拾取窗口
给对象分配标识符然后用观察体在处理一次场景,从而将拾取信息存储到拾取缓冲区中
恢复原来的观察和几何变换矩阵
确定被拾取的对象的数目,然后返回正常的绘制模式
处理拾取信息
以下命令用来设置一个拾取缓冲区:
glSelectBuffer(pickBuffSize,pickBuffer);
参数pickBuffer指定一个具有pickBufferSize个元素的整形数组。对于每次拾取选中的每一个对象,向拾取缓冲区数组中添加一个整数信息记录。拾取缓冲区中的每一条记录包含如下信息:
1.对象在名称栈中的位置,即名称栈中位于该对象之下的标识符数目。
2.拾取对象的最小深度。
3.拾取对象的最大深度。
4.从名称栈中第一个标识符(底部)到拾取对象的标识符之间的所有标识符的列表。
OpenGL的拾取操作由以下命令激活:
glRenderMode(GL_SELECT);
这个命令将当前模式设置为选择模式,即一个场景虽然通过观察流水线的处理,但是并不存储在帧缓存中。将参数改为GL_RENDER就可以将当前模式设置为正常的绘制模式,将参数改为GL_FEEDBACK则将对象的坐标及其他信息存储到一个反馈缓冲区中。
以下命令用于激活拾取操作的整型ID名称栈:
glInitname();
向栈顶放入一个无符号整数值:
glPushNames(ID);
替换栈顶:
glLoadName(ID);
定义拾取窗口:
gluPickMatrix(xPick,yPick,widthPick,heightPick,vpArray);
前两个参数给出了拾取窗口中心的坐标值,后两个窗口指定拾取窗口的宽和高。最后一个参数指定一个包含当前拾取的坐标位置和尺寸等参数的整型数组。
我们来看下一个示例:
#include <Gl/glut.h>
#include <stdio.h>
const GLint pickBuffsize = 32;
GLsizei winWidth = 400, winHeight = 400;
void init()
{
glClearColor(1.0, 0.0, 1.0, 1.0);
}
void rects(GLenum mode)
{
if (mode == GL_SELECT)
glPushName(30);//向栈中放入一个无符号整型
glColor3f(1.0, 0.0, 0.0);
glRecti(40, 130, 150, 260);
if (mode == GL_SELECT)
glPushName(10);
glColor3f(0.0, 0.0, 1.0);
glRecti(150, 130, 260, 260);
if (mode == GL_SELECT)
glPushName(20);
glColor3f(0.0, 1.0, 0.0);
glRecti(40, 40, 260, 130);
}
void processPicks(GLint nPicks, GLuint pickBuffer[])
{
GLint j, k;
GLuint objID, *ptr;
printf("Number of objects picked=%d\n", nPicks);
printf("\n");
ptr = pickBuffer;
for (j = 0; j < nPicks;j++)
{
objID = *ptr;
printf("Stack position=%d\n", objID);
ptr++;
printf("Min depth=%g,", float(*ptr / 0x7fffffff));
ptr++;
printf("Max depth=%g\n", float(*ptr / 0x7fffffff));
ptr++;
printf("Stack IDs are:\n");
for (k = 0; k < objID; k++)
{
printf("%d", *ptr);
ptr++;
}
printf("\n\n");
}
}
void pickRects(GLint button, GLint action, GLint xMouse, GLint yMouse)//鼠标消息回调函数
{
GLuint pickBuffer[pickBuffsize];
GLint nPicks, vpArray[4];
if (button != GLUT_LEFT_BUTTON || action != GLUT_DOWN)
return;
glSelectBuffer(pickBuffsize, pickBuffer);//设置缓冲区数组
glRenderMode(GL_SELECT);//激活选择模式
glInitNames();//初始化名称栈
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glGetIntegerv(GL_VIEWPORT, vpArray);//获得视口属性
gluPickMatrix(GLdouble(xMouse), GLdouble(vpArray[3] - yMouse), 5.0, 5.0, vpArray);//设置拾取窗口
gluOrtho2D(0.0, 300.0, 0.0, 300.0);
rects(GL_SELECT);
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glFlush();
nPicks = glRenderMode(GL_RENDER);//获取选中的对象数量
processPicks(nPicks, pickBuffer);//打印缓冲区数据
glutPostRedisplay();
}
void displayFcn(void)
{
glClear(GL_COLOR_BUFFER_BIT);
rects(GL_RENDER);
glFlush();
}
void winReshapeFcn(GLint newWidth, GLint newHeight)
{
glViewport(0, 0, newWidth, newHeight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0, GLdouble(newWidth), 0.0, GLdouble(newHeight));
winWidth = newWidth;
winHeight = newHeight;
}
void main(int argc, char**argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowPosition(100, 100);
glutInitWindowSize(winWidth, winHeight);
glutCreateWindow("An Example OpenGL Program");
init();
glutDisplayFunc(displayFcn);
glutReshapeFunc(winReshapeFcn);
glutMouseFunc(pickRects);//响应鼠标消息
glutMainLoop();//启动主循环,等待消息
}
这个程序显示了红、蓝、绿三个矩形,我们定义了一个5*5的选择窗口并通过拾取操作选中了三个矩形并打输出了缓冲区信息。