OpenGL超级宝典笔记——选择

有时咱们不单单是渲染场景,并且还要与渲染的场景进行交互。大多数状况下是使用鼠标进行交互。注:viewing volume(可视区域,视景体)数组

选择

OpenGL的选择模式容许你经过鼠标点击屏幕,来选择鼠标下面的物体。使用OpenGL的选择特性,当你点击屏幕时就指定了一个可视区域,决定了哪些物体在这个可视区域中。基于你的屏幕坐标和你指定的像素大小,glu库提供了一个有用的函数gluPickMatrix来产生一个矩阵,使用这个矩阵能够在你当前鼠标的位置产生更小的可视区域。而后你使用选择模式来测试这个可视区域,看哪些物体被包含在里面了。函数

在选择模式下,图像并不会被复制到帧缓冲区中(即帧缓冲区不会被修改)。反之,在可视区域中绘制的图元会在选择缓冲区中产生点击记录。这个缓冲区和其余的OpenGL缓冲区不一样,它是个整型数组。测试

首先咱们须要设置选择缓冲区,并为你的图元进行命名,这样才能在选择缓冲区中被标识。而后解析选择缓冲区获得哪些对象与可视区域相交。在可视区域外的物体将不会被绘制。为了挑选,咱们会指定一个位于鼠标点下面一段小空间的可视区域,而后测试有哪些被命名的物体在这个区域内被绘制。ui

命名你的图元

图元的名称就像显示列表的名称同样是整型数组。图元的名称列表被存在名称栈中。在你初始化名称栈以后,你就能够往这个栈存放名称。你能够往栈顶压如新的名称,或者用当前的名称替换掉栈顶的名称。若是须要单个点击能够返回多个名称。命名图元的代码示例以下:spa

#define SUN 1
#define EARTH 2
#define MOON 3

void RenderSphere()
{
  glPushMatrix();
  glTranslatef(0.0f, 0.0f, -10.0f);

  //初始化名称栈
  glInitNames();
  //往栈顶压栈,压如一个名称
  glPushName(0);
  glColor3f(1.0f, 0.0f, 0.0f);
  //用当前名称SUN替换掉栈顶名称
  glLoadName(SUN);
  glutSolidSphere(1.0, 26, 26);

  glRotatef(yRot, 0.0f, 1.0f, 0.0f);
  glTranslatef(2.0f, 0.0f, 0.0f);
  glColor3f(0.0f, 0.0f, 1.0f);
  //用EARTH替换掉栈顶名称
  glLoadName(EARTH);
  glutSolidSphere(0.3, 26, 26);

  glTranslatef(1.0f, 0.0f, 0.0f);
  glColor3f(0.25f, 0.25f, 0.75f);
  //用当前名称MOON替换掉栈顶名称
  glLoadName(MOON);
  glutSolidSphere(0.1, 26, 26);
  glPopMatrix();
}
注意只有在选择模式下glInitNames,glLoadName,glPushName才有效,
在GL_RENDER正常渲染模式下这些函数调用将被忽略

使用选择模式

OpenGL有三种不一样的渲染模式,默认的GL_RENDER模式,还有GL_SELECTION模式和GL_FEEDBACK模式。在使用选择模式以前,咱们须要切换到选择模式。调用以下:.net

glRenderMode(GL_SELECTION);指针

在选择模式渲染完物体以后,调用glRenderMode(GL_RENDER)返回点击记录。注意当调用glRenderMode(GL_RENDER)时只有在以前的模式选择模式GL_SELECTI和反馈模式下才会有返回值。返回值是点击记录,即在当前可视区域内被命名的物体的个数。code

在使用选择模式glRenderMode(GL_SELECTION);以前,要设置选择缓冲区:对象

void glSelectBuffer( GLsizei size, GLuint *buffer);

size为缓冲区的大小,buffer为缓冲区的指针。在进入选择模式时,OpenGl会将一个指针初始化指向这个选择缓冲区,当有点击记录产生时,就往这个缓冲区中写记录。若是在填充这个选择缓冲区时溢出了,那么OpenGL并设置一个溢出标志。
事件

选择缓冲区

在选择模式下,渲染的过程当中选择缓冲区由点击记录填充。点击记录由在可视区域内有多少个被命名的物体来产生的。在默认状况下,便可视区域为整个窗口。

选择缓冲区是一组无符号的整型数组。每个点击记录至少产生4个元素。这四个元素分别是名称栈中名称的个数,最小z值,最大z值,栈底的名称1,若是有更多继续往下添加。以下图:

image

挑选

许多状况下咱们想用鼠标去挑选某个物体。要使用选择模式实现这个功能,首先是在鼠标点击附近的范围内建立一个裁剪区域(可视区域),而后测试有哪些物体在这个可视区域内。GLU库中提供了一个函数gluPickMatrix,咱们能够用这个函数建立一个用于描述新的可视区域的矩阵,而后乘以当前的投影矩阵,就能够获得咱们想要的可视区域。

void gluPickMatrix(GLdouble x, GLdouble y, GLdouble width, GLdouble height, GLint viewport[4]);

x,y为窗口的坐标定义了可视区域的中心,咱们能够设置为鼠标点击的位置。width 和height指定了可视区域的宽高(以窗口中的像素为单位)。viewport是视口。咱们能够经过glGetIntegerv(GL_VIEWPORT, viewport);来得到。

