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内部执行混合!
