1. 程式人生 > >OpenGL選擇與拾取GL_SELECT 附原始碼

OpenGL選擇與拾取GL_SELECT 附原始碼

【一個提示】該方法雖然可行但是已經淘汰很多年,建議自行嘗試,後面也許會寫論文最好的方式是:

1. 使用Kd-tree組織場景中的物體,以便於快速查詢。

2. 使用螢幕座標->空間三維座標的逆矩陣變換,實現選取。

在介紹開始,首先給出工程和可執行程式的下載連結:

    程式的執行結果如下:

首先,我們在OpenGL的繪製方式有兩種:GL_RENDER和GL_SELECT。顧名思義,GL_RENDER是渲染模式,也就是預設的繪製方式,通俗的講,就是繪製操作都會被繪製在螢幕上;GL_SELECT則是選擇模式,這種方式不會被繪製在螢幕上,之後的矩陣變化為選取矩陣的變化。

這裡對GL_RENDER不做過多說明,因為這是預設方式,就是操縱顯示在螢幕上時的矩陣變換。

對於GL_SELECT:在用OpenGL進行圖形程式設計的時候,通常要用滑鼠進行互動操作,比如用滑鼠點選擇畫面中的物體,我們稱之為拾取(Picking),這裡我們介紹一下:在OpenGL中如何拾取,如何利用OpenGL提供的一系列函式來完成拾取,再簡單介紹下OpenGL的名字棧(Name stack),拾取矩陣(Picking Matrix)等等。

 1. 首先獲得一系列引數資訊:

           const int BUFSIZE = 1024;  //緩衝區selectBuf
           GLuint selectBuf[BUFSIZE];
           glSelectBuffer (BUFSIZE, selectBuf);

           GLint viewport[4];        //獲取視口viewport的資訊
           glGetIntegerv (GL_VIEWPORT, viewport);

2. 進入GL_SELECT模式,並初始化名字棧

           glRenderMode(GL_SELECT);
           glInitNames();
           glPushName(-1);

3. 儲存一下矩陣資訊:

           glPushMatrix();

4. 在GL_SELECT模式下,指定拾取視窗,並進行投影變換(GL_PROJECTION)

投影變換事實上在GL_RENDER模式下已經進行過一次,但是需要在GL_SELECT模式下,再進行一次。

           glMatrixMode (GL_PROJECTION);      // 投影變換
           glLoadIdentity ();
           gluPickMatrix((GLdouble)x, (GLdouble) (viewport[3]-y), 5.0, 5.0, viewport); // 指定選取視窗(x,y為滑鼠點選位置)
           gluPerspective(45.0, (GLfloat)width()/(GLfloat)height(), 0.1, 10000.0);     // 指定視景體

5. 在GL_SELECT模式下,進行模型檢視變換GL_MODELVIEW

           glMatrixMode(GL_MODELVIEW);    // 2: GL_SELECT下模型檢視變換
           glLoadIdentity();
           gluLookAt(0.0,0.0,10.0, 0.0,0.0,0.0, 0.0,1.0,0.0);   //指定攝像機位置
           /*
              為了保證選取結果的正確性,在GL_RENDER下進行的所有縮放、平移、旋轉等操作,這裡也要進行一次。
             *//*
              為了保證選取結果的正確性,在GL_RENDER下進行的所有縮放、平移、旋轉等操作,這裡也要進行一次。
             */

6. 在GL_SELECT重新繪製物體,這裡使用我上傳的程式中的例子。

呼叫繪製函式,總共有3個人物,繪製函式為renderNPC(GLenum mode);

          for(int i=0; i< 3 ;i++)
          {
               _characters[i]->renderNPC(GL_SELECT);  // 使用GL_SELECT方式渲染一次(並不繪製在螢幕上)
          }

關於renderNPC(GLenum mode)的具體實現,在後面給出。

7. 出棧,讓之前進行的矩陣操作釋放掉。

         glPopMatrix();

8. 對選取的名字棧進行處理(選取結果已經儲存在名字棧中了)

          // 切換回GL_RENDER模式
          GLint hits = glRenderMode (GL_RENDER); // 返回的hit:表示名字棧中結果的個數。
          glMatrixMode (GL_PROJECTION);    /// 此處需要重新進行一次GL_RENDER模式下的投影變換。
          glLoadIdentity ();
          gluPerspective(45.0, (GLfloat)width()/(GLfloat)height(), 0.1, 10000.0);
 
          if(hits)  ///如果選中物體,設定為"選中"狀態
          {
              _characters[selectBuf[3]]->_isSelected=!( _characters[selectBuf[3]]->_isSelected );
          }
  
          updateGL();  // 在螢幕上重繪,因為有的物體已經被選中。

       關於在上面出現的renderNPC(GLenum mode)函式,下面給出其內部細節(為了簡單表述,以畫方塊為例)。

          void renderNPC(GLenum model)
          {
              glPushMatrix();           
              /*
                 這裡進行屬於本物體的旋轉、平移、縮放變換
              */
             if(model == GL_SELECT)
             {
                 glLoadName(_name);   //這裡的_name為預先設計的該物體的名字,為一個GLuint值,比如1、2、3、.....
             }
             
             if(_isSelected == true)  ///如果被選中
             {   
              /*
                    對於選取的物體,進行諸如修改顏色啊啥啥啥的操作。
              */
             }
             glutSolidCube(3.0);     //繪製出物體,這裡以一個“方塊”舉例。
             glPopMatrix();
          }

此外,我們還需要了解的最後一點,名字棧的機制,名字棧最終儲存在selectBuffer[]中。當某物件被“拾取”(被光束射中)的時候,對應的名字和相關資訊便會被提交給HIT Record,儲存在selectBuffer裡面。相關資訊包括該物件離光束髮射處(相機/眼睛)最近的點的深度值和最遠的點的深度值等等,以下反映了當一個物件被拾取後,名字棧機制向HIT Record傳送的資訊(然後HIT Record把此資訊存入selectBuffer):  

擊中的物件的名字的數目
這個物件中最近的點的深度值
這個物件中最遠的點的深度值
擊中的物件的名字之一
擊中的物件的名字之二 
(若有多個名字,則如此類推...)