gluPickMatrix的效果是把裁剪区域变换为单位立方体-1<= (x,y,z) <=1(或-w<=(wx,wy,wz)<=w,挑选矩阵有效的执行一次正交变换,把裁剪区域映射到单位立方体上。

因为OpenGL的坐标原点在窗口的左下角,而Windows的坐标原点在窗口的左上角。

image

因此咱们调用时,要注意把Y轴的值反转一下。以下:

gluPickMatrix(xPos, viewport[3] – yPos + viewport[1], 2,2, viewport);

咱们能够用glut库提供的函数,设置回调函数来处理鼠标点击事件。

glutMouseFunc(MouseCallBack);

void MouseCallBack(int key, int state, int x, int y)
{
  if (key == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
  {
     ProcessSelection(x, y);
  }
}

整体的步骤:

  1. 调用glSelectBuffer设置选择缓冲区

  2. glRenderMode(GL_SELECTION)切换到选择模式。

  3. 使用glInitNames和glPushName初始化名称栈

  4. 定义用于选择的可视区域

  5. 为每一个图元命名,并绘制图元。

  6. 调用glRenderMode(GL_RENDER);返回点击记录

由于只有在GL_RENDER模式下glInitNames,glPushName,glLoadName将被忽略,因此咱们能够把这些函数和绘制图元的函数写在一个函数调用中。

示例代码片断:

void RenderSphere()
{
  glPushMatrix();
  glTranslatef(0.0f, 0.0f, -10.0f);

  //初始化名称栈
  glInitNames();
  //往栈顶压栈,压如一个名称
  glPushName(0);
  glColor3f(1.0f, 0.0f, 0.0f);
  //用当前名称SUN替换掉栈顶名称
  glLoadName(SUN);
  glutSolidSphere(1.0, 26, 26);

  glRotatef(yRot, 0.0f, 1.0f, 0.0f);
  glTranslatef(2.0f, 0.0f, 0.0f);
  glColor3f(0.0f, 0.0f, 1.0f);
  //用EARTH替换掉栈顶名称
  glLoadName(EARTH);
  //glPushName(EARTH);
  glutSolidSphere(0.3, 26, 26);

  glTranslatef(1.0f, 0.0f, 0.0f);
  glColor3f(0.25f, 0.25f, 0.75f);
  //用当前名称MOON替换掉栈顶名称
  glLoadName(MOON);
  //glPushName(MOON);
  glutSolidSphere(0.1, 26, 26);
  glPopMatrix();
}
void RenderScene()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  RenderSphere();
  glutSwapBuffers();
}

void ChangeSize(GLsizei w, GLsizei h)
{
  if(h == 0)
    h = 1;

  glViewport(0, 0, w, h);

  GLfloat fAspect = (GLfloat)w / (GLfloat)h;

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  gluPerspective(35.0f, fAspect, 1.0f, 50.0f);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void TimerFunc(int value)
{
  yRot += 0.5;
  if (yRot > 360.0f)
  {
    yRot = 0.0f;
  }
  glutPostRedisplay();
  glutTimerFunc(50, TimerFunc, 1);
}

//处理点击记录
void ProcessHit(int hits, GLuint *buf)
{
  for (int i = 1; i <= hits; ++i)
  {
    GLuint nameNum = *buf;
    printf("hit number %d \n", i);
    printf("name stack count is %d\n", *buf); buf++;
    printf("min z value is %g\n", (float)*buf/0x7FFFFFFF); buf++;
    printf("max z value is %g\n", (float)*buf/0x7FFFFFFF); buf++;
    printf("name value is : ");
    for (int j = 0; j < nameNum; ++j)
    {
      switch(*buf)
      {
      case SUN:
        printf("SUN \t");
        break;
      case EARTH:
        printf("EARTH \t");
        break;
      case MOON:
        printf("MOON \t");
        break;
      default:
        break;
      }
      buf++;
    }
    printf("\n");
  }
}

void ProcessSelection(int x, int y)
{
  GLint viewport[4], hits;

  static GLuint selectBuffer[BUFFER_LENGTH];
  //设置选择缓冲区
  glSelectBuffer(BUFFER_LENGTH, selectBuffer);

  //切换到投影矩阵,咱们须要建立 可视区域
  glMatrixMode(GL_PROJECTION);
  //保留原先的 投影矩阵,以便恢复
  glPushMatrix();
    glLoadIdentity();
    //得到视口
    glGetIntegerv(GL_VIEWPORT, viewport);
    //切换到选择模式
    glRenderMode(GL_SELECT);
    GLfloat aspect = (GLfloat)viewport[2]/(GLfloat)viewport[3];
    //建立一个描述可视区域的矩阵
    gluPickMatrix(x, viewport[3]-y+viewport[1], 2, 2, viewport);
    //与投影矩阵相乘,获得可视区域
    gluPerspective(35.0, aspect, 1.0, 200.0);
    //在选择模式下 渲染图元
    RenderSphere();
    //返回点击记录数。
    hits = glRenderMode(GL_RENDER);
    ProcessHit(hits, selectBuffer);
    glMatrixMode(GL_PROJECTION);
  glPopMatrix();

  glMatrixMode(GL_MODELVIEW);
}

当我只选择太阳时:

image

只有一个点击记录,一个物体在选择的可视区域内输出以下:

image

当太阳和地球重叠,而后我点击地球时:

image

有两个点击记录 输出以下:

image

相关文章
相关标签/搜索