第一,了解骨骼結構(Skeletal Structures)和骨層級(Bone Hierarchies):
骨骼結構就是連續很多的骨頭(Bone)相結合,形成的骨層級。第一個骨頭叫做根骨(root bone),是形成骨骼結構的關鍵點。其它所有的骨骼作為孩子骨(child bone)或者兄弟骨(sibling bone)附加在根骨之上。所謂的“骨”用一個幀(frame)對象表示。在Directx中,用一個D3DXFRAME結構或者X文件中的Frame template來表示幀對象。下面看一下Frame template和D3DXFRAME結構的定義:
template Frame
{
< 3D82AB46-62DA-11cf-AB39-0020AF71E433 >
FrameTransformMatrix frameTransformMatrix; // 骨骼相對于父節點的坐標變換矩陣,就是一個matrix
Mesh mesh; // 骨骼的Mesh
}
typedef struct _D3DXFRAME
{
LPSTR Name; // 骨骼名稱
D3DXMATRIX TransformationMatrix; // 相對與父節點的坐標變換矩陣
LPD3DXMESHCONTAINER pMeshContainer; // LPD3DXMESHCONTAINER對象,
//用來加載MESH,還有一些附加屬性,見SDK
struct _D3DXFRAME *pFrameSibling; // 兄弟節點指針,和下面的子節點指針
// 一塊作用構成骨骼的層次結構。
struct _D3DXFRAME *pFrameFirstChild; // 子節點指針
} D3DXFRAME, *LPD3DXFRAME;
注意D3DXFRAME * pFrameSibling和D3DXFRAME * pFrameFirstChild,主要是利用這兩個指針形成骨層級。pFrameSibling把一個骨頭連接到兄弟層級,相對的,pFrameFirstChild把一個骨頭連接到子層級。通常,你需要用建模軟件為你的程序創建那些骨骼結構,輸出骨層級到X文件以便使用。Microsoft有3D Studio Max和Maya的輸出插件(exporter),可以輸出骨骼和動畫數據到X文件。很多建模程序也都有這樣的功能。
利用D3DXFRAME pointers指針形成了一個兄弟幀和孩子幀的鏈表。
在前面template Frame中已經提及過每個Frame數據對象中存放著一個變換矩陣,這個矩陣描述了該骨骼相對于父骨骼的位置。另外在根Frame數據對象中內嵌了一個標準的Mesh數據對象。Frame定義了骨骼的層級,而Mesh中的SkinWeights數據對象定義了Frame代表的骨頭。我們用D3DXFRAME結構容納從X文件加載進來的Frame數據對象。為了更好的容納Frame數據對象,我們需要擴展下D3DXFRAME結構:
struct D3DXFRAME_EX : D3DXFRAME
{
D3DXMATRIX matCombined; // 組合變換矩陣,用于儲存變換的骨骼矩陣
D3DXMATRIX matOriginal; // 從X文件加載的原始變換矩陣
D3DXFRAME_EX()
{
Name = NULL;
pMeshContainer = NULL;
pFrameSibling = pFrameFirstChild = NULL;
D3DXMatrixIdentity(&matCombined);
D3DXMatrixIdentity(&matOriginal);
D3DXMatrixIdentity(&TransformationMatrix);
}
~D3DXFRAME_EX()
{
delete [] Name; Name = NULL;
delete pFrameSibling; pFrameSibling = NULL;
delete pFrameFirstChild; pFrameFirstChild = NULL;
}
}
利用我們以前介紹的cXParse類可以遍歷X文件的數據對象,從而加載出Frame數據對象。下面的代碼都是寫在方法ParseObject中,如下:
// 判斷當前分析的是不是Frame節點
if( objGUID == TID_D3DRMFrame )
{
// 引用對象直接返回,不需要做分析。一個數據段實際定義一次后可以被其他模板引用,例
//如后面的Animation動畫模板就會引用這里的Frame
// 節點,標識動畫關聯的骨骼。
if( pDataObj->IsReference() )
return true;
// D3DXFRAME_EX為D3DXFRAME的擴展結構,增加些數據成員
D3DXFRAME_EX *pFrame = new D3DXFRAME_EX();
// 得到名稱
pFrame->Name = GetObjectName( pDataObj );
// 注意觀察文件就可以發現一個Frame要么是根Frame,父節點不存在, 要么作為某
//個Frame的孩子Frame而存在。
if( NULL == pData )
{
// 作為根節點的兄弟節點加入鏈表。
pFrame->pFrameSibling = m_pRootFrame;
m_pRootFrame = pFrame;
pFrame = NULL;
// 將自定義數據指針指向自己,供子節點引用。
pData = ( void** )&m_pRootFrame;
}
else
{
// 作為傳入節點的子節點
D3DXFRAME_EX *pDataFrame = ( D3DXFRAME_EX* )( *pData );
pFrame->pFrameSibling = pDataFrame->pFrameFirstChild;
pDataFrame->pFrameFirstChild = pFrame;
pFrame = NULL;
pData = ( void** )&pDataFrame->pFrameFirstChild;
}
}
記住我們只需要做一件事情,判斷類型,分配匹配的對象然后拷貝數據,下面來分析Frame中的matrix,
// frame的坐標變換矩陣, 因為matrix必然屬于某個Frame所以pData必須有效
else if( objGUID == TID_D3DRMFrameTransformMatrix && pData )
{
// 我們可以肯定pData指向某個Frame
D3DXFRAME_EX *pDataFrame = ( D3DXFRAME_EX* )( *pData );
// 先取得緩沖區大小,應該是個標準的4x4矩陣
DWORD size = 0;
LPCVOID buffer = NULL;
hr = pDataObj->Lock( &size, &buffer );
if( FAILED( hr ) )
return false;
// 拷貝數據
if( size == sizeof( D3DXMATRIX ) )
{
memcpy( &pDataFrame->TransformationMatrix, buffer, size );
pDataObj->Unlock();
pDataFrame->matOriginal = pDataFrame->TransformationMatrix;
}
}
第二,修改和更新骨骼層級:
加載完骨骼層級之后,你可以操作它,更改骨骼的方位。你需要創建一個遞歸函數,按照名字找到相應的Frame數據對象。這個函數如下:
D3DXFRAME_EX *FindFrame(D3DXFRAME_EX *Frame, char *Name)
{
if(Frame && Frame->Name && Name) {
// 如果名字找到,返回一個Frame指針
if(!strcmp(Frame->Name, Name)) // strcmp函數比較兩個字符串,如果兩個字符串相等,返回0
return Frame;
}
// 在sibling frames找匹配的名字
if(Frame && Frame->pFrameSibling) {
D3DXFRAME_EX *FramePtr = FindFrame((D3DXFRAME_EX*)Frame->pFrameSibling, Name);
if(FramePtr)
return FramePtr;
}
// 在child frames找匹配的名字
if(Frame && Frame->pFrameFirstChild) {
D3DXFRAME_EX *FramePtr = FindFrame((D3DXFRAME_EX*)Frame->pFrameFirstChild,Name);
if(FramePtr)
return FramePtr;
}
// 如果沒有找到,返回 NULL
return NULL;
}
如果你想找到一個叫“Leg”的Frame,可以把“Leg”傳入FindFrame函數,并且提供指向RootFrame的指針:
// pRootframe 為D3DXFRAME_EX root frame 指針
D3DXFRAME_EX *Frame = FindFrame(pRootFrame, "Leg");
if(Frame) {
// 可以在這里做一些處理,比如旋轉操作
// 你在這里可以稍微的旋轉這個骨頭
D3DXMatrixRotationY(&Frame->TransformationMatrix, 1.57f);
}
一旦你修改變換骨頭,你需要更新整個骨骼層級,也就是把變換的組合矩陣存入D3DXFRAME_EX結構的matCombined成員中,用于后面的渲染。下面的函數應該增加到D3DXFRAME_EX結構中,如下:
void UpdateHierarchy(D3DXMATRIX *matTransformation = NULL)
{
D3DXFRAME_EX *pFramePtr;
D3DXMATRIX matIdentity;
// 如果為空,用一個全同矩陣
if(!matTransformation) {
D3DXMatrixIdentity(&matIdentity);
matTransformation = &matIdentity;
}
// 把變換矩陣組合到matCombined中
matCombined = TransformationMatrix * (*matTransformation);
// 更新兄弟層級
if((pFramePtr = (D3DXFRAME_EX*)pFrameSibling))
pFramePtr->UpdateHierarchy(matTransformation);
// 更新孩子層級
if((pFramePtr = (D3DXFRAME_EX*)pFrameFirstChild))
pFramePtr->UpdateHierarchy(&matCombined);
}
現在matCombined儲存著每個骨骼相對于原點的變換矩陣,然后只要把各個頂點附在相應的骨骼上,就能渲染了。
第三,使用蒙皮網格:
蒙皮網格和普通網格的唯一不同點就是看XskinMeshHeader和SkinWeights模版是否存在。如果把這兩個模版從任何一個蒙皮網格里面移走的話,就可以得到一個普通網格。在X文件中,我們將會發現一個GUID為TID_D3DRMMesh的模版,這表示模版里面存有一個網格。利用D3D的幫助函數D3DXLoadSkinMeshFromXof將會加載蒙皮網格和其它補充性數據。只需要向它傳遞一個IDirectXFileData指針,然后它將為你做剩下的事情。現在介紹下D3DXLoadSkinMeshFromXof函數:
HRESULT D3DXLoadSkinMeshFromXof(
LPD3DXFILEDATA pxofMesh, //X文件數據接口
DWORD Options, //加載參數
LPDIRECT3DDEVICE9 pD3DDevice, //使用的三維設備
LPD3DXBUFFER * ppAdjacency, //鄰接信息緩沖接口
LPD3DXBUFFER * ppMaterials, //材質緩沖接口
LPD3DXBUFFER * ppEffectInstances, //效果實例接口
DWORD * pMatOut, //材質數
LPD3DXSKININFO * ppSkinInfo, //蒙皮信息接口
LPD3DXMESH * ppMesh //加載的網格模型接口
);