青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

隨筆 - 505  文章 - 1034  trackbacks - 0
<2007年6月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567


子曾經(jīng)曰過:編程無他,唯手熟爾!

常用鏈接

留言簿(94)

隨筆分類(649)

隨筆檔案(505)

相冊(cè)

BCB

Crytek

  • crymod
  • Crytek's Offical Modding Portal

Game Industry

OGRE

other

Programmers

Qt

WOW Stuff

搜索

  •  

積分與排名

  • 積分 - 921900
  • 排名 - 14

最新隨筆

最新評(píng)論

閱讀排行榜

評(píng)論排行榜

我在《程序員》2008年2月刊上看到的(上),一搜果然搜到了。好文共分享!


游戲程序中的骨骼插件(上)

文/潘李亮

引言

  在3D引擎中,骨骼動(dòng)畫系統(tǒng)是非常重要的一個(gè)組成部分,雖然在一個(gè)游戲的真正開發(fā)過程中,一個(gè)優(yōu)秀的游戲引擎也許不需要用戶去關(guān)心它的骨骼動(dòng)畫系統(tǒng)是如何實(shí)現(xiàn)的,但是還是有很多人希望了解這樣的一個(gè)技術(shù)。

本文將會(huì)介紹骨骼動(dòng)畫系統(tǒng)里的一個(gè)基礎(chǔ)部件:3Ds MAX 的骨骼動(dòng)畫導(dǎo)出插件。

3Ds Max SDK和插件系統(tǒng)

最新版本MAX9的MAXSDK包含在安裝光盤里,在安裝完MAX后直接安裝SDK,并在工程里添加maxsdk的包含路徑和庫的路徑就可以開始編譯max插件了。MAX SDK還提供了3Ds Max Help for Visual Studio,這個(gè)幫助系統(tǒng)可以集成到Visual Studio .NET的幫助系統(tǒng)中,非常方便。建議在安裝的時(shí)候一起裝上。

MaxSDK主要目的就是用來開發(fā)MAX插件,雖然Max也提供了MaxScript,也可以用來做插件,但是對(duì)C++程序原來說,MaxSDK則更順手一些。

Max插件根據(jù)用途分為好幾種,每種對(duì)應(yīng)不同的擴(kuò)展名,在游戲開發(fā)中,我們通常比較關(guān)心三種類型的插件,他們分別是: 導(dǎo)入/導(dǎo)出插件,對(duì)應(yīng)擴(kuò)展名為dli/dle, utility 插件,對(duì)應(yīng)擴(kuò)展名為dlu,以及擴(kuò)展名為dlm的modifier。導(dǎo)入導(dǎo)出插件基本上說是MAX與其它工具交互的接口。Utility插件則可以為MAX增加很多操作功能面板。Modifier則是3DsMAX

3DsMax自帶的插件放在X:\3DsMax\maxsdk\stdplugs目錄下,而我們自己編寫的插件通常會(huì)放到X:\3DsMax\maxsdk\ plugins目錄下。只要把插件放到這兩個(gè)目錄下,Max在啟動(dòng)的時(shí)候就會(huì)自動(dòng)加載你的插件。很多初學(xué)者可能會(huì)問dlm/dle這些插件是怎么生成的呢?其實(shí)這些都是一些標(biāo)準(zhǔn)的dll程序,只是擴(kuò)展名不同而已。跟編譯一個(gè)普通的Windows DLL沒有區(qū)別。



初學(xué)MaxSDK最好的例子應(yīng)該就是MAXSDK自帶的sample。在maxsdk的安裝目錄下可以找到,一般是X:\3DsMax\maxsdk\samples 下。這個(gè)目錄下已經(jīng)對(duì)插件的種類進(jìn)行了分類。一般在做骨骼動(dòng)畫導(dǎo)出插件的時(shí)候,我們不會(huì)選擇導(dǎo)出插件而是選擇utility插件,這樣做的目的是ultility插件在max啟動(dòng)的時(shí)候就處于激活狀態(tài), 而導(dǎo)出插件則只會(huì)在用戶選擇export命令的那一刻,并且這些插件都可以訪問到MAX的整個(gè)環(huán)境,因此,使用utility插件會(huì)讓用戶更加的方便,本文的例子就是采用utility插件。

構(gòu)造第一個(gè)3Ds Max 插件

       本節(jié)我將講述如何快速的建立一個(gè)utility插件的框架, 因?yàn)殛P(guān)心的是導(dǎo)出插件本身的功能,而不是插件框架本身,因此我給大家提供一個(gè)個(gè)比較簡(jiǎn)潔的方法:使用3dsmaxPluginWizard. 這是MaxSDK提供的一個(gè)組件,位于X:\3dsmax\maxsdk\howto\3dsmaxPluginWizard下, 仔細(xì)閱讀一遍這個(gè)目錄下的ReadMe.txt文件的Installing一小節(jié),就可安裝好3DsMaxPluginWizard. 這時(shí)候打開Visual Studio 2005.在新建工程中就可以看到3Ds Max Plugin Wizard一項(xiàng),選擇后,看到標(biāo)簽頁一共有三頁,在第一頁P(yáng)lugin-Type里,選擇Utility項(xiàng),在接下來的Plugin Detail里填入詳細(xì)信息如圖2所示。



最后在Project Detials 選項(xiàng)卡里填入maxsdk的路徑,插件輸出路徑和3dsmax.exe所在的路徑就可以生成一個(gè)utility工程了。

