1. 程式人生 > >OSG場景圖狀態和狀態集第1部分

OSG場景圖狀態和狀態集第1部分

from:http://www.bricoworks.com/articles/stateset/stateset.html

## 你一直想直到的關於StateSet的一切(但不敢來問
作者: Tim Moore
翻譯:longlongwaytogo 
一些名詞翻譯: Open Scene Graph: OSG scene graph :場景樹 

對於OSG的優化策略,最好的辦法就是降低OpenGL切換狀態的次數,OpenGL被定義為一個複雜的狀態機,其狀態包含了能夠影響多邊形被繪製的所有內容。構成狀態的值,從很容易使用on/off進行切換的狀態值(如,啟用深度測試(depth test)或者光照(lighting)),到涉及佔用圖形處理器數千或數百萬位元組儲存單元且具有複雜結構型別的值(如,紋理和著色器程式).即使OpenGL驅動程式可能不需要將所有的的資料重新傳輸到顯示卡來改變狀態,但它依然很昂貴。通常,當狀態改變時,GPU上的所有渲染必須停止。更改著色器或者紋理的設定,可能需要更長的時間。

三維場景可以包含數千個不同的物件,但其中有很大一部分共享相同的圖形狀態。如果可以將相同狀態的物件收集到一起渲染,則可避免多餘的狀態切換,並且狀態更改的次數將被降低到最小。這些物件可以被程式進行有序的組織,但在類似OSG這樣的圖形系統中,物件所分佈的邏輯和空間層次,不一定與物件使用的圖形狀態相關。

在第一部分,我們將學習OSG如何體現OpenGL的圖形狀態。在第二部分,探討OSG優化渲染的一些方法,以最大限度地減少狀態變化的次數。

Drawable 和 StateSet

在OSG中,幾何圖形被儲存在場景圖葉子節點的Drawable物件中。為了渲染,Drawable提供了一個簡單的介面:draw()函式。當draw()函式被呼叫時,必須正確的設定OpenGL模型檢視矩陣和所有其它狀態。在每一幀繪製中,OSG都會遍歷場景樹。在一個被叫做"Culling"的處理過程中,確定了哪些Drawable物件是可見的,記錄模型檢視矩陣和圖形狀態,並將它們儲存到叫做:RenderBins的列表中。有些RenderBins的內容將通過視點和模型的距離進行有序儲存,但大多數的Drawbles會被分組到狀態樹中。當執行"draw"操作時,所有的Drawables將會按照順序渲染出來。

OSG如何表示狀態樹並識別物件具有相同的狀態呢?如果你已經編寫過OSG程式,就會知道,你沒有為場景樹中的一個物件指整個圖形狀圖;相反,你建立了一個名為StatSet的物件,它指定一小部分的OpenGL狀態,並將其附加到場景樹節點上。圖1 表示了場景樹的結構:

 
圖1.簡單的場景樹

StateSet可能是指定的紋理,燈光的材質資訊,用於shander程式的Uniform引數,以及一些OpenGL模式(如:啟用混合和alpha測試)。StateSet可以包含兩種型別的值,模式型別和StateAtrribute物件的指標。模式型別是一個常整型的數值,在傳遞給glEnable()/glDisable()函式時控制OpenGL功能。StateAtrribute是OpenGL狀態中的一個或一組值。屬性直接對應OpenGL狀態機中的引數,但某些OpenGL引數可能分組為單個StateAttribute屬性,並且StateAttribute可能會有與任何實際OpenGL引數不相對的成員。這方面的例子是MaterialState屬性,它將環境光(ambient),漫反射光(diffuse),鏡面反射光(specular),以及自發光材質(emission material)屬性組合到一個物件中,即使每一個引數在OpenGL中都可以獨立修改。Material通常擁有setAphla()之類的函式來統一修改材質引數的alpha值;這在使用原始OpenGL時必須手工完成。StateSet內部表現是十分動態的,從StateSet的角度來看,它沒有任何預定義的模式,只有系統已知的普通狀態屬性。StateSet根據紋理單元編號儲存紋理模式和屬性。

 圖 2 osg::stateSet

場景樹中的所有節點,從場景樹根節點,到葉子節點的Drawables,都可能關聯了StateSet。當一個Drawable真正被繪製時,OpenGL的狀態(它所具有的預設狀態,以及它繼承祖先的模式和屬性)才會生效。樹中較低的屬性優先於樹中StateSet較高物件設定的屬性,儘管有一個系統可以使屬性可以覆蓋圖中較低的屬性的值,相反,屬性可以保護自己免受被推翻。

osg::State

OSG通過State物件管理整個OpenGL狀態。除非需要自定義Drawable類,否則通常不會遇到此物件的drawImplementation()函式。drawImplementation()函式,是OSG在渲染場景時廣泛使用的。State使用與StateSet相同的模式和狀態屬性,在OpenGL中設圖形狀態。當前狀態是通過StateSet物件的設計來最小化將要進行的低級別OpenGL呼叫的數量來改變的。需要注意的是,狀態屬性相等性僅基於StateAttribute 物件的標識或指標值。狀態不會“檢視”狀態屬性或呼叫它們的compare()方法。

StateSets and the StateSet Stack

如前所述,一個StateSet,是一個小的模式和屬性的集合,它將被應用到當前的圖形狀態中,State維護著一個Stateset物件的棧(Stack),它包含了修改圖形狀態的公有介面:pushStateSet(),popStateSet(),apply()方法。本質上講,State維護著包含模式肯屬性型別的Stacks。當呼叫pushStateSet()或popStateSet(),StateSet被遍歷同,同時獨立的模式和屬性棧被push或popped。關於pushStateSet()和popStateSet(),重要的一點是,它們並沒有真正修改OpenGL的狀態。你必須呼叫apply()來強制OpenGL狀態與所有模式和屬性棧的當前頂一致。隨著個人模式和屬性的推出和彈出, State物件會做一些記錄,以確定堆疊頂部的值是否真的發生了變化。apply()方法只更新實際更改的OpenGL模式和屬性。這種惰性更新允許StateSet在實際改變OpenGL狀態之前推動和彈出幾個物件,這很可能與原始狀態相似或相同。您可能會想象在StateSet遍歷場景圖時發生堆疊修改。OSG渲染遍歷實際上並沒有直接做到這一點,但它確實以類似堆疊的方式執行類似的一系列“移動”,推送和彈出StateSet物件。  圖 3 osg::State

處理StateSet可能在OSG程式碼中看到的物件有一些便利方法。State::apply(const StateSet*)推送一個StateSet,呼叫apply(),然後StateSet一次性彈出全部。State::insertStateSet()彈出一些StateSets,插入一個新的StateSet,然後推回舊的。這個和其他類似的方法用於渲染遍歷中的各種效果。也有applyMode()和applyAttribute() 該做出改變並標記引數的堆疊改變,即使沒有被壓入或彈出方法。這將強制該引數在下次呼叫時正確更新apply。

在自定義Drawable中使用狀態

如前所述,通常您不會在OSG程式碼中使用State物件,但是如果您定義了自己的Drawable類,你需要重寫drawImplementation()方法。有關使用State的示例,請檢視OSG中osgText::Text的原始碼,這會使許多OpenGL呼叫其繪製方法。除了管理儲存在StateSet物件中的圖形狀態之外 ,State還控制較低級別的OpenGL狀態,例如使用頂點屬性陣列。State也有用於呼叫可能作為擴充套件實現的OpenGL函式的成員函式。

您應該在您自己的程式碼中使用狀態管理功能,而不是相同功能的較低級別的OpenGL程式碼; 否則,OpenGL狀態將與State物件模型不一致,導致視覺上錯誤甚至崩潰。另一種方法是使用glPushAttrib()和glPushClientAttrib() 函式將所有OpenGL狀態儲存在函式中,執行圖形程式碼並使用glPopClientAttrib() 和恢復狀態glPopAttrib()。這很慢,但如果包裝遺留程式碼可能沒有替代方案。

下一部分

第2部分描述了這個StateGraph類。該類被用於當OSG使用StateSet渲染場景並實際推送和彈出物件時。