骨骼動畫及微軟示例:Skinned-Mesh的解析_第1頁
骨骼動畫及微軟示例:Skinned-Mesh的解析_第2頁
骨骼動畫及微軟示例:Skinned-Mesh的解析_第3頁
骨骼動畫及微軟示例:Skinned-Mesh的解析_第4頁
骨骼動畫及微軟示例:Skinned-Mesh的解析_第5頁
已閱讀5頁,還剩6頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、精選優(yōu)質文檔-傾情為你奉上骨骼動畫及微軟示例:Skinned Mesh的解析骨骼動畫是D3D的一個重要應用。盡管微軟DXSDK提供了示例Skinned Mesh,但由于涉及眾多概念和技術細節(jié),示例相對于初學者非常復雜,難以看懂。在此,提供一些重要問題評論,以使初學者走出迷局,順利上手。文中所述都是參照各種資料加上自己的理解,也有可能出些偏差,有則回貼拍磚,無則權當一笑。  V1 M6 J# X$ P8 _" 0 T  b一 骨骼動畫原理原理方面在網上資料比較多,大家都基本明白。在此說一下重點:總體上,絕大部分動畫實現原理一致,就是“提供一種機制

2、,描述各頂點位置隨時間的變化”。有三種方法:! |' s9 _9 L; M! x" _1.1 關節(jié)動畫:由于大部分運動,都是皮膚隨骨骼在動,皮膚相對于它的骨骼本身并沒有發(fā)生運動,所以只要描述清楚骨骼的運動就行了。用矩陣描述各個骨骼的相對于父骨骼運動。(大多運動都是旋轉型) 易知,從子骨骼用矩陣乘法累積到最頂層根骨骼,就可以得到每個子骨骼相對于世界坐標系的轉換矩陣。  這種動畫,只須用普通Mesh保存最初始的各頂點坐標,以及一系列后續(xù)時刻所對應的各骨骼的運動矩陣。不用保存每時刻的頂點數據,節(jié)省了大量存儲空間。而且比較靈活,可以利用關鍵幀插值運算,便于通過運算

3、調節(jié)動作。缺點是在兩段骨骼交接處,容易產生裂縫,影響效果。1.2 漸變動畫:通過保存一系列時刻的頂點坐標來完成動畫。雖然比較逼真,但占用大量空間,靈活性也不高。1.3 骨骼蒙皮動畫(skinned Mesh)$ T3 P# J, y4 F9 Q& r" C- R6 U  相當于上面兩方法的折中。現在比較流行。/ A' R4 r5 L; R4 C  在關節(jié)動畫的基礎上,利用頂點混合(Vertex Blend)技術,對于關節(jié)附近的頂點,由影響這些頂點的兩段(或多段)骨骼運動,分別賦以權值,共同決定頂點位置。相當于在骨骼關節(jié)上動態(tài)蒙皮

4、,有效解決了裂縫問題。+ r2 E/ c+ O+ R: o  這里,引入一個D3D技術概念:“Vertex Blending”-頂點混合技術。比如說,你肯定用過SetTransform(D3DTS_WORLD,.),但SetTransform(D3DTS_WORLDMATRIX(i),.)是不是很奇怪?這個問題后文會講到。 你也可以在微軟的DXSDK的幫助文件中搜索“Geometry Blending”主題,有裂縫及其解決辦法圖示。  Z. u" 1 : r  A8 u二 X文件如何保存骨骼動畫理解X文件格式,對用好相關的D