生成的工程僅僅是一個(gè)架子,它包含了兩個(gè)類和一個(gè)IDD_PANEL的對(duì)話框。第一個(gè)類MyMaxSkinExporter是從UtilityObj派生下來的,代表了插件本身。另外一個(gè)類從ClassDesc2派生,用來描述這個(gè)插件的一些信息。IDD_PANEL則是我們插件的主界面,我們可以簡(jiǎn)單的理解它就是我們插件的主窗口。

MyMaxSkinExporter有兩個(gè)重要的函數(shù): BeginEditParams(Interface *ip,IUtil *iu)和EndEditParams(Interface *ip,IUtil *iu)這兩個(gè)函數(shù)。BeginEditParams可以簡(jiǎn)單的理解成插件的初始化函數(shù),EndEditParams則在插件退出時(shí)候被調(diào)用。參數(shù)Interface *ip 則代表整個(gè)Max對(duì)象,用它可以訪問到MAX程序的所有功能。

編譯這個(gè)工程,一個(gè)簡(jiǎn)單的utility插件就已經(jīng)生成了,你可以在剛才Project Detials選項(xiàng)卡里填入的插件輸出路徑里找到生成的插件。

3Ds MAX的場(chǎng)景組織和幾何管道

要編寫一個(gè)導(dǎo)出骨骼動(dòng)畫的插件,必須先了解MAX是如何組織場(chǎng)景的,并了解MAX中一個(gè)mesh對(duì)象從建立到最終輸出都經(jīng)歷那些階段。下面首先介紹一下MAX的場(chǎng)景組織。

MAX的整個(gè)場(chǎng)景是一個(gè)樹狀結(jié)構(gòu),樹的節(jié)點(diǎn)用INode來表示,整個(gè)樹的根節(jié)點(diǎn)可以通過Interface::GetRootNode來獲得,場(chǎng)景中的所有物體都是INode。INode中的NumberOfChildren函數(shù)和GetChildNode則用來訪問INode的子節(jié)點(diǎn)。要遍歷整個(gè)場(chǎng)景中對(duì)象,只需要通過Interface::GetRootNode和GetChildNode做一個(gè)遞歸Ѭ環(huán)就可以了。如果僅僅是想獲得在視口中選定的物體,則可以使用Interface::GetSelNodes函數(shù)。

INode僅僅是一個(gè)虛擬的節(jié)點(diǎn),它本身僅僅包含一些標(biāo)記和變換信息,并不表示實(shí)際的Object。實(shí)際的Object需要附著在INode上,并以INode的坐標(biāo)系為Object的本地坐標(biāo)系。Max中常見的Object有形狀(各種參數(shù)曲線),Camera,Mesh等。Object有自己的變換矩陣(TM), 在很多情況下這個(gè)矩陣都是單位矩陣。



INode的變換矩陣可以通過INode::GetNodeTM來獲得,而附著在INode上的Object的變換矩陣則通過INode::GetObjectTM來獲得,因?yàn)镺bject相對(duì)于Node的變換矩陣通常是單位矩陣,GetNodeTM和GetObjectTM獲得的矩陣通常也是一樣的,但是在必要的時(shí)候一定要加以區(qū)別。關(guān)于INode和Object的變換矩陣問題的詳細(xì)討論可以參考我blog上的一篇文章:MAX SDK的INode的變換矩陣,以及Object的一些常識(shí)(http://blog.csdn.net/Nhsoft/archive/2005/01/06/241629.aspx)

接下來我來看一下3DsMax一個(gè)幾何物體的Pipeline。前面說過Object是附著在INode上的,在MAX里,Node有一個(gè)Object Reference的指針,指向一個(gè)物體對(duì)象。熟悉MAX的操作方式的讀者都知道,我們?cè)贛AX里建立一個(gè)對(duì)象后,可以在上面添加各種修改器-Modifier。在Max的幾何管道中,我們建立的對(duì)象通常稱為Base Object。所有施加在這個(gè)物體上的修改器形成一個(gè)修改器堆棧-ModStack。BaseObject經(jīng)過這個(gè)ModStack后形成一個(gè)新的Object Reference。ModStack中的每個(gè)Modifier都是輸入一個(gè)Object Reference,輸出一個(gè)Object Reference, 并且在應(yīng)用第一個(gè)Modifier的時(shí)候會(huì)自動(dòng)在幾何管道里插入一個(gè)Derived Object。最終INode的Object Reference將指向這個(gè)Derived Object。

Modifier在管道中的應(yīng)用實(shí)例是ModApp對(duì)象,一個(gè)ModApp代表一個(gè)應(yīng)用在Object上Modifier修改,ModApp包含一個(gè)ModContext的數(shù)據(jù)對(duì)象,Modifier用ModContext中的數(shù)據(jù)來對(duì)Object進(jìn)行修改,以生成最終數(shù)據(jù)。

修改器按照應(yīng)用的坐標(biāo)系不同分成局部空間修改器和世界空間修改器(World Space Modifier)。局部空間修改器僅僅在Object的局部空間中修改Object,不會(huì)對(duì)坐標(biāo)系造成影響。世界空間的修改器比如水波紋修改器則要求先將物體變換到世界空間后再進(jìn)行修改,修改完成后的坐標(biāo)也是世界空間的坐標(biāo)。相對(duì)來說處理世界空間修改器會(huì)麻煩的多。(如果一個(gè)物體應(yīng)用了世界空間修改器,則通過Mesh對(duì)象取得的坐標(biāo)已經(jīng)是世界坐標(biāo)系中的了。不需要再乘以INode::GetObjectTM了)。

