dx实现了.x文件的加载,播放动画,但是都是对外只是提供了接口供大家调用,这样对于理解动画是如何播放的,在播放动画期间矩阵是如何影响动画效果的都成为了谜底,程序员,尤其是window平台的程序员,只要想学好用好任何一门语言,都必须首先成为了一个猜谜高手,想尽一切办法要把微软是怎么实现的,给猜透了,只有这样才能成为真正的专家,高手!
不过在学习dx动画的时候,市面上有一些书籍是讲底层实现原理的,并且用代码例子的形式重新实现了微软的功能,对于大家理解底层是大大有所帮助的,这里我感觉<<Advanced.Animation.with.DirectX>>重庆大学出版社出版,这本书简直把微软的dx动画给讲透了,读来受益匪浅!学dx的.x动画,这一本书足够!
但是学习dx的.x动画只是学习之用,理解概念之用,因为市面上现有的游戏动画,都没有超出这个动画理念的范畴,对于学习动画的原理确确实实很有益处,但是除此之外,确实没有大用,几乎所有的网游公司都是借用了现有的游戏引擎去开发,没有一家游戏引擎是采用.x作为动画的文件格式,所以对于.x懂了,会用了,即可,实际工作可以结合你公司所采用的游戏引擎去开发动画!
但是你学习dx又必须学习.x,有几个矩阵要搞清楚:(这里只谈蒙皮动画)
1、frame中的原始矩阵 组合矩阵
2、skinweights中的偏移矩阵
3、animationset中的关键帧矩阵
下面研究这几个矩阵是如何组合运用的:
结合dx高级动画这本书的例子研究:
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int nCmdShow)
{
WNDCLASSEX wcex;
MSG Msg;
HWND hWnd;
// Initialize the COM system
CoInitialize(NULL);
// Create the window class here and register it
wcex.cbSize = sizeof(wcex);
wcex.style = CS_CLASSDC;
wcex.lpfnWndProc = WindowProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInst;
wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = NULL;
wcex.lpszMenuName = NULL;
wcex.lpszClassName = g_szClass;
wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if(!RegisterClassEx(&wcex))
return FALSE;
// Create the main window
hWnd = CreateWindow(g_szClass, g_szCaption,
WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
0, 0, 640, 480,
NULL, NULL, hInst, NULL);
if(!hWnd)
return FALSE;
ShowWindow(hWnd, SW_NORMAL);
UpdateWindow(hWnd);
// Call init function and enter message pump
if(DoInit(hWnd) == TRUE) {
// Start message pump, waiting for user to exit
ZeroMemory(&Msg, sizeof(MSG));
while(Msg.message != WM_QUIT) {
if(PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
// Render a single frame
DoFrame();
}
}
// Call shutdown
DoShutdown();
// Unregister the window class
UnregisterClass(g_szClass, hInst);
// Shut down the COM system
CoUninitialize();
return 0;
}
BOOL DoInit(HWND hWnd)
{
// Initialize Direct3D
InitD3D(&g_pD3D, &g_pD3DDevice, hWnd);
// Load a skeletal mesh
LoadMesh(&g_Mesh, &g_Frame, g_pD3DDevice, "..//Data//tiny.x", "..//Data//");
// Load an animation collection
g_Anim.Load("..//Data//tiny.x");
// Map the animation to the frame hierarchy
g_Anim.Map(g_Frame);
// Load the guide texture and create the sprite interface
D3DXCreateTextureFromFileEx(g_pD3DDevice, "..//Data//Guide.bmp",
D3DX_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT, 0,
D3DFMT_A1R5G5B5, D3DPOOL_DEFAULT, D3DX_DEFAULT,
D3DX_DEFAULT, 0xFF000000,
NULL, NULL, &g_GuideTexture);
D3DXCreateSprite(g_pD3DDevice, &g_Guide);
// Clear toggles
memset(g_BlendFlags, 1, 5);
return TRUE;
}
void DoFrame()
{
static DWORD StartTime = timeGetTime();
DWORD ThisTime = timeGetTime();
// Clear the frames' transformation matrices
if(g_Frame)
g_Frame->Reset();
// Blend the animations
if(g_BlendFlags[0])
g_Anim.Blend("left_arm", (ThisTime-StartTime), TRUE);
if(g_BlendFlags[1])
g_Anim.Blend("right_arm", (ThisTime-StartTime), TRUE);
if(g_BlendFlags[2])
g_Anim.Blend("left_leg", (ThisTime-StartTime), TRUE);
if(g_BlendFlags[3])
g_Anim.Blend("right_leg", (ThisTime-StartTime), TRUE);
if(g_BlendFlags[4])
g_Anim.Blend("body", (ThisTime-StartTime), TRUE);
// Rebuild the frame hierarchy transformations
if(g_Frame)
g_Frame->UpdateHierarchy();
// Build the skinned mesh
UpdateMesh(g_Mesh);
// Calculate a view transformation matrix
D3DXMATRIX matView;
D3DXMatrixLookAtLH(&matView,
&D3DXVECTOR3(600.0f, 200.0f, -600.0f),
&D3DXVECTOR3(0.0f, 0.0f, 0.0f),
&D3DXVECTOR3(0.0f, 1.0f, 0.0f));
g_pD3DDevice->SetTransform(D3DTS_VIEW, &matView);
// Set a world transformation
D3DXMATRIX matWorld;
D3DXMatrixIdentity(&matWorld);
g_pD3DDevice->SetTransform(D3DTS_WORLD, &matWorld);
// Clear the device and start drawing the scene
g_pD3DDevice->Clear(NULL, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(0,0,64,255), 1.0f, 0);
if(SUCCEEDED(g_pD3DDevice->BeginScene())) {
// Render skinned mesh
DrawMesh(g_Mesh);
// Draw the guide
//g_Guide->Draw(g_GuideTexture, NULL, NULL, NULL, 0.0f, &D3DXVECTOR2(0.0f, 0.0f), 0xFFFFFFFF);
g_Guide->Draw(g_GuideTexture, NULL, &D3DXVECTOR3(0.0f, 0.0f,0.0f), &D3DXVECTOR3(0.0f, 0.0f,0.0f), 0xFFFFFFFF);
// End the scene
g_pD3DDevice->EndScene();
}
// Present the scene to the user
g_pD3DDevice->Present(NULL, NULL, NULL, NULL);
}
void cBlendedAnimationCollection::Blend( /
char *AnimationSetName, /
DWORD Time, BOOL Loop, /
float Blend)
{
cAnimationSet *AnimSet = m_AnimationSets;
// Look for matching animation set name if used
if(AnimationSetName) {
// Find matching animation set name
while(AnimSet != NULL) {
// Break when match found
if(!stricmp(AnimSet->m_Name, AnimationSetName))
break;
// Go to next animation set object
AnimSet = AnimSet->m_Next;
}
}
// Return no set found
if(AnimSet == NULL)
return;
// Bounds time to animation length
if(Time > AnimSet->m_Length)
Time = (Loop==TRUE)?Time%(AnimSet->m_Length+1):AnimSet->m_Length;
// Go through each animation
cAnimation *Anim = AnimSet->m_Animations;
while(Anim) {
// Only process if it's attached to a bone
if(Anim->m_Bone) {
// Reset transformation
D3DXMATRIX matAnimation;
D3DXMatrixIdentity(&matAnimation);
// Apply various matrices to transformation
// Scaling
if(Anim->m_NumScaleKeys && Anim->m_ScaleKeys) {
// Loop for matching scale key
DWORD Key1 = 0, Key2 = 0;
for(DWORD i=0;i<Anim->m_NumScaleKeys;i++) {
if(Time >= Anim->m_ScaleKeys[i].m_Time)
Key1 = i;
}
// Get 2nd key number
Key2 = (Key1>=(Anim->m_NumScaleKeys-1))?Key1:Key1+1;
// Get difference in keys' times
DWORD TimeDiff = Anim->m_ScaleKeys[Key2].m_Time-
Anim->m_ScaleKeys[Key1].m_Time;
if(!TimeDiff)
TimeDiff = 1;
// Calculate a scalar value to use
float Scalar = (float)(Time - Anim->m_ScaleKeys[Key1].m_Time) / (float)TimeDiff;
// Calculate interpolated scale values
D3DXVECTOR3 vecScale = Anim->m_ScaleKeys[Key2].m_vecKey -
Anim->m_ScaleKeys[Key1].m_vecKey;
vecScale *= Scalar;
vecScale += Anim->m_ScaleKeys[Key1].m_vecKey;
// Create scale matrix and combine with transformation
D3DXMATRIX matScale;
D3DXMatrixScaling(&matScale, vecScale.x, vecScale.y, vecScale.z);
matAnimation *= matScale;
}
// Rotation
if(Anim->m_NumRotationKeys && Anim->m_RotationKeys) {
// Loop for matching rotation key
DWORD Key1 = 0, Key2 = 0;
for(DWORD i=0;i<Anim->m_NumRotationKeys;i++) {
if(Time >= Anim->m_RotationKeys[i].m_Time)
Key1 = i;
}
// Get 2nd key number
Key2 = (Key1>=(Anim->m_NumRotationKeys-1))?Key1:Key1+1;
// Get difference in keys' times
DWORD TimeDiff = Anim->m_RotationKeys[Key2].m_Time-
Anim->m_RotationKeys[Key1].m_Time;
if(!TimeDiff)
TimeDiff = 1;
// Calculate a scalar value to use
float Scalar = (float)(Time - Anim->m_RotationKeys[Key1].m_Time) / (float)TimeDiff;
// slerp rotation values
D3DXQUATERNION quatRotation;
D3DXQuaternionSlerp(&quatRotation,
&Anim->m_RotationKeys[Key1].m_quatKey,
&Anim->m_RotationKeys[Key2].m_quatKey,
Scalar);
// Create rotation matrix and combine with transformation
D3DXMATRIX matRotation;
D3DXMatrixRotationQuaternion(&matRotation, &quatRotation);
matAnimation *= matRotation;
}
// Translation
if(Anim->m_NumTranslationKeys && Anim->m_TranslationKeys) {
// Loop for matching translation key
DWORD Key1 = 0, Key2 = 0;
for(DWORD i=0;i<Anim->m_NumTranslationKeys;i++) {
if(Time >= Anim->m_TranslationKeys[i].m_Time)
Key1 = i;
}
// Get 2nd key number
Key2 = (Key1>=(Anim->m_NumTranslationKeys-1))?Key1:Key1+1;
// Get difference in keys' times
DWORD TimeDiff = Anim->m_TranslationKeys[Key2].m_Time-
Anim->m_TranslationKeys[Key1].m_Time;
if(!TimeDiff)
TimeDiff = 1;
// Calculate a scalar value to use
float Scalar = (float)(Time - Anim->m_TranslationKeys[Key1].m_Time) / (float)TimeDiff;
// Calculate interpolated vector values
D3DXVECTOR3 vecPos = Anim->m_TranslationKeys[Key2].m_vecKey -
Anim->m_TranslationKeys[Key1].m_vecKey;
vecPos *= Scalar;
vecPos += Anim->m_TranslationKeys[Key1].m_vecKey;
// Create translation matrix and combine with transformation
D3DXMATRIX matTranslation;
D3DXMatrixTranslation(&matTranslation, vecPos.x, vecPos.y, vecPos.z);
matAnimation *= matTranslation;
}
// Matrix
if(Anim->m_NumMatrixKeys && Anim->m_MatrixKeys) {
// Loop for matching matrix key
DWORD Key1 = 0, Key2 = 0;
for(DWORD i=0;i<Anim->m_NumMatrixKeys;i++) {
if(Time >= Anim->m_MatrixKeys[i].m_Time)
Key1 = i;
}
// Get 2nd key number
Key2 = (Key1>=(Anim->m_NumMatrixKeys-1))?Key1:Key1+1;
// Get difference in keys' times
DWORD TimeDiff = Anim->m_MatrixKeys[Key2].m_Time-
Anim->m_MatrixKeys[Key1].m_Time;
if(!TimeDiff)
TimeDiff = 1;
// Calculate a scalar value to use
float Scalar = (float)(Time - Anim->m_MatrixKeys[Key1].m_Time) / (float)TimeDiff;
// Calculate interpolated matrix
D3DXMATRIX matDiff = Anim->m_MatrixKeys[Key2].m_matKey -
Anim->m_MatrixKeys[Key1].m_matKey;
matDiff *= Scalar;
matDiff += Anim->m_MatrixKeys[Key1].m_matKey;
// Combine with transformation
matAnimation *= matDiff;
}
// Get the difference in transformations
D3DXMATRIX matDiff = matAnimation - Anim->m_Bone->matOriginal;
// Adjust by blending amount
matDiff *= Blend;
// Add to transformation matrix
Anim->m_Bone->TransformationMatrix += matDiff;
}
// Go to next animation
Anim = Anim->m_Next;
}
}
blend函数的作用是通过读取关键帧中的矩阵进行插值得到
// Matrix
if(Anim->m_NumMatrixKeys && Anim->m_MatrixKeys) {
// Loop for matching matrix key
DWORD Key1 = 0, Key2 = 0;
for(DWORD i=0;i<Anim->m_NumMatrixKeys;i++) {
if(Time >= Anim->m_MatrixKeys[i].m_Time)
Key1 = i;
}
// Get 2nd key number
Key2 = (Key1>=(Anim->m_NumMatrixKeys-1))?Key1:Key1+1;
// Get difference in keys' times
DWORD TimeDiff = Anim->m_MatrixKeys[Key2].m_Time-
Anim->m_MatrixKeys[Key1].m_Time;
if(!TimeDiff)
TimeDiff = 1;
// Calculate a scalar value to use
float Scalar = (float)(Time - Anim->m_MatrixKeys[Key1].m_Time) / (float)TimeDiff;
// Calculate interpolated matrix
D3DXMATRIX matDiff = Anim->m_MatrixKeys[Key2].m_matKey -
Anim->m_MatrixKeys[Key1].m_matKey;
matDiff *= Scalar;
matDiff += Anim->m_MatrixKeys[Key1].m_matKey;
// Combine with transformation
matAnimation *= matDiff;
}
// Get the difference in transformations
D3DXMATRIX matDiff = matAnimation - Anim->m_Bone->matOriginal;
// Adjust by blending amount
matDiff *= Blend;//用偏移量矩阵乘以混合系数
// Add to transformation matrix
Anim->m_Bone->TransformationMatrix += matDiff;//frame的本地矩阵加上混合矩阵
以上函数的执行表明动画播放到了当前时间,因为先用animationset中的关键帧矩阵去混合,这样说明动画是在原地播放的,播放动画就是改变frame的以mesh的根节点为世界坐标系的本地矩阵,这里的功能相当于
//if (m_bPlayAnim && m_pAnimController != NULL)
// m_pAnimController->AdvanceTime( fElapsedAppTime, NULL );
// Function to combine matrices in frame hiearchy
void UpdateHierarchy(D3DXMATRIX *matTransformation = NULL)
{
D3DXFRAME_EX *pFramePtr;
D3DXMATRIX matIdentity;
// Use an identity matrix if none passed
if(!matTransformation) {
D3DXMatrixIdentity(&matIdentity);
matTransformation = &matIdentity;
}
// Combine matrices w/supplied transformation matrix
matCombined = TransformationMatrix * (*matTransformation);
// Combine w/sibling frames
if((pFramePtr = (D3DXFRAME_EX*)pFrameSibling))
pFramePtr->UpdateHierarchy(matTransformation);
// Combine w/child frames
if((pFramePtr = (D3DXFRAME_EX*)pFrameFirstChild))
pFramePtr->UpdateHierarchy(&matCombined);
}
更新frame的组合矩阵
///////////////////////////////////////////////////////////
//
// Update a skinned mesh
//
///////////////////////////////////////////////////////////
HRESULT UpdateMesh(D3DXMESHCONTAINER_EX *pMesh)
{
// Error checking
if(!pMesh)
return E_FAIL;
if(!pMesh->MeshData.pMesh || !pMesh->pSkinMesh || !pMesh->pSkinInfo)
return E_FAIL;
if(!pMesh->pBoneMatrices || !pMesh->ppFrameMatrices)
return E_FAIL;
// Copy the bone matrices over (must have been combined before call DrawMesh)
for(DWORD i=0;i<pMesh->pSkinInfo->GetNumBones();i++) {
// Start with bone offset matrix
pMesh->pBoneMatrices[i] = (*pMesh->pSkinInfo->GetBoneOffsetMatrix(i));
// Apply frame transformation
if(pMesh->ppFrameMatrices[i])
pMesh->pBoneMatrices[i] *= (*pMesh->ppFrameMatrices[i]);
}
// Lock the meshes' vertex buffers
void *SrcPtr, *DestPtr;
pMesh->MeshData.pMesh->LockVertexBuffer(D3DLOCK_READONLY, (void**)&SrcPtr);
pMesh->pSkinMesh->LockVertexBuffer(0, (void**)&DestPtr);
// Update the skinned mesh using provided transformations
pMesh->pSkinInfo->UpdateSkinnedMesh(pMesh->pBoneMatrices, NULL, SrcPtr, DestPtr);
// Unlock the meshes vertex buffers
pMesh->pSkinMesh->UnlockVertexBuffer();
pMesh->MeshData.pMesh->UnlockVertexBuffer();
// Return success
return S_OK;
}
偏移矩阵乘以父矩阵的组合矩阵,代表先把骨骼放置到原点下,然后再放置到mesh模型下的相应位置
接下来就可以绘制了
///////////////////////////////////////////////////////////
//
// Draw mesh functions
//
///////////////////////////////////////////////////////////
HRESULT DrawMesh(D3DXMESHCONTAINER_EX *pMesh)
{
IDirect3DDevice9 *pD3DDevice;
DWORD LastState, OldAlphaState, OldSrcBlend, OldDestBlend;
// Error checking
if(!pMesh)
return E_FAIL;
if(!pMesh->MeshData.pMesh)
return E_FAIL;
if(!pMesh->NumMaterials || !pMesh->pMaterials)
return E_FAIL;
// Get the device interface
pMesh->MeshData.pMesh->GetDevice(&pD3DDevice);
// Release vertex shader if being used
pD3DDevice->SetVertexShader(NULL);
pD3DDevice->SetVertexDeclaration(NULL);
// Save render states
pD3DDevice->GetRenderState(D3DRS_ALPHABLENDENABLE, &OldAlphaState);
pD3DDevice->GetRenderState(D3DRS_SRCBLEND, &OldSrcBlend);
pD3DDevice->GetRenderState(D3DRS_DESTBLEND, &OldDestBlend);
LastState = OldAlphaState;
// Setup pointer for mesh to draw, either regular or skinned
ID3DXMesh *pDrawMesh = (!pMesh->pSkinMesh)?pMesh->MeshData.pMesh:pMesh->pSkinMesh;
// Look through all subsets
for(DWORD i=0;i<pMesh->NumMaterials;i++) {
// Set material and texture
pD3DDevice->SetMaterial(&pMesh->pMaterials[i].MatD3D);
pD3DDevice->SetTexture(0, pMesh->pTextures[i]);
// Enable or disable alpha blending per material
if(pMesh->pMaterials[i].MatD3D.Diffuse.a != 1.0f) {
if(LastState != TRUE) {
LastState = TRUE;
pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
pD3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);//SRCCOLOR);
pD3DDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_DESTCOLOR);
}
} else {
if(LastState != FALSE) {
LastState = FALSE;
pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
}
}
// Draw the mesh subset
pDrawMesh->DrawSubset(i);
}
// Restore alpha blending states
if(LastState != OldAlphaState) {
pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, OldAlphaState);
pD3DDevice->SetRenderState(D3DRS_SRCBLEND, OldSrcBlend);
pD3DDevice->SetRenderState(D3DRS_DESTBLEND, OldDestBlend);
}
// Make sure to release the device object!
pD3DDevice->Release();
// Return success
return S_OK;
}
animationset中的是相对于frame中的原始矩阵的一系列插值矩阵,skinweights中的矩阵是把他偏移回原点,然后乘以父矩阵就又放置到了mesh下,跟模型相连接,这样就能完成一系列的动画效果!如果改变了animationset的话,就切换到了新的动画效果,理论上动画效果可以无限,在共用一份meshcontainer的情况下,真是爽耶!
skinweights中的顶点的blend权重,由dx内部执行混合!