歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> OpenGL超級寶典學習筆記——選擇

OpenGL超級寶典學習筆記——選擇

日期:2017/3/1 9:32:11   编辑:Linux編程

有時我們不僅僅是渲染場景,而且還要與渲染的場景進行交互。大多數情況下是使用鼠標進行交互。注:viewing volume(可視區域,視景體)

選擇

OpenGL的選擇模式允許你通過鼠標點擊屏幕,來選擇鼠標下面的物體。使用OpenGL的選擇特性,當你點擊屏幕時就指定了一個可視區域,決定了哪些物體在這個可視區域中。基於你的屏幕坐標和你指定的像素大小,glu庫提供了一個有用的函數gluPickMatrix來產生一個矩陣,使用這個矩陣可以在你當前鼠標的位置產生更小的可視區域。然後你使用選擇模式來測試這個可視區域,看哪些物體被包含在裡面了。

在選擇模式下,圖像並不會被復制到幀緩沖區中(即幀緩沖區不會被修改)。反之,在可視區域中繪制的圖元會在選擇緩沖區中產生點擊記錄。這個緩沖區和其他的OpenGL緩沖區不同,它是個整型數組。

首先我們需要設置選擇緩沖區,並為你的圖元進行命名,這樣才能在選擇緩沖區中被標識。然後解析選擇緩沖區得到哪些對象與可視區域相交。在可視區域外的物體將不會被繪制。為了挑選,我們會指定一個位於鼠標點下面一段小空間的可視區域,然後測試有哪些被命名的物體在這個區域內被繪制。

命名你的圖元

圖元的名稱就像顯示列表的名稱一樣是整型數組。圖元的名稱列表被存在名稱棧中。在你初始化名稱棧之後,你就可以往這個棧存放名稱。你可以往棧頂壓如新的名稱,或者用當前的名稱替換掉棧頂的名稱。如果需要單個點擊可以返回多個名稱。命名圖元的代碼示例如下:

#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模式。在使用選擇模式之前,我們需要切換到選擇模式。調用如下:

glRenderMode(GL_SELECTION);

在選擇模式渲染完物體之後,調用glRenderMode(GL_RENDER)返回點擊記錄。注意當調用glRenderMode(GL_RENDER)時只有在之前的模式選擇模式GL_SELECTI和反饋模式下才會有返回值。返回值是點擊記錄,即在當前可視區域內被命名的物體的個數。

在使用選擇模式glRenderMode(GL_SELECTION);之前,要設置選擇緩沖區:

void glSelectBuffer( GLsizei size, GLuint *buffer);

size為緩沖區的大小,buffer為緩沖區的指針。在進入選擇模式時,OpenGl會將一個指針初始化指向這個選擇緩沖區,當有點擊記錄產生時,就往這個緩沖區中寫記錄。如果在填充這個選擇緩沖區時溢出了,那麼OpenGL並設置一個溢出標志。

選擇緩沖區

在選擇模式下,渲染的過程中選擇緩沖區由點擊記錄填充。點擊記錄由在可視區域內有多少個被命名的物體來產生的。在默認情況下,即可視區域為整個窗口。

選擇緩沖區是一組無符號的整型數組。每一個點擊記錄至少產生4個元素。這四個元素分別是名稱棧中名稱的個數,最小z值,最大z值,棧底的名稱1,如果有更多繼續往下添加。如下圖:

挑選

許多情況下我們想用鼠標去挑選某個物體。要使用選擇模式實現這個功能,首先是在鼠標點擊附近的范圍內創建一個裁剪區域(可視區域),然後測試有哪些物體在這個可視區域內。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的坐標原點在窗口的左上角。

所以我們調用時,要注意把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);
}

當我只選擇太陽時:

只有一個點擊記錄,一個物體在選擇的可視區域內輸出如下:

當太陽和地球重疊,然後我點擊地球時:

有兩個點擊記錄 輸出如下:

OpenGL超級寶典 第4版 中文版PDF+英文版+源代碼 見 http://www.linuxidc.com/Linux/2013-10/91413.htm

OpenGL編程指南(原書第7版)中文掃描版PDF 下載 http://www.linuxidc.com/Linux/2012-08/67925.htm

OpenGL 渲染篇 http://www.linuxidc.com/Linux/2011-10/45756.htm

Ubuntu 13.04 安裝 OpenGL http://www.linuxidc.com/Linux/2013-05/84815.htm

OpenGL三維球體數據生成與繪制【附源碼】 http://www.linuxidc.com/Linux/2013-04/83235.htm

Ubuntu下OpenGL編程基礎解析 http://www.linuxidc.com/Linux/2013-03/81675.htm

如何在Ubuntu使用eclipse for c++配置OpenGL http://www.linuxidc.com/Linux/2012-11/74191.htm

更多《OpenGL超級寶典學習筆記》相關知識 見 http://www.linuxidc.com/search.aspx?where=nkey&keyword=34581

Copyright © Linux教程網 All Rights Reserved