OpenGL中的拾取模式( Picking)


 

1. Opengl中的渲染模式有三種:(1)渲染模式,默認的模式;(2)選擇模式, (3)反饋模式。如下

GLint glRenderMode(GLenum mode)

 mode可以選取以下三種模式之一:繪制模式(GL_RENDER),選擇模式(GL_SELECT),反饋模式(GL_FEEDBACK)。

  函數的返回值可以確定選擇模式下的命中次數或反饋模式下的圖元數量。

2. OpenGL進行圖形編程的時候,通常要用鼠標進行交互操作,比如用鼠標點選擇畫面中的物體,我們稱之為拾取(Picking).

  OpenGL中的拾取是對OpenGL圖形管線的一個應用。所以OpenGL中的拾取並不是像D3D一樣采用射線交叉測試來判斷是否選中一個目標,而是在圖形管線投影變換(Projection Transformation)階段利用拾取矩陣來實現的。

  

  OpenGL的圖形管線如上圖所示。

  總的來說,OpenGL圖形管線大體分為上面的五個階段,在Opengl中做了下述內容的合並:

  1.   把模型變換與視圖變換合並為模型視圖變換,即GL_MODELVIEW
  2.   歸一化視體並裁剪處理也整合進了投影變換處理中,Opengl統一為:投影變換

  因此,Opengl中可以由如下五大變換過程就簡化為了三種過程模型視圖變換 --> 投影變換 --> 視口變換

  在編程的時候使用glMatrixMode(GL_MODELVIEW),或者 glMatrixMode(GL_PROJECTION)就是告訴OpenGL我們是要在那個階段進行操作。

3. 投影變換過程

  先來看看投影變換,因為理解投影變換是理解OpenGL拾取的前提條件。

  為了簡單起見,這里以正交投影(Orthogonal Projection)為例。在OpenGL中,使用正交投影可以調用glOrtho (left, right, bottom, top, zNear, zFar),其中的六個參數分別對應正交投影視體的六個平面 到觀察坐標系原點的距離。一旦在程序中調用了這個函數,OpenGL會馬上創建根據給定的六個參數創建一個視體,並且把視體的大小歸一化到-1到1之間, 也就是說,OpenGL會自動把你給的參數所對應的x,y,z值轉換為-1到1之間的值,並且這個視體的中心就是觀察坐標系的原點。

  要注意的是,當視體歸 一化后,z軸的方向要反向,也就是說,這里OpenGL的右手坐標系要換成左手坐標系。原因很簡單,z軸朝向顯示器里的方向更符合我們的常識,越向里就離 我們越遠,z的值也就越大

  也可以這樣理解:世界坐標系中采用右手坐標系。但是,在Opengl中顯示投影之后的坐標系采用左手坐標系

 

   當我們調用了glOrtho()這個函數后,OpenGL會建立一個矩陣,也就是投影矩陣。這個矩陣可以分解為 三個步驟

  1.   首先將我們設置的視體移動到觀察坐標是的原點
  2.   然后在縮放為邊長為2的單位視體。因為轉化后的視體坐標都在-1和1之間,所以視體的邊長就是 2。
  3.   然后再對z進行反方向。最后的投影矩陣我們用 來表示的話,那么有

  

  上面的矩陣雖然看起來很復雜,其實很簡單。它就是進行移動,縮放,反號三個操作而已。

  現在我們在OpenGL中檢查一下是不是進行了這樣的操作,對程序代碼作下述測試:

//進入投影變換過程
glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(
-10, 10, -10, 10, -10, 10); GLfloat m[16]; glGetFloatv(GL_PROJECTION_MATRIX, m); //獲取當前投影矩陣的內容

  首先進入投影變換階段,然后我們使用glLoadIdentity()在矩陣棧中存入單位矩陣。設置視體為邊長為20的正方體。這里我們把glortho中的6個參數帶入上面公式計算,得到查看結果:

   

  

  可以看到,得到的數據和我們計算的一樣。說明OpenGL的確是創建了這樣的矩陣來進行計算。弄清楚了OpenGL中的投影變換,現在就開看看大家關心的拾取操作。

