一、概述

在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下调试通过。