有時我們不僅僅是渲染場景,而且還要與渲染的場景進行交互。大多數情況下是使用鼠標進行交互。注: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);
}
}
總體的步驟:
調用glSelectBuffer設置選擇緩沖區
glRenderMode(GL_SELECTION)切換到選擇模式。
使用glInitNames和glPushName初始化名稱棧
定義用於選擇的可視區域
為每個圖元命名,並繪制圖元。
調用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