• <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āng)自強而不息

            【ZT】D3D中的骨骼動畫

            作者:qlwuu


            相信這里沒有人沒玩過采用骨骼動畫技術(shù)的游戲,看看那些熱門的動作游戲,例如《波斯王子》、《分裂細(xì)胞》和《戰(zhàn)神》,你就知道骨骼動畫的威力了(我承認(rèn)是猜的)。骨骼動畫技術(shù)用來使我們的3D模型在屏幕上動起來,通過和動作捕捉技術(shù)結(jié)合,可以讓模型做出非常逼真的動作。而這樣一個極具威力的技術(shù),其原理卻相當(dāng)簡單。

            假設(shè)我們要讓游戲主角做出一個動作,例如波斯王子拿彎刀往前一劈。最簡單的方法,就是讓模型師建一個動畫序列,然后在程序中逐幀播放,就像放電影一樣。不過這樣一來工作量就太大了,玩家也需要N個G的硬盤來安裝這個游戲。與此不同,骨骼動畫技術(shù)采用了一種很聰明的方式。首先,建模師完成一個標(biāo)準(zhǔn)姿勢的3D模型,通常是雙手沿著肩的方向伸展平放,雙腳打開。所有的后繼動作將由這個基礎(chǔ)動作演變得到。在完成這個基準(zhǔn)模型之后,建模師再建一個骨骼結(jié)構(gòu),一系列相互關(guān)聯(lián)的頂點,就像一個骨架一樣,與人體模型各個關(guān)節(jié)匹配并且都會有一定數(shù)量的頂點與之關(guān)聯(lián)。想象一下人和人身上的骨骼就很容易知道我在說什么。之后,在我們想要完成的動畫序列中,挑選一些關(guān)鍵幀,對每個關(guān)鍵幀,將骨骼的位置與關(guān)鍵幀匹配。然后把這一系列的關(guān)鍵幀骨骼保存起來,除了骨骼的位置,同時保存的還有從基準(zhǔn)位置變換到當(dāng)前關(guān)鍵幀的旋轉(zhuǎn)、平移、縮放或者一個混合的坐標(biāo)變換矩陣。在我們引擎中,首先根據(jù)當(dāng)前時間查找這時候角色是處于哪兩個關(guān)鍵幀中間。找到之后以時間為參數(shù)在關(guān)鍵幀的坐標(biāo)變換矩陣之間求插值,用插值結(jié)果來決定骨骼當(dāng)前的位置。骨骼位置求出來后,所有和骨骼關(guān)聯(lián)的頂點的坐標(biāo)也可以相應(yīng)求出來了。通過使用骨骼動畫技術(shù),我們用相對較少的數(shù)據(jù)就可以播放很平滑的動畫!

            了解了相關(guān)原理,來看看如何在directx中播放骨骼動畫。我的參考書是《Advanced Animation with DirectX》。

            現(xiàn)在我們知道為了播放骨骼動畫,需要有骨骼 (bone)的數(shù)據(jù),模型(mesh)的數(shù)據(jù),關(guān)聯(lián)骨骼和模型上每個頂點的關(guān)聯(lián)數(shù)據(jù),以及關(guān)鍵幀的坐標(biāo)變換數(shù)據(jù)。所有這些數(shù)據(jù)必須以某種形式存在于某個地方供我們獲取才行。這里要介紹的MS的x文件格式以及從中獲取數(shù)據(jù)的方法。強烈建議大家都來學(xué)習(xí)一下x文件格式!你會發(fā)現(xiàn)它即簡單又強大,即使用來存放自定義數(shù)據(jù)也是相當(dāng)?shù)姆奖?,一旦掌握之后我保證你會對它愛不釋手。

            典型的x文件以數(shù)據(jù)模板和實際數(shù)據(jù)兩部分組成。數(shù)據(jù)模板類似c++中的結(jié)構(gòu)定義,不過更為靈活和開放。實際數(shù)據(jù)就是遵守模板定義的數(shù)據(jù)段。

            看一個例子:

            template Employee {
              
            <3D82AB43-62DA-11cf-AB39-0020AF71E433>  // 每個模板關(guān)聯(lián)唯一的GUID   
              STRING Name;                                                      // 姓名
              DWORD  Sex;                                                        // 性別 
              [ContactEntry]                                                       // 聯(lián)系方式, 另一個模板,模板可以嵌套
            }

            template  ContactEntry {
              
            <4C9D055B-C64D-4bfe-A7D9-981F507E45FF>  // GUID
              STRING PhoneNumber;                                         // 電話號碼
              STRING Address;                                                   // 地址
            }

            Employee David{
              "David";
              1;
              ContactEntry{
                "100-100000000";
                "far far away";
              }
            }

            從上面這個簡單的例子我們就可以看出x文件的大概模樣了,詳細(xì)的情況大家可以參考《Advanced  Animation with DirectX》。下面我們看如何來讀取這樣一個x文件,借助下幾個對象:

            ID3DXFile                    -- x文件格式文檔對象。例如Employee.x這樣一個文件。
            ID3DXFileEnumObject -- 用來枚舉x文檔的頂級模板數(shù)據(jù)。所謂頂級模板數(shù)據(jù)是指那些沒有父模板的數(shù)據(jù),例如上面的David數(shù)據(jù)段。
            ID3DXFILEDATA          -- 模板數(shù)據(jù)。上面的David和他的聯(lián)系方式都是ID3DXFILEDATA對象,自包含。

            下面看實際的分析函數(shù), 下面的代碼適用于DirectX 9.0  SDK Update (October 2004),原書的代碼有點過時了...

            //-----------------------------------------------------------------------------
            // 名稱 : Parse
            // 描述 : 分析x文件格式文檔
            //-----------------------------------------------------------------------------
            bool Parse( char *filename, void **pData )
            {
              LPD3DXFILE        lpD3DXFile;
              LPD3DXFILEENUMOBJECT  lpD3DXFileEnumObj;
              LPD3DXFILEDATA             lpD3DXFileData;

              
            // 參數(shù)檢查
              if( NULL == filename )
                
            return false;

              
            // 創(chuàng)建X文件對象
              HRESULT hr = D3DXFileCreate( &lpD3DXFile );
              
            if( FAILED( hr ) )
                
            return false;

              
            // 注冊標(biāo)準(zhǔn)模板
              hr = lpD3DXFile->RegisterTemplates( 
                        ( LPVOID )D3DRM_XTEMPLATES, D3DRM_XTEMPLATE_BYTES );
              
            if( FAILED( hr ) )
              {
                Release
            <LPD3DXFILE>( lpD3DXFile );
                
            return false;
              }

              
            // 創(chuàng)建X文件枚舉對象
              hr = lpD3DXFile->CreateEnumObject( 
                       filename, D3DXF_FILELOAD_FROMFILE, 
            &lpD3DXFileEnumObj );
              
            if( FAILED( hr ) )
              {
                 Release
            <LPD3DXFILE>( lpD3DXFile );
                 
            return false;
              }

              
            // 解析開始
              bool parseResult = BeginParse( pData );
              
            iftrue == parseResult )
              {
                 
            // 查詢頂級模板數(shù)
                 SIZE_T childCount = 0;
                 lpD3DXFileEnumObj
            ->GetChildren( &childCount );

                 
            // 分析每個訂級模板
                 for( DWORD i=0; i<childCount; i++ )
                 {
                   
            // 獲取當(dāng)前模板
                   hr = lpD3DXFileEnumObj->GetChild( i, &lpD3DXFileData );
                   
            if( FAILED( hr ) )
                     
            break;

                   
            // 分析
                   parseResult = ParseObject( lpD3DXFileData, NULL, 0, pData );

                   
            // 釋放FileData對象
                   Release<LPD3DXFILEDATA>( lpD3DXFileData );

                   
            // 出現(xiàn)錯誤,中斷分析
                   iffalse == parseResult )
                     
            break;
                  }

                  
            // 解析結(jié)束
                  if( parseResult ) 
                    parseResult 
            = EndParse( pData );
              }

              
            // 釋放相關(guān)對象
              Release<LPD3DXFILEENUMOBJECT>( lpD3DXFileEnumObj );
              Release
            <LPD3DXFILE>( lpD3DXFile );

              
            // 解析結(jié)束
              return parseResult;
            }

            //-----------------------------------------------------------------------------
            // 名稱 : ParseObject
            // 描述 : 遞歸解析頂級模板
            //-----------------------------------------------------------------------------
            bool ParseObject( 
            LPD3DXFILEDATA pDataObj,
            LPD3DXFILEDATA pParentDataObj,
            DWORD depth,
            void **pData )
            {
              LPD3DXFILEDATA pSubDataObj;
              
            bool parseResult = true;
              HRESULT hr;

              
            // 獲取子模板數(shù)目
              DWORD childCount;
              pDataObj
            ->GetChildren( &childCount );

              
            // 遍歷模板并分析
              for( DWORD i=0; i<childCount; i++ )
              {
                 
            // 取子模板對象
                 hr = pDataObj->GetChild( i, &pSubDataObj );
                 
            if( FAILED( hr ) )
                   
            break;

                 
            // 分析子模板
                 parseResult = ParseObject( pSubDataObj, pDataObj, depth+1, pData );

                 
            // 釋放數(shù)據(jù)對象
                 Release<LPD3DXFILEDATA>( pSubDataObj );

                 
            // 出現(xiàn)錯誤,停止分析
                 iffalse == parseResult )
                   
            break;
              }

              
            return parseResult;
            }

            就那么簡單,相信大家都看得明白。通過重載ParseObject方法,我們以判斷當(dāng)前分析的模板類型,然后創(chuàng)建實際的模板對象,從文檔中復(fù)制數(shù)據(jù)。有了上面的工具,我們就可以自己來讀取和解析x格式的骨骼動畫文件了。

            下面我們就來看看如何重載 ParseObject方法來獲得我們感興趣的數(shù)據(jù),不要擔(dān)心,絕對簡單。仔細(xì)看代碼,你會發(fā)現(xiàn)只需要做一件事情,判斷當(dāng)前數(shù)據(jù)段的類型(通過GUID),分配對應(yīng)的結(jié)構(gòu)對象,然后從數(shù)據(jù)段拷貝數(shù)據(jù)(所有SDK自定義模板的GUID都在頭文件rmxfguid.h中定義,  你需要把它加入你的工程中。所有預(yù)定義模板在這里可以找到)。先來看看如何獲取當(dāng)前數(shù)據(jù)段的GUID,

            GUID objGUID;
            pDataObj->GetType( &objGUID );

            簡單吧,下面開始我們的分析之旅。x動畫文件中骨骼是用Frame模板定義的,

            template Frame
            {
                    
            < 3D82AB46-62DA-11cf-AB39-0020AF71E433 >
                     FrameTransformMatrix frameTransformMatrix;       //  骨骼相對于父節(jié)點的坐標(biāo)變換矩陣
                    Mesh mesh;                                                       // 骨骼的Mesh
            }  

            只有兩個字段。FrameTransformMatrix就是一個matrix。Mesh稍微復(fù)雜,詳細(xì)格式大家自己參考MSDN,我們也會有專門的代碼來加載Mesh,現(xiàn)在關(guān)注Frame。為了加載Frame,我們要在程序中定義一個和Frame模板對應(yīng)的數(shù)據(jù)結(jié)構(gòu),SDK中經(jīng)默認(rèn)提供了一個,那就是 D3DXFRAME,
            typedef struct _D3DXFRAME
            {
                LPSTR                   Name;                            
            // 骨骼名稱 
                D3DXMATRIX         TransformationMatrix;        // 相對與父節(jié)點的坐標(biāo)變換矩陣

                LPD3DXMESHCONTAINER     pMeshContainer;  
            // LPD3DXMESHCONTAINER對象,用來
                                                                                 
            // 加載MESH,還有一些附加屬性,見SDK

                
            struct _D3DXFRAME       *pFrameSibling;         // 兄弟節(jié)點指針,和下面的子節(jié)點指針
                                                                                  
            // 一塊作用構(gòu)成骨骼的層次結(jié)構(gòu)。    
                struct _D3DXFRAME       *pFrameFirstChild;      // 子節(jié)點指針.
            } D3DXFRAME, *LPD3DXFRAME;

            這樣一個結(jié)構(gòu)已經(jīng)足夠容納Frame模板中的數(shù)據(jù)并形成一個層次結(jié)構(gòu),不過為了我們程序的需要,我們還需要其他字段,為此我們通常會擴展D3DXFRAME,
            typedef struct _D3DXFRAME_EX : public D3DXFRAME
            {
                D3DXMATRIX matCombined; 
            // 存儲當(dāng)前節(jié)點相對于根節(jié)點的位置偏移矩陣,沿著到
                                                            
            // 到根骨骼的路徑把所有的坐標(biāo)變換矩陣相乘得到。

                D3DXMATRIX matOriginal;         
            // 在播放動畫的時候有可能會改變原來結(jié)構(gòu)中的
                                                            
            // TransformationMatrix,因此我們聲名一個新的字段
                                                            
            // 將原來的坐標(biāo)變換矩陣保存起來以便在需要的時候恢
                                                            
            // 復(fù)回去。

                ...                                     
            // 忽略一些方法定義
            }

            我知道有些人已經(jīng)按捺不住了,那么動手吧,

            // 判斷當(dāng)前分析的是不是Frame節(jié)點
            if( objGUID == TID_D3DRMFrame )
            {
                    
            // 引用對象直接返回,不需要做分析。一個數(shù)據(jù)段實際定義一次后可以
                    
            // 被其他模板引用,例如后面的Animation動畫模板就會引用這里的Frame
                    
            // 節(jié)點,標(biāo)識動畫關(guān)聯(lián)的骨骼。
                    if( pDataObj->IsReference() )
            return true;

                    
            // 創(chuàng)建D3DXFRAME_EX結(jié)構(gòu),準(zhǔn)備拷貝數(shù)據(jù)
                    D3DXFRAME_EX *pFrame = new D3DXFRAME_EX();

                    
            // 拷貝名稱
                    pFrame->Name = GetObjectName( pDataObj );

                    
            // 注意觀察文件就可以發(fā)現(xiàn)一個Frame要么是根Frame,父節(jié)點不存在,
                    
            // 要么作為某個Frame的下級Frame而存在。
                    if( NULL == pData )
                    {
                            
            //  作為根節(jié)點的兄弟節(jié)點加入鏈表。
                            pFrame->pFrameSibling = m_pRootFrame;
                            m_pRootFrame 
            = pFrame;
                            pFrame 
            = NULL;

                            
            // 將自定義數(shù)據(jù)指針指向自己,供子
                             
            // 節(jié)點引用。
                            pData = ( void** )&m_pRootFrame;
                     }
                     
            else
                     {
                            
            // 作為傳入節(jié)點的子節(jié)點
                            D3DXFRAME_EX *pDataFrame = ( D3DXFRAME_EX* )( *pData );
                            pFrame
            ->pFrameSibling = pDataFrame->pFrameFirstChild;
                            pDataFrame
            ->pFrameFirstChild = pFrame;
                            pFrame 
            = NULL;

                            pData 
            = ( void** )&pDataFrame->pFrameFirstChild;
                      }
            }

            結(jié)束了!是不是很簡單,呵呵,記住我們只需要做一件事情,判斷類型,分配匹配的對象然后拷貝數(shù)據(jù),下面來分析Frame中的 matrix,

            // frame的坐標(biāo)變換矩陣, 因為matrix必然屬于某個Frame所以pData 必須有效
            else if( objGUID == TID_D3DRMFrameTransformMatrix && pData )
            {
                      
            // 我們可以肯定pData指向某個Frame
                      D3DXFRAME_EX *pDataFrame = ( D3DXFRAME_EX* )( *pData );

                      
            // 先取得緩沖區(qū)大小,應(yīng)該是個標(biāo)準(zhǔn)的4x4矩陣
                      DWORD size = 0;
                      LPCVOID buffer 
            = NULL;

                      hr 
            = pDataObj->Lock( &size, &buffer );
                      
            if( FAILED( hr ) )
            return false;

                      
            // 拷貝數(shù)據(jù)
                      if( size == sizeof( D3DXMATRIX ) )
                      {
                           memcpy( 
            &pDataFrame->TransformationMatrix, buffer, size );
                           pDataObj
            ->Unlock();

                           pDataFrame
            ->matOriginal = pDataFrame->TransformationMatrix;
                      }
            }

            這樣大家應(yīng)該對其他類型的模板數(shù)據(jù)分析代碼都應(yīng)該大致猜的出來了。具體的代碼我就不在這里提供,只是簡單的介紹一下它們的作用和關(guān)系,大家可以參考最后附上的工程。

            Frame -- 

            骨骼。正如大家已經(jīng)看到的那樣,我們可以用pFrameSibling和pFrameFirstChild兩個字段來構(gòu)成骨骼的層次結(jié)構(gòu)。骨骼模板包含了當(dāng)前骨骼相對父骨骼的坐標(biāo)變換矩陣和骨骼對應(yīng)的模型

            Mesh  -- 

            模型。角色的頂點數(shù)據(jù),包含vertex buffer, index  buffer等。我們可以直接用普通的ID3DXMesh來加載其中的數(shù)據(jù)。除此之外,Mesh中還包含了SkinWeight模板。

            SkinWeight --

            骨骼關(guān)聯(lián)的頂點已經(jīng)該骨骼的坐標(biāo)變換對該頂點的權(quán)重。實際中我們并不需要特殊處理這類模板數(shù)據(jù),ID3DXMesh已經(jīng)包含了對應(yīng)的代碼。

            AnimationSet --

            動畫集合。例如角色的各種動作“Kill”,“Jump”等等,包含多個Animation。

            Animation --

            動畫。由對應(yīng)骨骼的名稱和一組AnimationKey組成。

            AnimationKey --

            動畫鍵。包含一組時間戳以及在對應(yīng)時間戳應(yīng)用到骨骼上的平移、縮放、旋轉(zhuǎn)向量或者復(fù)合的坐標(biāo)變換矩陣。

            以上就是我們需要了解的全部了。至此,所有原料都已經(jīng)準(zhǔn)備齊全,各位大廚們下一步要做的就是骨骼動畫這道小菜啦!

            posted on 2007-05-24 00:03 lovedday 閱讀(5182) 評論(8)  編輯 收藏 引用 所屬分類: ■ DirectX 9 Program

            評論

            # re: 【ZT】D3D中的骨骼動畫 2007-07-23 18:12 creator

            你好,
            我是剛剛開始學(xué)習(xí)directx 9的,現(xiàn)在學(xué)到骨骼動畫卡住了。急需一份up-to-date的源代碼拿來研究研究。很多書上的代碼都是很陳舊的了。
            樓主文中說有附工程的,但是我找不到。如果可以的話,希望博主能夠幫忙發(fā)一份給我,我的郵件地址是
            zhaojianyin2000@hotmail.com 感激不盡!!  回復(fù)  更多評論   

            # re: 【ZT】D3D中的骨骼動畫 2007-07-23 18:16 lovedday

            這篇文章是轉(zhuǎn)載的,我也沒有源碼。  回復(fù)  更多評論   

            # re: 【ZT】D3D中的骨骼動畫 2007-07-23 18:20 lovedday

            這是作者的郵箱:qlwuu@hotmail.com,你可以給他發(fā)郵件。  回復(fù)  更多評論   

            # re: 【ZT】D3D中的骨骼動畫 2007-07-23 19:00 creator

            謝謝博主! 文章最前面的ZT是不是就是轉(zhuǎn)貼的意思啊,我一時沒有明白過來,呵呵。
            我看了你很多的文章,都寫得不錯。讓我受益匪淺阿!我會一直關(guān)注你的博客的,呵呵!  回復(fù)  更多評論   

            # re: 【ZT】D3D中的骨骼動畫 2007-07-23 19:13 lovedday

            謝謝,我會再接再厲,努力提高自己的水平,爭取以后寫些對大家有幫助的東西。  回復(fù)  更多評論   

            # re: 【ZT】D3D中的骨骼動畫 2008-01-29 20:39 吸佳佳

            感激博主對廣大正在學(xué)習(xí)3D游戲編程的朋友提供這么生動、易讀的文章。
            希望多多分享您的經(jīng)驗給我們學(xué)習(xí),太感謝了!  回復(fù)  更多評論   

            # re: 【ZT】D3D中的骨骼動畫 2009-12-09 12:14 laiyi

            Good  回復(fù)  更多評論   

            # re: 【ZT】D3D中的骨骼動畫[未登錄] 2011-10-13 21:29 nickolas

            好文章,深入淺出  回復(fù)  更多評論   

            公告

            導(dǎo)航

            統(tǒng)計

            常用鏈接

            隨筆分類(178)

            3D游戲編程相關(guān)鏈接

            搜索

            最新評論

            欧美精品一区二区久久| 久久婷婷久久一区二区三区| 国产综合久久久久| 久久99热这里只有精品66| 国产精品成人无码久久久久久 | 青青草国产97免久久费观看| 久久精品人人做人人爽电影蜜月 | 久久国产AVJUST麻豆| 国产精品一区二区久久精品无码 | 久久国产成人午夜AV影院| 日本欧美久久久久免费播放网| 亚洲七七久久精品中文国产| 久久精品成人免费国产片小草| 伊人久久精品线影院| 97久久香蕉国产线看观看| 996久久国产精品线观看| 久久精品国产精品国产精品污 | 精品久久久无码人妻中文字幕| 日韩精品久久久久久久电影| 伊人情人综合成人久久网小说| 久久无码专区国产精品发布| 久久精品人妻中文系列| 久久99久久99精品免视看动漫| 99久久久精品| 91性高湖久久久久| 久久国内免费视频| 久久精品国产亚洲沈樵| 国产成人无码精品久久久久免费| 亚洲人成网站999久久久综合 | 久久这里只有精品首页| 久久精品国产男包| 久久国产精品99久久久久久老狼| 精品久久久久久无码国产| 青青草国产97免久久费观看| 亚洲午夜无码久久久久| 99久久精品国产一区二区三区 | 日韩欧美亚洲国产精品字幕久久久| 久久无码AV一区二区三区| 国产精品久久永久免费| 中文字幕无码久久久| 93精91精品国产综合久久香蕉 |