DxDev.narod.ru

DirectX - скелетная анимация

В статье описано моё понятие скелетной анимации прочитав часть книги «DirectX Продвинутая анимация» Джима Адамса.

Скелетная анимация – упорядоченное изменение костей скелета. Каждая кость изменяется относительно родительской и даёт порядок дочерним (если таковые окажутся) изменяться относительно неё. Скелет загрузим из .х файла (в нашем случае tiny.x поставляемым DirectX SDK) в котором помимо меша содержатся ещё данные для скелета: иерархия костей в которой соблюдается порядок родительская кость кроме своих данных содержит встроенные данные на дочернюю кость. Например кость кисти встроена в кость локтя и т.д. Данные состоят из имени и матрицы трансформации кости. Кости помещены в в .х-овский шаблон Frame.

Для построения скелета воспользуемся загрузчиком открывающим .х файл, создающий объект могущий глянуть на все объекты описанные в .х, и сохранить данные этих объектов.


if(FAILED(DirectXFileCreate(&pDXFile)))
  return FALSE;

// Register the common templates
if(FAILED(pDXFile->RegisterTemplates((LPVOID)D3DRM_XTEMPLATES,
                                      D3DRM_XTEMPLATE_BYTES))) 
{
  pDXFile->Release();
  return FALSE;
}

   // Create an enumeration object
 if(FAILED(pDXFile->CreateEnumObject((LPVOID)Filename,
                                      DXFILELOAD_FROMFILE, 
                                      &pDXEnum))) 
{
    pDXFile->Release();
    return FALSE;
  }

// Loop through all top-level objects, breaking on errors
  BOOL ParseResult;
  while(SUCCEEDED(pDXEnum->GetNextDataObject(&pDXData))) 
  {
     ParseResult = ParseObject(pDXData, NULL, 0, Data, FALSE);
     ReleaseCOM(pDXData);

     if(ParseResult == FALSE)
         break;
    }

ParseObject с помощью ParseChildObjects строит иерархию костей. ParseChildObjects – втыкает, а не содержит ли объект встроенных(дочерних) объектов. Если да вертаемся на ParseObject если нет, то выходим

Сохраним кости (фреймы) в объекте
struct D3DXFRAME_EX : D3DXFRAME
{
  D3DXMATRIX matOriginal;   // Оригинал
  D3DXMATRIX matCombined;   // То что мы изменим

}
Теперь у нас есть объект содержащий иерархию фреймов. После этого мы к объекту хранящий скелетный меш
D3DXMESHCONTAINER_EX : D3DXMESHCONTAINER
{
  ID3DXMesh          *pSkinMesh; // меш для вывода

  D3DXMATRIX         *pBoneMatrices; //  кости исходного меша
  D3DXMATRIX        **ppFrameMatrices; // cюда кидаем иерархию

}

прикрепляем D3DXFRAME_EX::matCombined который пока ещё ничего не содержит.
pMesh->ppFrameMatrices[i-ая кость] = &pFrame->matCombined;

Ну, а потом при рендере мы можем получить скелет любой формы в нашем случае это форма которая описана в иерархии. Просто при выводе запишем данные в D3DXFRAME_EX::matCombined.

Выведем меш из D3DXMESHCONTAINER_EX, который хранит исходный меш и меш которой мы получим из исходного * иерархия.

Исходный меш * иерархию = меш на вывод

// Copy the bone matrices over (must have been combined before call DrawMesh)
for(DWORD i=0; i<количество костей; i++) 
{
  // Start with bone offset matrix
  pMesh->pBoneMatrices[i] =  (*pMesh->pSkinInfo->GetBoneOffsetMatrix(i));

  // Apply frame transformation
  pMesh->pBoneMatrices[i] *= (*pMesh->ppFrameMatrices[i]);
}

и прикладываем преобразования на меш содержащий предыдущую позицию.

void *SrcPtr, *DestPtr;
pMesh->MeshData.pMesh->LockVertexBuffer(D3DLOCK_READONLY, (void**)&SrcPtr);
pMesh->pSkinMesh->LockVertexBuffer(0, (void**)&DestPtr);

pMesh->pSkinInfo->UpdateSkinnedMesh(pMesh->pBoneMatrices, NULL, SrcPtr, DestPtr);
Последняя функция архиудобнейшая. Имея меш и его копию мы их блокируем и кидаем матрицу нужной позы(какая нам нравится) в копию.
И выводим копию.
pMesh->pSkinMesh-> DrawSubset()
Всё что можно тут сделать так это найти по имени конкретную кость и изменить её.
/**Перед UpdateHierarchy() **/
  D3DXFRAME_EX *pFrameTestScalling = g_Frame->Find("Bip01_L_Forearm");

  D3DXMATRIX matTemp;
  D3DXMatrixIdentity(&matTemp);

  matTemp._11 = 1;
  matTemp._22 = 1.01;
  matTemp._33 = 1;

  pFrameTestScalling->TransformationMatrix *= matTemp;//*/

Интересней если по этому принципу в иерархию добавлять матрицы тр., которые изменяют положение скелета в нужное нам время.

Для этого используем класс способный построить набор анимированной последовательности, и сопоставить её иерархии фреймов. Анимацию строим подобно фреймам в .х находим объект AnimationSet в котором расположены набор объектов Animation. Последний содержит имя кости тип анимации количествово и данные ключей. Ключ это метка определённого объекта Animation заданный в мс. Всё это добро помещаем в список AnimationSets и на начальном этапе соединяем к иерархии.

while(пройдёмся по объектам cAnimation)
{
  // найдём совпадение в иерархии и запишем в наш список
  сAnimationSet:: cAnimation:: D3DXFRAME_EX = RootFrame->Find(cAnimation::m_Name);

  // перейдём к следующему объекту в списке
  cAnimation = cAnimation::Next
}
Тип анимации оперделяет как поступить с костью (0 – повернуть, 1 – масштабировать, 2 – переместить, 4 – трансформировать). Количество ключей - число действий анимируемого объекта. Данные есть матрица трансформации накладываемая на кость в это действие(т.е. ключ). Как только будем выводить скелет соответствующий данным времени ключей, то у нас получится анимация. Правда переход между ключами будет не слишком плавным,

поэтому в эти моменты накладываем интерполированные матрицы

mat1, mat1, time1, time2 – матрицы и время 1-го и 2-го ключа

matTransformation = mat2 - mat1
time = time2 - time1;

Scalar = (текущее время - time1)  /  time

matTransformation *= Scala
matTransformation +=  time1
теперь иерархия будет содержать плавное преобразование хранящееся в matTransformation

Проект MVC 6.0 и bin здесь 640 Kb на голом DirectX 9.0 т.е. если стоит Update нужно изменить порядок путей к .h-ам и lib-ам
Hosted by uCoz