“材質(material)”,材質是一個很基本的術語,表示你的物體對光的反射方式。但是對它的實現並不像其解釋這樣簡單,我們會用這一章節進行詳細地介紹Ogre中相關的技術。
我們剛才提及過,材質定義了物體對光線反射的處理方法。這裡暗示了材質的表現與光源的類型相關:聚光(Spotlights)、點光源(point lights)以及有向光(directional lights)對材質的表現有著完全不同的影響。簡單來講,它們都是對物理世界真實光源類型的類比,所以材質對它們的反射也遵守著其在物理世界中的規則。
注意:雖然在Ogre裡面,材質定義了物體如何反射光線的方法,但實際上並沒有真的把光反射到附近的物體上。換句話說,情境中物體的輻射和反射屬性並沒有給情境增加任何光照。這是因為Ogre(以及目前絕大多數的即時渲染演算法中)使用了局部輻射的演算法來處理光照效果。
為了協助大家更好的理解局部輻射,在這裡講解一下與其相對的全域輻射演算法的概念。所謂全域輻射光照模型,指的是通過光線跟蹤等技術,計算情境中所有包括反射、輻射等所有光線的照明結果的演算法模型。雖然全域光照模型可以產生和真實照片媲美的渲染結果,但因為其演算法相對比較消耗時間,所以目前仍然無法應用於即時渲染領域(即時運算的3D應用程式,至少要每秒產生30幀畫面)。所以全域光照模型現在只活躍於用來產生照片品質動畫的“離線”渲染工具中,比如Pixar’s RenderMan(http://www.pixar.com),Mental ray(http://www.mentalimages.com,包括3D Studio Max 等商業軟體所使用的工具),POV-Ray(http://www.povray.org,一個開源的光線跟蹤渲染工具)以及Aqsis(一個免費開源的符合RenderMan標準的離線渲染器)中。
材質的101[1]報告
Ogre是一個硬體加速的事實3D渲染引擎。換句話說,它儘可能的使用硬體(GPU)來處理更多的渲染過程。而對於硬體來說,不論多麼複雜的材質,最終渲染到螢幕的結果只有一個類型,那就是顏色。而Ogre作為一個典型的渲染引擎,把物體上的顏色分解成四種不同的光照作用:環境反射(Ambient)、漫反射(Diffuse)、放射(Emissive)以及鏡面反射(Specular)。
物體著色的基礎
在前一個章節中,我們介紹了Ogre中光源的類型。而在Ogre所採用的局部輻射度模型的著色演算法中,對物體顏色產生影響的除了光源本身的屬性之外,還有觀察的角度以及物體本身的顏色(在實際處理中等同於的是攝像機的角度和物體所用材質屬性)。
下面列舉了所有四種影響最終顏色的材質屬性,以及它們的含義。
·環境反射:近似的類比了情境中的全域輻射;也就是用來近似類比所有光在情境中不斷散射的結果。材質中有相應的屬性來代表這種環境反射顏色。
·漫反射:這種顏色是接收到直接從光源發射的光之後產生的,“漫反射”這個詞來源於現實世界,描述光被物體反射到各個方向的效果(換句話說就是散射)。
·放射:指的是自發光物體所擁有的顏色。這裡有一個有趣的話題,因為在局部輻射光照模型中,放射光只能照亮自己卻不能對周圍任何物體產生影響,有時候這種效果卻可以讓人覺得極其虛幻(設想一下,在你的房間裡有一個灼熱的物體,卻不會照亮周圍任何東西,你就會瞭解這種感覺了)。
·鏡面反射:描述了物體對被光照後的“高光”效果。設想一下假如你有一個紅色皮球,而且擦得光亮。現在把它放在一個高瓦度的燈泡下面,你就會在上面看到一個被稱為鏡面高光的亮斑。這是因為光線被光滑的表面直接反射到你的眼睛中的緣故。
在這裡鏡面反射的高光的顏色是可以被使用者定義的。這是因為在現實世界中,和環境光線、漫反射以及輻射光不同,鏡面反射高光的顏色除了材質自身和光的顏色之外也受到周圍其他的一些因素的影響。所以這裡允許通過手動調整,使用較低的代價來達到對真實世界的簡單類比。另外我們也可以手動調整鏡面高光的反射能量,簡單的說就是可以用參數來控制紅色皮球上“光斑”的大小,同時調節物體發亮的程度:能量大的時候,光斑變得又小又亮。
圖6-1:在3D Studio Max中對反射高光的設定對話方塊以及相應的材質結果
圖6-1展示了在3D Studio Max軟體中材質編輯中的鏡面高光的屬性調整對話方塊,同時也有調整之後的渲染結果。其中“高光層級”參數等同於Ogre中“specular power(鏡面能量)”的概念。而另外的“光澤度”等同於Ogre中“shininess specular(鏡面亮度)”參數。鏡面能量決定能反射回來光的總量(表現為曲線的高度),另外的鏡面亮度定義了高光如何被“展開”(越高的數值曲線越光滑)。圖6-1(a)描述了一個膠皮球被一個光源照亮之後鏡面高光的效果。圖6-1(b)擁有一樣鏡面能量,但是調整鏡面亮度到一個小一點的值,最後在圖6-1(c)中和圖6-1(a)同樣鏡面亮度的前提下,減少了鏡面能量的數值。
這些基礎的著色參數在Ogre中是總是可用的,不論你的硬體是否支援幾何轉換和光照處理(T&L),這些參數都是硬體加速所需要最基本的資料。如果在材質中提供了多種技術方案(Technique在我們後面的“技術和方案”章節中會具體介紹)以供選擇的時候,至少對於其中使用固定功能著色(fixed-function shading)管線的來說這些參數仍然是必要的。
紋理貼圖(Texture Mapping)
大部分的圖形硬體都會至少支援一兩個紋理處理單元(Texture units)。這些單元允許你的程式把紋理(Texture)通過一個獨立的座標系映射到一個物體上。這裡的紋理不僅僅限於“2D圖片”的概念,也可以有MIP-map 細節度的資料、立方體(Cube)紋理或者體積紋理(Volume)等等。在構造紋理的時候,可以使用的2D圖片包括GIF,PNG或者網頁上面使用的JPG圖片格式,這些圖片通常可以從螢幕或者數位相機以及拍照手機中得到。簡單的說,使用了紋理貼圖,就等於用一個2D圖片包裹了3D物體,進而和物體表面的漫反射項進行混合(或者替代)來產生最終的顏色效果。
提示:紋理貼圖的製造通常可以被分成兩個步驟。首先是藝術部分:美工使用3D模型工具來調整的紋理座標(一般被稱為UV座標系)到模型的頂點上,進而產生一系列UV座標。然後是程式部分:這些UV座標被傳遞到圖形硬體上把模型頂點和紋理座標關聯起來。
前面提到的一些技術,除了圖形硬體上的幾何轉換和光照處理(T&L),我們還提到了固定功能管道(fixed-function pipeline),這裡面所謂的“固定(fixed)”的意思就是不可以編程的;你告知硬體物體的頂點和圖片上像素的對應關係,然後就交給硬體就進行下面的工作:把物體的頂點資訊映射到世界空間中去,然後結合紋理光柵化頂點的資料,最後繪製到螢幕上。
可程式化著色技術(Programmable Shading)
可程式化圖形管線(programmable graphics pipeline)的出現,是到目前為止即時渲染3D圖形加速技術中最大的飛躍。這意味著,簡單的對每一頂點(頂點著色)和對每一像素(像素著色或者稱為片斷著色)的運算可以放到程式片段中來進行,這些程式既可以使用進階語言也可以通過GPU的組合語言來實現,這樣就能用我們所寫的程式來取代之前固定功能管道所做的事情。注意,我在這裡用到了“能取代”這個詞,而不是“必須取代”。你仍然可以堅持使用固定功能管道,即便是在今天GEFORCE 7950 GTX2(在我寫這文章時候市場上最強的圖形卡)上面。如果決定了採用可程式化著色語言,那麼你就可以在渲染通路重處理任何頂點以及它們之間的片斷資料。紋理在這個時候成為了程式的輸入資料,需要說明一下,這裡的輸入資料可以是任何你需要的東西。因為如果你足夠聰明,GPU對你而言只不過是一個高速的並行計算引擎而以,你甚至可以用它完成你任何希望的計算工作(比如人工智慧的計算,在遊戲編程精粹4中有相應的文章)。
Ogre支援所有風格的GPU著色語言,其中包括低級的彙編和諸如Cg,GLSL和HLSL這種進階語言。然而“支援”這個詞同時意味著Ogre並沒有為你做什麼具體的事情;你仍然要靠自己雙手去寫這些著色程式,不過還好Ogre提供了一個靈活並且可變的架構來協助你在GPU上進行著色工作。你會在之後的“材質和可程式化管線”章節中瞭解這個架構的具體資訊。
材質和程式設計
在上一個章中,我們介紹了Ogre對物體中的定義的實體和幾何模型之間的關係。同時我們也知道需要通過材質來讓它們看起來更像現實世界中的樣子。在更進一步瞭解Ogre材質機理之前,我們還要瞭解一些更進階的編程概念。
批次(Batching)
Ogre最基本的渲染單元被稱為可渲染對象(Renderable),它們通過不同的渲染狀態被分類傳遞到渲染隊列中去。相同渲染狀態的可渲染對象只需要一次的繪圖操作,也就是說它們在一個繪圖批次中完成了渲染過程。當渲染到不同的材質或者模型的時候都會導致渲染狀態的改變,而每次改變都會引起新的繪圖操作(新的繪圖批次)。繪圖操作都是一個耗費時間的過程,如同畫家在繪製新作品前要清洗調色盤一樣,圖形硬體也需要進行頂點和紋理的更新操作。為了渲染效率的提高,程式設計者要盡量減少渲染狀態的改變,換句話說也就是減少繪圖批次的數量。
注意:通常而言,在你設定了渲染情境之後,所有的紋理都會被一次性的載入顯存,除非你的紋理佔用了過多的顯存,否則不會在每一幀都傳遞紋理資料到顯存中去。因此,你需要盡量的謹慎的處理紋理的使用:一旦確定紋理已經在顯存,在它失效之前就不要再嘗試去重載它。
Ogre引擎在力所能及的範圍內盡量把相同渲染狀態的物體一起渲染,進而減少渲染狀態改變。然而即便如此,Ogre仍然尊重使用者佈建的Renderable整體(也就是說Renderable是處理批次的最小原子結構),並不進行拆分。舉例來說,如果你有一個擁有20個使用相同材質的片段組成的模型,你應該把它們合并成一個Renderable對象而不分割成二十個不同的小對象批次:這樣就可以省略掉系統檢查這些片斷是否是一個相同的渲染狀態的步驟,從而可以一次處理完畢。在渲染的每一幀中,都會有一個明確的可以處理批次數量的上限,但是對這方面的討論超出了本書所介紹的範圍。如果你希望得到更多的資訊,可以去查閱NVIDIA的工程師Matthias Wloka在CGD(遊戲開發人員大會)上發表的相關論文[1]。在這本書的後面,你會學到如何在代碼中控制渲染狀態的改變。為了達到以上的目的,你可能會需要使用3D模型工具來修改你所用的模型。
材質複製
在Ogre中材質是被所有引用所共用的。當你從材質管理器重得到了一個指向材質的指標的時候,其他的使用相同材質對象也在處理一樣的指標。這意味著如果你希望改變某個物體上面材質的任何屬性的話,你都要把這個材質為它單獨複製一份,否則這個改變就會同時影響到所有使用相同材質的物體。在渲染通道中都需要再產生一個完全不同的材質。不過這裡也有一種可以替代的方法存在,把材質作為顏色資料,通過改變可程式化GPU著色的參數來實現不同材質的效果。這樣就可以避免產生一個新的批次。
GPU著色
GPU(圖形處理器)在設計之初就有著明確的目標,提供高效能的並行向量計算以供針對3D圖形處理使用。而具體如何使用和怎麼使用的權力都交給了使用者。GPU的存在是為了減少CPU(中央處理器)的工作壓力,使用者負責分配具體的計算工作給它們。需要指出的是,GPU在其專署領域的運算速率大大的高於CPU,而且似乎在未來一個時期內這個差距仍然會增加。因此,你需要有計劃的開始在你設計中的管線加入可程式化著色技術,尤其是當你將要計劃使用即時陰影技術或者更進階的渲染到紋理(Render-to-Texture)技術的時候更需要這方面的支援。
技術(Technique) 和方案(Scheme)
可以說技術和方案是Ogre引擎材質中最強大和活躍的兩個特性。在Ogre中,每個材質中都至少包含了一種“技術”實現,這種實現允許你對不同效能顯示卡和硬體平台使用不同的材質屬性群組合。簡而言之,技術就是“一種對物體的渲染方法”。通常來說對具體適用哪個渲染技術是由Ogre引擎自動甄選出來的(根據硬體效能、方案以及細節等級等資訊),但是如果你希望的話也可以在代碼中完全控制這個過程。
“方案”是Ogre使用的進階話題之一,事實上它是一個渲染技術集合的描述。舉例來說,你可能有三個不同的技術方案:高品質,中等品質,低品質。在遊戲啟動並執行時候,允許使用者通過選擇這三個方案中的任意一個來確定在遊戲中具體使用的渲染技術集合。
Ogre在渲染的時候,會有一個自動甄選所需渲染技術的固定流程:首先過濾掉那些不在當前方案中的所有技術(預設情況下當前方案是“Default”);然後選擇適配當前細節等級(LoD)的那些;最後在剩下的當中挑選當前硬體環境中可以執行的最優技術(最好效果的)。當Ogre找不到任何一個可以使用的渲染技術時,就會把物體渲染成單調的白色表面。換句話說,如果你看到了一片雪白,就要檢討一下你對材質的配置了。另外在預設的情況下,材質中所有技術的細節等級(LoD)都被設定成為0,也就是最高的細節等級。換句話說,Ogre總是在儘可能的協助你選擇最優材質技術。
似乎技術和方案會帶來很多複雜的處理細節。但在實際的執行過程中,你只要在材質指令碼中提供了充足的內容,Ogre就會接替你來管理這些瑣碎的細節。當然如果你喜歡,也可以用程式碼完成指令碼所進行的工作。
材質細節等級(Material LoD)
電腦圖形學中的細節等級(Level of detail)這個術語經常是用來描述幾何體複雜度等級和攝像機距離的關係。同樣的,這種描述也可以類似的用在材質上。例如說,你可以通過指令碼把當前材質定義成多層紋理(用不同的方法來混合它們),並且同時擁有頂點和片斷兩種GPU著色程式。就算在一個很近的距離,也能在你的模型上面表現出“無與倫比”的漂亮材質細節。然而,當這個模型在螢幕上縮小到12像素的時候(就是說距離變遠了),你認為這樣做還值得嗎?
答案明顯是不!解決這個問題的方法就是使用材質細節等級(LoD)管理。繼續我們上面的例子,好地解決方案是利用Ogre可以讓你在不同的細節等級中使用不同的技術實現,進而更有效利用當前GPU的資源。為了實現這個效果,你需要首先定義各個細節等級所對應的實際距離,然後把相應的技術索引到這些等級中去。這裡鼓勵你在每個細節等級中儘可能多的設定不同的技術實現,以供給不同的方案以及硬體選擇出最適用的實現。
材質的組成
在6-2中,展示了Ogre的材質之中各種組成成分之間的關係。一份完整的材質至少有一種技術實現,每種技術實現中至少要有一個渲染通路。在圖示中,我們看到材質中包含了N種的技術實現,而在真正的渲染時,只會有一種技術被啟用並進入渲染過程(選擇啟用技術的工作一般交給Ogre自動完成)。如果我們啟用了技術0,那麼技術0中所有通路都會被傳到圖形硬體中去依次渲染(換句話說,Ogre渲染了啟用技術中的所有通路)。
圖6-2:在Ogre中,材質,技術以及通路之間的關係
通路(Pass)
在Ogre中通路是最基本的渲染單位,同時也是可渲染對象(Renderable)用來標示自己渲染狀態的基本單元。每個可渲染對象都會有自己的材質,Ogre在材質中甄選出最適合當前應用的技術實現。然後把當前技術中所有的“通路”依次放入圖形硬體的渲染通路中。顧名思義,Ogre材質中的“通路”對應於圖形硬體中“渲染通路”的概念。也就是說當前技術中如果包含了3個通路,那麼在繪製是用這個材質的模型的時候,在每一幀就要進行3次渲染。
在實際的使用中,通路裡面還有“紋理單元(texture unit)”的定義,你可以在一個通路中定義任意數量的紋理單元,當然一個不用也是沒問題的。
紋理單元(Texture Unit)
在Ogre對材質的定義中,紋理單元的概念對應於圖形硬體中的紋理採樣(texture sampler)。為了運行Ogre程式,至少需要一個硬體紋理採樣支援。不過這並不是什麼大問題,因為現代的圖形硬體基本上都會有多個紋理採樣,因此我們可以在一次渲染通路的執行中,同時處理多個紋理單元。
顧名思義,紋理單元裡都會包含一張紋理。你可以直接用硬碟中的圖片檔案,也可以通過即時的渲染來得到,甚至可以通過一個視頻流來動態產生紋理圖案。在Ogre中並沒有對通路中紋理單元的數量進行限制,這是因為Ogre能根據圖形硬體能力動態拆分通路(這裡假設沒有使用硬體著色程式)。具體點說,如果你的圖形硬體只能同時處理4個紋理採樣,但是應用程式卻使用了一個6紋理單元的通路。這時候Ogre會自動的把這個6紋理通路拆分成兩個分別兩次進行渲染,不過雖然最後的渲染結果和預期的一樣,但是仍然是通過兩次渲染通路來實現的,對效率的影響不言自明。
最後需要注意的是,紋理在被真正的拋棄之前,都會一直存在顯存中,而並不是每幀從記憶體傳遞到顯存。但是如果紋理數量太多或者體積過大的時候,硬體無法同時處理所有紋理,效率會嚴重降低。
紋理壓縮
很多現代圖形硬體都會支援壓縮紋理(比如DXTC方案),不過Ogre只是簡單的載入紋理然後把它傳到圖形硬體上,並沒有執行紋理的任何壓縮過程。如果需要紋理壓縮的支援,就需要離線進行圖片的壓縮工作(多數情況是把圖片轉換成壓縮的DDS格式),然後把這些預壓縮的紋理交給程式使用。如果你的硬體不支援壓縮圖片格式,Ogre會協助你在程式啟動並執行時候進行解壓縮,然後再交給硬體使用。
視頻流
雖然Ogre並沒有在紋理單元中內建對視頻流的支援,不過因為紋理單元可以支援外部資料源的處理,所以通過這個這個簡單的機理可以協助實現對視頻流的支援。Ogre社區已經提供了一個針對Theora(http://www.theora.org)這個視頻流處理器為基礎的外掛程式。通過這個外掛程式你可以把任何你所喜歡的流加入到你的Ogre程式中,甚至可以直接從實況電視轉播中得到視頻源:任何你能想象得到的應用都能幫你實現。
實體(Entity)
實體是Ogre中比較神奇和複雜的概念之一。在每個實體中都包含著一些子實體(SubEntity)的實現,這些子實體是真正的可渲染對象,它們維護著具體的材質特性。而每個子實體又和一個子模型(SubMesh)對應(通常來源於3D模型工具建立的模型)。概括的來說,實體和子實體是物體渲染特性的入口,而模型(Mesh)與子模型是物體結構特性(幾何體資料)的入口。
材質的例子
為了便於大家更好的理解,我們會通過講解一些簡單的例子來繼續這個章節。這裡大多是從Ogre的示範程式中找到的例子,不過也有我寫的的幾個材質指令碼的最簡單實現。
材質與固定功能管道(Fixed-Function Pipeline)
代碼6-1展示了一個最簡單的材質指令碼,裡麵包含一個最簡單的技術實現,技術實現裡只有一個通路。如果你用它對你的物體進行渲染,會得到一個灰色的結果。
代碼6-1:宇宙中最簡單的Ogre材質
material VerySimple
{
technique
{
pass
{
diffuse 0.5 0.5 0.5
}
}
}
上面代碼6-1中定義的材質,並沒有什麼特殊漂亮的地方,但是對於我們用來介紹Ogre材質指令碼層次和結構來說已經足夠了。通過它你可以直觀地瞭解到指令碼內部的嵌套關係:材質包含了技術,而技術包含了通路。通路中定義了漫反射參數。
現在讓我們看一個更複雜一點的例子,在下面代碼6-2中,我們在材質中建立了兩個技術,而且提供了更多的固定功能著色(Fixed-function shading)的控制。
代碼 6-2:對代碼6-1的簡單擴充
material NotQuiteAsSimple
{
technique
{
pass
{
diffuse 0.5 0.5 0.5
ambient 0.1 0.2 0.3
specular 0.8 0.8 0.8 68
texture_unit
{
texture ReallyCool.jpg
colour_op modulate
}
}
}
technique
{
pass
{
diffuse 0.5 0.5 0.5
}
}
}
代碼6-2已經很接近在實際中使用的材質定義了。在其中第一個技術的通路裡面使用了texture_unit標記來定義所使用的紋理貼圖,這裡假設有一張叫做ReallyCool.jpg的圖片。在這個定義裡面有趣的部分是定義如何把紋理的像素點混合到已經存在的顏色上:colour_op modulate這段指令碼讓Ogre把紋理上面的顏色資料和當前像素上的顏色資料(之前顏色混合的結果)相乘。在我們的例子中,所謂的使用中色彩資料,其實指的就是程式中對局部光照模型混合的結果,具體點來說就是在這個相同的通路中對環境光線,漫反射以及鏡面反射採樣的計算結果。
代碼6-2也是我們第一次在材質指令碼中加入一個用來“墊底”的技術實現。如果程式不幸的運行在一個不能支援紋理資料的硬體環境上的時候,第二個備用的技術實現就會被系統拿出來工作,把你的模型渲染成單一的灰色,雖然不怎麼好看,但至少保證了程式安全運行。因為我們定義的兩個技術都被系統預設的設定成相同的“方案”(預設的“Default”方案)以及相同的LoD(細節等級:0),所以的這個“墊底”才能良好的被運行。
注意:在你造成上面的問題之前,可能還有更嚴重的問題會出現。如果你的硬體真的很“老”,以至於無法支援紋理貼圖屬性,這時候甚至基本的3D API都不能運行(不論是DirectX還是OpenGL),進而導致Ogre不能啟動。雖然Ogre是個強大的引擎,但是不能幫你升級硬體。
代碼6-3是一個真正在Ogre例子裡面使用的材質指令碼——Example.material。你在Ogre目錄下面的Samples/Media/materials/scripts 子目錄下面可以找到這個檔案。
代碼6-3:一個定義紋理貼圖的材質指令碼
material Examples/EnvMappedRustySteel
{
technique
{
pass
{
texture_unit Diffuse
{
texture RustySteel.jpg
}
texture_unit Environment
{
texture spheremap.png
colour_op_ex add src_texture src_current
colour_op_multipass_fallback one one
env_map spherical
}
}
}
}
上面代碼和之前最大的不同就是增加了材質的命名:Examples/EnvMappedRustySteel。雖然看起來似乎好像存在著一個目錄結構,但事實上並不真的存在任何層級關係,這樣命名只不過是一個為了更方便處理的習慣罷了:Ogre系統只是簡單的認為,在material標籤右面相同行的所有字母都是這個材質的名字。這個名字在整個程式中必是唯一的。因為在Ogre中對於材質的名稱並沒有一個類似“命名空間”的概念:就算在不同的資源載入的材質,它們的名字也是同時存在一個空間的。如果你真的希望可以用一樣的名字命名不同的材質,就最好遵從我們上面提到的習慣,用類似路徑名的方法命名材質,其中路徑就可以替代命名空間的實現。
Ogre會給所有材質之中的元素(包括技術,通路以及紋理單元)提供一個預設的名字。還是用我們之前的指令碼作為例子,在材質Examples/EnvMappedRustySteel中,裡面的唯一的技術實現被命名為“0”,而通路因為一樣的原因得到了“0”這個名字。兩個紋理單元分別被命名為“0”和“1”。這個名稱是和他在材質指令碼中的順序相匹配的。當你要通過“繼承”的方式擴充指令碼的時候(後面回講到具體細節),有必要給這些元素手動命名,以便實現擴充功能的作用。相對於材質本身,Ogre並沒有要求這些元素名稱具有全域唯一性(當然,在它們自己的容器中,還是需要有不同的名字來辨別的)。
在代碼6-3中,第二個紋理單元被指定成為一個球體環境貼圖(通過env_map標記來實現)。環境貼圖是一種用很低的代價實現的類比反射表面的效果,其中並沒有真的使用光線跟蹤演算法。再加上前面使用的“生鏽的鋼鐵”紋理,最後混合上環境貼圖的效果。最後能產生什麼樣子呢?那就要參考下面的幾幅和貼圖來瞭解了。
圖6-3:RustySteel.jpg圖片(生鏽的鋼鐵表面)
圖6-4:spheremap.png圖片以及球體上的環境貼圖
圖6-5:Ogre中Demo_EnvMapping示範程式最終渲染結果
材質的繼承(Material Inheritance)
Ogre提供了體面的紋理繼承機制,可以協助我們更簡單的改變紋理的某些部分。比如在前面我們定義了材質指令碼Examples/EnvMappedRustySteel,如果我們需要換成壓縮紋理。這時候既不用在原指令碼上修改,也不用重新寫另外一個。通過材質指令碼的繼承機制,我們可從原有的指令碼中派生出新的紋理出來。
不過在Ogre的代碼和文檔中把這種行為稱為材質拷貝(material copying),這種稱為揭示了“材質繼承”與物件導向中的繼承概念的區別。Ogre並沒有對材質指令碼實現物件導向的繼承關係;也就是說在代碼中Ogre系統名沒有真正的使用原是材質進行繼承,而只不過是簡單的對原始指令碼的屬性進行了拷貝,其結果是:改變父指令碼的屬性不會影響子指令碼。
雖然不怎麼地道,但是確實是如繼承一樣方便的方法。你可以建立一個新的材質,把已經存在的材質作為基礎,拷貝所有舊材質的屬性到新材質裡面,並對其中的屬性做輕微的調整。這樣做既可以減少工作量也可以讓指令碼看起來更清晰簡單。
命名的重要性
雖然大多數情況可以依賴系統對材質中成員的預設名稱。但如果你使用了繼承,並且準備在新的材質中增加技術,通道或者新的紋理單元。這時候就要考慮手動對程式進行命名了,這是因為需要明確的區分父材質和子材質(也就是原材質指令碼和目標材質指令碼)中各元素的名稱。如果你在之前的材質中使用了稱為“Primary”的技術,在擴充的拷貝材質中就不能增加一個被稱為“Primary”的技術,這是因為相同的名稱會覆蓋原材質的屬性。
紋理覆蓋(Texture Aliases)
在很多情況下,派生材質的目的可能只是需要改變已有指令碼中的紋理圖片。這時候可以通過簡單的紋理覆蓋機制來實現。下面的代碼片斷展示了如何通過紋理覆蓋機制把父材質中的圖片換成相應的壓縮格式。
material Examples/EnvMappedCompressedRustySteel : Examples/EnvMappedRustySteel
{
set_texture_alias Diffuse rustySteel.dds
set_texture_alias Environment aphereMap.dds
}
上面代碼實現了我們需要的功能,現在Examples/EnvMappedCompressedRustySteel的使用的紋理已經不同於Examples/EnvMappedRustySteel,其他屬性仍然繼承了下來。一目瞭然,這種方法比麻煩的拷貝粘貼方法要好上很多。
我們再來看一個稍微特別一點的材質實現,下面的材質指令碼是Ogre示範程式中真實使用的例子。
代碼6-4:在Sampls/Media/materials/scripts/Example.material中使用的材質紋理效果
material Examples/TextureEffect4
{
technique
{
pass
{
ambient 0.3 0.3 0.3
scene_blend colour_blend
cull_hardware none
cull_software none
texture_unit
{
texture Water02.jpg
scrool_anim 0.01 0.01
}
}
}
}
雖然圖6-6所展示的水紋是有流動的,但是在書中的靜態圖片卻看不出來實際效果。如果你運行了在程式中調用這個材質就能更好地看到這個效果了,但如果現在手邊沒有的話,那麼就來想象一下紋理單元中的紋理捲軸的效果,這就是代碼6-4所能實現的。
圖6-6:在Demo_TextureFX示範程式中把當前紋理改成Examples/TextureEffect4之後的效果
在預設的情況下,Ogre會使用硬體揀選來過濾掉逆時針的表面(也就是說圖形硬體只渲染那些邊是面向攝像機順時針排列的三角形)。而有一些情境管理器只會渲染那些法線面向攝像機的三角形。當我們希望平面的兩個面在情境中都可見的時候,就需要關掉這兩種揀選方式,通過指令碼中所用的cull_hardware none和cull_software none兩行來實現。
在圖6-6中我們可以看到背景的藍天白雲透過半透明的材質被展現出來。這是材質和情境之中已經有的顏色進行混合的結果;因為紋理圖片本身並沒有透明效果(也就是沒有自己的Alpha通道),所以使用了:scene_blend colour_blend來實現了簡單的混合效果。如果使用了有Alpha通道的透明效果紋理,則可以使用:scene_blend alpha_blend來得到更好的效果。
材質和可程式化渲染管線
在之前所提及的所有材質都使用了固定的渲染管線。而現在我們要接觸更複雜一些的可程式化渲染管線。雖然之前所有的指令碼也都可以在可程式化的管線中正常使用,但是當我們調用了著色程式之後,其中大部分的設定都會被忽略掉。
在進行Ogre材質的GPU程式設計的時候有三件事情需要關心:程式本身、程式聲明以及在材質中的使用。在這裡首先讓我們看一個簡單一些GPU程式產生材質的例子:一個在Ogre的某個Demo程式中使用的硬鐵皮表面材質。
代碼6-5:在檔案Samples/Media/materials/programs/Example.cg中的Cg程式
/*
Single-weight-per-vertex hardware skinning, 2 lights
The trouble with vertex programs is they're not general purpose, but
fixed function hardware skinning is very poorly supported
*/
void hardwareSkinningOneWeight_vp(
float4 position : POSITION,
float3 normal : NORMAL,
float2 uv : TEXCOORD0,
float blendIdx : BLENDINDICES,
out float4 oPosition : POSITION,
out float2 oUv : TEXCOORD0,
out float4 colour : COLOR,
// Support up to 24 bones of float3x4
// vs_1_1 only supports 96 params so more than this is not feasible
uniform float3x4 worldMatrix3x4Array[24],
uniform float4x4 viewProjectionMatrix,
uniform float4 lightPos[2],
uniform float4 lightDiffuseColour[2],
uniform float4 ambient)
{
// transform by indexed matrix
float4 blendPos = float4(mul(worldMatrix3x4Array[blendIdx], position).xyz, 1.0);
// view / projection
oPosition = mul(viewProjectionMatrix, blendPos);
// transform normal
float3 norm = mul((float3x3)worldMatrix3x4Array[blendIdx], normal);
// Lighting - support point and directional
float3 lightDir0 = normalize(
lightPos[0].xyz - (blendPos.xyz * lightPos[0].w));
float3 lightDir1 = normalize(
lightPos[1].xyz - (blendPos.xyz * lightPos[1].w));
oUv = uv;
colour = ambient +
(saturate(dot(lightDir0, norm)) * lightDiffuseColour[0]) +
(saturate(dot(lightDir1, norm)) * lightDiffuseColour[1]);
}
就算你不瞭解6-5這個程式具體是什麼意思也不要緊,現在你只要簡單的注意一下用黑體字標明的函數名稱就好了。另外,對於儲存GPU程式的檔案可以任意命名,如果你喜歡甚至可以改變副檔名。一會兒你就能瞭解到,這個檔案的名字對於Ogre系統來說沒有任何意義。
注意:在這本書裡面並沒有要講解GPU程式設計的計劃。這裡所列出的所有GPU程式碼都只是為了作為實際的例子,如果你不理解這些代碼的意思又對其有興趣,就需要找一些資料來學習一下相關的知識了。
一旦你完成了上面的程式定義,你接下來需要做的工作是建立一個程式聲明(declaration)。這種聲名被用來給材質指令碼中使用,下面代碼6-6就是對上面程式的聲明。