熊 偉
(武漢大學計算機學院,湖北武漢430079)
隨著DirectX的發展和成熟,DirectX已經成為當今游戲和多媒體開發的首選技術。然而,DirectX的功能都是以COM組件的形式提供的,應用程序更多地采用了面向過程的設計思想,這種方式使得應用程序的業務邏輯和DirectX的API混合在一起,破壞了系統的模塊化,增大了模塊間的耦合度,系統也不易于維護和擴展。
本文提出一個基于IoC模式的D3D(DirectX中的Direct3D)渲染工作流引擎的設計方案,該方案將剝離應用程序邏輯和D3D實現之間的關系,采用工作流的設計思想來封裝D3D的API,并使用IoC容器來創建構件和組裝其依賴關系,最后通過實例來驗證本引擎的可行性和實現效果。
3D渲染過程就是三維物體的成像過程。三維物體通過建模的微分方法,根據不同的精確程度,將表面切分為多個三角形面,從而將表面的繪制轉化為三角形面的繪制。渲染由眾多三角形面組成的三維場景,需要引入多個坐標系,經過這些坐標系的轉化后,三維場景最終會作為一個二維的場景被繪制出來。[1]
在三維場景中,每個物體最初都擁有自己的局部坐標系,局部坐標系為物體進行三角形頂點的坐標量化。然后每個場景都有一個世界坐標系,該坐標系描述了各個物體之間的位置關系(如果有光照,還需要對頂點的顏色值進行計算)。這時,一個攝影坐標系將會描述該場景的取景范圍,不可見的三角形面將被裁剪。對攝影坐標系的頂點進行剪裁和透視投影處理后,將可以得到輸出于視覺區域中的投影坐標系的頂點信息。最后,將平面投影的點變換到計算機屏幕的視口中,如果采用了紋理貼圖,還需要對像素顏色值進行混色計算。固化渲染代碼以管道流水線的形式來實現三維物體的渲染,D3D提供了API來簡化這個渲染過程,它通過設置該管道流水線的各種過程參數,最終啟動該管道流水線的執行代碼來實現三維的圖形顯示。[2-5]
傳統的D3D應用程序設計是基于面向過程思想的,應用程序在開啟渲染模式后,根據程序邏輯來設置管道流水線的過程參數,并最終將結果繪制在顯示器上。這種面向過程的設計思想導致了程序邏輯和D3D的具體實現混合在一起,使得程序設計復雜、模塊化差、耦合性低和易讀性差。本引擎將以面向對象的設計思想來封裝D3D的API,并將管道流水線的渲染過程抽象為工作流,最終基于IoC容器來配置該工作流,使D3D渲染過程更為靈活和方便,從而徹底剝離了應用程序和D3D的具體實現之間的關系。
引擎的主要任務是將應用程序與D3D具體實現分離,使得應用程序充當生產工人的角色,而D3D具體實現充當工作流的角色。引擎將按照工作流的設計思想將D3D對象和API封裝成工作流構件,并提供相應的接口給應用程序,從而保證兩者的分離和通信。另外,為了進一步降低構件間的耦合,引擎將引入 IoC容器來托管引擎的主要構件對象。引擎設計將包括工作流設計和IoC容器設計兩個部分,其中,工作流設計主要是依照管道流水線的渲染過程來設計各個構件等,并按照抽象工廠的設計模式來抽象C++對象;IoC容器主要是現實對象的動態創建和采用XML來配置三維場景。
工作流中的對象按照角色分為基本構件、過程構件、管理器和處理器[6]。其關系如圖1所示。

圖1 工作流構件的關系
2.1.1 基本構件
基本構件是工作流中的基本單位,其作用是實現工作流分配給自己的任務,而無需知道自己在整個工作流中的作用。它主要包括4個接口:創建、設置參數、獲取參數和釋放,記為M={C,S,G,R},當一個基本構件被創建后,外界將需要對其進行一些設置,然后在被釋放之前,外界都可以在它身上獲取到需要的信息和接口。
在引擎中,基本構件包括了頂點、索引、紋理、材質、燈光等D3D基本元素,比如,類 GLight描述了D3D中的燈光的信息和行為,它封裝了D3DLIGHT9對象,并提供了相應接口(C,S,G,R),其子類 GPointLight、GDirectionalLight和GSpotLight分別實現了點光源、平行光和聚光源的具體設置和行為(重載了父類的C,S,G,R)。
2.1.2 過程構件
過程構件描述了基本構件之間的關系和工作流的邏輯流程,其作用是按照工作流設計出流程路線和邏輯關系。主要包括4個接口:創建、裝配、運行和釋放,記為L={C,A,P,R},過程構件一般只有一個實例,當它被創建后,外界將其需要的所有基本構件都裝配在其實例上,然后運行該實例,直至它被釋放。在引擎中,類 GScene實現了D3D管道渲染的過程,它首先啟動渲染,然后分別獲取裝配在它身上的頂點、索引、紋理、材質、燈光等信息,通過預定的渲染方式來渲染場景,直至渲染過程結束。
2.1.3 管理器
管理器負責管理所有基本構件和過程構件實例,其主要接口包括創建、新建新構件、查找構件、刪除構件和釋放,記為 G={C,N,F,D,R}。管理器分為基本構件管理器和過程構件管理器,基本構件管理器來創建和釋放基本構件實例,過程構件管理器則負責創建和釋放過程構件。管理器的主要作用是對所有散亂的基本和過程構件集中管理,增強工作流的靈活性,實現高內聚、低耦合的目的。在引擎中,每個基本構件和過程構件都有其對應的管理器,如類GTextureHolder為紋理對象的管理器,而 GMaterialHolder為材質對象的管理器。
2.1.4 處理器
處理器負責執行工作流,其主要接口包括創建、輸入、處理、輸出和釋放,記為 P={C,I,P,O,R}。處理器首先從管理構件中獲取到過程構件實例,然后根據初始化信息獲取到所有過程構件實例所需的基本構件實例,然后將這些基本構件實例組裝到過程構件實例中,運行過程構件實例,在運行過程中,處理器會根據用戶輸入或其他消息來創建新的基本構件實例或釋放那些不再需要的基本構件實例,還會通過實例的設置接口來初始化和更新所有的實例,直至程序結束。
在引擎中,類 GGraphics實現了處理器的功能,它封裝了 IDirect3DDevice9對象,完成了 d3d的渲染過程,并對邏輯程序提供了相應接口,使邏輯程序可以更新最終的輸出結果。
2.1.5 抽象工廠模式設計
工作流的運行原理是首先由處理器來做一些初始化的工作,然后根據輸入信息從管理器中獲取到所需對象(管理器會根據處理器的要求來創建對象),并將那些基本構件裝配到過程構件上。然后處理器啟動過程構件,由于過程構件中已定義好所需基本構件的接口,它將按照自己定義的流程來運行,運行結束后,處理器結束過程構件。
可見,工作流是基于不同構件之間的接口來設計的,它并不依賴于具體的對象實例,這種使用抽象接口來創建一組相關或依賴的產品組的模式,可以通過抽象工廠來實現。這樣,工作流就從具體的產品中被解耦,圖2是本引擎采用抽象工廠所實現的構件間的關系及其渲染過程。

圖2 引擎渲染時序圖
在圖2中,所有構件都是通過接口來實現的,引擎并不知道所需要渲染的對象有哪些,它只需要從一個XML的文檔中動態去解析各種所需對象的信息,然后裝配到 GScene對象中,一切渲染的過程都由 GScene對象來完成,這樣就徹底分離了應用程序與引擎間的關系。
在工作流中,不同類型的構件之間存在著各種依賴關系,導致了構件間耦合度低,裝配靈活性低等缺點,為了協調各構件的依賴關系,提高構件的重用性和移植性,運用 IoC容器[6]來配置構件間的關系是一個很好的方法。
IoC指不直接創建對象,而只是描述它們的創建方式,容器負責來將這些聯系在一起。這種控制權由應用代碼轉到了外部容器的行為,稱為反轉。IoC的主要作用有:(1)描述組件間的依賴關系,減小其創建成本;(2)對組件的組裝可以脫離于代碼控制,減小其耦合度;(3)充分利用組裝的靈活性,便于測試。
IoC容器的主要任務是根據對象的配置文件(一般為XML)去動態地創建該對象,并完成配置文件所規定的依賴關系的組裝。但是,C++本身并不具備反射機制,以至于無法直接通過C++來動態創建對象并動態調用其方法,所以容器需要其他實現動態創建對象的機制。另外,為了保證某些特殊對象的唯一性,容器將采用單例的設計模式來實現這個特性。
2.2.1 對象的動態創建
對象的動態創建一般是通過反射機制來實現了,而C++本身并沒有反射機制,為了在程序運行時動態地識別對象類型并創建對象,就必須把那些需要具備該能力的類信息記錄起來,這樣才可以實現動態創建。
本系統用類 GRuntimeClass來描述所有有用的類信息,并提供靜態方法CreateObject來動態創建對象,這樣,每個需要動態創建的類(如GTexture)都需要有一個對應的 GRuntimeClass對象(如classGTexture),所以在定義 GTexture時,需要額外地定義一個靜態對象classGTexture,并對classGTexture初始化。
由于對象的動態創建往往是根據其名字來創建的,所以可以將這些 GRuntimeClass對象放進一個全局的map(GRuntimeClass::m_mpRuntimeClasses)中,這樣就可以快速地根據名字尋找到對應的 GRuntimeClass對象并動態地創建所需的對象了。當然,這些額外地工作可以通過宏來簡化,比如使用DECLARE_DYNCREATE來申明classGTexture和 CreateObject,而使用 IMPL EMENT_DYNCREATE來實現和初始化。
在創建對象時,有一種情況是需要特別處理的,就是有些類它只有一個實例,為了實現這種特性,本引擎采用了單例的設計模式。在傳統的單例模式中,單例類有一個靜態的對象,并提供一個全局的訪問點,通過這種方式來保證類對象的唯一性。在本引擎中,這個靜態對象(m_pSingletonObject)和全局訪問點(CreateSingleton)被移到了 GRuntimeClass中,這樣通過 GRuntime-Class的CreateObject方法來動態地創建對象,用CreateSingleton方法來動態地創建單例。
2.2.2 配置文件解析
IoC容器需要實現根據給定格式的配置文件(本引擎采用XML)來動態地創建對象,并組裝其依賴的其他對象。本引擎用類 GIoCContainer來實現這個功能,GIoCContainer有一個靜態的IXMLDOMDocumentPtr類型的成員變量pXMLDoc,首先通過該變量的load方法來讀取配置文件,然后,GIoCContainer提供了一個靜態方法GObject* CreateObject(char*beanId)來動態創建對象,并組裝其依賴對象,該方法算法如下:
GObject* CreateObject(char*beanId){
獲取靜態對象pXMLDoc;
if(pXMLDoc==NULL)
載入對應的XML文件;
獲取 beans節點的 IXMLDOMElementPtr對象;
遍歷beans節點中的所有bean節點
{
if(bean節點的id為beanId)
{
if(bean節點的scope不為prototype)//不是單例
{
object = GRuntimeClass.CreateObject(bean的class);
遍歷bean節點中的所有property節點
{
propertyInstance= CreateObject(property的ref);
動態調用方法使object的名為property的name的對象為propertyInstance;
}
}
else{//單例
object = GRuntimeClass.CreateSingleton(bean的class);
遍歷bean節點中的所有property節點
{
if(object的名為property的name的對象==NULL)
{
propertyInstance= CreateObject(property的ref);
動態調用方法使object的名為property的name的對象為propertyInstance;
}
}
}
return object;
}
}
return NULL;
}
本引擎實現了從一個場景配置文件來動態生成相關對象,這些對象將作為工作流的輸入數據,當工作流啟動后,工作流按照其流程來渲染所有三維物體。配置文件獨立于程序,當需要修改場景時,引擎無需修改源碼,而只需要修改相應的配置項則可。下面是使用該引擎實現的一個具體實例,在該實例中,將渲染一個草地和藍天的場景,草地是個平板對象,天空是個半球體。實現該實例,只需在場景配置文件中添加上這兩個對象,配置文件如下:
<bean id="ground"class="GPlane"scope="prototype">
<property name="m_strTexturePath"value="c:ground.jpg"></property>
<property name="m_fLength"value="800"></property>
<property name="m_fWidth"value="800"></property>
</bean>
<bean id="sky"class="GGlobe"scope="prototype">
<property name="m_fRadius"value="800"></property>
<property name="m_dAlfa"value="15"></property>
<property name="m_dBeta"value="15"></property>
</bean>
<bean id="gview"class="GView">
<property name="m_lpModels">
<list>
<ref bean="ground"/>
<ref bean="sky"/>
</list>
</property>
</bean>
圖3顯示了該實例的最終渲染效果。

圖3 實例渲染效果
D3D是當前3D渲染的主流技術,它實現了管道流水線渲染三維物體的過程。D3D的API采用COM組件的形式,這樣使得程序的設計繁雜化,易讀性較差,而以一般的面向對象思想來封裝則增加了各個對象的耦合。本引擎采用了以工作流的設計思想抽象了管道流水線的渲染過程,并采用IoC容器來協調工作流各構件的依賴關系,提高構件的重用性和移植性,從而剝離了應用程序的邏輯與D3D具體渲染的關系。
本引擎的創新點在于:(1)采用工作流的設計思想來設計并封裝D3D的API;(2)利用 IoC容器在構件組裝方面的開發性來配置引擎中的對象。目前還只能渲染一些基本幾何體,下一步將會以更開放的形式,來渲染復雜的骨骼動畫等。
[1] 林少芬,姜明.CAXA三維實體設計教程[M].北京:機械工業出版社,2005.
[2] 李江平.Direct X中3DMAX模型的應用[J].湖北工學院學報,2003(4):58-59.
[3] 況衛飛,彭四偉.三維渲染引擎編輯器的研究[J].電子設計工程,2009(9):91-92.
[4] 周演,陳天滋.基于Direct3D的大規模地形渲染技術研究[J].鄭州輕工業學院學報:自然科學版,2008(6):107-111.
[5] 杜園園,侯彤璞,郭艷霞.Direct3D場景中3D模型的處理方法[J].遼寧石油化工大學學報,2008(4):86-90.
[6] 朱啟明,汪詩林.基于IOC容器的工作流引擎的設計[J].微計算機信息,2008(21):10-12.