本篇緊隨上篇,,探討一下OpenGL內(nèi)置的拾取機制,給出一個完整的拾取部分實現(xiàn)代碼,,請有心人批評指正,。前篇是:[亂彈OpenGL選擇-拾取機制(上)] —— ZwqXin.com 前篇主要講了名字棧機制,這是拾取機制中對拾取結(jié)果進行識別的機制,,是后處理的前提,。而本篇則從拾取機制的“拾取”部分,揭露一下我所理解的OpenGL內(nèi)置拾取機制,。 本文來源于 ZwqXin (http://www./), 轉(zhuǎn)載請注明 3. 真正的拾取機制 還記得前篇提及的一般游戲引擎中所使用的“射線檢測”機制嗎,?這確實是實現(xiàn)成本非常小的技術(shù),而且無關(guān)OPENGL或D3D管道,。但是依靠人工的進行點面檢測將涉及兩個難題:屏幕場景中很多物件怎么辦,?要把射線與所有這些物件都做一次叉叉檢測么;物件由很大量三角形面組成怎么辦,?連渲染模型本身都夠嗆了,,何況還得在點選時把射線方程與各個面糾纏一次直到檢測到或全部檢測完發(fā)現(xiàn)做了無用功為止……(所以現(xiàn)在都改射線-包圍盒檢測了~但前一問題尤在。) OpenGL內(nèi)置拾取機制利用的是固定渲染管道的東西——視截體裁減,。留意過3D圖形學(xué)的朋友應(yīng)該記得流水線是怎么“砍掉”場景中不在視野內(nèi)的部分的,,這里我就不探討其數(shù)學(xué)原理了——其實也就相交檢測之類,不過交給更可靠的流水線去快速實現(xiàn)而已,。而這里,,OpenGL也是應(yīng)用的流水線的這個能力,只不過用來“砍”場景的平頭視錐題是個“萎縮版”的而已,。
這是去年9月做的一個小DEMO,,場景中有三個模型(物件),能進行鼠標(biāo)拾取操作從而對模型進行平面移動,。如你所見,,整個渲染窗口是一個投影平面,也是視錐的近截面,。流水線把視錐之外的都喀嚓掉了,,譬如船模的船頭部分,實際上已經(jīng)被切斷,,然后在切斷處形成暫時的流水線頂點,。那么,我們看鼠標(biāo)吧,。它正在拾取其中一個白色模型,,在點擊鼠標(biāo)進行拾取的剎那,,在渲染窗口屏幕上形成了一個看不見的小矩形(我在畫圖工具里用個黃色矩形形象化表示~),這個小矩形就是在拾取時(renderMode == GL_SELECT),,OPENGL世界中唯一能顯示出來的區(qū)域——沒錯,,它就是拾取剎那產(chǎn)生的新平頭視截體的近平面!這個新視截體在下圖草草表現(xiàn)出來: 注意紅色橢圓圈著的就是新視截體的近,、遠截面,,與之間的四條連線構(gòu)成一個小型平頭錐體,是不是形同于意同于“目光”呢,?其實也算一種射線吧哈哈,。(注意,在屏幕上的小矩形實際上長寬不過10個像素而已,,形象化出來的框不代表真實比例的大小,。)在拾取階段,,小錐體外的東西都被裁掉了,。OPENGL世界里只剩下黃色框內(nèi)的東西會被渲染……該部分屬于白色模型——對,它被選上了,。 怎么生成這個小視截體呢,?你已經(jīng)猜到了。投影矩陣,! 既然原來gluPerspective設(shè)定的投影矩陣來把視圖空間下的坐標(biāo)轉(zhuǎn)為投影空間下的坐標(biāo)表示,,同時形成一個視截體描述[亂彈OpenGL中的矩陣變換(上)] ,那么肯定也有另一個投影矩陣能做類似的事情——它在原來投影矩陣功效的基礎(chǔ)上,,再對三維空間進行裁切,。輸入這個矩陣的參數(shù)肯定包括鼠標(biāo)的屏幕坐標(biāo),目標(biāo)屏幕矩形(上圖的黃色框)的長寬,,可能還應(yīng)該把當(dāng)前的VIEWPORT給它來準(zhǔn)確裁切(VIEWPORT一般在投影之后才給出,,所以它沒可能預(yù)知道)。OpenGL有一個神秘的矩陣設(shè)置函數(shù)能幫我們完成輸入到輸出的轉(zhuǎn)換: void gluPickMatrix(GLdouble x, GLdouble y, GLdouble witdth, GLdouble height, GLint viewport[4]); 參數(shù)分別就是OPENGL下鼠標(biāo)的屏幕窗口坐標(biāo)(OPENGL以左下角為原點,,WINDOWS下以左上角為原點),,目標(biāo)屏幕矩形的長寬和VIEWPORT(包含起點的屏幕窗口坐標(biāo),長和寬,,當(dāng)前的VIEWPORT可以由glGetIntegerv(GL_VIEWPORT, viewportbuffer)獲得),。目標(biāo)屏幕矩形的長寬也叫容差,代表選取時可能的誤差范圍,,可以料想,,容差越大,誤選取的可能性大些,;容差越小,,選取所需越靈敏,,越難一擊選中物件。而因為我們在WINDOWS下得到的鼠標(biāo)位置都是WINDOWS標(biāo)準(zhǔn)的,,因此y值要經(jīng)過一個小轉(zhuǎn)換到OPENGL標(biāo)準(zhǔn)(viewport[3] - mouse.y,,前者為窗口屏幕的寬)??创a:
這是鼠標(biāo)左鍵單擊時消息處理代碼的前半部分,。前面四行本可扔進構(gòu)造函數(shù)和/或初始化函數(shù),為了功能一致性就留在這里了,。glRenderMode決定了之前前篇(亂彈OpenGL選擇-拾取機制Ⅰ] )所說的兩種渲染模式的選擇(GL_SELECT和GL_RENDER),。注意,OpenGL拾取機制限定前篇所說的名字棧機制只有在glRenderMode幫助應(yīng)用程序設(shè)定GL_SELECT后才會生效,,所以關(guān)于Name的函數(shù)應(yīng)該在GL_SELECT標(biāo)志設(shè)定后才弄,。 glMatrixMode(GL_PROJECTION)轉(zhuǎn)入投影矩陣的設(shè)置。對了,,首先在改變OPENGL內(nèi)部的矩陣形式前,,得把當(dāng)前的非拾取用的矩陣變換設(shè)置存儲下來[glPushMatrix()],等應(yīng)用程序返回GL_RENDER正常模式的時候再彈回出來[glPopMatrix()]作為正常渲染使用,。而在這PUSH和POP之間,,就是OpenGL拾取-選擇邏輯了。 重置單位陣后,,接連的gluPickMatrix,,gluPerspective,然后設(shè)定GL_MODELVIEW標(biāo)志而對模型視圖矩陣進行設(shè)定——相機的視圖變換和RenderObjects內(nèi),,每物件的渲染函數(shù)內(nèi)可能有的模型變換,。RenderObjects(GL_SELECT)類似于上篇中的那段代碼。最終問題來了:在代碼邏輯上,,名字棧是怎么通知應(yīng)用程序——該物件被選擇了的呢,?不妨設(shè)我們選中的是Object1。另外,,不妨展開代碼看清楚點:
注意,無論是RenderObject1()還是RenderObject2(),,這類函數(shù)的核心特性就是通過頂點畫三角形,。這些頂點是按順序一批一批地進入渲染管道的,通過T&L,,進行頂點處理……也就是說,,當(dāng)?shù)?行(//2)產(chǎn)生的頂點進入流水線時,第5行產(chǎn)生的頂點還跟在后頭呢,一般不會干涉,。 所以,,情況是Object1和它的名字OBJECT1先進入流水線處理,進行坐標(biāo)變換—— 它們(頂點)先被左乘一個模型矩陣(可能是單位陣也可能是其他,,看你RenderObject1里面有無移動旋轉(zhuǎn)縮放類操作了),,然后被左乘一個視圖矩陣(由相機camera.Look(),實質(zhì)即gluLookAt設(shè)定和操作),,然后被左乘投影矩陣(由gluPerspective設(shè)定和操作),,然后又是被一個投影矩陣gluPickMatrix(又可直接叫拾取矩陣)左乘,而形成(被裁切而剩下)之前所述的小黃框內(nèi)部分的頂點(或者加上暫時流水線頂點),。因此應(yīng)用程序找到對應(yīng)的名字棧中的名字OBJECT1發(fā)送到HIT Record中,,存儲在SelectBuffer中。 好吧,,Object2的頂點來了,,但是在gluPickMatrix里被冷落了(根據(jù)鼠標(biāo)坐標(biāo)判斷出裁減視錐里沒它嘛),同時不會在它那里形成框框,,對應(yīng)的名字沒被“記住”,,應(yīng)用程序于是不發(fā)送HIT Record了……其他的,類似吧,。(提示:對該代碼邏輯過程混亂的朋友可以在看完本文后順便補補OpenGL矩陣知識哦,,當(dāng)然,也是聽我亂彈的- -[亂彈OpenGL中的矩陣變換(下)] ) 于是你明白了吧,,即使名字棧被不斷壓入彈出,它起指涉作用的也就在處理其對應(yīng)的Object的頂點的剎那而已,,之后它就該為下一位到達流水線的Object負(fù)責(zé)了,。 4. 拾取的后處理 好,最耐人尋味的已經(jīng)講完了,。說一下處理完拾取邏輯后的事情吧:hits = glRenderMode (GL_RENDER)一句可以返回HIT數(shù),,即當(dāng)次拾取所擊中的物件數(shù)(可能是OPENGL內(nèi)部對發(fā)送HIT Record的次數(shù)進行記數(shù)了吧)。
加了//E//的語句其實可要可不要,。因為有時候我們會希望每次選擇邏輯結(jié)束后馬上重繪整個場景,,而不是等到下個渲染循環(huán)(注意,鼠標(biāo)操作是一種消息中斷,,期間渲染主循環(huán)被暫停了哦),。這樣就得額外實施一次主循環(huán)里干的事情了,如果場景大,,一次渲染的過程長,,那么為了保證過度,這樣的強制重繪是很有必要的。這時//E//語句是要的 拾取的后處理,,取決于你具體想通過拾取來干嘛了,。畢竟OpenGL只負(fù)責(zé)給你做拾取檢測,提供被拾取物件的個數(shù),,名字,,和最近最遠的Z-BUFFER增大值(見上篇中HIT Record結(jié)構(gòu))。譬如這里就返回離眼睛最近的物件的名字(ID)吧~你看,,固定每個記錄的項數(shù)大小為4的HIT Record事后取數(shù)據(jù)起來就是方便就是好啊,。 最后順帶一提,別以為選擇模式下render的物體會有可能在屏幕上靈光一閃哦,!事實上我是聽說只要HIT Record一被發(fā)送(或者沒得發(fā)),,流水線就都會舍棄掉眼前這些頂點不再往下處理的……啊,我只是聽說,,聽說,。 本兩篇長文的巨大輔助參考:Picking - LightHouse 本文圖片的DEMO(2008.9.byZwqXin)可在我的google code上下載。 本文來源于 ZwqXin (http://www./), 轉(zhuǎn)載請注明 |
|