一、概述
在OpenGL中绘制物体通常是在其缺省的绘图模式下进行的,而为了对物体进行标记以区分在指定区域上绘制了那些物体,则需要进入选择模式;选择模式为用户提供了一种拾取物体的机制。下面通过先列出应用选择模式的一般步骤,再通过一个例子具体说明选择模式的应用。
二、应用选择模式的一般步骤
1、创建返回命中记录的数组,由glSelectBuffer()函数实现;
2、进入选择模式,由glRenderMode (GL_SELECT)实现;
3、使用glInitNames()和glPushName()初始化名称堆栈;
4、定义用于选择模式的视景体,拾取区域;
5、建立名称堆栈;
6、推出选择模式,处理返回的信息(命中记录)。
三、应用实例
第一步、在VC中建立一个名为Selection的单文档工程
在SelectionView.h文件头部添加:
#include "gl\gl.h"
#include "gl\glu.h"
#include "gl\glaux.h"
#include "math.h"
#define DR 0.01745329252//pi/180
打开菜单Project,选择Settings,在弹出的对话框中选择Link标签,在Object/Libaray Modules 栏中增加OpenGL32.lib、glu32.lib和glaux.lib几个文件。
第二步、OpenGL初始化
在CSelectionView类中声明Public型成员变量:
CClientDC *m_pDC;
CRect rect;
GLdouble fovy; //视场角
GLfloat aspect; //高度与宽度的比率
GLdouble zNear; //近剪切面离视点的距离
GLdouble zFar; //远剪切面离视点的距离
GLdouble eyex,eyey,eyez;//视点
GLdouble lookat_X,lookat_Y,lookat_Z;//观察点
GLdouble A,E,R; //观察点相对于视点的方位角、高低角、距离
GLdouble xMove,yMove,zMove;//x,y,z方向的偏移量
int What_flag;
添加保护成员函数:
BOOL bSetupPixelFormat();//设置像素格式
void Init();//初始化
GLvoid ReSize();
GLvoid Calculate();
void CreateSence();
void DrawNameObjects(GLenum mode);
void DrawScene();//绘场景
void WhatHits(Glint hits, GLuint* buffer);//拾取提示
编辑以上函数:
BOOL CSelectionView::bSetupPixelFormat()
{
static PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW |PFD_SUPPORT_OPENGL|
PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,
24,
0, 0, 0, 0, 0, 0,
0,
0,
0,
0, 0, 0, 0,
32,
0,
0,
PFD_MAIN_PLANE,
0,
0, 0, 0
};//参数的含义请参看联机帮助
int pixelformat;
if ((pixelformat=ChoosePixelFormat(m_pDC->GetSafeHdc(), &pfd)) == 0)
{
MessageBox("ChoosePixelFormat failed");
return FALSE;
}
if (SetPixelFormat(m_pDC->GetSafeHdc(), pixelformat, &pfd) == FALSE)
{
MessageBox("SetPixelFormat failed");
return FALSE;
}
return TRUE;
}
void CSelectionView::Init()
{
PIXELFORMATDESCRIPTOR pfd;
int n;
HGLRC hrc;
//设置像素格式
m_pDC = new CClientDC(this);
ASSERT(m_pDC != NULL);
if (!bSetupPixelFormat()) return;
//创建并设置着色描述表
n =::GetPixelFormat(m_pDC->GetSafeHdc());
::DescribePixelFormat(m_pDC->GetSafeHdc(), n, sizeof(pfd), &pfd);
hrc = wglCreateContext(m_pDC->GetSafeHdc());
wglMakeCurrent(m_pDC->GetSafeHdc(), hrc);
}
GLvoid CSelectionView::ReSize()
{
//OpenGL视口及视景体
GetClientRect(rect);
aspect = (GLfloat) rect.right/rect.bottom;
glViewport(0,0,rect.right,rect.bottom);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(fovy,aspect,zNear,zFar);
}
GLvoid CSelectionView::Calculate()
{
eyex=lookat_X+R*cos(A*DR)*cos(E*DR);
eyey=lookat_Y+R*sin(A*DR)*cos(E*DR);
eyez=lookat_Z+R*sin(E*DR);
}
void CSelectionView::CreateSence()
{
glNewList(1,GL_COMPILE_AND_EXECUTE);
glDisable(GL_LIGHTING);
glDisable(GL_DEPTH_TEST);
glColor3f(0.0f,1.0f,0.0f);
glPushMatrix();
auxSolidTeapot(5.0);
glPopMatrix();
glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHTING);
glEndList();
}
void CSelectionView::DrawNameObjects(GLenum mode)
{
if(mode == GL_SELECT) glLoadName(1);
for(int j=1;j<=1;j++)//循环显示列表
{
if(mode == GL_SELECT)glPushName(j);
glCallList(j);
if(mode == GL_SELECT)glPopName();
}
}
void CSelectionView::DrawScene()
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
Calculate();
gluLookAt(eyex+xMove,eyey+yMove,eyez+zMove,
lookat_X+xMove,lookat_Y+yMove,lookat_Z+zMove,
0.0,0.0,1.0);
DrawNameObjects(GL_RENDER);
glFinish();
SwapBuffers(wglGetCurrentDC());
}
void CSelectionView::WhatHits(GLint hits, GLuint* buffer)
{
What_flag=0;
if(hits==0) return;
for(int i=0;i<hits;i++) What_flag=(*(buffer+4+i*5));
}
变量初始化:
CSelectionView::CSelectionView()
{
m_pDC=NULL;
fovy=60.0;
zNear=1.0; //近剪切面离视点的距离
zFar=1000.0;//远剪切面离视点的距离
eyex=eyey=eyez=0.0;
lookat_X=lookat_Y=lookat_Z=0.0;
A=270.0;E=75.0;R=80.0;
xMove=yMove=zMove=0.0;
}
在CSelectionView类中添加WM_CREATE消息响应函数OnCreate:
int CSelectionView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1) return -1;
Init();
CreateSence();
return 0;
}
编辑OnDraw函数如下:
void CSelectionView::OnDraw(CDC* pDC)
{
CSelectionDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
DrawScene();
pDC->SetTextColor(RGB(255,0,90));
pDC->SetBkMode(TRANSPARENT);
switch(What_flag)
{
case 1: MessageBox("拾取茶壶");break;
default:break;
}
}
添加WM_LBUTTONDOWN消息响应函数OnLButtonDown ,该函数完成拾取模式的主要步骤:
void CSelectionView::OnLButtonDown(UINT nFlags, CPoint point)
{
GLuint selectBuf[512];
GLint hits;
GLint viewport[4];
glGetIntegerv (GL_VIEWPORT, viewport);
glSelectBuffer (512, selectBuf);
glRenderMode (GL_SELECT);
glInitNames();
glPushName((unsigned)-1);
glMatrixMode (GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
gluPickMatrix(point.x,viewport[3]-point.y,5.0,5.0, viewport);
gluPerspective(fovy, aspect,zNear,zFar);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(eyex+xMove,eyey+yMove,eyez+zMove,
lookat_X+xMove,lookat_Y+yMove, lookat_Z+zMove,
0.0,0.0,1.0);
DrawNameObjects(GL_SELECT);
glMatrixMode(GL_PROJECTION);
glPopMatrix ();
hits = glRenderMode(GL_RENDER);
WhatHits (hits,selectBuf);
Invalidate();
CView::OnLButtonDown(nFlags, point);
}
添加WM_SIZE消息响应函数OnSize :
void CSelectionView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
ReSize();
}
为避免闪烁添加WM_ERASEBKGND消息响应函数OnEraseBkgnd:
BOOL CSelectionView::OnEraseBkgnd(CDC* pDC)
{
return TRUE;//CView::OnEraseBkgnd(pDC);
}
编译执行程序,当在物体(茶壶)上单击鼠标左键时,提示拾取信息。
四、结束语
本文以gluPerspective透视投影为例介绍了OpenGL中的拾取模式,其他投影变换与此类似;需注意的是,为了能在场景变化的情况下正确拾取物体,绘图模式和拾取模式的透视投影应该一致。读者可添加光照、纹理、贴图等以改善显示效果。
读者可通过改变例子中的视场角、视点、观察点和方位角、高低角、距离以及x,y,z方向的偏移量等的值体验变场景下的物体拾取;也可参看例子中的OnKeyDown函数。该函数通过小键盘上的“+”、“-”键完成距离(焦距)的增减,通过方向键高低、方位角的增减,通过小键盘上的“*”、“/”键完成视场角的增减等。
例子程序在Windows2000、VC6.0下调试通过。