有時我們不僅僅是渲染場景,而且還要與渲染的場景進行交互。大多數情況下是使用鼠標進行交互。注: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