摘要:該文介紹一種3D場景中景物拾取的方法,此方法是基于OpenGL的,具有較強的通用性能,且巧妙的避開了復(fù)雜的圖形學(xué)公式。
關(guān)鍵詞:OpenGL;拾??;選擇模式;名稱堆棧。
中圖分類號:TP311文獻標識碼:A文章編號:1009-3044(2008)28-0196-01
The Picking Method in The 3D Scene
XIA Nu
(SEU. Software College, Nanjing 210007, China)
Abstract: This article introduce one method which is used in picking the object in the 3D scene, this method basing on OpenGL has very good general ability and ingenuously avoid those complex graphics formulas.
Key words: openGL; picking; selecting-mode; name stack
1 引言
目前許多優(yōu)秀的圖形工具能為我們繪制惟妙惟肖的虛擬現(xiàn)實場景,在面對這些場景時候我們除了欣賞之外更多的是希望能與之互動,做為編程人員我們可以使用代碼輕松的重新構(gòu)造場景,但對于終端用戶而言,他們也希望自己也能對造場景進行一些操作,如添加,刪除等等。對于這些操作而言,首先我們要做的是能讓用戶使用鼠標來選擇他所希望操作的對象,這就是本文要討論的一個重點:拾取,這是一種在許多交互性程序中有基礎(chǔ)地位的操作,是對屏幕中對象進行定位,并確定你所選擇的是哪個物體。然而該操作給我們提出了一些難題,首先,我們需要對對象進行屆定。其次,我們必須對“拾取目標”進行定義。這就需要我們確定單擊的位置是在構(gòu)成對象的圖元上,還是對象附近的位置以及考慮如果選取點落在兩個以上物體交集部分如何處理等等問題。本文利用了OpenGL中的選擇模式進行有效的對象判別和拾取。
2 選擇模式
選擇模式其對場景對象進行繪制,但與顯示圖像不同的是這種模式下的繪制并不存儲到正在被顯示的顏色緩存中去。也就是說通過這種“繪制”我們可以判斷對象是否位于特定的裁剪體中以此來確定選擇的對象。具體來說就是在你選擇的區(qū)域里(鼠標點擊點周圍一個很小的區(qū)域)可以繪制的圖形都會在一個選擇緩沖區(qū)中產(chǎn)生一個命中記錄,這個緩沖區(qū)是個整型數(shù)組。下面我們通過一些代碼來了解相關(guān)的原理和過程。
為了使用選擇模式,我們必須把渲染模式改為選擇模式,通過:glRendMode(GL_SELECTION);
接著我們處理鼠標點擊事件:
Void MouseCallback(int button,int state,int x,int y)
{if(button==GLUT_LEFT_BUTTONstate=GLUT_DOWN)
ProcessSelection(x,y);// 接受鼠標點擊坐標并處理事件}
為了能通過鼠標點擊來判斷哪些景物被選中,我采取的方法是設(shè)置一個經(jīng)過修改過的可視區(qū)域,這個區(qū)域是在鼠標點擊點的周圍一個很小的區(qū)域,這個區(qū)域用途是為了選定在鼠標周圍出現(xiàn)的景物,這樣可以縮小比較范圍,因為在選擇模式下,景物會在這個小的可視區(qū)域內(nèi)重繪,那些重繪的景物就是我們要選擇的景物。具體代碼如下:
Void glupickMatrix(GLdouble x,GLdouble y,Gldouble width,Gldouble height,Glint viewport[4])
其中x和y是目標可視區(qū)域的中心,我在此處插入鼠標位置,width和height確定了可視覺區(qū)域的范圍,viewport定義了當前定義的視口的窗口坐標。
為了使用gluPickMatrix, 你首先應(yīng)該保存當前的投影矩陣狀態(tài)(即保存當前的可視區(qū)域)。然后再調(diào)用glLoaddentity創(chuàng)建一個單位可視矩陣,再調(diào)用gluPickMatrix把這個可視區(qū)域移動到正確的位置。最后,還必須應(yīng)用那些你在原場景中可能用到的透視投影。否則,你將無法獲得真正的映射。
3 名稱堆棧
我們對場景中的圖元進行命名,這些名字和顯示列表的名字一樣,僅僅是一些無符號的整數(shù)而已。這些名字是在名稱堆棧中維護。我們把一個名字壓入到堆棧中,如果在選擇模式下出現(xiàn)一次鼠標點擊事件,當前名稱堆棧中的所有名字都被添加到選擇緩沖區(qū)的尾部。也就是說如果一個對象在鼠標點擊的那個小視區(qū)里出現(xiàn),那么它的名字就會被壓入堆棧中,并替換掉堆棧頂部的元素,也就是說當我們用鼠標確定了重繪的范圍后,如果一個對象需要重繪那它將把自己的名稱壓棧并產(chǎn)生一個命中記錄,之后選擇緩沖區(qū)將從堆棧中取走這個名字存放。我們先來看一下如何使用名稱堆棧。
先定義物體的名稱:
#define object1 1
…………………
#define objectN N
名稱堆棧初始化:
glInitNames();
glPushName(0);9//將0壓入堆棧,這樣保證堆棧中至少有一個元素。
繪制物體時候壓棧如舉例壓入object1:
glLoadName(object1);
Drawobject1//繪制函數(shù)
在這里還有一個分層選擇的方法,打個比方即你希望選擇一個小孩,并希望同時顯示選中了其的父母,我們假設(shè)兒子為sun 母親為mom,代碼如下:
glLoadName(mom);
Drawmom//繪制mom
…...
glLoadName(sun);
Drawsun//繪制sun;
glPopName()//彈出棧底的mom
這樣當你選中sun時候 mom也會被選中;
4 選擇緩沖區(qū)
在渲染過程中,選擇緩沖區(qū)由點擊記錄所填充,當一個圖元在渲染之后位于可視區(qū)域內(nèi),它就產(chǎn)生一條命中記錄。緩沖區(qū)是一個無符號整數(shù)數(shù)組,每條點擊記錄在數(shù)組中至少占據(jù)4個元素。具體的數(shù)據(jù)結(jié)構(gòu)如下:
選擇緩沖區(qū) [0]-----當前名稱堆棧中名字的數(shù)量
[1]-----點擊記錄可視區(qū)域所包含頂點最小Z值
[2]-----…………………………………最大Z值
[3]-----名稱堆棧底部
[4]-----如果名稱堆棧底部還有名字則這里是新的一個記錄的開始。
……
當我們把渲染模式從GL_SELECT切回到GL_RENDER時候,我們就可以得到緩沖區(qū)中點擊記錄的數(shù)量了。具體代碼如下:
Static GLuint selectBUff(BUFFER_LENGTH)//選擇緩沖區(qū)的空間大小
glSelectBuffer(BUFFER_LENGTH,selectBuff)//設(shè)置選擇緩沖區(qū)
通過緩沖區(qū)我們可以在最后得到能夠在我們設(shè)置的小可視區(qū)域中出現(xiàn)的對象,當然如果對象是重疊的,那么我們可以根據(jù)不同對象到觀察點的距離(即Z值)來判斷,我們一般都選擇離我們近的物體。
5 結(jié)論
這種通過使用OpenGL的選擇機制的物體拾取方法雖然要付出重新繪制小區(qū)域里的對象的額外代價,但相比較而言一些使用選擇線,并通過模型與線的碰撞機制的方法而言,選擇機制的這種方法不需要太多的圖形學(xué)公式,對于導(dǎo)入的外來3D圖形數(shù)據(jù)操作更加簡便,并且由于設(shè)置了小視區(qū)它所要比較的對象就減小了很多,提高了判別的效
參考文獻:
[1] 孫家廣,胡事民.計算機圖形學(xué)基礎(chǔ)教程[M].北京:清華大學(xué)出版社,2005.
[2] 王汝傳.計算機圖形技術(shù)原理及應(yīng)用[M].北京:人民郵電出版社,1998.
[3] Angel E. OpenGL編程基礎(chǔ)[M].3版.北京:清華大學(xué)出版社,2008.