5、X函數是非常重要的。不含動畫的普通X文件,有一個Mesh單元,保存了各頂點信息、各三角面的索引信息、材質種類及定義等。) f& N5 t  , O   G7 F: Z0 o. c動畫X文件,則在這個單元中增加了“各骨骼蒙皮信息”、“骨骼層次及結構信息”、“各時刻骨骼矩陣信息”等。( ?  m% A5 1 w( q: 2.1 網格蒙皮信息:首先,在Mesh單元中,在原有的普通網格頂點數據基礎上,新增了XSkinMeshHeader結構,以及多個SkinWeights結構。用以描述各個骨骼的蒙皮信息。0   S

6、2 I; C; b2 b. t& Z7 R其中,XSkinMeshHeader是總括,舉一實例,如下:  K- t. ?" ?9 J3 r9 G0 b2 G8 D. |XSkinMeshHeader& a2 8 J- o( l- p% m  R$ p8 R2,/一個頂點可以受到骨骼影響的最大骨骼數,可用于計算共同作用時減少遍歷次數  x7 & l! A0 d) b# O9 5 7 G4,/一個三角面可以受到骨骼影響的最大骨骼數。這個數字對硬件頂點混合計算提出了基本要求。35 /當前Mesh的骨骼總數。

7、由于每個骨骼的蒙皮信息都需要用SkinWeights結構去描述,所以有多少塊骨骼,在Mesh中就有多少個SkinWeights對象。1 l% q9 V4 j+ / ?3 J# _+ s注意,一般把SkinWeights視作Mesh的一部分。這種Mesh又稱Skinned Mesh (蒙皮網格)SkinWeights 結構如下:+ t4 + 0 B/ q& 3 Y4 G; K  STRING      transformNodeName;      /骨骼名8 p- J7 G/ e- y+

8、 B8   DWORD       nWeights;               /權重數組的元素個數,即該骨骼相關的頂點個數  array DWORD vertexIndicesnWeights;/受該骨骼控制的頂點索引,實際上定義了該骨骼的蒙皮2 - Q! S1 M% l) Q  array float weightsnWeights;      /蒙

9、皮各頂點的受本骨骼影響的權值$ L  G( * C$ p  Matrix4x4   matrixOffset;           /骨骼偏移矩陣,用來從初始Mesh坐標,反向計算頂點在子骨骼坐標系中的初始坐標。0 d, c# S, c" C) R0 K2 Q, Y) / Y; q0 d# R在有的書中,把上面的matrixOffset叫骨骼權重矩陣,是不恰當的。應該稱為骨骼偏移矩陣比較合適。* d* J2 : S- * L) W4 N問題

10、60;在整個動畫過程中,子骨骼運動矩陣的數值是不斷變化的。上面的骨骼偏移矩陣變化嗎?有沒有必要重新計算?它在什么時候使用?1 |1 u4 i% q1 M, L$ X&   P* j答:各骨骼的偏移矩陣matrixOffset專門用來從原始Mesh數據計算出各頂點相對于骨骼坐標系的原始坐標。在繪制前,把它與當前變換矩陣相乘,就可以得到該骨骼的當前的最終變換矩陣。 總之,骨骼偏移矩陣是與原始Mesh頂點數值相關聯的,在整個動畫過程中是不變的,也不應該變。在動畫過程中變化是當前骨骼變換矩陣,可由.X中的AnimatonKey中的各時刻矩陣得到。這個矩陣乘法在示例中的對應代

11、碼如下:D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatricesiMatrixIndex, pMeshContainer->ppBoneMatrixPtrsiMatrixIndex );: 9 + t8 a# U; v' i即,D3DXMatrixMultiply(輸出最終世界矩陣, 該骨骼的偏移矩陣, 該骨骼的變換矩陣)' F8 z; # v. W" S2.2 骨骼層次信息4 M2 H6 & 3 ?- 2 E在X文件中,Frame是基本的組成單元。又稱框架

12、Frame。 一個.x可以有多個Frame。(注意此處的Frame不是幀,與幀沒什么關系)框架Frame允許嵌套,這樣就存在父子框架了。而并列的框架,稱為兄弟框架。這兩種關系組合在一起,即可以縱深,又可以并列,形成一種層次結構。這種結構,可用二叉樹描述。# ) K5 9 f2 0   L每個框架結構的最前面,有一個FrameTransformMatrix矩陣數據,描述了該框架相對于父框架的變換矩陣。也就是說,該框架中的坐標,與該矩陣相乘,可轉換為父框架坐標系的坐標。8 D; j/ H4 Z% c! : W這種層次結構,使得X文件能描述許多復雜的物體。如地形場景。在骨骼動畫文

13、件中,框架結構可直接拿來描述人物骨骼的層次結構??蚣艿拿滞ǔ閷墓趋烂?。如“左上臂->左前臂->手掌->手指”就形成一個父子骨骼鏈。而左上臂與右上臂是并行關系。- n2 D5 V- & G數據示例: D:D9XSDKSamplesMediatiny.x, P7 P5 + r2 T1 eFrame ., F; J: u  y& m5 c  ., I! |: y: l) ?! b+ _% ,   Frame Bip01_R_Calf /子骨骼      