導(dǎo)出骨骼動(dòng)畫數(shù)據(jù)

       了解了MAX的場(chǎng)景管理和幾何管道以后,我們就可以很方便的建立一個(gè)如何取得MAX場(chǎng)景中定點(diǎn)數(shù)據(jù)的流程了。

骨骼動(dòng)畫系統(tǒng),首先應(yīng)該包括物體的蒙皮數(shù)據(jù)和頂點(diǎn)與骨骼的綁定信息。我們分兩部分介紹皮膚數(shù)據(jù)的導(dǎo)出。第一步,我們要導(dǎo)出蒙皮數(shù)據(jù),為了簡(jiǎn)單起見,在這里只導(dǎo)出蒙皮的位置,法向量與切向量紋理坐標(biāo)等信息留給讀者自己去研究。在3DsMax里。要建立骨骼動(dòng)畫模型,可以使用兩種修改器Skin和,Physique。其中Physique是屬于CharacterStudio的,他的API相對(duì)比較復(fù)雜,本文只介紹使用Skin修改器制作的骨骼動(dòng)畫模型文件。


在界面上增加一些按鈕

在了解了那么多理論后,我們可以開始做一些實(shí)質(zhì)上的事情了,首先我們要給我們的插件增加一些按鈕,通過這些按鈕,使用可以下達(dá)保存/加載骨架,導(dǎo)出動(dòng)作,導(dǎo)出皮膚等任務(wù)。

我們?cè)诘谌?jié)中生成的IDD_PANEL的對(duì)話框中加入幾個(gè)按鈕,分別用于保存骨架,加載骨架,導(dǎo)出動(dòng)作,導(dǎo)出皮膚。并在對(duì)話框的WM_COMMAND消息中加入按鈕響應(yīng)代碼。對(duì)話框的窗口過程為MyMaxSkinExporterDlgProc。

增加完的IDD_PANEL對(duì)話框看上去如圖4。


定義頂點(diǎn)數(shù)據(jù)類型

       骨骼動(dòng)畫的頂點(diǎn)數(shù)據(jù)應(yīng)該包含頂點(diǎn)位置,紋理坐標(biāo),法向量,影響的骨骼編號(hào)和權(quán)重,一般影響到某個(gè)頂點(diǎn)的骨骼數(shù)目不會(huì)超過4個(gè)。同時(shí),頂點(diǎn)位置也有兩種記錄方法:相對(duì)于世界空間的和相對(duì)骨骼空間的,這里我們采用相對(duì)于世界空間的記錄方法,因?yàn)檫@種方案比較直觀,只需要記錄一個(gè)頂點(diǎn)位置就可以。麻煩的地方在于,因?yàn)楣趋赖淖儞Q矩陣要求頂點(diǎn)是相對(duì)于該骨骼的局部空間的,因此頂點(diǎn)在參與骨骼蒙皮計(jì)算的時(shí)候,需要先乘上骨骼的初始位置的矩陣的逆,以變換到骨骼空間。

  
struct Vertex_t

   {

       Point pos , normal , texCoord;

      
int    matID;

       
int   nEffBone;

       
struct{

          
int    boneIdx;

           
float weight;

       }Bone[
4];

   };

導(dǎo)出骨架

       骨骼動(dòng)畫系統(tǒng)中骨架為動(dòng)畫的載體,所有的蒙皮都附著在骨架之上。同時(shí)要保證屬于一個(gè)角色的所有的蒙皮都使用同一個(gè)骨架來建立和導(dǎo)出,這是一套換裝系統(tǒng)的基本需求。因此骨架的導(dǎo)出和保存通常是一次性的,后續(xù)導(dǎo)出皮膚的時(shí)候都應(yīng)該以這個(gè)骨架為基準(zhǔn)。這也要求我們?cè)趯?dǎo)出骨架的時(shí)候就需要導(dǎo)出所有的骨骼。

       骨架上的骨骼其實(shí)也是一個(gè)INode,骨骼僅僅是一些變換矩陣的信息而已。目前沒有特別好的辦法鑒定哪些INode是骨骼,比較可行的辦法是把所有Skin修改器使用到的INode都列為骨骼,同時(shí)美工還可以手動(dòng)指定哪些Node為骨骼,并把這些標(biāo)記用INode:: SetUserPropBool("IsBone",bIsBone);記錄到MAX文件中。

保存骨架的時(shí)候,需要保存骨骼的父子關(guān)系。并需要保存這個(gè)骨骼的第一幀數(shù)據(jù)。這要求如果美工在兩個(gè)不同的MAX文件里制作不同的動(dòng)作的時(shí)候,除了保證骨架相同以外,第一幀也需要完全相同.

骨架的保存和加載代碼如下:


  
struct Bone_t{

    Matrix NodeInitTM;

    
char   Name[32],ParantName[32];

   };

   
