OpenGL-選擇與拾取

轉自 http://blog.sina.com.cn/s/blog_4a9aa55c0100vu57.html

以下內容主要整理《OpenGL編程指南》第13章的內容。主要解決以下問題:

 

(1)如何允許用戶選擇屏幕上的一塊區域或者挑選屏幕上所繪製的一個物體?

 

一. 選擇

 

1. OpenGL的選擇機制如何實現

 

當我們打算使用OpenGL的選擇機制時:

(1)首先把整個場景繪製到幀緩衝區中;

(2)然後進入選擇模式,並且對場景進行重繪,此時,幀緩衝區的內容將不會被修改;

(3)退出選擇模式時,OpenGL就會返回與視景體相交的圖元列表,與視景體相交的每一個圖元都產生一個所謂的“選擇點擊”。

 (圖元列表:實際上是以點擊記錄的形式返回,包含了圖元的名稱以及相關的數據。我們可以訪問該列表並處理其中的內容)

 

2. 基本步驟

 

//在繪製了場景之後,進入以下步驟

 

(1)

#define BUFSIZE 512

GLuint selectBuf[BUFSIZE];

glSelectBuffer( BUFSIZE, selectBuf );   //指定將“圖元列表”(點擊記錄)返回到selectBuf數組中

 

(2)

glRenderMode( GL_SELECT );   //進入選擇模式

 

(3)

glInitNames();    // //初始化名字堆棧並壓入初始元素

glPushName();

 

(4)

glPushMatrix();   //爲重繪設置好投影矩陣,注意,爲了不影響繪製模式,要用glPushMatrix和glPopMatrix

glMatrixMode(GL_PROJECTION);

glLoadIdentity();

glOrtho(0.0,5.0,0.0,5.0,0.0,10.0);

glMatrixMode(GL_MODELVIEW);

glLoadIdentity();

 

(5)

glLoadName(1);   //將名字堆棧的堆頂設置爲1,因此,之後繪製的物體的名字爲1

drawTri();

 

glLoadName(2);   //將名字堆棧的堆頂設置爲2,因此,之後繪製的物體的名字爲2

drawTri();

 

glLoadName(3);   //將名字堆棧的堆頂設置爲3,因此,之後繪製的物體的名字爲3

drawTri();

drawTri();

 

(6)

glPopMatrix();   //對應glPushMatrix

glFlush();

 

(7)

//返回繪製模式,hits記錄的是產生的點擊的個數,即在視景體內的圖元的個數

Gluint hits = glRenderMode(GL_RENDER); 

 

(8)

//處理點擊記錄

printf("hits = %d\n", hits); //輸出一共產生的點擊的個數

 

//注意:

//圖元列表(點擊記錄)selectBuf中記錄了所有的點擊記錄
//某一個點擊記錄來說,由四個項目組成:
//(1)當點擊發生時,名字堆棧中的名稱數量

//(2)自上一個點擊記錄之後,與視景體相交的所有頂點的最小和最大窗口座標z值
//(3)當點擊發生時,名稱堆棧的內容,從最底部的元素開始
 

GLuint *ptr = selectBuf;

GLuint names;

for( i=0; i<hits; i++ )

{

    names = *ptr;   //點擊發生時,名字堆棧中的名稱數量

    printf("names = %d\n",names );

    ptr++;

 

    ptr += 2;  //跳過兩個z值

 

    for( j=0; j<names; j++ )

    {

        printf("%d", *ptr);  //輸出點擊發生時,名字堆棧中所有的名稱

        ptr++;

    }

}

 

二. 拾取

 

1. 如何拾取

 

前一節介紹了OpenGL的選擇機制如何實現和使用,這一節將深入的介紹如何利用選擇模式來確定一個物體是否被挑選。

爲了實現這個目的,可以在選擇模式中使用一個特殊的挑選矩陣,結合投影矩陣,把繪圖限制在視口的一個小區域內,一般是在靠近光標的位置。這樣,只有靠近光標位置的物體纔會引起點擊。

 

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

(x,y)就是挑選區域的中心的窗口座標,width和height定義了屏幕座標下這個挑選區域的大小,viewport是指視口邊界,通過glGeIntegerv(GL_VIEWPORT, GLint *viewport)獲得

 

(2) 具體實例

 

下面,用OpenGL編程指南上的實例來介紹如何實現拾取。

 

//該程序完成的功能是:繪製9個方塊,鼠標左鍵點擊,改變方塊的顏色

#include <gl/glut.h> 

int board[3][3];   //存儲幾個方塊的顏色

#define BUFSIZE 512

//處理點擊記錄:
//hits爲產生的點擊的數量,buffer中存儲點擊記錄,每個點擊記錄由四個項目組成
void processHits(GLint hits, GLuint buffer[])
{
 unsigned int i, j;
 GLuint ii, jj, names, *ptr;

 ptr = ( GLuint * )buffer;   

 

 for( i=0; i<hits; i++ )    //處理每一個點擊記錄
 { 

 //某一個點擊記錄來說,由四個項目組成:
//(1)當點擊發生時,名字堆棧中的名稱數量

//(2)自上一個點擊記錄之後,與視景體相交的所有頂點的最小和最大窗口座標z值
//(3)當點擊發生時,名稱堆棧的內容,從最底部的元素開始

  names = *ptr;      //獲得名字堆棧中的名稱數量
  ptr += 3;               //跳過前三個記錄

  for( j=0; j<names; j++ ) //開始處理名字堆棧中的內容,獲取被選中的方塊的index
  {
   //對應於繪製方塊時,壓入名字堆棧中的內容

   if ( j == 0)        //x方向上的index
    ii = *ptr;
   else if( j== 1)  //y方向上的index
    jj = *ptr;
   ptr++;
  }

 }
 board[ii][jj] = (board[ii][jj] + 1) % 3;   //改變被選中方塊的顏色

}

 

//繪製所有方塊,參數有GL_RENDER和GL_SELECT兩種模式
void drawSquares(GLenum mode)
{
 GLuint i,j;

 for(i=0; i<3; i++)
 {
  if( mode == GL_SELECT )       //如果是在選擇模式下,將名字堆棧的首元素換成x方向上的索引
   glLoadName(i);

 

  for( j=0; j<3; j++ )
  {
   if( mode == GL_SELECT )    //將y方向上的索引壓入名字堆棧
     glPushName(j);

 

   //繪製方塊,在GL_SELECT模式下,某一個方塊會被選中,因此,會產生一個點擊記錄

   //該點擊被記錄時,名字堆棧中有兩個名稱,分別是i和j的值,也就是被選中方塊的索引
   glColor3f( (GLfloat) i / 3.0, (GLfloat) j / 3.0, (GLfloat) board[i][j] / 3.0 );
   glRecti(i,j,i+1,j+1);

  

   if( mode == GL_SELECT ) //彈出名字
     glPopName();
  }
 }
}

 

//當鼠標左鍵點擊窗口時,進入選擇模式開始繪製;繪製之後,處理點擊記錄
void pickSquares(int button, int state, int x, int y)
{
 GLuint selectBuf[BUFSIZE];   //存儲點擊記錄
 GLint hits;                                 //點擊記錄的個數
 GLint viewport[4];                    //視口

 

 if( button != GLUT_LEFT_BUTTON || state != GLUT_DOWN )
  return;

 

 glGetIntegerv(GL_VIEWPORT, viewport);  //獲得視口

 glSelectBuffer( BUFSIZE, selectBuf );    //指定存儲點擊記錄的數組
 glRenderMode( GL_SELECT );          //進入選擇模式

 

 glInitNames();           //初始化名字堆棧並壓入初始元素
 glPushName(0);

 

 glMatrixMode(GL_PROJECTION);
 glPushMatrix();
 glLoadIdentity();
 
 //設置挑選矩陣,挑選區域的中心座標是(x,viewport[3]-y),大小是(5,5)
 gluPickMatrix( (GLdouble) x, (GLdouble) ( viewport[3] - y ) , 5.0, 5.0, viewport ); 
 //設置投影矩陣
 gluOrtho2D(0.0, 3.0, 0.0, 3.0 );

 //在選擇模式下繪製方塊
 drawSquares(GL_SELECT);
 
 glMatrixMode(GL_PROJECTION);
 glPopMatrix();
 glFlush();        //繪製結束

 

//處理點擊記錄

 hits = glRenderMode(GL_RENDER); //獲取記錄下的點擊的個數
 processHits(hits, selectBuf);           //處理點擊記錄selectBuf

 glutPostRedisplay();
}

 

void init()
{
 glEnable(GL_DEPTH_TEST);
 glShadeModel(GL_FLAT);
 for( int i=0;i <3; i++ )            //初始化9個方塊的顏色
  for( int j=0; j<3; j++ )
   board[i][j] = 0;
}
void display()
{
 glClearColor(0.0,0.0,0.0,0.0);
 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 drawSquares(GL_RENDER);  //基本繪製
 glFlush();
}

void reshape( int w, int h )
{
 glViewport(0,0,w,h);
 glMatrixMode(GL_PROJECTION);
 glLoadIdentity();
 gluOrtho2D( 0.0, 3.0, 0.0, 3.0 );
 glMatrixMode(GL_MODELVIEW);
 glLoadIdentity();
}

int main(int argc, char ** argv)
{
 glutInit(&argc, argv);
 glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
 glutInitWindowSize(200,200);
 glutInitWindowPosition(100,100);
 glutCreateWindow("pick");
 init();
 glutMouseFunc(pickSquares);   //當鼠標點擊時,調用pickSquares,進入選擇模式進行繪製
 glutReshapeFunc(reshape);
 glutDisplayFunc(display);      //display只完成基本的繪製
 glutMainLoop();
 return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章