DirectX 9.0c遊戲開發手記之RPG編程自學日誌之17: Drawing with DirectX Graphics (用DirectX圖形繪圖)(第13節)

        本文由哈利_蜘蛛俠原創,轉載請註明出處!有問題請聯繫[email protected]

 

        這一次我們繼續來講述Jim Adams 老哥的RPG編程書籍第二版第二章的第13節:Working with Meshes (使用網格模型)。這一節分爲四個小節,我們列在下面:

1、 The .X Files (.X文件)

2、 The .X File Format (.X文件的格式)

3、 Creating .X Meshes (創建.X網格模型)

4、 Parsing .X Files


        原文翻譯:

 

===============================================================================

 

2.13 Working with Meshes (使用網格模型)

 

        在其最底層,Direct3D並不使用網格模型,而是隻使用多邊形。但是D3DX通過給你能夠包含和渲染這樣的網格模型的一系列對象,而向Direct3D系統中增加了使用網格模型的功能。

        在其最底層,網格模型是由可能上千個頂點和多邊形構成的,這都需要複雜的操作。幸運的是,Direct3D 附帶了一種與生俱來的3-D文件格式來儲存描述一個3-D模型的信息,包括(但不限於)頂點、面、法向量和紋理數據。這個文件格式被稱爲.X。

 

 

2.13.1 The .X Files (.X文件)


        一個.X文件(通過它的擴展名——.X,而進行識別)是歸屬於微軟的一種高度通用的3-D模型儲存格式。(對不起,我們不會被Mulder和Scully所拜訪。——這裏作者小小地幽默了一下:MulderScully是美國科幻劇《The X Files》的主角。)它是由模板驅動的,並且完全可擴展,這意味着你可以使用它來滿足所有的文件儲存需求。對於我們的情況,我正在談論的文件存儲是你的3-D網格模型數據。

        雖然在現在我可以繼續談論有關.X文件的格式的複雜的細節,但是這樣做的話,這些只有最hard-core(不知道怎麼翻譯好)的程序員和圖形藝術家纔會使用的信息會讓本章內容不堪重負的。讓我們面對它吧——努力地手動編輯一個包含了上千個頂點的網格模型是可笑的。爲什麼在你可以使用一個類似trueSpace或MilkShape 3D的程序在一個用對戶友好環境中來設計你的網格模型的時候,卻要去做那種令人退縮的繁重工作呢?那就是我所剛纔的話的意思!

 

        注意

===============================================================================

        如果你真的對.X文件格式的複雜細節感興趣,你應該查查我的書《Advanced Animation with DirectX》。在此書中,我說明了如何在你的項目中使用.X文件的細節,從使用標準的DirectX數據對象到創建你自己的數據集——全都在那兒!查閱附錄A,“參考文獻”,來獲得更多信息。(參考文獻這一部分我今後是不會翻譯的。)

===============================================================================


        相反,我只會給你快速瀏覽一下使用.X文件格式化的方法;然後你就可以轉移到好的事情上了,也就是在你的遊戲中使用模型!

   

2.13.2  The .X File Format (.X文件的格式)

 

        一個.X文件,通過其.X擴展名而被識別,是高度通用的(versatile)。它可以是基於文本的,以使得編輯更加容易;也可以是基於二進制的,以使得文件更小,並且更容易免受偷窺者的干擾。.X的整個格式是基於模板的,這與C的結構體相似。

        爲了讀取並使用一個.X文件,你採用一些個COM對象來剖析(parsethrough)在這個.X文件中遇到的每個數據對象。數據對象以一個字節數組(arrayof bytes)的形式被傳遞給你;你只需要將這個數組轉型爲一個可以使用的結構體,就可以輕易地讀取包含在該對象中的數據了。

        根據儲存在.X文件中的數據不同,這些對象也會不同。在這裏,這些對象代表網格模型以及與網格有關的數據(例如骨骼結構(bone structures),叫做幀繼承(frame hierarchies),以及動畫數據)。剖析這些對象、載入網格數據、建立動畫表以及構造幀繼承,這些都是你的工作。

        我會回到整個的網格和動畫的事情上的。首先呢,我想要談談我剛纔提過的至關重要的幀繼承。

 

Using a Frame Hierarchy (使用幀繼承)

 

        你使用一個幀模板(frame template),也叫做frame of reference 模板,來將一個或多個數據對象(通常是網格模型)組合在一起以便更好地操控。你還可以創建一個單個的網格,並使用多個幀來包含網格引用(references),這讓你可以多次使用一個網格。

        舉例來說,如果你有一個檯球的模型。因爲要設定15個球,所以你創建15個幀,每一幀包含原始的球網格模型的一個引用。從此時起,你可以(通過使用一個幀變換矩陣對象)將每一幀擺放在檯球桌上,使得每一個網格實例與幀一起運動(move with the frame,不知道翻譯得對不對)。

        本質上來說,你從單獨的一個網格模型中創建了15個檯球網格實例。除了創建一個網格模型的多個實例以外,你還可以使用它們來創建一個幀繼承(frame hierarchy)。幀繼承定義了一個場景的結構或者網格的組織(grouping of meshes)。當一個幀運動時,所有嵌入其內的幀也會移動。

        舉例來說,將一副人類骨骼想象成一個幀的集合(如圖2.24所示)。在繼承的頂部是胸部。從胸部往下,你可以將每一根骨頭與前面一根骨頭連在一起 —— 也就是說,胸部連到臀部,臀部連到腿部,腿部連到腳。往相反的方向走(向上),你發現胸部連接到手臂,而手臂連接到手。這個排序會一直進行下去,直到所有的骨頭以這樣或那樣的方法連回到胸部。



        到了這裏,你有了一個根幀(root frame),它正是胸部。根沒有父幀(parent frames),這表示它是繼承的頂部,並且不屬於另外一幀。與其他幀相連的幀稱爲子幀(child frames)(也叫做結點(nodes))。例如,上臂是下臂的父親,而手是下臂的孩子。從繼承中往上,手、下臂和上臂都是胸部的孩子。

        當一個幀運動的時候,其所有的子幀也會運動。如果你移動你的上臂,你的下臂和手也會運動。另一方面(無意使用雙關語——原文On the other hand,而我們也在談論hand,所以作者提醒讀者不要聯想到雙關語),如果你移動你的手,metaphorically speaking (不知道是什麼意思),只有你的手會移動位置,因爲它沒有子幀(下臂是手的父幀)。

        使用高級網格和動畫的技術時,幀繼承是非常重要的。事實上,爲了使用蒙皮網格等技術(關於蒙皮網格的更多信息,請參見本章稍後的“Meshes with D3DX”這一節),幀繼承是必備知識。

        使用幀繼承的另外一個理由是對場景的某部分進行孤立。這樣,你可以通過移動特定的幀來改變場景的一小部分,而讓場景的其餘部分維持原樣。例如,如果你有一個代表你的房子的幀,以及一個代表一扇門的幀,你可以在不影響房子的幀的情況下改變門的幀。

 

   

2.13.3  Creating .X Meshes (創建.X網格模型)

 

        你可以通過很多種方式創建你自己的.X文件。微軟寫了一些你可以用在3D Studio Max 或Maya 等建模程序中的導出器。如果你買不起這些酷炫的建模程序的話,你可以手動地創建模型(通過手動地將每個頂點和多邊形的數據輸入一個文本.X文件中),或者使用一個低成本的替代程序,例如MilkShape 3D,它是由Mete Ciragan開發的低面數建模器。

        MilkShape 3D是爲了創建遊戲Half-Life (《半條命》)的模型而產生的建模軟件,但是它的應用範圍已經變得廣泛得多了。MilkShape 3D現在支持多種模型格式(主要用於遊戲),但是它仍然是一款有用的程序。

        你知道這扯得有點遠了,所以我就直言不諱了(原文比較難翻譯:You know this is getting somewhere, so I’ll go ahead andlet the cat out of the bag—— MilkShape 3D有一個你可以使用的.X文件導出器,它是你最喜歡的作家(對,沒錯,正是在下!)編寫的。登陸MilkShape 3-D的官方網站(位於http://www.swissquake.ch/chumbalum-soft)來下載它吧。

        除了使用第三方的程序來爲你創建.X文件以外,你唯一剩下的選擇就是手動創建了。然而,因爲你自己去創建模型不是最佳的選擇,所以我不會在此書中講述這個話題。

 

   

2.13.4  Parsing .X Files (剖析.X文件)

 

        本章以及本書之後的部分,手動地剖析一個.X文件以提取出重要信息將會變成必行之事。爲了剖析一個.X文件,你使用一組IDirectXFile對象,它們的工作是打開一個.X文件並枚舉(enumerating through)文件的數據對象,從而將其以一種容易訪問的方式送給你。

 

        注意

===============================================================================

        爲了使用IDirectXFile組件,你必須包含進dxfile.h、rmxfguid.h和rmxftmpl.h頭文件。另外,你必須將dxguid.lib 和d3dxof.lib 庫文件鏈接到你的項目中。

===============================================================================

 

        剖析一個.X文件實際上並不像它一眼看上去那麼難。訣竅就是掃描整個對象的繼承來尋找你想要使用的數據對象——一般來說,是代表網格模型和幀的對象。最難的部分是要記住,物體可以嵌套在另外的物體之中,所以你也許遇到的不是對象,而是對象的引用(這些引用需要被解析 (resolved) 以便訪問原始的對象數據)。圖2.25說明了數據對象的組織方式。



        下列代碼打開一個.X文件,並剖析包含在其中的對象。記住,這些函數在本章(以及本書其餘部分)中的學習中是非常必要的,所以暫時不用擔心太多關於使用它們的事情。

BOOL ParseXFile(char *Filename)
{
    IDirectXFile           *pDXFile = NULL;
    IDirectXFileEnumObject *pDXEnum = NULL;
    IDirectXFileData       *pDXData = NULL;

    // Create the .X file object
    if(FAILED(DirectXFileCreate(&pDXFile)))
        return FALSE;

    // Register the templates in use
    // Use the standard retained mode templates from Direct3D
    if(FAILED(pDXFile->RegisterTemplates((LPVOID)                 \
                      D3DRM_XTEMPLATES, D3DRM_XTEMPLATE_BYTES))) {
        pDXFile->Release();
        return FALSE;
    }

    // Create an enumeration object
    if(FAILED(pDXFile->CreateEnumObject((LPVOID)Filename,          \
                                 DXFILELOAD_FROMFILE, &pDXEnum))) {
        pDXFile->Release();
        return FALSE;
    }

    // Enumerate all top-level objects
    while(SUCCEEDED(pDXEnum->GetNextDataObject(&pDXData))) {
        ParseXFileData(pDXData);
        ReleaseCOM(pDXData);
    }

    // Release objects
    ReleaseCOM(pDXEnum);
    ReleaseCOM(pDXFile);

    // Return a success
    return TRUE;
}


void ParseXFileData(IDirectXFileData *pData)
{
    IDirectXFileObject         *pSubObj = NULL;
    IDirectXFileData          *pSubData = NULL;
    IDirectXFileDataReference *pDataRef = NULL;
    const GUID *pType = NULL;
    char *pName = NULL;
    DWORD dwSize;
    char *pBuffer;

    // Get the object type
    if(FAILED(pData->GetType(&pType)))
        return;
   
    // Get the object name (if any)
    if(FAILED(pData->GetName(NULL, &dwSize)))
        return;
    if(dwSize) {
        if((pName = new char[dwSize]) != NULL)
            pData->GetName(pName, &dwSize);
    }

    // Give a default name if none found
    if(pName == NULL) {
        if((pName = new char[9]) == NULL)
            return;
        strcpy(pName, “Template”);
    }

    // See what the object was and deal with it
    // This is where you’ll jump in with your own code
    // Scan for embedded objects
    while(SUCCEEDED(pData->GetNextObject(&pSubObj))) {

        // Process embedded references
        if(SUCCEEDED(pSubObj->QueryInterface(                      \
            IID_IDirectXFileDataReference, (void**)&pDataRef))) {
            if(SUCCEEDED(pDataRef->Resolve(&pSubData))) {
                ParseXFileData(pSubData);
                ReleaseCOM(pSubData);
            }

            ReleaseCOM(pDataRef);
        }

        // Process non-referenced embedded objects
        if(SUCCEEDED(pSubObj->QueryInterface(                       \
                       IID_IDirectXFileData, (void**)&pSubData))) {
            ParseXFileData(pSubData);
            ReleaseCOM(pSubData);
        }
        ReleaseCOM(pSubObj);
    }

    // Release name buffer
    delete[] pName;
}

        ParseXFile 和ParseXFileData 函數一起合作來剖析.X文件中的每一個數據對象。ParseXFile 函數打開該.X文件並對其進行枚舉,尋找繼承中最頂部的對象。當每一個對象被發現時,它被傳遞到ParseXFileData函數中。

        ParseXFileData 函數處理數據對象的數據。它首先得到對象的類型以及對象實例的名字(如果有的話)。從中你可以處理對象的數據,然後讓該函數使用遞歸來枚舉所有的子對象。這個過程會一直持續到所有的對象被處理完爲止。

        你只需要用你想要處理的.X文件的名字來調用ParseXFile 函數,然後這兩個函數就會處理剩下的事情了。你會在本章稍後的“Skinned Meshes”這一節中學會如何讓這兩個函數好好發揮作用的。


===============================================================================


        好啦,這一節其實是介紹性的,更刺激的事情在後面呢!而且,事實上,要弄懂關於在DirectX程序中使用網格模型的事情,單靠這一章的內容是遠遠不夠的。本書後面的部分還會花上不少篇幅來講述其方方面面。另外,“龍書”第二版的第14章-第16章也是非常值得參考和借鑑的內容!

        此外,關於我們使用的建模軟件,我這裏稍微提一下。雖然作者說我們自己去建模型不是最好的選擇(也就是說,作爲遊戲程序員,不應該同時兼任建模者的角色),不過對於我們這些基本上是孤軍奮戰的人來說,自己做一些簡單的模型,然後用在自己的Direct3D 程序中難道不是一件令人興奮的事情嗎?當然,你可以選擇從網上下載一些免費的(免費也分好幾種,有“用於任意用途的免費”和“用於非商業用途的免費”等等)、可以轉換成.X文件的3D模型,不過自己做一個模型不是更令人期待嗎?

        除了3D Studio Max 和Maya 這樣的大型而昂貴的3D建模軟件外,作者還提到了另外兩款小巧的、便宜的、但是功能也不弱的3D建模軟件——trueSpace 3D和MilkShape 3D,尤其是偏愛後者。不過我下載了MilkShape 3D後發現用起來還是不太方便的,而且由於體積小,所以功能還是比較有限的(另外,它還是一個低面數建模軟件,沒法制作面數太多的模型)。

        在這裏小編向大家強烈推薦一款免費開源的3D建模軟件,它的名字叫做Blender。引用一下百度百科的介紹:

 

        Blender 是一個開源的跨平臺全能三維動畫製作軟件,提供從建模、動畫、材質、渲染、到音頻處理和視頻剪輯等一系列動畫短片製作解決方案,以及綠屏摳像、攝像機反向跟蹤、遮罩處理、後期結點合成等高級影視解決方案。還內置有卡通描邊(FreeStyle)和基於 GPU 技術 Cycles 渲染器。擁有方便在不同工作下使用的多種用戶界面,Blender 以 Python 爲內建腳本,支持多種第三方渲染器,同時還內建遊戲引擎。

 

        基本上,3D Studio Max能做到的事情,Blender也能做到,甚至能夠做得更快更好。另外這款軟件非常小巧,功能最強大的最新版的體積也只有不到60M。用它製作出來的3D模型是完全可以放心使用的,你不必擔心版權問題(不過用它附帶的遊戲引擎製作出來的遊戲還是要遵循跟開源軟件相關的協議的)!下面是某位大神做出來的一個模型的渲染後的效果圖:



        然後關於教程,這裏推薦《blender權威指南》,是中國人寫的,據說寫得不錯,網上有電子掃描版。


 

        好啦,這一期就到這裏啦,咱們下期再見!





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章