class CSkeleton {

   
public:    vector<Bone_t> m_Bones;

       
void loadSkeleton(const char* skeFileName){

    ifstream 
in(skeFileName , ios::binary);

       
while(!in.eof()){

       Bone_t _bone;

       
in.read((char*)&_bone , sizeof(Bone_t));

       m_Bones.push_back(_bone);

       }

       
in.close();

       }

   
int findBoneIndex(INode* pNode)    {//

   
for(int i = 0 ; i < m_Bones.size() ; i ++)

        
if(string(m_Bones[i].Name) == pNode->GetName() )    return i;

           
return -1;

}

    
void saveBone(ostream& out , INode* pNode , bool bRoot){

       Bone_t _bone;

       _bone.NodeInitTM 
= pNode->GetNodeTM(0) ;

       strncpy(_node.Name, pNode
->GetName() , 32);

       
if(bRoot) _bone.ParantName[0= 0;

       
else{

    INode
* pPNode =pNode->GetParantNode();

      strncpy(_bone.ParantName, pPNode
->GetName() , 32);

       }

     
out.write( (char*)&_bone , sizeof(Bone_t) );

     
for(int i = 0 ; i < pNode->NumberOfChildren() ; i ++)

     saveBone(
out,pNode->GetChildNode(i), false);

}

     
void saveSkeleton(const char* skeFileName , INode* pRootNode){

    ofstream 
out(skeFileName , ios::binary);

    saveBone(
out , pRootNode , true);

    
out.close();

   }

   };

findBoneIndex函數(shù)的目的是在把從文件中加載的骨骼和MAX中的Node對(duì)應(yīng)起來.因?yàn)槭歉鶕?jù)名字來進(jìn)行查找比較,因此要求所有的Node都必須要有唯一的名字.同時(shí),骨骼之間的父子關(guān)系也是通過名字來標(biāo)記的.每個(gè)Bone都記錄了它的父節(jié)點(diǎn)的名字.Save Skeleton骨架的按鈕響應(yīng)代碼如下:

   
void OnSaveSkeleton()

   {

          CSkeleton
* pSkeleton = GetGlobalSkeleton();

              Assert(ip
->GetSelNodeCount() == 1); //導(dǎo)出骨架的時(shí)候只能選擇一個(gè)節(jié)點(diǎn)



              
const char* filename = GetSaveFileName() ;



              
if(filename){

              pSkeleton
-> saveSkeleton(filename , ip->GetSelNode(0) );

}

   }

導(dǎo)出骨架動(dòng)作

骨架導(dǎo)出后,我們需要進(jìn)一步導(dǎo)出這個(gè)骨架的動(dòng)作。在導(dǎo)出動(dòng)作的時(shí)候,需要加載一個(gè)事先已經(jīng)導(dǎo)出的骨架。然后遍歷這個(gè)骨架中所有的骨骼,找到這個(gè)骨骼對(duì)應(yīng)的INode對(duì)象。然后確定動(dòng)畫的長(zhǎng)度和幀數(shù),為每一個(gè)骨頭的保存一個(gè)變換矩陣。

void OnExportAnimation()

   {

           
const char* fileName = GetSaveFileName();

ofstream 
out(fileName , ios::binary);

Interval ARange 
= ip->GetAnimRange(); //獲得動(dòng)畫的長(zhǎng)度



            TimeValue   tAniTime 
= ARange.End() - ARange.Start();

       TimeValue   tTime 
= ARange.Start();

       
int nFrame = tAniTime/GetTicksPerFrame();

//計(jì)算動(dòng)畫幀數(shù)

out.write((char*)&nFrame , sizeof(int));

//記錄有多少frame;

                
for(int i = 0 , ; i < nFrame ; i ++ ,tTime += GetTicksPerFrame()){

   CSkeleton
* pSkeleton = GetGlobalSkeleton();

   
for(int iBone = 0 ; iBone < pSkeleton->m_Bones.size() ; iBone ++){

   Bone_t
& bone = pSkeleton->m_Bones[iBone];

   INode
* pBoneNode = GetNodeByName(bone.Name);

//通過名字獲得INode指針

   Matrix mat 
= pBoneNode->GetNodeTM(tTime,NULL);

                        
out((char*)&mat , sizeof(Matrix));

              }

   }

                
out.close();

   }

這里演示里我們記錄的是骨骼的絕對(duì)變換矩陣,而不是相對(duì)父骨骼的變換矩陣,這省去了我們從根骨骼開始計(jì)算骨架的麻煩,但是也多了很多限制,比如不能進(jìn)行動(dòng)作混合,不能做動(dòng)作的插值等,使用相對(duì)父骨骼的局部矩陣的算法留給讀者自己去實(shí)現(xiàn),也可以參考Cal3D和我開源的XReal3D的導(dǎo)出插件。 此外,因?yàn)槲覀冊(cè)陧旤c(diǎn)數(shù)據(jù)中只保存了相對(duì)世界空間的位置,所以骨骼中的NodeInitTM將用來把相對(duì)世界空間的頂點(diǎn)位置變換到骨骼的局部空間中,皮膚混合的時(shí)候計(jì)算公式將如下:


其中M(t,i)為第i塊骨頭在t時(shí)刻的變換矩陣。

同樣的,我們只是簡(jiǎn)單的導(dǎo)出每一幀的變換矩陣,而沒有處理關(guān)鍵幀,使用關(guān)鍵幀加上相對(duì)父節(jié)點(diǎn)的局部變換矩陣的四元數(shù)插值,在保準(zhǔn)動(dòng)作的準(zhǔn)確性前提下能大大的降低動(dòng)作文件磁盤占用。



 游戲程序中的骨骼插件(下)

查找Skin修改器

