您的位置:首页 > 其它

关于影响蒙皮动画的几个矩阵的研究

2011-02-19 15:34 225 查看
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内部执行混合!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: