Effect Framework
|
|||||||||||||||||||||
Effect Framework 摘要 本文簡要介紹了在DirectX 9 SDK中提供的Effect Framework支持,以及DirectX FX文件結構和Microsoft Hight Level Shading Language的基本知識。本文假定讀者對DirectX Graphics有一定了解,並正在學習DirectX Effect Framework。希望能夠與各位讀者共同探討、切磋。 簡介 Effect的起源 在計算機3維場景中,物體表面的材質代表了其光學特性。最簡單的材質可以表現爲Diffuse顏色,Specular顏色,Emissive顏色等信息的集合;而爲了表現物體表面的細節,可以 在材質中加入一張紋理——這些就構成了最基本的材質信息。在以前的Direct3D程序中,這些信息可以直接傳送給設備,由設備自動根據它們來計算物體表面的光學效果。但是, 僅僅有這些基本的材質信息,已經不足以滿足遊戲製作者的要求和遊戲玩家的要求了——他們希望場景中的材質更加複雜,具有更多的細節,更加逼真。 在Direct3D中,除了材質的概念,還存在一個渲染狀態(Render State)的概念。在Direct3D Device中存在很多的渲染狀態,它們可以在Direct3D進行渲染時控制渲染的流程和效 果,從而實現某些帶有特效的材質。程序員可以通過IDirect3DDevice*::SetRenderState()方法來設置這些狀態。所有的渲染狀態都是一些特定的數值。對狀態的設置可以通過硬 編碼完成,即在程序中調用SetRenderState()方法,將設置什麼樣的狀態“寫死”在程序裏,但是這樣做的缺點就是太不靈活了——如果想要實現一種新的渲染狀態,就需要修改 程序代碼。所以更好的一種方法是將爲了實現某一種特效材質的一些渲染狀態值記錄到一個“效果文件”中,通過在程序運行時讀取該文件,從中分析出這些值,並將它們作爲參 數調用SetRenderState()。這樣,要想實現一種新的特效,只需修改“效果文件”而不用更改代碼。 Direct3D SDK是通過Effect Framework來支持這種機制的。而前面所述的“效果文件”在Direct3D中是以*.fx文件存在的。在fx文件中保存了爲實現某一特效的渲染狀態,包括狀態名 稱和它們的對應值。所以在9.0以前版本的DirectX中就已經有Effect Framework和FX文件了,早期的Effect Framework僅僅是爲了實現對渲染狀態進行控制。 但是隨着計算機顯示硬件技術的發展,圖形處理單元(GPU)正在重複CPU所走過的路——新一代的GPU已經具有了可編程特性,程序員可以通過對GPU編寫一段程序來控制其渲染的 輸出效果。這種程序一般稱爲Shader程序。目前的Shader程序分成兩種,Vertex Shader和Pixel Shader。Vertex Shader主要用於對3維網格模型的每一個頂點進行處理,而Pixel Shader主要用於對要繪製到屏幕上的每一個象素進行處理。通過Shader,程序員可以製作出相當豐富的實時渲染效果。 同CPU一樣,對GPU編程也是藉助一定的編程語言來進行的。 在DirectX 8中,對GPU的編程是通過一種類似於彙編的語言進行的。而在DirectX 9中,使用了一種類似於C語言的高級語言——Microsoft High Level Shading Language (HLSL) 。 無論使用什麼語言對GPU編程,都需要把編譯好的Shader程序輸送到顯卡去。Direct3D Device可以通過SetVertexShader()和SetPixelShader()來向顯卡輸送Shader程序。然而,由 於Shader和材質具有十分緊密的關係——一般來說,一個Shader就是爲了實現一種特殊的材質——最好是能夠將Shader程序與材質進行整合。 所以,在DirectX 8和DirectX 9中,原來的Effect Framework發生了擴充——在原來的基礎上加入了對Shader程序的支持。程序員可以把Vertex Shader和Pixel Shader程序以函數 的方式直接書寫在FX文件中。 .FX文件 FX文件中的內容大致可以分成幾部分:
預編譯標誌:預編譯標誌包括
其中最常用到的是#include和#define,同C語言中的意義很相似:用#include可以在一個FX文件中引入另外一個或多個文件。#define可以定義FX文件中的宏替換。例如:
#include帶來的好處和C中也是一樣的-您可以在一個頭文件中定義一些公有變量、函數等,在其他文件中引用它們-就不用寫很多遍了。例如: 文件public.fh:
//定義一個名爲VS_OUTPUT的結構。關於結構體定義,下文中會有介紹
//定義一個名爲CaculateWorldPosition的函數。關於函數定義,下文中會有介紹
這樣,當我們在另外一個文件中include這個頭文件時,上面所有的定義都可以直接使用了。 文件client.fx:
這樣不僅可以複用代碼,還可以使變量名、結構體、函數名的定義統一。 而#define不僅僅可以使某些常量具有比較有意義的名稱,通過與#ifdef,#ifndef, #else, #endif等結合使用,還可以用來根據一些配置控制編譯過程。 變量表 每個FX文件都可以有若干參數變量,通過Effect Framework可以在程序中識別出這些參數的類型、名稱和用途,這樣就可以將程序中的一些參數輸送到Effect中去,從而更加靈活 的控制效果。參數的類型很多,可以是int, float, matrix, texture等等。例如:
另外,每個變量還有前綴修飾符、Semantic、Annotation等,限於篇幅,在這裏不再贅述,具體的介紹請參考DirectX C++幫助文檔的DirectX Graphics > Reference > HLSL Shader Reference > Variable Declaration Syntax條目。 結構定義 在FX文件中可以定義結構體,這些結構體一般用於Shader函數的參數和返回值。結構體的定義與C語言方式及其類似,例如:
上面每個成員變量後面的標識符是該變量的semantic,HLSL編譯器根據這個標識符來確定該變量的用處。不一定非得是結構體成員變量纔有semantic,一般來說Shader的輸入輸出參數變量都可以有semantic。 函數 函數部分是在Effect Framework加入了對Vertex Shader和Pixel Shader的擴充後才加入到FX文件中的。FX文件中的函數的內容可以用匯編形式書寫,也可以用HLSL編寫。目前一般 都是使用HLSL。用這種語言書寫的函數與C語言函數十分類似,可以說,只要學過C語言,書寫Shader函數就綽綽有餘了。在同一個fx文件中可以定義很多函數,在函數中也可以互 相調用,但是最終Shader程序的入口將在fx文件的Technique部分中指定。對於哪個函數是Vertex shader函數,哪個是Pixel shader函數,也是在Technique中指定的。關於Technique,將在下文中介紹。 例子:
正如上文中所說,Shader程序分爲兩種:Vertex shader和Pixel shader。在fx函數中就有兩種相應的函數:Vertex shader函數和Pixel Shader函數。Vertex shader函數的輸入參數是網格模型中的每一個頂點數據,其輸出是經過該函數特殊處理的頂點數據;而Pixel Shader函數的輸入,則是經過硬件光柵化過程後經過插值的Vertex shader輸出結果。至於Pixel shader的輸出,一般就是經過該函數計算得到的一個顏色值,即要畫到後備緩衝中一個象素上的顏色值。但是這個“顏色值”有時並不一定代表顏色。而且對於支持多RenderTarget的硬件,Pixel shader還可以有多個輸出,分別對應不同的RenderTarget。 technique technique是FX文件的主體,是真正設置各種渲染狀態的地方,也是指定所使用的Shader程序入口的地方。在理解Technique前首先要理解Pass的概念。Pass是Technique的組成部分 ,一個Pass就代表了繪製時的一遍。通常爲了達到一種效果,僅僅繪製一遍網格模型是不夠的,需要向framebuffer中多次繪製,並利用設置渲染狀態中的BlendState進行Alpha混合。這就是經常提到的“Muli-pass Rendering”。但是隨着硬件越來越強大,Shader程序的功能越來越強,目前的趨勢是所有的特效材質都將可以用越來越少個Pass來完成。 一個technique中,可以存在一個或多個Pass,但是至少要有一個Pass時該technique纔會起實際作用。當存在多個pass時,默認情況下在渲染時將會按照pass在文件中的前後順序 作爲渲染時的前後順序。technique和Pass中的內容都是以大括號括起來。 technique的例子:
ID3DXEffect接口 上面介紹了很多fx文件相關內容,但是在程序中如何讀取和分析這些fx文件呢?在程序中對於讀取fx文件,控制渲染狀態、設置Shader程序等工作都是通過D3DX庫中的ID3DXEffect接口來實現的。ID3DXEffect接口提供了大量的方法,基本上分爲幾個方面:
ID3DXEffect接口的創建: 通過D3DX庫中的D3DXCreateEffectFromFile()函數,可以根據一個指定的文件的內容來創建一個ID3DXEffect接口。在該函數執行成功後,所創建的接口中就包含了文件裏所對應的 所有內容,包括參數變量表、Shader程序、technique和pass等。 ID3DXEffect接口的使用: 通過該接口的方法可以獲得FX文件中的所有信息,並設置參數變量和當前的technique。所有的參數變量、technique、pass、shader等等都有自己的名稱,根據這些名稱,通過調用ID3DXEffect::GetParameterByName()、ID3DXEffect::GetTechniqueByName()和ID3DXEffect::GetPassByName()等方法就可以獲得這些對象的句柄,從而在調用 ID3DXEFFECT::Set***()進行設置時使用句柄而不是字符串進行索引來提高效率。 通過ID3DXEffect::GetParameterDesc()、ID3DXEffect::GetTechniqueDesc()和GetPassDecs等方 法可以獲得關於指定對象的所有細節描述信息。 D3DX庫中定義了一個D3DXPARAMETER_DESC結構來專門表示參數的類型。通過ID3DXEffect::GetParameterDesc()方法,可以爲一個參數獲得這樣一個結構的數據。
其中的參數類型可以有下列幾種:
而真正要讓Effect起作用,需要在繪製網格模型前後調用ID3DXEffect::BeginPass()和EndPass方法。在調用這兩個函數之前和之後,還需調用ID3DXEffect::Begin()和 ID3DXEffect::End()方法來界定此次Effect設置的起止。大致的形式如下:
3DS MAX對DirectX 9 Shader Material的支持;Effect數據獲取和導出 在FX文件中的參數,大部分都是用來調整FX文件所指定的Effect的一些細節,例如,一種帶有凹凸紋理的效果,可能在FX中就有一個參數控制着凹凸不平的程度。而這些參數是需要由美工來調整的。另外,美工也需要有一個途徑能夠將FX文件定義的Effect賦予到一個模型或它的一部分上去。同時美工也應該能夠實時的預覽該Effect在模型上的實際效果。 在以前,實現這些要求只能通過自制效果預覽器、模型編輯器或者爲DCC軟件編寫插件來完成。這對於小的工作組和工期較短的項目來說是非常困難的。幸運的是,目前的DCC軟件 已經開始爲遊戲製作提供豐富的支持功能。通過DCC軟件自身就可以預覽到實時Effect的效果,並調整其參數。在《龍的傳說》這個項目中,我們使用3DS MAX 6.0來作爲模型建立 工具和Shader預覽工具。 在3DS MAX 6.0中,新加入了一種材質類型——DirectX 9 Shader材質。這種材質是基於FX文件的。一個FX文件就可以代表一種材質。它可以同MAX中的其他材質一樣賦予到模型上 。在MAX的Viewport中可以實時地觀察到FX文件中設計的效果。通過爲FX中的參數設置特定的Semantic,可以將這些參數與MAX中的場景信息聯繫起來,如攝像機位置、世界變換矩 陣等。對於控制Effect效果的一些本地參數,可以通過爲它們添加特定的Annotation,使MAX能夠直接識別這些參數並在用戶界面中顯示它們的名稱和調節控件。對應不同類型的參 數,MAX可以爲它們生成不同的調節控件。這樣,這種材質就和MAX的其他材質一樣,可以更改紋理等等的參數了。 對於美工來說,通過這種用戶界面就可以調整該FX材質的參數達到最好的效果。 但是美工所調整的結果必須要能夠保存下來纔有意義。這個工作就得由程序員來完成了。要保存3DS MAX中編輯的所有內容,需要爲3DS MAX編寫文件導出插件,將其內部數據保存在特定格式的文件中。在《龍的傳說》這個項目中,我們使用Microsoft DirectX .X 文件。如果從頭開始寫導出插件,工作量是相當大的;幸運的是,在Microsoft DirectX SDK Extra中提供了一個能夠導出模型和3DS MAX標準材質到X文件的插件源代碼。通過修改該源代碼,可以使它能夠導出DirectX 9 Shader材質。在3DS MAX 6 SDK中新增加了一個IDxMaterial接口,通過查詢一個IMaterial接口是否爲IDxMaterial接口,就可以確定該材質是否爲DirectX 9 Shader材質。通過IDxMaterial接口可以獲得該材質對應的FX文件的文件名,以及其參數信息。這樣就可以將它們導出了。 .X文件中對Effect的支持;EffectInstance和EffectDefault Microsoft DirectX .X文件的格式是基於模板的、可擴展的文件格式。通過爲其制定新的模板,就可以在其中加入新的內容。一般的.X文件中的內容有三維場景的物體層級關係、網格模型幾何數據、材質信息、動畫信息等。在DirectX的衆多X文件模板中有一個模板是專門用來代表Effect的實例的。當一個FX文件的參數被美工加以調整從而具備一些特定的值之後,該 FX文件和這些參數值的集合就形成了一個Effect實例。該模板的定義如下:
其中, EffectFilename代表了該Effect實例中的FX文件名, [ ... ]代表在其中可以插入任何X文件模板對應的數據。這樣就可以代表任何類型的參數值。 然而要想讓Direct3D程序能夠識別[ ... ]中的內容,需要使用X文件模板中的EffectParam系列模板,包括EffectParamDWord, EffectParamFlaots, EffectParamString。通過這三種模板對應的數據,所有類型的Effect參數值都可以被記錄在X文件中。 最後,EffectInstance數據需要被放置在Material數據中纔可以被識別。 Material模板:
前面的一些顏色模板表明在Material數據中這些顏色信息是必須有的,而最後的[ ... ]則代表可以插入任何X文件模板對應的數據。我們的EffectInstance數據就可以放置在這裏 。 舉一個簡單的例子:
當我們在程序中調用D3DXLoadMeshFromX()或D3DXLoadMeshHierarchyFromX()時,就可以通過其LPD3DXBUFFER *ppEffectInstances參數來接收到網格所用的所有EffectInstance的信息。 在程序中,對應於X文件中的EffectInstance模板和EffectParam系列模板,有兩個結構體用來代表Effect數據:
在調用了D3DXLoadMeshFromX()和D3DXLoadMeshHierarchyFromX()之後,X文件中的所有Effect數據信息就以上述結構體的形式放置在ppEffectInstances中了。 另外,在從3DS MAX導出參數到X文件時,對於整型和浮點數組型的變量,它們的值將直接導出到X文件中去;而對於所有的紋理貼圖文件參數,導出的僅僅是該文件的文件名。所以 在程序中需要再根據這些文件名來建立紋理對象。 在《龍的傳說》中,我們使用一個自定義的CEffectInstance類來處理將文件名轉換爲紋理對象的過程。 一般來說,建立一個完整的CEffectInstance的過程如下: 根據D3DXEFFECTINSTANCE結構中的pEffectFilename字符串尋找對應的FX文件;
而在最新推出的DirectX 9 SDK Summer 2004中,通過ID3DXEffect::BeginParameterBlock()和ID3DXEffect::EndParameterBlock()方法,我們可以將Effect參數設置過程統一綁定到一個ParamBlock句柄上。這樣,在調用ID3DXEffect::Begin()之前就可以直接使用ID3DXEffect::ApplyParameterBlock()方法來設置所有被綁定的參數值。例如: [以前的做法]: 在讀取參數時:獲得每一個參數的句柄
[在DirectX 9 SDK Summer 2004中的做法]: 在讀取參數時:綁定所有參數設置到同一句柄
這樣不僅簡化了在讀取時對參數的分析過程,而且提高了實際繪製時參數設置過程的效率。 總結 以上就是一些對於在DirectX 9.0中對Effect Framework的使用的簡要介紹。總之,使用Effect來替代以前的標準材質是目前實時圖形領域的發展趨勢。通過Effect Framework,程序員和美工可以爲實時三維程序實現多種多樣的材質效果和視覺效果。 由於內容實在太多,限於篇幅,本文只是對Effect Framework中相關概念的一個總體概括和簡要介紹,所以顯得有些晦澀。在以後的文章中,將分批對這個Framework以及在其之上進行工作的流程進行比較詳細的介紹 |