要找到一個(gè)Mesh上是不是有Skin修改器,根據(jù)MAX的幾何管道的結(jié)構(gòu),需要遍歷整個(gè)ModStack中的Derived Object。判斷應(yīng)用在這些Derived Object上的修改器的類型。MAX中所有的對(duì)象都有一個(gè)類似COM的GUID的唯一標(biāo)記ClassID。Skin修改器的ClassID為SKIN_CLASSID,在獲得Derived Object的修改器后只需要檢查修改器的ClassID是不是SKIN_CLASSID即可。示例代碼如下:

ISkin * FindSkinModifier(INode *pINode){

Object 
* pObject = pINode->GetObjectRef();

if(pObject == 0return 0;

// 循環(huán)檢測(cè)所有的DerivedObject

while(pObject->SuperClassID() == GEN_DERIVOB_CLASS_ID)

{

    IDerivedObject 
* pDerivedObject = 

      static_cast
<IDerivedObject *>(pObject);

    
for(int stackId = 0

      stackId 
< pDerivedObject->NumModifiers(); 

      stackId
++)

    {

      Modifier 
* pModifier = 

        pDerivedObject
->GetModifier(stackId);

      
//檢測(cè)ClassID是不是Skin修改器

      
if(pModifier->ClassID() == SKIN_CLASSID) {

   
return (ISkin*) pModifier->GetInterface(I_SKIN);

      }

    }

    
//下一//個(gè)Derived Object    

    pObject 
= pDerivedObject->GetObjRef();

}

return 0;

}

獲取Mesh對(duì)象

根據(jù)第四節(jié)中描述的,要從一個(gè)INode中獲得Mesh對(duì)象,首先應(yīng)該從INode中獲得Object對(duì)象,然后再轉(zhuǎn)成Mesh對(duì)象。具體代碼如下:

Mesh* GetMesh(INode* pNode , int iMaxTime){

NullView view; 

//NullView是自定義的View類。詳細(xì)參見完整的插件代碼

BOOL bNeedDelete 
= false;

ObjectState os 
= pNode->EvalWorldState(iMaxTime);

Object
* pObj = os.obj;

TriObject 
* triObject = (TriObject *)pObj->

ConvertToType(iMaxTime, triObjectClassID);

GeomObject
* pGeoObj = (GeomObject*)pObj;

Mesh 
* pMesh = pGeoObj->GetRenderMesh( 

    iMaxTime , m_pNode , view , bNeedDelete );

return pMesh;

}

獲取皮膚數(shù)據(jù)與頂點(diǎn)的骨骼綁定信息

在成功獲取到一個(gè)節(jié)點(diǎn)的ISkin對(duì)象和Mesh以后,就可以使用這兩個(gè)對(duì)象來提取物體的頂點(diǎn)數(shù)據(jù)和骨骼的綁定信息了。

Mesh中的數(shù)據(jù)保存在不同的數(shù)組中,常用的包含以下幾種:頂點(diǎn)位置信息,顏色信息,法向量,UV坐標(biāo),MapChannel信息等。其中法向量的信息不是特別的準(zhǔn)確,需要考慮平滑組,面法向量與頂點(diǎn)法向量的差異等。MapChannel用于僅僅有多層紋理貼圖坐標(biāo)的情況,在只有一層紋理坐標(biāo)的情況下則不需要考慮,使用UV坐標(biāo)就足夠了。本文僅僅演示如何導(dǎo)出頂點(diǎn)位置,單層紋理和骨骼綁定信息,如何準(zhǔn)確的計(jì)算法向量以及處理多層紋理坐標(biāo)的內(nèi)容不在本文的討論范圍。

Mesh中的numVerts變量標(biāo)記了Mesh有幾個(gè)頂點(diǎn)位置,numTexCoords標(biāo)記了有幾個(gè)紋理坐標(biāo)。而通常這兩個(gè)值是不一樣的。

要提取骨骼綁定信息,首先需要從ISkin對(duì)象中查詢到ISkinContextData接口: ISkinContextData* pSkinContext

= pSkin->GetContextInterface(pNode); 通過ISkinContextData的GetNumAssignedBones函數(shù)可以得到影響到這個(gè)頂點(diǎn)的骨骼的個(gè)數(shù),進(jìn)而通過GetAssignedBone和GetBoneWeight接口可以到這個(gè)Bone的index已經(jīng)這個(gè)Bone對(duì)這個(gè)頂點(diǎn)的權(quán)重。

void GetVertexBoneInfo(INode* pNode , ISkin* pSkin , Mesh* mesh , int vertexIdx , int uvIdx , Vertex_t& vOut) {

CSkeleton
* pSkeleton = GetGlobalSkeleton();

ISkinContextData
* pSkinCtx = 

    pSkin
->GetContextInterface(pNode);

int nBones = 

    pSkinCtx
->GetNumAssignedBones(vertexIdx);

vOut.pos 
= mesh->verts[vertexIdx] * 

    pNode
->GetObjectTM(0,NULL);//第一幀的數(shù)據(jù)

vOut.texCoord 
= mesh->texCoords[uvIdx];

vOut.nEffBone 
= nBones;

for(int jBone = 0; jBone < nBones; jBone ++) {

    INode
* pBone = pSkin->GetBone(

      pSkinCtx
->GetAssignedBone(vertexIdx, Bone));

    vOut.Bone[jBone].weight 
= 

      pSkinCtx
->GetBoneWeight(vertexIdx,jBone);

    vOut.Bone[jBone].boneIdx 
= 

      pSkeleton
->findBoneIndex(pBone);

}

}

上述示例代碼中并沒有考慮到有個(gè)別頂點(diǎn)的骨骼數(shù)量超過4種情況,在正式的代碼中應(yīng)該對(duì)所有影響到這個(gè)頂點(diǎn)的骨骼權(quán)重進(jìn)行排序,取前4個(gè)權(quán)重最大的,丟棄其余的骨骼。并要考慮有有些重復(fù)骨骼的問題。

導(dǎo)出面的和材質(zhì)信息

在成功解決了頂點(diǎn)和骨骼的數(shù)據(jù)提取后,還需要獲得面的信息,即頂點(diǎn)的拓?fù)潢P(guān)系。3D渲染器能處理的面一般都是三角形面,因此一般要求美工在制作模型的時(shí)候首先將模型轉(zhuǎn)換成Editable Mesh。這樣能確保在Mesh取到的面都是三角形。

MAX中的面有兩種,用來表示形狀的Face類和用來表示紋理的TVFace類。Face和TVFace是一一對(duì)應(yīng)的。就是說要獲得一個(gè)三角形的完整數(shù)據(jù),必須同時(shí)獲取Face類和TVFace類。

Face類中的信息比較重要的有三個(gè)頂點(diǎn)的位置索引,可以通過Face::v[i]來獲得,得到這個(gè)索引后,在Mesh::verts數(shù)組里就可以獲得位置的數(shù)據(jù)。同理TVFace也一樣。

Face類里除了頂點(diǎn)信息外,還保存了和材質(zhì)相關(guān)的信息:MaterialID。一個(gè)Mesh上通常只能應(yīng)用一個(gè)Material,那為什么會(huì)有MaterialID這個(gè)概念呢?因?yàn)樵贛ax里除了標(biāo)準(zhǔn)材質(zhì)以外,還有一種美工非常有用的材質(zhì)叫多材質(zhì)(Multi-Material),這種材質(zhì)可以可以包含很多個(gè)標(biāo)準(zhǔn)材質(zhì),我們可以通過判斷材質(zhì)的ClassID來判斷它是不是一個(gè)多材質(zhì),如果是,就遍歷它的所有子材質(zhì)(Sub-Mateiral)。MaterialID就是對(duì)應(yīng)的子材質(zhì)的序號(hào)。在繪制的時(shí)候,MaterialID相同的三角形表示它們有相同的紋理和材質(zhì),在導(dǎo)出的時(shí)候應(yīng)該按照MaterialID進(jìn)行排序。

Max中的Material使用Mtl類表示,可以通過INode::GetMtl()來獲得。Mtl:: NumSubMtls加GetSubMtl則可訪問到這個(gè)Material的所有Material。對(duì)于一個(gè)標(biāo)準(zhǔn)材質(zhì),我們可以獲得這個(gè)材質(zhì)的各種屬性,包括紋理貼圖,紋理貼圖使用的貼圖坐標(biāo)通道(MapChannel),以及環(huán)境光,高光等屬性。處理材質(zhì)的示例代碼如下:

void SaveMaterial(const char* fileMat , INode* pNode){

ofstream 
out(fileMat,ios::binary);

Mtl
* pMtl = pNode->GetMtl();

int nSubMat = pMtl->NumSubMtls();

if (nSubMat == 0) {

    
//處理和保存一個(gè)標(biāo)準(zhǔn)材質(zhì)的代碼,詳見參考資料

    saveStdMaterial(
out , (StdMat*)pMtl); 

else {

    
for(int i = 0 ; i < pMtl->NumSubMtls(); i++ )

      saveStdMaterial(
out , 

       (StdMat
*)pMtl->GetSubMtl(i));      

}

}

   以下為簡(jiǎn)單的提取三角形數(shù)據(jù)的代碼。

void SaveMesh(const char* fileMesh , INode* pNode , Mesh* pMesh) {

ISkin
* pSkin = GetSkinModifier(pNode);

ofstream 
out(fileMesh , ios::binary);

int nFace = pMesh->numFaces ; 

out.write( (char*)&nFace,sizeof(int) );

for(int i = 0 ;i < pMesh->numFaces ; i ++) {

    TVFace
& tvface = pMesh->tvFace[i];

    Face
& face = pMesh->faces[i];

    
for(int j = 0 ; j < 3 ; j++) {

      
//一個(gè)三角形三個(gè)頂點(diǎn)

       Vertex_t vert ;

       vert.matID 
= face.getMatID();

       GetVertexBoneInfo(pNode, pSkin, pMesh,

         face.v[j], tvface.getTVert(j), vert);

       
//保存到文件

       
out.write( (char*)&vert,sizeof(vert) ); 

    }

}

}

   上述代碼僅僅是簡(jiǎn)單的保存了每一個(gè)面的信息,真正應(yīng)用的時(shí)候,至少還應(yīng)該把所有的面按照MateiralID來進(jìn)行排序,并且GetVertexBoneInfo函數(shù)在生成頂點(diǎn)數(shù)據(jù)的時(shí)候必然有很多頂點(diǎn)是完全相同的,應(yīng)該把這些相同的頂點(diǎn)都去掉。面的信息使用Index Buffer來保存。鑒于處理這些代碼過長(zhǎng),就不在文中一一舉例。

除了頂點(diǎn)材質(zhì)相關(guān)的信息之外,F(xiàn)ace類中比較重要的是法向量相關(guān)的信息,如RVertex和平滑組數(shù)據(jù)(smooth group)等。讀者可以參閱參考資料。

進(jìn)一步的工作

現(xiàn)在我們已經(jīng)基本可以獲得一個(gè)簡(jiǎn)單的骨骼動(dòng)畫系統(tǒng)所需要的大部分?jǐn)?shù)據(jù)了。但是這僅僅局限于一個(gè)演示性的骨骼動(dòng)畫系統(tǒng),離一個(gè)完整魯棒的系統(tǒng)還有很多事情要做。

上述例子中,我們僅僅導(dǎo)出了皮膚的頂點(diǎn)和第一層紋理坐標(biāo)。讀者還需要進(jìn)一步處理多層貼圖,頂點(diǎn)顏色,法向量等數(shù)據(jù)。在導(dǎo)出的骨架中,還應(yīng)該能更方便的處理骨架的層次關(guān)系,以及能區(qū)分角色的上身和下身。因?yàn)橥ǔR粋€(gè)骨骼動(dòng)畫系統(tǒng)是需要進(jìn)行上下身的動(dòng)作融合的。其次骨骼的變換矩陣應(yīng)該是保存相對(duì)于父骨骼的局部變換矩陣,局部矩陣可以分解成四元數(shù),平移和縮放,使用四元數(shù)能進(jìn)行更平滑的插值和動(dòng)作間的過渡,而且為了減少動(dòng)作文件的大小,關(guān)鍵幀動(dòng)畫也是值得考慮的一個(gè)技術(shù)點(diǎn)。

在數(shù)據(jù)組織方面,我認(rèn)為一個(gè)完善的骨骼動(dòng)畫系統(tǒng)應(yīng)該能合理而自然的管理所有數(shù)據(jù),XReal3D的數(shù)據(jù)組織采用的是流式存儲(chǔ),所有的動(dòng)作、骨架、頂點(diǎn)位置、紋理坐標(biāo)都擁有自己的流。這樣的存儲(chǔ)方式,在保持彈性的前提下大大的減少了一個(gè)骨骼動(dòng)畫角色的文件數(shù)量,方便管理和維護(hù)。

此外,現(xiàn)在的角色動(dòng)畫系統(tǒng)還應(yīng)該包括表情動(dòng)畫和柔體系統(tǒng)。表情動(dòng)畫,一般稱為在3DsMax的例子里有一個(gè)修改器就是表情動(dòng)畫,編譯這個(gè)插件和使用它的頭文件,我們可以訪問Max里的表情動(dòng)畫。這個(gè)插件的位置在X:\3dsmax\maxsdk\samples\modifiers\morpher中。柔體系統(tǒng)也叫布料系統(tǒng),可以用它來制作衣服飄動(dòng)的效果。

最后一個(gè)需要說的IGame, IGame是3DsMaxSDK中附帶一個(gè)用來為導(dǎo)出3D游戲相關(guān)數(shù)據(jù)的開發(fā)包。基本上,IGame是3DsMAXSDK的一個(gè)包裝,使用起來更加方便而已。要深入的了解和掌握3DsMax插件的開發(fā),還是需要對(duì)3DsMaxSDK有一定的熟悉程度。

總而言之,一個(gè)完整的骨骼動(dòng)畫系統(tǒng)插件是非常復(fù)雜的工程,鑒于文章篇幅,不可能介紹的面面俱到,希望本文對(duì)那些初次接觸MAX插件開發(fā)的讀者能有一定的幫助。

參考資料

1.3DsMAX Help

2. Cal3D : https://gna.org/projects/cal3d/

3. XReal3D MAX Exporter : http://gforge.osdn.net.cn/projects/xreal3d/

作者簡(jiǎn)介:

潘李亮,2003年畢業(yè)于西北工業(yè)大學(xué),愛好計(jì)算機(jī)圖形學(xué),曾在游戲公司負(fù)責(zé)3D引擎開發(fā)工作,目前在Corel公司從事多媒體軟件開發(fā)。

 

 

posted on 2009-07-11 02:09 七星重劍 閱讀(5332) 評(píng)論(1)  編輯 收藏 引用 所屬分類: Game Graphics

