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

来源:互联网 发布:买动作死飞的淘宝店 编辑:程序博客网 时间:2024/06/03 15:13

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

原创粉丝点击