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):
擊中的物件的名字的數目 |
這個物件中最近的點的深度值 |
這個物件中最遠的點的深度值 |
擊中的物件的名字之一 |
擊中的物件的名字之二 (若有多個名字,則如此類推...) |