4. Opengl中的選擇模式拾取Pick操作的原理

  OpenGL的拾取就是利用投影變換歸一化視體這個處理中來實現的

  拾取的時候,我們可以想象想用一個方框來選擇我們要選擇的物體。比如一個邊長為2的正方形,我們用鼠標在窗口上點擊的時候,一旦點到一個位置,那么就在這個位置生成一個邊長為2的正方形,那么正方形內包圍的物體就是我們要選擇的物體,如果這個正方形內沒有包圍任何東西,那么就說明什么都沒有選擇到。這個過程就和我們歸一化投影,然后再剪裁的過程是一樣的。

  OpenGL會自動剪裁掉在歸一化視體之外的物體,那么如果我們把選擇物體用的方框轉換為用投影時的視體,那么在這個方框外的東西,也就是我們沒有選擇的東西,OpenGL會自動的為我們扔掉。所以OpenGL提供了選擇模式glRenderMode(GL_SELECT)當 我們進行拾取前進入這個模式,然后設定好我們的選擇框的大小,再為我們要選擇的物體設定好名字,也就是我們說的名字棧。

  接下來的操作和投影變換就一樣了,就是歸一化視體的處理過程。即 先把這個選擇框移歸一化為邊長為-1到1的正方體,然后移動到原點,最后放大為我們窗口的大小。(這時OpenGL已經把在選擇框外的東西剪裁掉了,如果 這個時候我們顯示投影矩陣中的內容的話,就會只看到我們選擇到的東西,並且放大和窗口一樣大。)然后OpenGL會把選中的物體信息記錄在一個叫做SelectBuffer的緩沖中,這個緩沖就是個一維數組,里面保存了名字棧中名字的個數,選擇到的物體的最小最大深度值,也就是z的值,這個值是個0到1之間的值,也就是里我們最近的為0,最遠的為1。selectBuffer是個整型的數組,所以這里保存的深度值是乘以0xFFFFFFFF后的值。當然最重要的,其中還保存了我們選擇到的物體的名字,這樣只要在程序中判斷選擇到物體的名字,我們就可以判斷是不是選擇到了要選擇的物體了

  整個拾取的過程可如下:

   

   上圖中左邊的正方體是我們歸一化的視體,拾取的時候就是在這個空間中拾取的。紅色的小框是我們的選擇框,即鼠標所在位置的選擇矩形。里面的紅色就是我們選擇到的物體的一部分。現在要做的就是把這個小框轉變為視體,這樣OpenGL才能為我們把不要的東西扔掉。所以,首先還是把這個小框移動到觀察坐標系的原點,然后再放大為我們歸一化視體的大小,這樣整個視體中就只有我們選中的東西了,上圖中間顯示了這個過程。視體外的東西已經被OpenGL剪裁掉選中的記錄會保存到selectbuffer中。因為這些操作是在選擇模式下完成的,所以看不到我們選擇的過程,但是如果我們把選擇的過程顯示出來的話,就會看到上圖右邊的樣子。整個窗口就鋪滿了我們選擇的部分。

  在OpenGL中,提供了這個設置拾取框的函數。

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

  其中x,y是鼠標點擊到窗口上的坐標width和height就是這個拾取框的長寬viewport是為了得到我們窗口的大小

   一但調用了該函數,OpenGL就會創建一個拾取矩陣,分解這個矩陣的話,可以看到,這個矩陣就是上面的移動拾取框到原點,然后再放大為視體大小這兩個步驟。

  所以即使我們不使用這個函數,也可以自己計算出這個拾取矩陣.該矩陣的形式如下:

  

  同樣,我們還是在OpenGL中代碼測試,檢查一下,是不是做了這樣的操作。在OpenGL中添加下面的代碼。

 

  1 void SelectObject(GLint x, GLint y)    
  2   
  3 {    
  4   GLuint selectBuff[32]={0};//創建一個保存選擇結果的數組    
  5   GLint hits, viewport[4];      
  6   
  7   glGetIntegerv(GL_VIEWPORT, viewport); //獲得viewport    
  8   glSelectBuffer(64, selectBuff); //告訴OpenGL初始化  selectbuffer    
  9 
 10    //進入選擇模式    
 11   glRenderMode(GL_SELECT);   
 12   
 13   glInitNames();  //初始化名字棧    
 14   glPushName(0);  //在名字棧中放入一個初始化名字,這里為‘0’    
 15   
 16   glMatrixMode(GL_PROJECTION);    //進入投影階段准備拾取    
 17   
 18   glPushMatrix();     //保存以前的投影矩陣    
 19   glLoadIdentity();   //載入單位矩陣    
 20   
 21   float m[16];    
 22   glGetFloatv(GL_PROJECTION_MATRIX, m);  //監控當前的投影矩陣 
 23   
 24   gluPickMatrix( x,           // 設定我們選擇框的大小,建立拾取矩陣,就是上面的公式  
 25    viewport[3]-y,    // viewport[3]保存的是窗口的高度,窗口坐標轉換為OpenGL坐標(OPengl窗口坐標系)   
 26    2,2,              // 選擇框的大小為2,2    
 27    viewport          // 視口信息,包括視口的起始位置和大小    
 28    );        
 29   
 30     glGetFloatv(GL_PROJECTION_MATRIX, m);//查看當前的拾取矩陣  
 31     //投影處理,並歸一化處理
 32     glOrtho(-10, 10, -10, 10, -10, 10);     //拾取矩陣乘以投影矩陣,這樣就可以讓選擇框放大為和視體一樣大    
 33     glGetFloatv(GL_PROJECTION_MATRIX, m);    
 34   
 35     draw(GL_SELECT);    // 該函數中渲染物體,並且給物體設定名字    
 36   
 37     glMatrixMode(GL_PROJECTION);    
 38 
 39     glPopMatrix();  // 返回正常的投影變換    
 40   
 41 
 42 
 43     glGetFloatv(GL_PROJECTION_MATRIX, m);//即還原在選擇操作之前的投影變換矩陣 
 44   
 45     hits = glRenderMode(GL_RENDER); // 從選擇模式返回正常模式,該函數返回選擇到對象的個數    
 46   
 47     if(hits > 0)    
 48   
 49         processSelect(selectBuff);  //  選擇結果處理    
 50   
 51 }    
 52   
 53   
 54   
 55     void draw(GLenum model=GL_RENDER)    
 56   
 57     {    
 58          if(model==GL_SELECT)    
 59         {    
 60             glColor3f(1.0,0.0,0.0);    
 61             glLoadName(100);  //第一個矩形命名
 62             glPushMatrix();    
 63             glTranslatef(-5, 0.0, 10.0);    
 64             glBegin(GL_QUADS);    
 65                 glVertex3f(-1, -1, 0);    
 66                 glVertex3f( 1, -1, 0);    
 67                 glVertex3f( 1, 1, 0);    
 68                 glVertex3f(-1, 1, 0);    
 69             glEnd();    
 70             glPopMatrix();    
 71 
 72 
 73 
 74             glColor3f(0.0,0.0,1.0);    
 75             glLoadName(101); //第二個矩形命名
 76             glPushMatrix();    
 77             glTranslatef(5, 0.0, -10.0);    
 78             glBegin(GL_QUADS);    
 79                 glVertex3f(-1, -1, 0);   
 80                 glVertex3f( 1, -1, 0);    
 81                 glVertex3f( 1, 1, 0);    
 82                 glVertex3f(-1, 1, 0);    
 83             glEnd();    
 84             glPopMatrix();    
 85   
 86     }    
 87      else //正常渲染  
 88     {    
 89         glColor3f(1.0,0.0,0.0);    
 90         glPushMatrix();    
 91             glTranslatef(-5, 0.0, -5.0);    
 92             glBegin(GL_QUADS);    
 93             glVertex3f(-1, -1, 0);    
 94             glVertex3f( 1, -1, 0);    
 95             glVertex3f( 1, 1, 0);    
 96             glVertex3f(-1, 1, 0);    
 97         glEnd();    
 98         glPopMatrix();    
 99   
100         
101   
102        glColor3f(0.0,0.0,1.0);    
103        glPushMatrix();   
104        glTranslatef(5, 0.0, -10.0);   
105        glBegin(GL_QUADS);    
106        glVertex3f(-1, -1, 0);    
107             glVertex3f( 1, -1, 0);    
108             glVertex3f( 1, 1, 0);    
109             glVertex3f(-1, 1, 0);    
110             glEnd();    
111         glPopMatrix();    
112    }    
113   
114 }

 

 然后設點斷點來檢查一下

  

  上面看到我們的視口的起始位置就是窗口的原點,大小和窗口的大小一樣,500×500。然后在gluPickMatrix( x,  viewport[3]-y,    2,2,  viewport )函數調用后,會馬上建立一個拾取矩陣,根據提供的參數,在屏幕上點擊的坐標為 x=136,y=261,這是屏幕坐標,即單位是(像素),且原點在左上角

  轉換成openGL坐標為x=136,y=500-261=239,即在歸一化后的窗口坐標,因為窗口坐標是左手坐標系,因此原點在左下角。拾取框的另一個坐標就是138,242,因為這個給的長,寬都是2。現在就得到了拾取框的2個坐標了,,然后把這2個坐標再轉換為-1到1之間的坐標得到:

  

  把這個坐標帶入拾取矩陣中計算,可以得到

  

  然后,在程序中設置斷點,獲取當前操作矩陣檢查一下:

  

  的確和我們計算的結果一樣。然后代碼中使用了glPopMatrix(),由於我們的拾取操作已經完成,到里位置,拾取的結果信息已經被OpenGL保存到了selectbuffer中了,所以這時我們就要不在需要這個拾取矩陣,由於之前使用了glPushMatrix()保存了原來的投影矩陣,現在只要glPopMatrix()就可以了。glPopMatrix()后,我們可以再設置斷點看一下是不是真的返回到原來的投影矩陣。

  

  同樣我們可以從上圖中看到,的確在PopMatrix后,返回了原來的投影變換,所以現在從選擇模式返回到正常模式的時候我們就可以看到正常的畫面。

  

  在前面的代碼中,我們已經為紅色正方形命名為100,藍色的為101,現在我們來看看selectbuffer里被選中的物體。

   

  可以看到數組結果中:每4個一組作為一個被選中的item的信息:該解析如下:

  第一個表示被選中物體的個數,當然現在是1。

  第二個表示和第三個表示物體的最小深度值和最大深度值,由於物體是個平面,所以這兩個值是一樣的。這里的深度值是整數,除以0xffffffff以后就得到了0到1之間的深度值了。

  第四個值,也就是我們選擇到的物體的名字。這里就是紅色的正方形的Name。

   OpenGL的整個拾取過程就是這樣的。它是利用了圖形管線中投影變換階段來實現拾取操作的。對於OpenGL圖形管線不了解的朋友可能對這種方法會感到困難。但是一旦理解了圖形管線后,再來理解拾取就很容易了。

 

 

 

 

endl;

參考網址:

  http://blog.csdn.net/archielau/article/details/7646739

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM