• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            麒麟子

            ~~

            導(dǎo)航

            <2011年5月>
            24252627282930
            1234567
            891011121314
            15161718192021
            22232425262728
            2930311234

            統(tǒng)計(jì)

            常用鏈接

            留言簿(12)

            隨筆分類

            隨筆檔案

            Friends

            WebSites

            積分與排名

            最新隨筆

            最新評論

            閱讀排行榜

            評論排行榜

            關(guān)于骨骼動畫及微軟示例Skinned Mesh的解析

             

            原文鏈接:http://www.gameres.com/document.asp?TopicID=87707

            這是我自個寫的,第一次發(fā). 沒想到這個貼子編輯器極差. 原文是有字體字色的.現(xiàn)在只能清一色了.
            版主,發(fā)貼的編輯器太難用! 你有必要向上反映一下. 下面的字體是我敲html標(biāo)記加上的,大家湊和看.

             

            關(guān)于骨骼動畫及微軟示例Skinned Mesh的解析

            骨骼動畫是D3D的一個重要應(yīng)用。盡管微軟DXSDK提供了示例Skinned Mesh,但由于涉及眾多概念和技術(shù)細(xì)節(jié),示例相對于初學(xué)者非常復(fù)雜,難以看懂。在此,提供一些重要問題評論,以使初學(xué)者走出迷局,順利上手。文中所述都是參照各種資料加上自己的理解,也有可能出些偏差,有則回貼拍磚,無則權(quán)當(dāng)一笑。


            一 骨骼動畫原理
            原理方面在網(wǎng)上資料比較多,大家都基本明白。在此說一下重點(diǎn):
            總體上,絕大部分動畫實(shí)現(xiàn)原理一致,就是“提供一種機(jī)制,描述各頂點(diǎn)位置隨時間的變化”。有三種方法:
            1.1 關(guān)節(jié)動畫:由于大部分運(yùn)動,都是皮膚隨骨骼在動,皮膚相對于它的骨骼本身并沒有發(fā)生運(yùn)動,所以只要描述清楚骨骼的運(yùn)動就行了。用矩陣描述各個骨骼的相對于父骨骼運(yùn)動。(大多運(yùn)動都是旋轉(zhuǎn)型) 易知,從子骨骼用矩陣乘法累積到最頂層根骨骼,就可以得到每個子骨骼相對于世界坐標(biāo)系的轉(zhuǎn)換矩陣。
              這種動畫,只須用普通Mesh保存最初始的各頂點(diǎn)坐標(biāo),以及一系列后續(xù)時刻所對應(yīng)的各骨骼的運(yùn)動矩陣。不用保存每時刻的頂點(diǎn)數(shù)據(jù),節(jié)省了大量存儲空間。而且比較靈活,可以利用關(guān)鍵幀插值運(yùn)算,便于通過運(yùn)算調(diào)節(jié)動作。缺點(diǎn)是在兩段骨骼交接處,容易產(chǎn)生裂縫,影響效果。

            1.2 漸變動畫:通過保存一系列時刻的頂點(diǎn)坐標(biāo)來完成動畫。雖然比較逼真,但占用大量空間,靈活性也不高。

            1.3 骨骼蒙皮動畫(skinned Mesh)
              相當(dāng)于上面兩方法的折中。現(xiàn)在比較流行。
              在關(guān)節(jié)動畫的基礎(chǔ)上,利用頂點(diǎn)混合(Vertex Blend)技術(shù),對于關(guān)節(jié)附近的頂點(diǎn),由影響這些頂點(diǎn)的兩段(或多段)骨骼運(yùn)動,分別賦以權(quán)值,共同決定頂點(diǎn)位置。相當(dāng)于在骨骼關(guān)節(jié)上動態(tài)蒙皮,有效解決了裂縫問題。

              這里,引入一個D3D技術(shù)概念:“Vertex Blending”---頂點(diǎn)混合技術(shù)。比如說,你肯定用過SetTransform(D3DTS_WORLD,....),但SetTransform(D3DTS_WORLDMATRIX(i),....)是不是很奇怪?這個問題后文會講到。 你也可以在微軟的DXSDK的幫助文件中搜索“Geometry Blending”主題,有裂縫及其解決辦法圖示。

             

            二 X文件如何保存骨骼動畫

            理解X文件格式,對用好相關(guān)的DX函數(shù)是非常重要的。

            不含動畫的普通X文件,有一個Mesh單元,保存了各頂點(diǎn)信息、各三角面的索引信息、材質(zhì)種類及定義等。

            動畫X文件,則在這個單元中增加了“各骨骼蒙皮信息”、“骨骼層次及結(jié)構(gòu)信息”、“各時刻骨骼矩陣信息”等。

            2.1 網(wǎng)格蒙皮信息:首先,在Mesh{}單元中,在原有的普通網(wǎng)格頂點(diǎn)數(shù)據(jù)基礎(chǔ)上,新增了XSkinMeshHeader{}結(jié)構(gòu),以及多個SkinWeights{}結(jié)構(gòu)。用以描述各個骨骼的蒙皮信息。

            其中,XSkinMeshHeader是總括,舉一實(shí)例,如下:

            XSkinMeshHeader
            {
            2,//一個頂點(diǎn)可以受到骨骼影響的最大骨骼數(shù),可用于計(jì)算共同作用時減少遍歷次數(shù)
            4,//一個三角面可以受到骨骼影響的最大骨骼數(shù)。這個數(shù)字對硬件頂點(diǎn)混合計(jì)算提出了基本要求。
            35 //當(dāng)前Mesh的骨骼總數(shù)。
            }

            由于每個骨骼的蒙皮信息都需要用SkinWeights結(jié)構(gòu)去描述,所以有多少塊骨骼,在Mesh中就有多少個SkinWeights對象。
            注意,一般把SkinWeights視作Mesh的一部分。這種Mesh又稱Skinned Mesh (蒙皮網(wǎng)格)

            SkinWeights 結(jié)構(gòu)如下:
            {
              STRING      transformNodeName;      //骨骼名
              DWORD       nWeights;               //權(quán)重?cái)?shù)組的元素個數(shù),即該骨骼相關(guān)的頂點(diǎn)個數(shù)
              array DWORD vertexIndices[nWeights];//受該骨骼控制的頂點(diǎn)索引,實(shí)際上定義了該骨骼的蒙皮
              array float weights[nWeights];      //蒙皮各頂點(diǎn)的受本骨骼影響的權(quán)值
              Matrix4x4   matrixOffset;           //骨骼偏移矩陣,用來從初始Mesh坐標(biāo),反向計(jì)算頂點(diǎn)在子骨骼坐標(biāo)系中的初始坐標(biāo)。
            }
            在有的書中,把上面的matrixOffset叫骨骼權(quán)重矩陣,是不恰當(dāng)?shù)摹?yīng)該稱為骨骼偏移矩陣比較合適。

            [問題] 在整個動畫過程中,子骨骼運(yùn)動矩陣的數(shù)值是不斷變化的。上面的骨骼偏移矩陣變化嗎?有沒有必要重新計(jì)算?它在什么時候使用?
            答:各骨骼的偏移矩陣matrixOffset專門用來從原始Mesh數(shù)據(jù)計(jì)算出各頂點(diǎn)相對于骨骼坐標(biāo)系的原始坐標(biāo)。在繪制前,把它與當(dāng)前變換矩陣相乘,就可以得到該骨骼的當(dāng)前的最終變換矩陣。 總之,骨骼偏移矩陣是與原始Mesh頂點(diǎn)數(shù)值相關(guān)聯(lián)的,在整個動畫過程中是不變的,也不應(yīng)該變。在動畫過程中變化是當(dāng)前骨骼變換矩陣,可由.X中的AnimatonKey中的各時刻矩陣得到。這個矩陣乘法在示例中的對應(yīng)代碼如下:
            D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatrices[iMatrixIndex], pMeshContainer->ppBoneMatrixPtrs[iMatrixIndex] );

            即,D3DXMatrixMultiply(輸出最終世界矩陣, 該骨骼的偏移矩陣, 該骨骼的變換矩陣)


            2.2 骨骼層次信息

            在X文件中,F(xiàn)rame是基本的組成單元。又稱框架Frame。 一個.x可以有多個Frame。(注意此處的Frame不是幀,與幀沒什么關(guān)系)

            框架Frame允許嵌套,這樣就存在父子框架了。而并列的框架,稱為兄弟框架。這兩種關(guān)系組合在一起,即可以縱深,又可以并列,形成一種層次結(jié)構(gòu)。這種結(jié)構(gòu),可用二叉樹描述。

            每個框架結(jié)構(gòu)的最前面,有一個FrameTransformMatrix矩陣數(shù)據(jù),描述了該框架相對于父框架的變換矩陣。也就是說,該框架中的坐標(biāo),與該矩陣相乘,可轉(zhuǎn)換為父框架坐標(biāo)系的坐標(biāo)。
            這種層次結(jié)構(gòu),使得X文件能描述許多復(fù)雜的物體。如地形場景。

            在骨骼動畫文件中,框架結(jié)構(gòu)可直接拿來描述人物骨骼的層次結(jié)構(gòu)。框架的名字通常為對應(yīng)的骨骼名。
            如“左上臂->左前臂->手掌->手指”就形成一個父子骨骼鏈。而左上臂與右上臂是并行關(guān)系。

            數(shù)據(jù)示例: D:\D9XSDK\Samples\Media\tiny.x

            Frame ...{
              .....

              Frame Bip01_R_Calf { //子骨骼
                  
                   FrameTransformMatrix {
                    1.000000,-0.000691,-0.000000,0.000000,0.000691,1.000000,-0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,119.231522,0.000021,-0.000011,1.000000;;
                   }

                    Frame Bip01_R_Foot {//--孫子骨骼
                  
                    FrameTransformMatrix {
                     0.988831,0.124156,0.082452,0.000000,-0.122246,0.992109,-0.027835,0.000000,-0.085257,0.017445,0.996206,0.000000,119.231476,-0.000039,0.000023,1.000000;;
                    }

                    ....縮進(jìn)
                }
            }

            [問題]查看示例tiny.x文件,發(fā)現(xiàn)只有根框架下有一個Mesh,包含了所有頂點(diǎn)信息。其它各個Frame都沒有Mesh數(shù)據(jù)。怎么理解?
            答: 一般來說,每個動畫文件只有一個Mesh網(wǎng)格,包含物體所有頂點(diǎn)信息。
                 其它Frame,只是借用來描述各骨骼的層次信息,沒必要再定義骨骼網(wǎng)格。每塊骨骼對應(yīng)的蒙皮頂點(diǎn)信息,由根Mesh中的相應(yīng)骨骼的SkinWeights中蒙皮頂點(diǎn)索引描述的。在動畫過程中,各個頂點(diǎn)的新坐標(biāo),要借助SkinWeights中的頂點(diǎn)索引來進(jìn)行重新計(jì)算。

            2.3 動畫信息:
            由一系列AnimatonKey組成,數(shù)據(jù)示例如下:

              AnimationKey {
               4;--動畫類型 4表示矩陣
               62; --動畫幀數(shù),即下面矩陣個數(shù)
               0;16;1.000000,-0.000691,-0.000000,0.000000,0.000691,1.000000,0.000000,0.000000,0.000000,-0.000000,1.000000,0.000000,119.231514,-0.000005,0.000001,1.000000;;,
               80;16;0.992696,-0.120646,-0.000000,0.000000,0.120646,0.992696,0.000000,0.000000,-0.000000,-0.000000,1.000000,0.000000,119.231514,0.000002,-0.000002,1.000000;;,

               ..上面紅數(shù)字表示時刻tick,蘭數(shù)字表示數(shù)值的個數(shù)。
               ...其它各時刻矩陣...

               { Bip01_R_Calf }--對應(yīng)的骨骼對象引用
              }


            注意:
            (1)每塊骨骼都有一個AnimationKey{}.
            (2)在上面數(shù)據(jù)結(jié)構(gòu)中,主要保存了各典型時刻的該骨骼相對于父的變換矩陣.
            (3)在0時刻的矩陣,與該骨骼對應(yīng)的前面的Frame所對應(yīng)的矩陣是相同的。如Frame Bip01_R_Calf{}中的變換矩陣,與Bip01_R_Calf所對應(yīng)的AnimationKey 的第0時刻矩陣是一樣的。這說明,在以后動畫運(yùn)行時,DX會提供一種功能,用AnimatonKey中的對應(yīng)數(shù)據(jù)刷新初始的變換矩陣(也可能啟用關(guān)鍵幀插值算法)。這個功能對應(yīng)于示例中的m_pAnimController->SetTime(...)語句。

            三 怎樣從X文件加載骨骼動畫信息?
            3.1 負(fù)責(zé)加載的函數(shù):
              可能有多種加載方式,在此以SDK中的示例為準(zhǔn),敘述一種標(biāo)準(zhǔn)加載方式,需要用到DX函數(shù)D3DXLoadMeshHierarchyFromX(),函數(shù)字面意思是讀取Mesh層次信息。
            HRESULT WINAPI
                D3DXLoadMeshHierarchyFromX(
                    LPCSTR Filename,                 //.x文件名
                    DWORD MeshOptions,               //Mesh選項(xiàng),一般選D3DXMESH_MANAGED
                    LPDIRECT3DDEVICE9 pD3DDevice,    //指向D3D設(shè)備Device
                    LPD3DXALLOCATEHIERARCHY pAlloc,  //自定義數(shù)據(jù)容器
                    LPD3DXLOADUSERDATA pUserDataLoader,  //一般選NULL
                    LPD3DXFRAME *ppFrameHierarchy,       //返回根Frame指針,指向代表整個骨架的Frame層次結(jié)構(gòu)
                    LPD3DXANIMATIONCONTROLLER *ppAnimController //返回相應(yīng)的動畫控制器
            );

            這個函數(shù)后面的兩個輸出參數(shù)很重要,也很好理解,但輸入?yún)?shù)中的自定義數(shù)據(jù)容器是怎么回事呢?
            原來,鑒于動畫數(shù)據(jù)的復(fù)雜性,需要你配合完成加載過程。比如你是否用到自定義擴(kuò)展結(jié)構(gòu),Mesh等數(shù)據(jù)保存在哪里,怎樣使用戶自己創(chuàng)建容器,自己決定卸載等等。
            DX提供了ID3DXALLOCATEHIERARCHY接口,提供了這個自定義的機(jī)會,你重載這個接口的虛函數(shù),在加載過程中,它就像回調(diào)函數(shù)那樣運(yùn)作。

            你需要像下面這樣建立一個自定義數(shù)據(jù)容器類:
            class CAllocateHierarchy: public ID3DXAllocateHierarchy
            {
            public:
                STDMETHOD(CreateFrame)(THIS_ LPCTSTR Name, LPD3DXFRAME *ppNewFrame);
                STDMETHOD(CreateMeshContainer)(THIS_ LPCTSTR Name, LPD3DXMESHDATA pMeshData,
                                        LPD3DXMATERIAL pMaterials, LPD3DXEFFECTINSTANCE pEffectInstances, DWORD NumMaterials,
                                        DWORD *pAdjacency, LPD3DXSKININFO pSkinInfo,
                                        LPD3DXMESHCONTAINER *ppNewMeshContainer);
                STDMETHOD(DestroyFrame)(THIS_ LPD3DXFRAME pFrameToFree);
                STDMETHOD(DestroyMeshContainer)(THIS_ LPD3DXMESHCONTAINER pMeshContainerBase);
                CAllocateHierarchy(CMyD3DApplication *pApp) :m_pApp(pApp) {}
            public:
                CMyD3DApplication* m_pApp;
            };

            [問題]上面的STDMETHOD是什么意思?
            答:相當(dāng)于virtual   HRESULT   __stdcall 的宏。<評論> 因?yàn)檫@種類要與D3D的COM接口打交道,不僅僅在C++內(nèi)部使用,所以,所有類方法必須做成stdcall的,可對外開放的。
            #define   STDMETHOD(method)               virtual   HRESULT   STDMETHODCALLTYPE   method  
            #define   STDMETHODCALLTYPE               __stdcall  
            這樣當(dāng)寫一個函數(shù)STDMETHOD(op1(int   i))      
            展開后成為:     virtual   HRESULT   __stdcall   op1(int   i);  

            3.2 自定義數(shù)據(jù)容器以及具體的讀取過程:
            根據(jù).X文件,在加載過程中,主要有兩方面數(shù)據(jù)需要保存,一個是骨架Frame信息,一個是網(wǎng)格蒙皮Mesh信息。這兩個信息保存在如下結(jié)構(gòu)中。

            框架信息(對應(yīng)于骨骼)
            typedef struct _D3DXFRAME
            {
                LPSTR                   Name;
                D3DXMATRIX              TransformationMatrix; //本骨骼的轉(zhuǎn)換矩陣

                LPD3DXMESHCONTAINER     pMeshContainer;       //本骨骼所對應(yīng)Mesh數(shù)據(jù)

                struct _D3DXFRAME       *pFrameSibling;       //兄弟骨骼
                struct _D3DXFRAME       *pFrameFirstChild;    //子骨骼
            } D3DXFRAME, *LPD3DXFRAME;

            自定義數(shù)據(jù)容器,其數(shù)據(jù)來源由上面接口的CreateMeshContainer()函數(shù)提供
            typedef struct _D3DXMESHCONTAINER
            {
                LPSTR                   Name;       //容器名
                D3DXMESHDATA            MeshData;   //Mesh數(shù)據(jù),可創(chuàng)建SkinMesh取代這個Mesh
                LPD3DXMATERIAL          pMaterials; //材質(zhì)數(shù)組
                LPD3DXEFFECTINSTANCE    pEffects;  
                DWORD                   NumMaterials;//材質(zhì)數(shù)
                DWORD*                  pAdjacency;  //鄰接三角形數(shù)組
                LPD3DXSKININFO          pSkinInfo;   //蒙皮信息,其中含.x中的各個skinweight蒙皮頂點(diǎn)索引及各骨骼偏移矩陣等。
                struct _D3DXMESHCONTAINER *pNextMeshContainer;
            } D3DXMESHCONTAINER, *LPD3DXMESHCONTAINER;


            [評論]
            .在動畫文件中,框架通常用來描述骨骼。可以把Frame視做骨骼,所以不細(xì)加區(qū)分。
            .在上面D3DXFRAME結(jié)構(gòu)中,pFrameSibling, pFrameFirstChild兩個指針,常用于遞歸函數(shù)中,遍歷整個骨架。
            .在D3DXFRAME結(jié)構(gòu)中有一個pMeshContainer指針,難道框架與Mesh是一一對應(yīng)的嗎?
            有一個框架(骨骼)就有一個Mesh嗎?怎么.X文件中只有一個Mesh?難道加載時拆開存放?
            答:從D3DXFrame結(jié)構(gòu)上看,每個Frame都有一個pMeshContainer指針。這就有三種解釋:
               第一種,加載到內(nèi)存后所有的pMeshContainer都指向同一個全局Mesh
               第二種,加載到內(nèi)存后,只有一個主框架的pMeshContainer不為空,其它Frame的pMeshContainer均為NULL,因?yàn)樵?X中,它們沒有定義自己的Mesh
               第三種,加載到內(nèi)存后,D3D將Mesh拆分,分開到各骨骼所對應(yīng)的Frame,每個Frame都有自己的Mesh。
               這個問題我以前也不是很清楚,通過查看示例源碼及跟蹤發(fā)現(xiàn),正確解釋應(yīng)該是第2種。唯一的一個全局Mesh存放在Frame "body"下的無名Frame中。而其它Frame由于沒有自己專門的Mesh而指向NULL. 應(yīng)該大致如此。這個問題之所以讓人困繞,是因?yàn)閺暮罄m(xù)代碼上看,在渲染DrawFrame時,是遍歷每一個frame分別繪制它們對應(yīng)的Mesh. 如果對應(yīng)于同一個mesh,就繪制多遍。如果對應(yīng)各自mesh,那么變換矩陣怎么組織運(yùn)算等等。所以,根據(jù)第二種解釋,由于只有一個pMeshContainer不為NULL,所以參與繪制及蒙皮的只有這一個MeshContainer,人體所有頂點(diǎn)數(shù)據(jù)及蒙皮信息都在這個mesh中。
            所以,讀取tiny.x文件后,會產(chǎn)生多個D3DXFRAME對象,但只有一個D3DXMESHCONTAINER對象。

            在示例代碼的CMyD3DApplication::InitDeviceObjects()中,有:
                hr = D3DXLoadMeshHierarchyFromX(strMeshPath, D3DXMESH_MANAGED, m_pd3dDevice, &Alloc, NULL, &m_pFrameRoot, &m_pAnimController);
                if (FAILED(hr))
                    return hr;
            其中的Alloc是就自定義的數(shù)據(jù)容器對象。m_pFrameRoot是根骨骼,對遍歷很重要。m_pAnimController是動畫控制器,對刷新矩陣很重要。

            你在運(yùn)行完這句話后,下一個斷點(diǎn),觀察m_pFrameRoot,會發(fā)現(xiàn)如下內(nèi)容:

            m_pFrameRoot 0x00c59380 {Name=0x00c53630 "Scene_Root" .....} //根框架
            pMeshContainer 0x00000000
            pFrameSibling 0x00000000
            pFrameFirstChild 0x00c59428 {Name=0x00c53ca8 "body" pMeshContainer=0x00000000...}//子框架 骨骼body
               +---  pMeshContainer 0x00000000
                       +---  pFrameSibling 0x01419f00 {Name=0x00c5ffd8 "Box01" pMeshContainer=0x00000000 ...}//兄弟框架
                  +---  pFrameFirstChild 0x00c594d0 {Name=0x00000000 pMeshContainer=0x00c59828 //子框架---該框架就是.x中含有唯一全局Mesh的無名框架


            可見,在內(nèi)存中的Frame布局是與.x中一一對應(yīng)的。除了pFrameFirstChild 0x00c594d0這個地方的Frame中的pMeshContainer不為空,其它框架的這個mesh指針都是空值。
            另外一點(diǎn)可以看出,并不是每個Frame都對就一塊骨骼,有的是別的用途。也就是說Frame對象的個數(shù)可能多于骨骼數(shù)。

            3.3 分析CAllocateHierarchy類
            下面繼續(xù)研究自定義數(shù)據(jù)容器CAllocateHierarchy,顧名思義,該類是在加載過程中自行分配層次數(shù)據(jù)空間。它有4個成員,都是重載D3D的接口虛函數(shù)。
            它的成員CreateFrame()是用來創(chuàng)建D3DXFrame對象的,而CreateMeshContainer()是用來創(chuàng)建Mesh數(shù)據(jù)對象的。你可以在這兩個函數(shù)中下斷點(diǎn),發(fā)現(xiàn)CreateFrame會運(yùn)行多次,而CreateMeshContainer只運(yùn)行一次,再次驗(yàn)證了上面的說法。

            值得注意的是,示例對上面的D3DXFRAME,D3DXMESHCONTAINER兩個結(jié)構(gòu)做了擴(kuò)展,分別代之以D3DXFRAME_DERIVED結(jié)構(gòu)和D3DXMESHCONTAINER_DERIVED結(jié)構(gòu),以集中存儲數(shù)據(jù)方便程序處理。

            CreateFrame()處理比較簡單,你只是new一個Frame對象空間,填入傳進(jìn)來的Name,其它內(nèi)容由DX負(fù)責(zé)維護(hù)填充。

            CreateMeshContainer()較為復(fù)雜。它的任務(wù)一是保存?zhèn)魅氲木W(wǎng)格數(shù)據(jù)數(shù)據(jù),二是根據(jù)這些數(shù)據(jù)及蒙皮信息調(diào)用GenerateSkinnedMesh()函數(shù)生成蒙皮網(wǎng)格。只有這個新的BlendMesh才能在Render()時支持頂點(diǎn)混合,完成蒙皮的顯示。在D3DXMESHCONTAINER_DERIVED結(jié)構(gòu)中,用pOrigMesh保存舊的Mesh普通網(wǎng)格信息。而Meshdata.Mesh則指向新產(chǎn)生的BlendMesh

            在這個函數(shù)中,多次用到了AddRef(),對COM不熟悉的新手容易困惑。D3D是COM組件,它在服務(wù)進(jìn)程中運(yùn)行,而不在當(dāng)前的客戶進(jìn)程中。在DX組件運(yùn)行過程中,要創(chuàng)建一系列接口對象,如CreateDevice()返回接口指針,這些接口及其占用內(nèi)存什么時候釋放,要通過“引用計(jì)數(shù)”的技術(shù)來解決。AddRef()給這個接口指針的計(jì)數(shù)加1,而Release()會將之減1。一旦減到0,表示沒有客戶使用了,相關(guān)的接口就釋放了。 由此可知,每次調(diào)用Rlease()后,并不一定會釋放內(nèi)存,而是當(dāng)引用計(jì)數(shù)歸0時釋放內(nèi)存。
            這樣,對接口指針的使用,就像維護(hù)堆棧的平衡一樣,要仔細(xì),而且按照某種約定規(guī)則使用。

            但平時D3D編程中,怎么不用AddRef()呢?這是由于一個接口指針,如ID3DDevice,或VertexBuf指針,都是D3DXCreate出來的,在Create時候,在內(nèi)部已經(jīng)事先AddRef()了,你就不需要再做這工作了。只要你在不用時,調(diào)用 p指針->Relase()就釋放了。一般編程,特別是小型示例程序,都是初始化時建立一次,關(guān)閉時釋放,都遵守了這種約定,所以不存在這種問題。

            但在CreateMeshContainer()函數(shù)中,以多種方式使用了指針,在局部指針變量中來回傳遞,所以問題復(fù)雜化了。在COM編程中約定,任何時候地接口指針賦值(復(fù)制),都要AddRef(),在指針變量結(jié)束生命期前,再Release(). 但許多程序員都不是嚴(yán)格這么做。因?yàn)樵诰植孔兞坑猛昃蛷U了,先AddRef()增加計(jì)數(shù)再Release()減少,和直接使用最后是等效的。幾乎是多此一舉。這與編程習(xí)慣有關(guān)系。一旦引用計(jì)數(shù)不對,如果沒有統(tǒng)一的習(xí)慣,不好排查。在CreateMeshContainer()中,對接口指針的使用有三種方式,例舉如下:

            方式一:不使用AddRef()。和普通指針一樣,臨時變量是左值,接口指針是右值,直接賦值使用。如:
                    pMesh = pMeshData->pMesh;
                    這是由于pMesh是局部變量,它只是臨時引用一下,沒必要為它先AddRef(),后Release()。

            方式二:隱式的使用AddRef()。 由于用到了一些內(nèi)部有AddRef()動作的函數(shù),就要按照COM約定,在子程序結(jié)束前Release()
                    pMesh->GetDevice(&pd3dDevice);//此處d3d設(shè)備引用計(jì)數(shù)已經(jīng)加1
                    ....
                    SAFE_RELEASE(pd3dDevice);//--此處將引用計(jì)數(shù)減1,并不是真的釋放d3d設(shè)備
                    在本例中,pd3dDevice在GetDevice()中已經(jīng)Addref()過了,所以,在退出CreateMeshContainer()前,必須pd3dDevice->Release()

            方式三:顯式的使用AddRef()。 如果一個指針值,不是由D3DXCreate出來的,而是通過賦值方式復(fù)制給一個全局變量或長期變量的。 所以,可以通過AddRef()的方式來延遲該對象的釋放。因?yàn)椋绻籄ddRef(),極有可能在函數(shù)返回該對象就可能釋放了。它就像一個加油站,使得傳入對象的壽命延長至自己控制范圍內(nèi)。用了AddRef(),就要在相關(guān)的Destroy中添加Release()。

            在本函數(shù),有三處這樣的語句:
                    pMeshContainer->MeshData.pMesh = pMesh;
                    pMeshContainer->MeshData.Type = D3DXMESHTYPE_MESH;
                    pMesh->AddRef();
                     ....
                    pMeshContainer->pSkinInfo = pSkinInfo;
                    pSkinInfo->AddRef();

                    pMeshContainer->pOrigMesh = pMesh;
                    pMesh->AddRef();
                     ....

                    將來在DestroyMeshContainer()中,要釋放這些指針:
                    ....
                    SAFE_RELEASE( pMeshContainer->MeshData.pMesh );
                    SAFE_RELEASE( pMeshContainer->pSkinInfo );
                    SAFE_RELEASE( pMeshContainer->pOrigMesh );

                    由于這些指針值的創(chuàng)建、更改等都是用戶自己經(jīng)營的,所以務(wù)必要加前后吻合,在CreateMeshContainer()中AddRef(),在DestroyMeshContainer()中Release().


            再來看數(shù)據(jù)的保存部分。
            在CreateMeshContainer()的傳入?yún)?shù)中,有pMeshData,pMaterials,pEffectInstances,NumMaterials,pAdjacency,pSkinInfo
            你需要把這些數(shù)據(jù)保存到自己的D3DXMESHCONTAINER對象中。并且其中的所有數(shù)組所需的空間都要在全局堆中new出來。所以在該代碼中,有如下new:
            pMeshContainer = new D3DXMESHCONTAINER_DERIVED;//自定義的擴(kuò)展數(shù)據(jù)容器對象
            memset(pMeshContainer, 0, sizeof(D3DXMESHCONTAINER_DERIVED));//初始化pMeshContainer,清0
                ...
            pMeshContainer->pMaterials = new D3DXMATERIAL[pMeshContainer->NumMaterials];//準(zhǔn)備保存材質(zhì)
            pMeshContainer->ppTextures = new LPDIRECT3DTEXTURE9[pMeshContainer->NumMaterials];//準(zhǔn)備創(chuàng)建紋理對象。它聲明在擴(kuò)展部分。
            pMeshContainer->pAdjacency = new DWORD[NumFaces*3];//準(zhǔn)備保存鄰接三角形數(shù)組,NumFaces = pMesh->GetNumFaces();

            然后,對數(shù)據(jù)進(jìn)行memcpy保存。pEffectInstances由于在繪制中不需要,并沒進(jìn)行保存。對于沒有貼圖的賦以默認(rèn)材質(zhì)屬性。
            值得注意的是,所有這些new,必須在DestroyMeshContainer()時進(jìn)行delete.

            接下來的處理中,如果發(fā)現(xiàn)Mesh的FVF中沒有法向量,要用CloneMeshFVF()重建Mesh,計(jì)算頂點(diǎn)平均法向量。以備光照處理。

            最后,我們看看蒙皮信息pSkinInfo的處理。這是重頭戲。
            如果發(fā)現(xiàn)pSkinInfo!=NULL,就準(zhǔn)備著手從各個蒙皮骨骼信息創(chuàng)建SkinMesh.
            首先,用擴(kuò)展容器結(jié)構(gòu)D3DXMESHCONTAINER_DERIVED中的各屬性保存原Mesh指針值,pMeshContainer->pOrigMesh = pMesh, 因?yàn)榻酉聛砦覀円獎?chuàng)建SkinMesh替代原Mesh.然后,把SkinInfo中的各骨骼的偏移矩陣保存到pMeshContainer->pBoneOffsetMatrices中
                  cBones = pSkinInfo->GetNumBones();
                  pMeshContainer->pBoneOffsetMatrices = new D3DXMATRIX[cBones];
                  .....
                 每個“骨骼偏移矩陣”pBoneOffsetMatrices,在將來DrawMeshContainer()中是必須要用的。因?yàn)樵糓esh中的頂點(diǎn)數(shù)據(jù)乘以“骨骼偏移矩陣”,再乘以“變換矩陣”,才能求得各骨骼頂點(diǎn)在世界坐標(biāo)系中的坐標(biāo)。 即:
                骨骼上各點(diǎn)在世界坐標(biāo)系中的新坐標(biāo)=初始網(wǎng)格中的各點(diǎn)坐標(biāo)*骨骼偏移矩陣*骨骼當(dāng)前的變換矩陣
                其中,“初始網(wǎng)格中的各點(diǎn)坐標(biāo)*骨骼偏移矩陣” = 骨骼上各點(diǎn)初始時刻在該骨骼坐標(biāo)系中的局部坐標(biāo)

            做了以上工作后,調(diào)用GenerateSkinnedMesh(pMeshContainer),創(chuàng)建SkinMesh. 接下來,我們看看GenerateSkinnedMesh()做了哪些工作。

            3.4 怎樣生成蒙皮網(wǎng)格SkinMesh? GenerateSkinnedMesh()分析

            由于要重定義pMeshContainer->MeshData.pMesh,所以先SAFE_RELEASE( pMeshContainer->MeshData.pMesh ); 釋放原pMesh

            在這個函數(shù)中,是根據(jù)當(dāng)前繪圖方式設(shè)置進(jìn)行加載數(shù)據(jù)的。因?yàn)轫旤c(diǎn)混合,有無索引的頂點(diǎn)混合,有含索引的頂點(diǎn)混合,所使用的函數(shù)和對應(yīng)的SkinMesh數(shù)據(jù)內(nèi)容也有所不同。
            在示例中,自定義了枚舉m_SkinningMethod,主要分為D3DNONINDEXED和D3DINDEXED,以有純軟件渲染等。運(yùn)行示例后,你可以選擇菜單中的Options選擇不同的渲染方式。

            我們著重分析一下帶索引的蒙皮網(wǎng)格。在程序中,就是D3DINDEXED相關(guān)的部分。
            if (m_SkinningMethod == D3DINDEXED){ ....}

            注意! 示例默認(rèn)工作在D3DNONINDEXED下,如果要跟蹤D3DINDEXED部分的代碼,必須選擇菜單中的Options選擇indexed!


            最主要的,要通過DX的ConvertToIndexedBlendedMesh()函數(shù),生成支持“索引頂點(diǎn)混合”的SkinMesh.有關(guān)索引頂點(diǎn)混合的技術(shù),你可以在DXSDK幫助文件中搜索“Indexed Vertex Blending”主題,對著英文和插圖將就看,確有收獲。

            要想用硬件對頂點(diǎn)進(jìn)行混合,那么參與混合者不能太多。也就是說同時影響一個頂點(diǎn)的骨骼數(shù)不能多。我們假定一個頂點(diǎn)最多同時受4個骨骼的影響(也就是同時最多有4個骨骼矩陣參與加權(quán)求和),那么同時影響一個三角形面的骨骼數(shù)最多就是3*4=12個。
            我們用NumMaxFaceInfl表示影響一個三角面的最多骨骼矩陣數(shù),那么,通過調(diào)用pSkinInfo->GetMaxFaceInfluences()獲取這個數(shù)值,一般也就3-4。如果這個數(shù)值太大,我們強(qiáng)制使用NumMaxFaceInfl = min(NumMaxFaceInfl, 12);來最多取值12。

            用NumMaxFaceInfl 這個數(shù)值干什么呢? 我們用來它分析當(dāng)前的顯卡倒底行不行。

            if (m_d3dCaps.MaxVertexBlendMatrixIndex + 1 < NumMaxFaceInfl)//如果顯卡達(dá)不到該要求
            {
                  //很奇怪。2005年底買的GeForce 6600GT顯卡,竟然m_d3dCaps.MaxVertexBlendMatrixIndex=0, 不支持索引頂點(diǎn)混合!是驅(qū)動問題還是怎么了?
                  //但它支持非索引混合。或者,也許要用HLSL支持混合。看起來,3D編程要多考慮。
                   ..
                  pMeshContainer->UseSoftwareVP = true;//用軟件渲染頂點(diǎn)。顯然不實(shí)用。
            }
            else
            {
                  pMeshContainer->NumPaletteEntries = min( ( m_d3dCaps.MaxVertexBlendMatrixIndex + 1 ) / 2,
                                                                 pMeshContainer->pSkinInfo->GetNumBones() );//--什么意思?
                  pMeshContainer->UseSoftwareVP = false;//采用硬件頂點(diǎn)混合。
                  Flags |= D3DXMESH_MANAGED;
            }

            [評論]在上面有一行代碼:
                 pMeshContainer->NumPaletteEntries = min( ( m_d3dCaps.MaxVertexBlendMatrixIndex + 1 ) / 2,pMeshContainer->pSkinInfo->GetNumBones() );
            盡管作者加了大段注釋,還是讓人一頭霧水。其實(shí),我們做一個實(shí)驗(yàn),反爾更能理解它的用途。
            第一步,你在這句話后面下一個斷點(diǎn),看一下在你機(jī)器上這個數(shù)值。我的ATI 9550顯卡機(jī)器上是19。比tiny.x中的骨骼數(shù)35少很多。
            第二步,你將上面=右邊瞎填一個大于4的數(shù)字,比如6。編譯后照樣運(yùn)行。而且效果上幾乎看不出任何差別。
            為什么會這樣呢? 我們在繪制代碼部分,看看這個數(shù)值起什么作用。
            在DrawMeshContainer()代碼中,我們查找D3DINDEXED相關(guān)的部分。在mesh各子集的DrawSubset()之前,有如下代碼:
                  for (iAttrib = 0; iAttrib < pMeshContainer->NumAttributeGroups; iAttrib++)
                  {
                            // first calculate all the world matrices
                            for (iPaletteEntry = 0; iPaletteEntry < pMeshContainer->NumPaletteEntries; ++iPaletteEntry)
                            {
                                iMatrixIndex = pBoneComb[iAttrib].BoneId[iPaletteEntry];
                                if (iMatrixIndex != UINT_MAX)
                                {
                                    D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatrices[iMatrixIndex], pMeshContainer->ppBoneMatrixPtrs[iMatrixIndex] );
                                    m_pd3dDevice->SetTransform( D3DTS_WORLDMATRIX( iPaletteEntry ), &matTemp );
                                }
                            }
                   ...
                  }
            下面仔細(xì)評估一下這些代碼.
            先注意看其中奇怪的D3DTS_WORLDMATRIX()宏,我們以前還沒這樣用過。它是做什么用的呢?通過查DXSDK幫助,我們在Geometry Blending主題中找到相關(guān)說明,并在"Indexed Vertex Blending"主題中給出了內(nèi)部實(shí)現(xiàn)原理。原來,當(dāng)你用m_pd3dDevice->SetRenderState(D3DRS_INDEXEDVERTEXBLENDENABLE, TRUE);開啟了索引頂點(diǎn)混合后,在硬件上就啟用了“palette of matrices”,即矩陣寄存器組,它最多支持同時256個索引。就像過去用256色調(diào)色板來表現(xiàn)彩色一樣。D3DTS_WORLDMATRIX()宏就是有256-511這256個數(shù)表示矩陣索引號。

            這些矩陣參與如下計(jì)算:

            V最終頂點(diǎn)位置=V*M[索引值1]*權(quán)重1 + V*M[索引值2]*權(quán)重2 + ....+V*M[索引n]*(1-其它權(quán)重和)

            這個公式的來源,相信大家在眾多資料上見過,不贅述。 當(dāng)然,我們也可以用程序完成這個蒙皮計(jì)算過程,但逐個讀頂點(diǎn)卻很麻煩。現(xiàn)在是由硬件代勞了。我們只設(shè)矩陣就行了。
            我們用m_pd3dDevice->SetTransform( D3DTS_WORLDMATRIX( iPaletteEntry ), &matTemp );這種方式設(shè)定各索引對應(yīng)的矩陣。

            那么權(quán)重呢?我們怎么設(shè)?原來在上面所說的DX提供的ConvertToIndexedBlendedMesh()函數(shù)中,生成SkinMesh時,各網(wǎng)格頂點(diǎn)格式FVF已經(jīng)有變化了,增加了新格式,D3DFVF_XYZB2,D3DFVF_LASTBETA_UBYTE4,用以記錄頂點(diǎn)對應(yīng)的權(quán)重值以及矩陣索引。如下
            struct VERTEX
            {
                float x,y,z;
                float weight;
                DWORD matrixIndices;
                float normal[3];
            };
            #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZB2 | D3DFVF_LASTBETA_UBYTE4 |D3DFVF_NORMAL);

            D3DFVF_LASTBETA_UBYTE4對應(yīng)于DWORD數(shù)值,用于矩陣索引時,每個字節(jié)表示一個索引,最多可以允許4個索引,同時有4個矩陣參于該點(diǎn)的混合。如果一次繪制中涉及了9塊骨骼矩陣,你可以把這9個矩陣全部用SetTransform設(shè)置到矩陣寄存器中,但每個頂點(diǎn)在渲染時,最多使用其中的4個。由此可知,pMeshContainer->NumPaletteEntries這個數(shù)值,確定了一趟DrawSubset繪制所用到的矩陣個數(shù),個數(shù)越多,在一趟繪制中就可以納入的更多頂點(diǎn)。所以,當(dāng)我們減少pMeshContainer->NumPaletteEntries這個數(shù)值時,pMeshContainer->NumAttributeGroups數(shù)值就會增加。也就是說,一趟繪制中所允許涉及的骨骼數(shù)越少,那么子集的數(shù)量NumAttributeGroups就會增加,需要多繪幾趟。
            你可以在此下斷點(diǎn)觀察,當(dāng)NumPaletteEntries=19時,NumAttributeGroups=3 當(dāng)NumPaletteEntries=6時,NumAttributeGroups=12 當(dāng)NumPaletteEntries=4時,NumAttributeGroups=31,幾乎和無索引時的分組一樣多了。

            頂點(diǎn)中的權(quán)重weight存放了它當(dāng)前骨骼的權(quán)重。(一個頂點(diǎn)對應(yīng)的多個骨骼權(quán)重怎么存放?是不是在當(dāng)前子集中有多個同樣的頂點(diǎn),權(quán)重不同,對應(yīng)的矩陣索引不同,然后混合)


            由上所述,ConvertToIndexedBlendedMesh()是一個很重要函數(shù),由DX自動將Mesh頂點(diǎn)分組成多個子集,以便DrawSubset. 你必須把它的返回參數(shù)都記錄下來,在繪制時使用。

             

            四. 怎樣繪制顯示動畫?

            DrawFrame()用來繪制整個X框架。它遍歷各個框架,找到Mesh不為空的進(jìn)行繪制。(其實(shí)整個.x中通常只有一個不為空,見上文所述)
            DrawMeshContainer()是繪制函數(shù)。

            4.1 怎樣開啟頂點(diǎn)混合?
            注意應(yīng)用有關(guān)的Vertex Blending技術(shù)。如在索引方式的繪制中,
            m_pd3dDevice->SetRenderState(D3DRS_VERTEXBLEND, pMeshContainer->NumInfl - 1);
            其實(shí)是設(shè)定了D3DVBF_2WEIGHTS或D3DVBF_3WEIGHTS
            注意要m_pd3dDevice->SetRenderState(D3DRS_INDEXEDVERTEXBLENDENABLE, TRUE);

            4.2 矩陣的刷新:
            首先,在FrameMove()調(diào)用m_pAnimController->SetTime()設(shè)置當(dāng)前時間(或在DX9.0c中用AdvanceTime()設(shè)置時間差),從而刷新各個pFrame->TransformationMatrix,即骨骼轉(zhuǎn)換矩陣
            其次,調(diào)用UpdateFrameMatrices()做乘法累積,計(jì)算出各骨骼坐標(biāo)系到根世界轉(zhuǎn)換矩陣。
            最后,在繪制前,將該轉(zhuǎn)換矩陣左乘偏移矩陣,得到最終的轉(zhuǎn)換矩陣。
                  D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatrices[iMatrixIndex], pMeshContainer->ppBoneMatrixPtrs[iMatrixIndex] );

            由此可見,你如果注釋掉了m_pAnimController->SetTime,畫面肯定停了。

            4.3 繪制輸出 是在DrawMeshContainer()中,調(diào)用SkinMesh的DrawSubset進(jìn)行繪制。一些細(xì)節(jié)內(nèi)容如D3DTS_WORLDMATRIX(),在上面已經(jīng)有說明,不再羅嗦。

             

            4.4 關(guān)于示例中多種繪制方式分析
            在示例中,用到了多種渲染方式,包括傳統(tǒng)的非索引頂點(diǎn)混合,還有新興的HLSL方式。而且我發(fā)現(xiàn),ATI RADEON 9550 顯卡MaxVertexBlendMatrixIndex=37,而價格更高的Gefoce 6600GT MaxVertexBlendMatrixIndex竟然為0,不支持index vertex blending!
            所以,還是有必要分析一下該示例中各種vertex blending方式的處理,以便掌握多種繪制方式適應(yīng)不同顯卡。
            經(jīng)測試,示例中所涉及的多種方式,由慢到快,依次是以下幾種:
                SOFTWARE,
                D3DNONINDEXED,
                D3DINDEXED,
                D3DINDEXEDVS,
                D3DINDEXEDHLSLVS,

            從最慢的SW到最快的HLSL,大約相差20%,有時會大到40%。 差別不是特別懸殊的原因,主要是頂點(diǎn)混合并不是瓶頸。

            關(guān)于頂點(diǎn)處理方式,是在創(chuàng)建D3D設(shè)備時指定的。共有三種方式:
               D3DCREATE_SOFTWARE_VERTEXPROCESSING 軟件頂點(diǎn)運(yùn)算  (簡記 sw vp)
               D3DCREATE_HARDWARE_VERTEXPROCESSING 硬件頂點(diǎn)運(yùn)算。必須有這項(xiàng)才支持有HAL (簡記 hw vp)
               D3DCREATE_MIXED_VERTEXPROCESSING 混合頂點(diǎn)運(yùn)算,即硬件+軟件 (簡記 mixed vp)

            一旦用D3DCREATE_HARDWARE_VERTEXPROCESSING方式創(chuàng)建設(shè)備,就只能在硬件方式下進(jìn)行頂點(diǎn)處理。如果調(diào)用m_pd3dDevice->SetSoftwareVertexProcessing(TRUE)來切換到軟件頂點(diǎn)處理,HRESULT會返回失敗。
              所以,如果你對客戶的顯卡沒有足夠的信息,就用D3DCREATE_MIXED_VERTEXPROCESSING方式創(chuàng)建設(shè)備。它默認(rèn)工作方式是HAL。一旦發(fā)現(xiàn)進(jìn)行某種繪制時硬件能力不夠,就可以調(diào)用調(diào)用m_pd3dDevice->SetSoftwareVertexProcessing(TRUE)切換到軟件模式。在示例中就是這么做的,啟動示例后,運(yùn)行在mixed模式下。

            在Gefoce6600GT顯卡中,由于D3DINDEXED方式不支持,采用了軟件混合方式,在這種方式下速度甚至比SOFTWARE慢。HLSL還好,還是最快。

            要確定設(shè)備的硬件頂點(diǎn)處理能力,可以參考D3DCAPS9結(jié)構(gòu)的VertexProcessingCaps成員。可以獲取下列屬性
            MaxActiveLights,MaxUserClipPlanes,MaxVertexBlendMatrices,MaxStreams,MaxVertexIndex

            (1)D3DNONINDEXED方式:

            首先看GenerateSkinnedMesh()中怎樣創(chuàng)建蒙皮網(wǎng)格的。
            這種方式下,用ConvertToBlendedMesh()建立蒙皮網(wǎng)格,而不是ConvertToIndexBlendedMesh()

            為了繪制蒙皮,在這個函數(shù)中對Mesh各子集的頂點(diǎn)再次進(jìn)行的分組。分組的標(biāo)準(zhǔn)是各頂點(diǎn)(或三角面)所涉及的骨骼矩陣個數(shù)不超過pMeshContainer->NumInfl個。(這個數(shù)字是由在ConvertToBlendedMesh()時,由參數(shù)pMaxFaceInfl返回的)。一個Mesh子集可能被拆開成多個分組。 最后,分組的屬性保存在pBoneCombinationBuf中,如子集ID,該子集的各骨骼ID,起始三角面,三角面?zhèn)€數(shù)等供繪制時使用,分組的個數(shù)保存在pMeshContainer->NumAttributeGroups中。

            接下來檢查每個分組所涉及的骨骼數(shù),是不是超過硬件允許的最大混合矩陣數(shù)---MaxVertexBlendMatrices。如果超過了就把所有分組截為兩大部分,前一部分用硬件混合,后一部分采用軟件混合。而且,一旦發(fā)現(xiàn)有需要軟件混合,要采用CloneMeshFVF(D3DXMESH_SOFTWAREPROCESSING|...)的方式重新生成網(wǎng)格。

            再來看繪制部分DrawMeshContainer()

            用pBoneComb指向骨骼分組屬性,掃描各分組。找出其中骨骼數(shù)滿足硬件性能的用進(jìn)行繪制。
            然后開啟軟件頂點(diǎn)渲染m_pd3dDevice->SetSoftwareVertexProcessing(TRUE),對那些骨骼數(shù)超出硬件性能的進(jìn)行繪制。
            SetSoftwareVertexProcessing()需要當(dāng)前d3d設(shè)備以D3DCREATE_MIXED_VERTEXPROCESSING方式創(chuàng)建。

            (2)D3DINDEXED,這種方式上面分析過了,從略。用pMeshContainer->UseSoftwareVP表示是否采用軟件繪制。
            值得注意的是在這種方式下,一旦硬件性能不足,會徹底使用軟件頂點(diǎn)渲染,而不是像上面一樣拆為兩部分。

            (3)D3DINDEXEDVS,D3DINDEXEDHLSLVS
            這種情況下使用了著色器和高級著色語言。超出本文主旨,討論從略。

            (4)SOFTWARE--軟件方式? 讓人有些迷惑,與上面的m_pd3dDevice->SetSoftwareVertexProcessing(TRUE)有何區(qū)別?

            從代碼看,這種方式下反而比較簡單。GenerateSkinnedMesh()中,
            先直接從原始Mesh克隆一個Mesh,然后讀取它的材質(zhì)屬性數(shù)組。開辟一個空間m_pBoneMatrices,用以存放各塊骨骼的轉(zhuǎn)換矩陣。

            在繪制時,從pMeshContainer中的變換矩陣乘以偏移矩陣,放在pBoneMatrices中。把這個矩陣數(shù)組,以原Mesh的頂點(diǎn)作為源頂點(diǎn),以新克隆的MeshData.pMesh做為目標(biāo)頂點(diǎn),調(diào)用pSkinInfo->UpdateSkinnedMesh(),用軟件方式計(jì)算各骨骼頂點(diǎn)的新位置(相當(dāng)于軟件計(jì)算方式蒙皮)。

            然后調(diào)用MeshData.pMesh->DrawSubset()繪制。

            可見,在SOFTWARE方式下,最終頂點(diǎn)的渲染還是HAL方式的,只不過蒙皮計(jì)算是由軟件完成的。它和上面的m_pd3dDevice->SetSoftwareVertexProcessing(TRUE)直接設(shè)置軟件頂點(diǎn)渲染還是有區(qū)別的。

            posted on 2009-11-16 00:47 麒麟子 閱讀(2040) 評論(2)  編輯 收藏 引用 所屬分類: DirectX

            評論

            # re: 關(guān)于骨骼動畫及微軟示例Skinned Mesh的解析 2012-05-16 08:19 文西

            正在學(xué)習(xí) 感謝樓主精彩分享  回復(fù)  更多評論   

            # re: 關(guān)于骨骼動畫及微軟示例Skinned Mesh的解析 2013-03-26 18:35 永遇樂

            作者花了大氣力分析,很受用,謝謝分享。  回復(fù)  更多評論   

            狠狠色婷婷久久一区二区| 91精品国产高清91久久久久久| 久久久久黑人强伦姧人妻| 久久亚洲日韩看片无码| 成人妇女免费播放久久久| 久久综合成人网| 国内精品久久久久影院优| 久久久精品无码专区不卡| 色偷偷偷久久伊人大杳蕉| 国产精品熟女福利久久AV| 中文字幕日本人妻久久久免费 | 亚洲精品乱码久久久久久蜜桃| 久久综合亚洲色一区二区三区| 久久99精品久久久久久hb无码| 亚洲精品WWW久久久久久| 91精品婷婷国产综合久久 | 国产成人久久激情91| 久久久久久久久66精品片| 精品无码久久久久久久动漫| 精品熟女少妇a∨免费久久| 亚洲精品久久久www| 久久国产视屏| 久久精品人妻一区二区三区| 久久99中文字幕久久| 亚洲va中文字幕无码久久不卡| 久久亚洲电影| 久久强奷乱码老熟女| 国产成人无码精品久久久久免费 | 久久青青草原精品国产| 亚洲人成无码www久久久| 93精91精品国产综合久久香蕉| 97久久国产露脸精品国产| 久久人人爽人人爽人人片AV高清 | 996久久国产精品线观看| 久久亚洲精品成人AV| 新狼窝色AV性久久久久久| 婷婷久久香蕉五月综合加勒比| 狠狠色婷婷久久一区二区| 亚洲AV无码一区东京热久久| 东方aⅴ免费观看久久av| 久久人人妻人人爽人人爽|