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

相關文章
相關標籤/搜索