FeedBack:
# re: 野豬寫的《游戲程序中的骨骼插件》[未登錄] 2010-02-04 10:45 cxf
雖然很基礎(chǔ) 但是好東西啊 我初學(xué)的時(shí)候連點(diǎn)資料都找不到  回復(fù)  更多評(píng)論
  
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
              国产自产在线视频一区| 欧美一级片久久久久久久| 亚洲国产另类精品专区| 欧美岛国在线观看| 久久亚洲私人国产精品va媚药| 国产精品视频一区二区高潮| 亚洲欧美www| 亚洲欧美精品在线观看| 欧美丝袜一区二区| 中文欧美日韩| 欧美成人免费网站| 一本色道久久综合亚洲精品高清 | 欧美国产综合| 亚洲午夜精品| 国产精品热久久久久夜色精品三区 | 免费在线一区二区| 亚洲深夜福利视频| 亚洲电影在线| 欧美一级淫片aaaaaaa视频| 狠狠入ady亚洲精品经典电影| 美女在线一区二区| 久久精视频免费在线久久完整在线看| 亚洲精品久久久久| 鲁大师成人一区二区三区| 亚洲欧美精品伊人久久| 亚洲最新合集| 国产精品99久久久久久www| 亚洲午夜精品视频| 欧美国产视频在线| 这里只有精品视频| 欧美一区二区精品久久911| 亚洲天堂av图片| 亚洲色图在线视频| 亚洲大片在线观看| 亚洲美女中出| 一本色道久久88精品综合| 一本久久a久久免费精品不卡| 久久不射中文字幕| 欧美高清你懂得| 欧美性生交xxxxx久久久| 欧美日韩在线高清| 国产亚洲精品久久久久久| 尤物yw午夜国产精品视频| 亚洲综合国产| 亚洲人成免费| 久久久久久久久久久久久女国产乱 | 亚洲激情自拍| 一本色道综合亚洲| 蜜臀91精品一区二区三区| 亚洲大片在线| 久久久国产91| 91久久久亚洲精品| 亚洲视频大全| 久久久久久噜噜噜久久久精品| 欧美大片18| 亚洲精品一区二区三区蜜桃久 | 亚洲看片一区| 欧美在线关看| 久久国产精品久久久久久| 欧美高清在线视频观看不卡| 国产亚洲精品久久久久婷婷瑜伽| 亚洲女与黑人做爰| 亚洲精品欧美一区二区三区| 午夜精品亚洲| 国内精品久久久久影院色| 亚洲欧美日本国产有色| 亚洲欧洲在线免费| 欧美性猛交99久久久久99按摩 | 欧美精品久久久久a| 国产一区二区三区高清在线观看| 一区二区三区日韩欧美| 亚洲高清免费在线| 欧美一区在线直播| 亚洲第一网站免费视频| 老司机aⅴ在线精品导航| 久久激情久久| 亚洲欧美视频一区| 欧美怡红院视频| 国产精品日韩在线播放| 午夜亚洲伦理| 欧美精品粉嫩高潮一区二区| 亚洲日本中文字幕| 久久亚洲高清| 亚洲国产精品v| 夜夜夜久久久| 亚洲天堂网在线观看| 欧美a级一区二区| 亚洲综合不卡| 久久精品一区蜜桃臀影院| 国内久久婷婷综合| 亚洲伊人观看| 日韩视频二区| 欧美伊人久久| 欧美在线播放高清精品| 欧美伦理影院| 欧美在线视频全部完| 老**午夜毛片一区二区三区| 久久岛国电影| 国产精品婷婷| 一区二区三区视频在线播放| 国产精品成人免费视频 | 欧美电影专区| 欧美肥婆在线| 一本大道久久a久久精品综合| 欧美国产高潮xxxx1819| 免费日韩av| 红桃视频一区| 欧美亚洲一区二区在线| 亚洲国产日韩综合一区| 久久亚洲国产精品一区二区| 亚洲福利视频二区| 亚洲片区在线| 欧美v国产在线一区二区三区| 免播放器亚洲一区| 亚洲综合精品四区| 在线成人小视频| 欧美激情视频网站| 美国成人直播| 影音先锋一区| 嫩草影视亚洲| 亚洲香蕉在线观看| 性欧美长视频| 久久精品国产免费| 欧美日韩国产综合视频在线观看中文 | 久久视频这里只有精品| 亚洲国产天堂网精品网站| 亚洲免费在线看| 亚洲天堂免费观看| 久久亚洲欧美| 亚洲社区在线观看| 欧美成人精品| 国产一区激情| 久久久亚洲国产天美传媒修理工 | 日韩一级精品| 欧美一级专区| 国产一区二区三区无遮挡| 亚洲一区二区三区高清不卡| 免费亚洲电影在线观看| 这里是久久伊人| 免费久久99精品国产自| 国产精品欧美久久| 一本综合精品| 欧美成人情趣视频| 久久精品国产99国产精品澳门| 国产精品国产成人国产三级| 亚洲视频欧美在线| 亚洲视频日本| 国产亚洲欧美色| 久久综合伊人77777| 女人色偷偷aa久久天堂| 免费观看成人鲁鲁鲁鲁鲁视频| 欧美日韩免费观看一区二区三区| 国产日韩专区在线| 久久九九久久九九| 欧美亚州在线观看| 99综合精品| 一区二区激情| 国产一区二区三区四区在线观看 | 欧美日韩三区四区| 一区二区三区自拍| 亚洲精品久久嫩草网站秘色 | 裸体歌舞表演一区二区| 久久精品视频99| 一本色道久久88精品综合| 亚洲人成网站在线观看播放| 欧美韩日高清| 欧美在线观看一二区| 久久视频精品在线| 亚洲日本在线视频观看| 久久久成人精品| 玖玖国产精品视频| 宅男噜噜噜66一区二区| 午夜精品99久久免费| 影音先锋日韩精品| 欧美在线首页| 亚洲精品乱码久久久久久蜜桃麻豆| 99精品免费| 亚洲国产精品一区二区www在线| 99精品欧美一区二区三区综合在线| 国产日韩亚洲欧美| 亚洲精品激情| 亚洲精品在线电影| 欧美一区91| 久久久久91| 精品999网站| 免费视频一区| 亚洲人成亚洲人成在线观看| 亚洲国产经典视频| 欧美激情视频在线播放 | 亚洲精品日韩一| 亚洲理论电影网| 欧美在线日韩精品| 欧美亚洲视频在线观看| 亚洲精品久久久久久久久久久| 国产精品久久久久毛片软件 | 国产亚洲精品v| 午夜精品久久久久久久白皮肤| 亚洲一区亚洲二区| 欧美高清一区| 在线综合亚洲|