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

    技术2026-06-11  0

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

    最新回复(0)