14、60;      FrameTransformMatrix ( K& f& y% q' M  f        1.,-0.,-0.,0.,0.,1.,-0.,0.,0.,0.,1.,0.,119.,0.,-0.,1.;# V  7 T( ! o; w5                Frame Bip01_R_Foot /-孫子骨骼5 g&#

15、160; Z. c7 i, o+ N! l, J      : m; A3 U( - m6 Z! c* o7         FrameTransformMatrix : X4 q8 l2 n: v/ Q' # M2          0.,0.,0.,0.,-0.,0.,-0.,0.,-0.,0.,0.,0.,119.,-0.,0.,1.;/ K3 s2 ' 0 h! / H  C

16、0;               .縮進    & 3 K/ + S7 i+ l4 Z. |問題查看示例tiny.x文件,發(fā)現只有根框架下有一個Mesh,包含了所有頂點信息。其它各個Frame都沒有Mesh數據。怎么理解?答: 一般來說,每個動畫文件只有一個Mesh網格,包含物體所有頂點信息。3 L; _3 f( c* M0 ?1 R     其它Frame,只是借用來描述各骨骼的層次信息,沒必要再定義骨骼網格。每塊骨

17、骼對應的蒙皮頂點信息,由根Mesh中的相應骨骼的SkinWeights中蒙皮頂點索引描述的。在動畫過程中,各個頂點的新坐標,要借助SkinWeights中的頂點索引來進行重新計算。: f7 |* J# I6 $ x/ Z3 p2.3 動畫信息:& O& H* M6 q4 F由一系列AnimatonKey組成,數據示例如下:3 R6 I- v7 Q8 G0 H4 a  AnimationKey 3 & _+ z) |1 S; 0 b   4;-動畫類型 4表示矩陣   62; -動畫幀數,即下面矩陣個數3 Y%

18、m1 k/ j/ g. |2 t& W   0;16;1.,-0.,-0.,0.,0.,1.,0.,0.,0.,-0.,1.,0.,119.,-0.,0.,1.;,   80;16;0.,-0.,-0.,0.,0.,0.,0.,0.,-0.,-0.,1.,0.,119.,0.,-0.,1.;,   .上面紅數字表示時刻tick,蘭數字表示數值的個數。   .其它各時刻矩陣., R: T# v& D' F  I" H' H# n   

19、Bip01_R_Calf -對應的骨骼對象引用3 |; H7 u0 U6 j) N, l6 D& b  - p$ h, h# i* G" O  - t' L注意:) n* m! Y+ j/ W4 q+ e/ Z. a(1)每塊骨骼都有一個AnimationKey.9 K; y8 z/ ?9 u5 A2 F(2)在上面數據結構中,主要保存了各典型時刻的該骨骼相對于父的變換矩陣.(3)在0時刻的矩陣,與該骨骼對應的前面的Frame所對應的矩陣是相同的。如Frame Bip01_R_Calf中的變換矩陣,與Bip01_R_Calf所對

20、應的AnimationKey 的第0時刻矩陣是一樣的。這說明,在以后動畫運行時,DX會提供一種功能,用AnimatonKey中的對應數據刷新初始的變換矩陣(也可能啟用關鍵幀插值算法)。這個功能對應于示例中的m_pAnimController->SetTime(.)語句。三 怎樣從X文件加載骨骼動畫信息?3.1 負責加載的函數:  可能有多種加載方式,在此以SDK中的示例為準,敘述一種標準加載方式,需要用到DX函數D3DXLoadMeshHierarchyFromX(),函數字面意思是讀取Mesh層次信息。1 q$ I5 Z6 g6 n' w+ g3 GHRES

21、ULT WINAPI % F( B) % b1 t    D3DXLoadMeshHierarchyFromX(        LPCSTR Filename,                 /.x文件名& T: ! O8 R. X0 z1 W8 # Q9 N        DWORD MeshOptions,

22、0;              /Mesh選項,一般選D3DXMESH_MANAGED0 & H6 a! V( j7 Z1 i0 G$         LPDIRECT3DDEVICE9 pD3DDevice,    /指向D3D設備Device4 n* o0 a/ * v9 x( W5 e4 & o        LPD3DXALLOCATEHIER

23、ARCHY pAlloc,  /自定義數據容器# " K3 Y" 2 S5 I/ P8 # p        LPD3DXLOADUSERDATA pUserDataLoader,  /一般選NULL        LPD3DXFRAME *ppFrameHierarchy,       /返回根Frame指針,指向代表整個骨架的Frame層次結構3 v# P) J7 / x;

24、 / o        LPD3DXANIMATIONCONTROLLER *ppAnimController /返回相應的動畫控制器; 8 A" g. + ?+ h);這個函數后面的兩個輸出參數很重要,也很好理解,但輸入參數中的自定義數據容器是怎么回事呢?原來,鑒于動畫數據的復雜性,需要你配合完成加載過程。比如你是否用到自定義擴展結構,Mesh等數據保存在哪里,怎樣使用戶自己創(chuàng)建容器,自己決定卸載等等。 DX提供了ID3DXALLOCATEHIERARCHY接口,提供了這個自定義的機會,你重載這個接口的虛函數,在

25、加載過程中,它就像回調函數那樣運作。你需要像下面這樣建立一個自定義數據容器類:, y3 Y, d' i, y/ Y- c$ H6 class CAllocateHierarchy: public ID3DXAllocateHierarchypublic:9 q1 6 d: V0 a. T+ 3 B& ! _    STDMETHOD(CreateFrame)(THIS_ LPCTSTR Name, LPD3DXFRAME *ppNewFrame);9 ! d0 B) z/ _$ H( X    STDMETHOD(CreateMeshCo

26、ntainer)(THIS_ LPCTSTR Name, LPD3DXMESHDATA pMeshData,                            LPD3DXMATERIAL pMaterials, LPD3DXEFFECTINSTANCE pEffectInstances, DWORD NumMaterials,         

27、0;                   DWORD *pAdjacency, LPD3DXSKININFO pSkinInfo,                             LPD3DXMESHCONTAINER *ppNewMeshContainer);  

28、60; STDMETHOD(DestroyFrame)(THIS_ LPD3DXFRAME pFrameToFree);6 C  G. n* O9 A: W2 a" S    STDMETHOD(DestroyMeshContainer)(THIS_ LPD3DXMESHCONTAINER pMeshContainerBase);# 6 U4 4 f3 ?5 p' O- P    CAllocateHierarchy(CMyD3DApplication *pApp) :m_pApp(pApp) 4 A% q"

29、; ' P; % M1 U8 public:    CMyD3DApplication* m_pApp;- h: J; j6 R0 j$ P* ?1 I- c;9 ?2 N* A% D8 f. Z2 t問題上面的STDMETHOD是什么意思?0 x3 X: C; M$ k答:相當于virtual   HRESULT   _stdcall 的宏。<評論> 因為這種類要與D3D的COM接口打交道,不僅僅在C+內部使用,所以,所有類方法必須做成stdcall的,可對外開放的。5 O& h+ D' _+ v

30、9 ?1 ?* n) C* p#define   STDMETHOD(method)               virtual   HRESULT   STDMETHODCALLTYPE   method     ?/ j% 5 n% , M# #define   STDMETHODCALLTYPE      

31、60;        _stdcall   這樣當寫一個函數STDMETHOD(op1(int   i)       2 d1 z5 K& . 5 : , Z展開后成為:     virtual   HRESULT   _stdcall   op1(int   i);   " . W/ n1 T8 t

32、% J$ b. L' w3.2 自定義數據容器以及具體的讀取過程:5 S' W2 Q% j4 ( N. M6 W  O根據.X文件,在加載過程中,主要有兩方面數據需要保存,一個是骨架Frame信息,一個是網格蒙皮Mesh信息。這兩個信息保存在如下結構中??蚣苄畔?對應于骨骼)typedef struct _D3DXFRAME2 R* N0 s4 h/ e" x9 X* :     LPSTR                

33、;   Name;4 v$ s8 V& F8 O+ g5 p    D3DXMATRIX              TransformationMatrix; /本骨骼的轉換矩陣% F* T! y% b$ s3 O3 |/ |) i$ 6 Z    LPD3DXMESHCONTAINER     pMeshContainer;       /本骨骼所對應Mes

34、h數據    struct _D3DXFRAME       *pFrameSibling;       /兄弟骨骼5 ; c3 - i+ m+ k    struct _D3DXFRAME       *pFrameFirstChild;    /子骨骼9 Y/ U. b8 H, v! g9 g D3DXFRAME, *LPD3DXFRAME;自定義數據容器,其數據來源由上面接口的CreateMeshContain

35、er()函數提供, U  h' f' T: Ctypedef struct _D3DXMESHCONTAINER    LPSTR                   Name;       /容器名    D3DXMESHDATA            MeshData; 

36、;  /Mesh數據,可創(chuàng)建SkinMesh取代這個Mesh    LPD3DXMATERIAL          pMaterials; /材質數組    LPD3DXEFFECTINSTANCE    pEffects;       DWORD                   NumMaterials

37、;/材質數5 6 I  _6 X- H( k1 E9 s    DWORD*                  pAdjacency;  /鄰接三角形數組    LPD3DXSKININFO          pSkinInfo;   /蒙皮信息,其中含.x中的各個skinweight蒙皮頂點索引及各骨骼偏移矩陣等。&

38、#160; i9 R; b+ H/ W( Y, V&     struct _D3DXMESHCONTAINER *pNextMeshContainer; D3DXMESHCONTAINER, *LPD3DXMESHCONTAINER;評論, 3 , P+ r% # 6 ?.在動畫文件中,框架通常用來描述骨骼??梢园袴rame視做骨骼,所以不細加區(qū)分。.在上面D3DXFRAME結構中,pFrameSibling, pFrameFirstChild兩個指針,常用于遞歸函數中,遍歷整個骨架。' g" 3 I/ x( N+ F! m, j5 z

39、.在D3DXFRAME結構中有一個pMeshContainer指針,難道框架與Mesh是一一對應的嗎?有一個框架(骨骼)就有一個Mesh嗎?怎么.X文件中只有一個Mesh?難道加載時拆開存放?答:從D3DXFrame結構上看,每個Frame都有一個pMeshContainer指針。這就有三種解釋:# k9 z0 3 s  l4 Z4 d2 / q   第一種,加載到內存后所有的pMeshContainer都指向同一個全局Mesh( m4 z: |6 k# w& v   第二種,加載到內存后,只有一個主框架的pMeshConta

40、iner不為空,其它Frame的pMeshContainer均為NULL,因為在.X中,它們沒有定義自己的Mesh   第三種,加載到內存后,D3D將Mesh拆分,分開到各骨骼所對應的Frame,每個Frame都有自己的Mesh。3 I' W4 p! x7 Z% h9 W+ |4 L   這個問題我以前也不是很清楚,通過查看示例源碼及跟蹤發(fā)現,正確解釋應該是第2種。唯一的一個全局Mesh存放在Frame "body"下的無名Frame中。而其它Frame由于沒有自己專門的Mesh而指向NULL. 應該大致如此。這個問題之所以讓

41、人困繞,是因為從后續(xù)代碼上看,在渲染DrawFrame時,是遍歷每一個frame分別繪制它們對應的Mesh. 如果對應于同一個mesh,就繪制多遍。如果對應各自mesh,那么變換矩陣怎么組織運算等等。所以,根據第二種解釋,由于只有一個pMeshContainer不為NULL,所以參與繪制及蒙皮的只有這一個MeshContainer,人體所有頂點數據及蒙皮信息都在這個mesh中。0 b" d+ m7 * z" 1 4 q所以,讀取tiny.x文件后,會產生多個D3DXFRAME對象,但只有一個D3DXMESHCONTAINER對象。在示例代碼的CMyD3DApplicatio

42、n:InitDeviceObjects()中,有:    hr = D3DXLoadMeshHierarchyFromX(strMeshPath, D3DXMESH_MANAGED, m_pd3dDevice, &Alloc, NULL, &m_pFrameRoot, &m_pAnimController);    if (FAILED(hr)3 u3 j8 c3 D6 1 ! w1 E0 Q+ w! o        return hr;其中的Alloc是就自定義的數據

43、容器對象。m_pFrameRoot是根骨骼,對遍歷很重要。m_pAnimController是動畫控制器,對刷新矩陣很重要。; |  D: |, N, u  你在運行完這句話后,下一個斷點,觀察m_pFrameRoot,會發(fā)現如下內容:- Y% b4 g) i# Q/ L( am_pFrameRoot 0x00c59380 Name=0x00c53630 "Scene_Root" . /根框架  pMeshContainer 0x 8 " c; R5 r0 W  P! 5 c

44、* I4 w4 ?* fpFrameSibling 0x  q  I! i* 3 l: A0 IpFrameFirstChild 0x00c59428 Name=0x00c53ca8 "body" pMeshContainer=0x./子框架 骨骼body   +-  pMeshContainer 0x            +-  pFrameSibling  0x01419

45、f00 Name=0x00c5ffd8 "Box01" pMeshContainer=0x ./兄弟框架, |! A) h2 O" & % x6 m! p/ M. X      +-  pFrameFirstChild 0x00c594d0 Name=0x pMeshContainer=0x00c59828 /子框架-該框架就是.x中含有唯一全局Mesh的無名框架4 c* X; _7 P! L8 r* E5 K( z& _7 g可見,在內存中的Frame布局是與.x中一

46、一對應的。除了pFrameFirstChild 0x00c594d0這個地方的Frame中的pMeshContainer不為空,其它框架的這個mesh指針都是空值。另外一點可以看出,并不是每個Frame都對就一塊骨骼,有的是別的用途。也就是說Frame對象的個數可能多于骨骼數。) H% b  E0 u- S& j+ s) Z$ p- A3.3 分析CAllocateHierarchy類下面繼續(xù)研究自定義數據容器CAllocateHierarchy,顧名思義,該類是在加載過程中自行分配層次數據空間。它有4個成員,都是重載D3D的接口虛函數。它的成員CreateFram

47、e()是用來創(chuàng)建D3DXFrame對象的,而CreateMeshContainer()是用來創(chuàng)建Mesh數據對象的。你可以在這兩個函數中下斷點,發(fā)現CreateFrame會運行多次,而CreateMeshContainer只運行一次,再次驗證了上面的說法。' Q. / x: _; y& X) Z& B; Z+ n+ b/ x值得注意的是,示例對上面的D3DXFRAME,D3DXMESHCONTAINER兩個結構做了擴展,分別代之以D3DXFRAME_DERIVED結構和D3DXMESHCONTAINER_DERIVED結構,以集中存儲數據方便程序處理。% L'

48、z+ N8 - G7 r1 l  p% z2 H! " T3 pCreateFrame()處理比較簡單,你只是new一個Frame對象空間,填入傳進來的Name,其它內容由DX負責維護填充。CreateMeshContainer()較為復雜。它的任務一是保存?zhèn)魅氲木W格數據數據,二是根據這些數據及蒙皮信息調用GenerateSkinnedMesh()函數生成蒙皮網格。只有這個新的BlendMesh才能在Render()時支持頂點混合,完成蒙皮的顯示。在D3DXMESHCONTAINER_DERIVED結構中,用pOrigMesh保存舊的Mesh普通網格信息。而Mes

49、hdata.Mesh則指向新產生的BlendMesh在這個函數中,多次用到了AddRef(),對COM不熟悉的新手容易困惑。D3D是COM組件,它在服務進程中運行,而不在當前的客戶進程中。在DX組件運行過程中,要創(chuàng)建一系列接口對象,如CreateDevice()返回接口指針,這些接口及其占用內存什么時候釋放,要通過“引用計數”的技術來解決。AddRef()給這個接口指針的計數加1,而Release()會將之減1。一旦減到0,表示沒有客戶使用了,相關的接口就釋放了。 由此可知,每次調用Rlease()后,并不一定會釋放內存,而是當引用計數歸0時釋放內存。這樣,對接口指針的使用,就像維護堆棧的平衡

50、一樣,要仔細,而且按照某種約定規(guī)則使用。* f! ; _! T  # p/ G7 n1 d但平時D3D編程中,怎么不用AddRef()呢?這是由于一個接口指針,如ID3DDevice,或VertexBuf指針,都是D3DXCreate出來的,在Create時候,在內部已經事先AddRef()了,你就不需要再做這工作了。只要你在不用時,調用 p指針->Relase()就釋放了。一般編程,特別是小型示例程序,都是初始化時建立一次,關閉時釋放,都遵守了這種約定,所以不存在這種問題。$ d7 4 C- T: Z* C( m5   P但在CreateMesh

51、Container()函數中,以多種方式使用了指針,在局部指針變量中來回傳遞,所以問題復雜化了。在COM編程中約定,任何時候地接口指針賦值(復制),都要AddRef(),在指針變量結束生命期前,再Release(). 但許多程序員都不是嚴格這么做。因為在局部變量用完就廢了,先AddRef()增加計數再Release()減少,和直接使用最后是等效的。幾乎是多此一舉。這與編程習慣有關系。一旦引用計數不對,如果沒有統(tǒng)一的習慣,不好排查。在CreateMeshContainer()中,對接口指針的使用有三種方式,例舉如下:方式一:不使用AddRef()。和普通指針一樣,臨時變量是左值,接口指針是右值,

52、直接賦值使用。如:        pMesh = pMeshData->pMesh; + " X( K5 o9 k% d# u( F/ K        這是由于pMesh是局部變量,它只是臨時引用一下,沒必要為它先AddRef(),后Release()。方式二:隱式的使用AddRef()。 由于用到了一些內部有AddRef()動作的函數,就要按照COM約定,在子程序結束前Release()      

53、0; pMesh->GetDevice(&pd3dDevice);/此處d3d設備引用計數已經加1        .        SAFE_RELEASE(pd3dDevice);/-此處將引用計數減1,并不是真的釋放d3d設備; H0 6 _% F+ L8 g4 J+ X        在本例中,pd3dDevice在GetDevice()中已經Addref()過了,所以,在退出Creat

54、eMeshContainer()前,必須pd3dDevice->Release()方式三:顯式的使用AddRef()。 如果一個指針值,不是由D3DXCreate出來的,而是通過賦值方式復制給一個全局變量或長期變量的。 所以,可以通過AddRef()的方式來延遲該對象的釋放。因為,如果不AddRef(),極有可能在函數返回該對象就可能釋放了。它就像一個加油站,使得傳入對象的壽命延長至自己控制范圍內。用了AddRef(),就要在相關的Destroy中添加Release()。在本函數,有三處這樣的語句:" h& o! e6 x2 H. i) |% z   

55、;     pMeshContainer->MeshData.pMesh = pMesh;        pMeshContainer->MeshData.Type = D3DXMESHTYPE_MESH;        pMesh->AddRef();         .2 8 F7 o/ $ O' k  u- B0  

56、       pMeshContainer->pSkinInfo = pSkinInfo;        pSkinInfo->AddRef();( H3 q# v& E! E        pMeshContainer->pOrigMesh = pMesh;        pMesh->AddRef();% / j3 u3 X$ Y%

57、 A         .5 b4 q& v# b/ p! R( : o"         將來在DestroyMeshContainer()中,要釋放這些指針:        .        SAFE_RELEASE( pMeshContainer->MeshData.pMesh );6 Z0 m2 h7 K- G6 z) e7 k

58、        SAFE_RELEASE( pMeshContainer->pSkinInfo );: R  ?: w3 B9 g" m. 2 Y1 N! $ S        SAFE_RELEASE( pMeshContainer->pOrigMesh );        由于這些指針值的創(chuàng)建、更改等都是用戶自己經營的,所以務必要加前后吻合,在CreateMeshCont

59、ainer()中AddRef(),在DestroyMeshContainer()中Release().再來看數據的保存部分。( z. $ i( P. ' I% T- w# w3 r在CreateMeshContainer()的傳入參數中,有pMeshData,pMaterials,pEffectInstances,NumMaterials,pAdjacency,pSkinInfo你需要把這些數據保存到自己的D3DXMESHCONTAINER對象中。并且其中的所有數組所需的空間都要在全局堆中new出來。所以在該代碼中,有如下new:pMeshContainer = new D3DXMES

60、HCONTAINER_DERIVED;/自定義的擴展數據容器對象memset(pMeshContainer, 0, sizeof(D3DXMESHCONTAINER_DERIVED);/初始化pMeshContainer,清0$ g  W) R9 " V* % Q3     .pMeshContainer->pMaterials = new D3DXMATERIALpMeshContainer->NumMaterials;/準備保存材質  e7 M9 W* P+ k4 Z! RpMeshContainer->

61、;ppTextures = new LPDIRECT3DTEXTURE9pMeshContainer->NumMaterials;/準備創(chuàng)建紋理對象。它聲明在擴展部分。pMeshContainer->pAdjacency = new DWORDNumFaces*3;/準備保存鄰接三角形數組,NumFaces = pMesh->GetNumFaces();- 4 x3 g/ ! X9 y- . T然后,對數據進行memcpy保存。pEffectInstances由于在繪制中不需要,并沒進行保存。對于沒有貼圖的賦以默認材質屬性。7 p7 Z2 s+ o' l! _值得注意

62、的是,所有這些new,必須在DestroyMeshContainer()時進行delete.接下來的處理中,如果發(fā)現Mesh的FVF中沒有法向量,要用CloneMeshFVF()重建Mesh,計算頂點平均法向量。以備光照處理。% p8 G: P* N2 Y4 r2 Z5 W3 1 h( 最后,我們看看蒙皮信息pSkinInfo的處理。這是重頭戲。# _0 |4 F7 U" M: i7 R如果發(fā)現pSkinInfo!=NULL,就準備著手從各個蒙皮骨骼信息創(chuàng)建SkinMesh.9 w  " 6 5 G4 l6 Q8 j首先,用擴展容器結構D3DXMESHC

63、ONTAINER_DERIVED中的各屬性保存原Mesh指針值,pMeshContainer->pOrigMesh = pMesh, 因為接下來我們要創(chuàng)建SkinMesh替代原Mesh.然后,把SkinInfo中的各骨骼的偏移矩陣保存到pMeshContainer->pBoneOffsetMatrices中      cBones = pSkinInfo->GetNumBones();      pMeshContainer->pBoneOffsetMatrices = new D3DXMA

64、TRIXcBones;      .& U9 e- B0 A: R3 Z) i# R     每個“骨骼偏移矩陣”pBoneOffsetMatrices,在將來DrawMeshContainer()中是必須要用的。因為原始Mesh中的頂點數據乘以“骨骼偏移矩陣”,再乘以“變換矩陣”,才能求得各骨骼頂點在世界坐標系中的坐標。 即: ' l5 u+ % e( Z/ f    骨骼上各點在世界坐標系中的新坐標=初始網格中的各點坐標*骨骼偏移矩陣*骨骼當前的變換矩陣# i9 T

65、! r8 S$ C3 R6 K) H1 L    其中,“初始網格中的各點坐標*骨骼偏移矩陣” = 骨骼上各點初始時刻在該骨骼坐標系中的局部坐標# . . 8 i& S3 s做了以上工作后,調用GenerateSkinnedMesh(pMeshContainer),創(chuàng)建SkinMesh. 接下來,我們看看GenerateSkinnedMesh()做了哪些工作。3.4 怎樣生成蒙皮網格SkinMesh? GenerateSkinnedMesh()分析由于要重定義pMeshContainer->MeshData.pMesh,所以先SAFE_RELEASE( pMe

66、shContainer->MeshData.pMesh ); 釋放原pMesh! w5 q! j8 U$ P; ( k: e在這個函數中,是根據當前繪圖方式設置進行加載數據的。因為頂點混合,有無索引的頂點混合,有含索引的頂點混合,所使用的函數和對應的SkinMesh數據內容也有所不同。+ 4 L' y! : * K, y( n5 C在示例中,自定義了枚舉m_SkinningMethod,主要分為D3DNONINDEXED和D3DINDEXED,以有純軟件渲染等。運行示例后,你可以選擇菜單中的Options選擇不同的渲染方式。我們著重分析一下帶索引的蒙皮網格。在程序中,就是D3DI

67、NDEXED相關的部分。if (m_SkinningMethod = D3DINDEXED) .+ q; " 5 T! f( G7 % X$ ' e注意! 示例默認工作在D3DNONINDEXED下,如果要跟蹤D3DINDEXED部分的代碼,必須選擇菜單中的Options選擇indexed!3 , z3 X' r1 C最主要的,要通過DX的ConvertToIndexedBlendedMesh()函數,生成支持“索引頂點混合”的SkinMesh.有關索引頂點混合的技術,你可以在DXSDK幫助文件中搜索“Indexed Vertex Blending”主題,對著英文和插

68、圖將就看,確有收獲。2 m. L9 B, D0 J5 v% K要想用硬件對頂點進行混合,那么參與混合者不能太多。也就是說同時影響一個頂點的骨骼數不能多。我們假定一個頂點最多同時受4個骨骼的影響(也就是同時最多有4個骨骼矩陣參與加權求和),那么同時影響一個三角形面的骨骼數最多就是3*4=12個。我們用NumMaxFaceInfl表示影響一個三角面的最多骨骼矩陣數,那么,通過調用pSkinInfo->GetMaxFaceInfluences()獲取這個數值,一般也就3-4。如果這個數值太大,我們強制使用NumMaxFaceInfl = min(NumMaxFaceInfl, 12);來最多取

69、值12。用NumMaxFaceInfl 這個數值干什么呢? 我們用來它分析當前的顯卡倒底行不行。if (m_d3dCaps.MaxVertexBlendMatrixIndex + 1 < NumMaxFaceInfl)/如果顯卡達不到該要求# O5 : V0 A. X      /很奇怪。2005年底買的GeForce 6600GT顯卡,竟然m_d3dCaps.MaxVertexBlendMatrixIndex=0, 不支持索引頂點混合!是驅動問題還是怎么了?      /但它支持非索引混合?;蛘?,也許要用H

70、LSL支持混合??雌饋?,3D編程要多考慮。       .      pMeshContainer->UseSoftwareVP = true;/用軟件渲染頂點。顯然不實用。  Q$ I4 N9 : g# I+ y8 kelse      pMeshContainer->NumPaletteEntries = min( ( m_d3dCaps.MaxVertexBlendMatrixIndex + 1 ) / 2, % q4 d$ L

71、( b" E: T& l                                                     pMeshContainer->pSkinInfo->GetNumBones()

72、 );/-什么意思?* n/ l& k' O, Y4 |9 c7 7 z' Q" O      pMeshContainer->UseSoftwareVP = false;/采用硬件頂點混合。" v6 p( E! * n  Q( n! g      Flags |= D3DXMESH_MANAGED;0 I7 Z3 l( H8 c5 ?6 T* E. q評論在上面有一行代碼:     pMeshContai

73、ner->NumPaletteEntries = min( ( m_d3dCaps.MaxVertexBlendMatrixIndex + 1 ) / 2,pMeshContainer->pSkinInfo->GetNumBones() );盡管作者加了大段注釋,還是讓人一頭霧水。其實,我們做一個實驗,反爾更能理解它的用途。第一步,你在這句話后面下一個斷點,看一下在你機器上這個數值。我的ATI 9550顯卡機器上是19。比tiny.x中的骨骼數35少很多。第二步,你將上面=右邊瞎填一個大于4的數字,比如6。編譯后照樣運行。而且效果上幾乎看不出任何差別。為什么會這樣呢? 我們在

74、繪制代碼部分,看看這個數值起什么作用。在DrawMeshContainer()代碼中,我們查找D3DINDEXED相關的部分。在mesh各子集的DrawSubset()之前,有如下代碼:      for (iAttrib = 0; iAttrib < pMeshContainer->NumAttributeGroups; iAttrib+)                      / first calculate all the world matrices                for (iPaletteEntry = 0; iPaletteEntry < pMeshContainer->NumPaletteEntries; +iPaletteEntry)# d4 h6 s; N. L                  

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論