本文原创版权归 GameRes wc4790150 所有,如有转载,请按如下方式显式标明原创作者及出处,以示尊重!!

作者:wc4790150
 出处:http://bbs.gameres.com/showthread.asp?threadid=83957

我对骨骼动画的理解(最精减的骨骼动画类)

本人研究骨骼动画时间不久,勉强写出一个可以运行的类,算是对骨骼动画入门了吧.感慨刚开始研究时资料的缺乏,我决定将我对骨骼动画的理解发表出来希望对正在研究的朋友们能有点帮助!但本人水平有限,文中难免会有不少错误,如果你对文章有异义或有什么不明白的请与我联系:QQ:517082060 邮箱:hoho4790150@126.com
        要利用X文件实现骨骼动画,首先必须清楚X文件中是如何储存动画数据的.打开一个X文件,除去各种模板等东西,首先你看到的应该是一个Frame(即帧),每一帧对应了一根骨骼,而帧里面又有可能包含帧,正是通过帧的包含与被包含关系来体现骨骼的父子关系.帧里面还可能包含mesh(即网格), 为什么要把网格包含在帧里面呢?因为我们画网格的时候要把它画到世界坐标系中去,那么这个世界转换矩阵从哪来呢?显然网格绑定在哪根骨骼上就从对应的帧里面获取,所以要将网格包含在帧里面.当然帧里面还会包含一个矩阵, 这个数据是用来将骨头的位置初始化的.
        接下来你就应该看到一个AnimationSet(动画集),但为了通俗起见,我称他为动作.动作里面包含了实现该动作时每根骨骼各个时刻的动画键(通俗起见,我又称它为旋转矩阵),动作里面包含的Animation就储存了一根骨骼实现该动作时各时刻的旋转矩阵,至于这个Animation对应哪根骨骼就由它里面所包含的引用决定.一般每个Animation里面都会有一个帧的引用(就是用大括号括起来的一个字符串),如{Root_Scence}.你搜索这个字符串,决对会发现前面有个同名的帧.
        了解了帧与动画集的意义后就可以读取里面的数据来实现骨骼动画了.先来说说流程:
        首先我们必须先读取所有的帧,并利用帧之间的包含关系来构建二叉树(构建二叉树的目的是为了清楚的表示每根骨骼的父骨骼,只要能实现这个目的,链表二叉树都一样).在构建二叉树的同时顺便把帧里面的矩阵和网格读取出来,矩阵就保存在对应的保存帧的容器里面,而网格最好提取出来保存在一个链表里面以方便后面绘制.
        读取完帧的数据之后就可以读取动作数据了,每读取一个动作就把它放到一个动作的链表中去,而读取动作里面的Animation数据时也要把它们连接到一个链表里(这个链表的头结点是动作里面的一个元素),并且通过Animation里面的引用将之与帧二叉树里面对应的帧相关连(这是很重要的,我们通过Animation计算出中间的旋转矩阵后是要存储到二叉树中的,然后通过递归遍历二叉树将这些矩阵累积相乘).
        存储完数据之后接下来的事情就很简单了,如果我们想显示一个名为walk的动作,就遍历动作链表找到那个动作,然后遍历动作里面的Animation链表,用线性插值法计算每根骨骼此时的旋转矩阵,并把它存储到相关连帧里面去.然后我们回到二叉树,递归遍历二叉树将计算出来的插值矩阵累积相乘得到混合矩阵.最后调用系统函数ID3DXSkinInfo::UpdateSkinnedMesh利用矩阵更新网格,接着就可以画出来了.如果你想了解UpdateSkinnedMesh函数是如何工作的,建议你去看DirectX自带例子Skinmesh里面的fx文件,如果你不懂HLSL就听我说说我的理解.我的理解是这样的:每一个网格的顶点里面除了包含坐标,纹理,材质等基础的数据之外还包含有一个骨骼的索引数组和一个对应权重值数组(权重数组里面的第几号元素就对应了骨骼数组里面第几号索引所表示的骨骼对该顶点的影响比例).遍历骨骼数组找到每一根骨骼,就可以查找到对应的帧了,从而可以提取帧里面的矩阵将其与权重值相乘得到的这个矩阵再来影响顶点坐标就实现了皮肤网格的改变.
        接下来我将的的类贴到下面,大家有兴趣的可以自己看看:

SkinMesh.h
///
#pragma once
#include <d3dx9mesh.h>
#include <tchar.h>
#include <stdio.h>
#include <windows.h>
#include <objbase.h>
#include <mmsystem.h>
#include <malloc.h>
#include "XFile.h"
#include <dxfile.h>
//这个不用多说,大家都应该明白,就是释放内存的
#define GXRELEASE(_p) do{if((_p)!= NULL){(_p)->Release();(_p) = NULL;}}while(0)
/*这个结构体用来储存X文件里的帧数据的,主要是用来存储帧的层次关系。
我曾在一本书上看过把'帧'和'动画集'以及'动画'数据放到一个Frame里的情况,这对初学者来说是很难理解的,所以我分开了,Frame只保存帧的层次结构以及一些必要数据,另外用专门的动画集结构体和动画结构体保存AnimationSet和Animation*/
struct Frame
{
char *Name;                //帧名称。这是很重要的数据,应为我们从一帧的二叉树中查找指定的帧时是通过名
称的字符串的匹配来实现的。
Frame* pFrameSibling;
Frame* pFrameChild;
D3DXMATRIX matRot;         //保存帧对应骨骼的插值矩阵
D3DXMATRIX matRotOrig;     //原始矩阵。文件中每个Frame里面都会有一个矩阵,而且都是Frame里的第
一个元素,当我们的模型没有执行任何动作时我们往往用这些矩阵来让我们的模型处于一个默认的动作。
D3DXMATRIX matCombined;    //混合矩阵。即我们求出每一段骨骼的插值矩阵之后,将其按父子关系累积
相乘得到的结果
Frame()
{
Name = NULL;
pFrameChild = pFrameSibling = NULL;
D3DXMatrixIdentity(&matCombined);
D3DXMatrixIdentity(&matRotOrig);
D3DXMatrixIdentity(&matRot);
}
~Frame ()
{
delete []Name;
delete pFrameSibling;
delete pFrameChild;
}
};
struct MeshContainer
{
char *Name;
D3DMATERIAL9 *pMaterials;        //网格所用到的材质的指针(可能有多种材质)
DWORD NumMaterials;//材质数量,DrawSubet(i)时来用到的
LPDIRECT3DTEXTURE9 *pTextures;//纹理指针(X文件中只储存了纹理的文件名,而且是储存在材质结构体中的,所以要把它从材质结
构体的对象中读取出来,再利用这个文件名加载纹理
ID3DXMesh *pMesh;      //利用矩阵将原始网格更新之后的网格,也就是要绘制的网格
ID3DXMesh *pSkinMesh;  //储存从文件中读取出来的原始网格,这个网格不能改变,因为每次更新网格前
都要用它来更新
LPD3DXSKININFO pSkinInfo;   //蒙皮信息,如果有的话(可以通过检测它是否为NULL来判定网格是否为蒙皮网格)
D3DXMATRIX** pBoneMatrix; //即指向Frame结构体中matCombined的指针,只是把这些矩阵的指针放到
了一个数组中以便于操作,而不用遍历二叉树才能找出这些  matCombined
D3DXMATRIX* pBoneOffsetMat;
DWORD* pAdjacency;
MeshContainer *pMeshContainerNext;
Frame* pFrame;  //X文件中每个网格都是储存在Frame内部的,这就是那个对应的帧。
假设我们的蒙皮网格分成两部分,头部网格和其它部分的网格,那么我们画头部网格之前肯定要先设置头部网格在世界坐标系中的位置,即pd3dDevice->SetTransform(D3DTS_WORLD, &pmc->pFrame->matCombined),用对应帧的混合矩阵来确定网格在世界坐标系中的位置,朝向,和缩放等。
MeshContainer()
{
Name = NULL;
pMaterials = NULL;
pTextures = NULL;
NumMaterials = 0;
pMesh = NULL;
pSkinInfo = NULL;
pSkinMesh = NULL;
pBoneMatrix = NULL;
pBoneOffsetMat = NULL;
pAdjacency = NULL;
pMeshContainerNext = NULL;
pFrame = NULL;
}
~MeshContainer()
{
delete []pMaterials;
if (pTextures)
{
for(DWORD i = 0; i < NumMaterials; i++)
{
GXRELEASE(pTextures[i]);
}
delete []pTextures;
}
GXRELEASE(pMesh);
GXRELEASE(pSkinInfo);
GXRELEASE(pSkinMesh);
delete []pBoneMatrix;
delete []Name;
delete []pAdjacency;
delete []pMeshContainerNext;
delete pFrame;
}
};
//为方便读取文件中的动画键数据而创建的结构体
struct RotateKeyXFile
{
DWORD dwTime;
DWORD dwFloats;
float w;
float x;
float y;
float z;
};
struct D3DKeyXFile
{
DWORD dwTime;
DWORD dwFloats;
D3DXVECTOR3 vVec;
};
struct MatrixKeyXFile
{
DWORD dwTime;
DWORD dwFloats;
D3DXMATRIX mat;
};
//为储存从文件中读取出的动画键数据而创建的结构体
struct RotateKey
{
DWORD dwTime;
D3DXQUATERNION quatRotate;
};
struct PositionKey
{
DWORD dwTime;
D3DXVECTOR3 vPos;
};
struct ScaleKey
{
DWORD dwTime;
D3DXVECTOR3 vScale;
};
struct MatrixKey
{
DWORD dwTime;
D3DXMATRIX mat;
};
//骨骼的动画键(说白点就是旋转矩阵)就储存在这里面。每一个Animation都对应一个帧,即对应一个骨骼。Animation之所以能和帧对应起来是因为每个Animation里面都会有一个帧的引用。打开X文件,搜索Animation你会发现它里面有类似这样的一行字符“{ 帧名}”,这个帧名必定可以在前面的Frame中找到对应名称的帧。
struct Animation
{
char *Name;
Frame *pFrame;
Animation *pAnimationNext;
PositionKey *pPositionKeys;
UINT nPositionKeys;
RotateKey *pRotateKeys;
UINT nRotateKeys;
ScaleKey *pScaleKeys;
UINT nScaleKeys;
MatrixKey *pMatrixKeys;
UINT nMatrixKeys;
Animation()
{
Name = NULL;
pFrame = NULL;
pAnimationNext = NULL;
pPositionKeys = NULL;
nPositionKeys = 0;
pRotateKeys = NULL;
nRotateKeys = 0;
pScaleKeys = NULL;
nScaleKeys = 0;
pMatrixKeys = NULL;
nMatrixKeys = 0;
}
~Animation()
{
delete []Name;
delete pFrame;
delete pAnimationNext;
delete []pPositionKeys;
delete []pRotateKeys;
delete []pScaleKeys;
delete []pMatrixKeys;
}
};
//一个动画集就是一个动作。它里面储存了每根骨骼在执行这个动作时的动画键数据。
struct AnimationSet
{
char *Name;
DWORD NumAnimation;
Animation *pAnimation;
AnimationSet *pAnimationSetNext;
DWORD CurTime;
};
class SkinMesh
{
public:
SkinMesh(LPDIRECT3DDEVICE9 g_pd3dDevice);//主要作用是传递D3D设备进去。
~SkinMesh(void);
Frame* FindFrame(Frame* frame, char* Name);//从帧的二叉树中查找对应的帧。(比如根据Animation提供的帧引用的名称查找对应的帧,将帧里面的matCombined与网格里的pBoneMatrix一一映射时也要用到这个函数)
void AddFrame(Frame* parentFrame, Frame* pFrame);//每次从文件中读到一个帧时就要将它添加到帧的二叉树中。(要注意的是,文件中帧的层次不一定是二叉树的,所以要转化一下。将三叉树四叉树等结构转化成二叉树形式的。)
void AddMesh(Frame* frame, MeshContainer* pMeshContainer);//将网格连成一个链表。(绘制网格时只需要循环这个链表就可以了。我曾经看过一本书上的作法是把网格储存在Frame结构体中,结果每次要画网格时不得不递归的遍历二叉树才能把所有的网格都画出来)
HRESULT FindBones(void);//将网格的pBoneMatrix数组元素指向对应的帧里面的matCombined。
HRESULT FrameMove(void);//对每一段骨骼调用SetTime函数。
HRESULT DrawFrames(void);//即调用DrawMeshContainer函数画网格。画之前先用网格在文件中所处的那一帧的matCombined设置网格的世界矩阵。
void SetTime(Animation* pAnim, float fGlobalTime);//对对应的骨骼进行插值运算,求出插值矩阵。并会根据每个Animation里面对帧的引用关系,将求出的矩阵存放到帧结构体中去。
HRESULT DrawMeshContainer(MeshContainer* pmcMesh);//根据网格是否为蒙皮网格而做不同的处理。如果是蒙皮网格则将网格包含的pBoneMatrix与BoneOffsetMat相乘后去影响原始网格(pSkinMesh)得到最终网格(pMesh).
HRESULT Render();
HRESULT LoadFromXFile(TCHAR* pFileName);//加载X文件。因为文件最表层的数据只有Frame和AnimationSet,所以该函数根据数据类型(数据类型由type表示)的不同而调用不同的函数。
HRESULT LoadMesh(LPD3DXFILEDATA pFileData, Frame* pFrameParent);//该函数最重要的一句就是通过调用D3DXLoadSkinMeshFromXof()函数来加载蒙皮网格。
HRESULT UpdateFrames(Frame* pFrame, D3DXMATRIX& mat);//将帧里面的matRot累积相乘。(mat会传递给根结点,它将传递下去影响所有的骨骼。所以我们要想对模型整体进行平移,旋转,缩放等操作,只需要将对应矩阵传递给根结点就行了)。
HRESULT SetAnimationName(char* AnimateName);//根据字符串AnimateName在动画集链表中查找指定的动画集,并用一个全局变量pAnimSetCur保存起来。当你调用Render函数时,模型就会执行AnimateName所指定的动作。
HRESULT LoadFrame(LPD3DXFILEDATA pFileData, Frame* pFrameParent);
HRESULT LoadAnimationSet(LPD3DXFILEDATA pFileData);
HRESULT LoadAnimation(LPD3DXFILEDATA pFileData);
LPDIRECT3DDEVICE9 pd3dDevice;
AnimationSet* pAnimSetHead;
Frame* pFrameRoot;
MeshContainer* pMeshHead;
DWORD Options;
DWORD CurTime;
AnimationSet* pAnimSetCur;
};
SkinMesh.cpp
///
#include "StdAfx.h"
#include ".\skinmesh.h"
SkinMesh::SkinMesh(LPDIRECT3DDEVICE9 g_pd3dDevice)
: pAnimSetHead(NULL)
, pFrameRoot(NULL)
, pMeshHead(NULL)
, Options(0)
, CurTime(0)
, pAnimSetCur(NULL)
{
pd3dDevice = g_pd3dDevice;
Options = 0;
pFrameRoot = new Frame();
pAnimSetHead = NULL;
pMeshHead = NULL;
CurTime = 0;
pAnimSetCur = pAnimSetHead;
}
SkinMesh::~SkinMesh(void)
{
}
//递归查找
Frame* SkinMesh::FindFrame(Frame* frame, char* Name)
{
Frame *pFrame;
if((frame->Name != NULL)&&strcmp(frame->Name, Name) == 0)
return frame;
if(frame->pFrameChild != NULL)
{
pFrame = FindFrame(frame->pFrameChild, Name);
if(pFrame != NULL)
return pFrame;
}
if(frame->pFrameSibling != NULL)
{
pFrame = FindFrame(frame->pFrameSibling, Name);
if(pFrame != NULL)
return pFrame;
}
return NULL;
}
void SkinMesh::AddFrame(Frame* parentFrame, Frame* pFrame)
{
if (parentFrame->pFrameChild == NULL)
{
parentFrame->pFrameChild = pFrame;
}else
{         //将多叉树结构转化成二叉树存储
Frame* frame = parentFrame->pFrameChild;
while (frame->pFrameSibling != NULL)
{
frame = frame->pFrameSibling;
}
frame->pFrameSibling = pFrame;
}
}
void SkinMesh::AddMesh(Frame* frame, MeshContainer* pMeshContainer)
{
pMeshContainer->pMeshContainerNext = pMeshHead;
pMeshHead = pMeshContainer;
pMeshContainer = NULL;
if(frame)
{
//将网格与它在文件中包含它的帧绑定,以便绘制前调用那一帧的matCombined设置世界矩阵,将网格画在正确的位置。
pMeshHead->pFrame = frame;
}
}
HRESULT SkinMesh::FindBones(void)
{
MeshContainer *pMeshContainer;
pMeshContainer = pMeshHead;
while(NULL != pMeshContainer)
{
if(pMeshContainer->pSkinInfo)
{
for( DWORD i = 0; i<pMeshContainer->pSkinInfo->GetNumBones(); i++)
{
Frame *frame = FindFrame(pFrameRoot, (char*)pMeshContainer->pSkinInfo->GetBoneName(i));
pMeshContainer->pBoneMatrix[i] = &(frame->matCombined);
}
}
pMeshContainer = pMeshContainer->pMeshContainerNext;
}
return E_NOTIMPL;
}
HRESULT SkinMesh::FrameMove(void)
{
CurTime = CurTime+100;
if(CurTime>1.0e15f)
CurTime = 0;
Animation *pAnimCur = pAnimSetCur->pAnimation;
while (pAnimCur != NULL)
{
SetTime(pAnimCur, CurTime);
pAnimCur = pAnimCur->pAnimationNext;
}
return E_NOTIMPL;
}
HRESULT SkinMesh::DrawFrames(void)
{
MeshContainer *pmc = pMeshHead;
while (pmc != NULL)
{
//注意下面这一句
pd3dDevice->SetTransform(D3DTS_WORLD, &pmc->pFrame->matCombined);
DrawMeshContainer(pmc);
pmc = pmc->pMeshContainerNext;
}
return E_NOTIMPL;
}
//计算每一段骨骼的插值矩阵。(根据矩阵类型不同代码略有不同。尤其要注意旋转矩阵的计算过程)
void SkinMesh::SetTime(Animation* pAnim, float fGlobalTime)
{
UINT iKey;
UINT dwp2;
UINT dwp3;
D3DXMATRIX matResult;
D3DXMATRIX matTemp;
float fTime1;
float fTime2;
float fLerpvalue;
D3DXVECTOR3 vScale;
D3DXVECTOR3 vPos;
D3DXQUATERNION quat;
float fTime;
if (pAnim->pMatrixKeys )
{
//将传递进来的时间模上最后一该的时间戳,得到fTime。随着fGlobalTime的不断增大,fTime会在0与最后时间戳中循环递增。所以模型的动作也是持续的。
fTime = (float)fmod(fGlobalTime, pAnim->pMatrixKeys[pAnim->nMatrixKeys-1].dwTime);
//找出离fTime最近的前后两个时间戳,利用这两个时间戳所处的动画键的矩阵值来计算插值矩阵的值。具体原理就是fTime与前后两个时间戳之间的比例关系等于所求插值矩阵与前后动画键的矩阵的比例。(只是当动画键类型为普通矩阵类型,即“dwKeyType == 4”时,我们不需要求插值,直接取离fTime最近的动画键的矩阵就行了。估计是当3DMAX导出X文件时,如果我们选择动画键类型为Matrix时3DMAX就为我们计算出了插值矩阵了。此[来源:GameRes.com]外还要注意的是,用这种方法对旋转矩阵求插值其结果会不准确,所以要将数据转化为四元数再求值,求完了又转回矩阵去 )。
for (iKey = 0 ;iKey < pAnim->nMatrixKeys ; iKey++)
{
if ((float)pAnim->pMatrixKeys[iKey].dwTime > fTime)
{
dwp3 = iKey;
if (iKey > 0)
dwp2= iKey - 1;
else
dwp2 = iKey;
break;
}
}
fTime1 = (float)pAnim->pMatrixKeys[dwp2].dwTime;
fTime2 = (float)pAnim->pMatrixKeys[dwp3].dwTime;
if ((fTime2 - fTime1) ==0)
fLerpvalue = 0;
else
fLerpvalue =  (fTime - fTime1) / (fTime2 - fTime1);
if (fLerpvalue > 0.5)
iKey = dwp3;
else
iKey = dwp2;
pAnim->pFrame->matRot = pAnim->pMatrixKeys[iKey].mat;
}
else
{
D3DXMatrixIdentity(&matResult);
if (pAnim->pScaleKeys)
{
dwp2 = dwp3 = 0;
fTime = (float)fmod(fGlobalTime, pAnim->pScaleKeys[pAnim->nScaleKeys-1].dwTime);
for (iKey = 0 ;iKey < pAnim->nScaleKeys ; iKey++)
{
if ((float)pAnim->pScaleKeys[iKey].dwTime > fTime)
{
dwp3 = iKey;
if (iKey > 0)
dwp2= iKey - 1;
else
dwp2 = iKey;
break;
}
}
fTime1 = (float)pAnim->pScaleKeys[dwp2].dwTime;
fTime2 = (float)pAnim->pScaleKeys[dwp3].dwTime;
if ((fTime2 - fTime1) ==0)
fLerpvalue = 0;
else
fLerpvalue =  (fTime - fTime1) / (fTime2 - fTime1);
D3DXVec3Lerp(&vScale,
&pAnim->pScaleKeys[dwp2].vScale,
&pAnim->pScaleKeys[dwp3].vScale,
fLerpvalue);
D3DXMatrixScaling(&matTemp, vScale.x, vScale.y, vScale.z);
D3DXMatrixMultiply(&matResult, &matResult, &matTemp);
pAnim->pFrame->matRot = matResult;
}
else if (pAnim->pRotateKeys )
{
dwp2 = dwp3 = 0;
fTime = (float)fmod(fGlobalTime, pAnim->pRotateKeys[pAnim->nRotateKeys-1].dwTime);
for (iKey = 0 ;iKey < pAnim->nRotateKeys ; iKey++)
{
if ((float)pAnim->pRotateKeys[iKey].dwTime > fTime)
{
dwp3 = iKey;
if (iKey > 0)
dwp2= iKey - 1;
else
dwp2 = iKey;
break;
}
}
fTime1 = (float)pAnim->pRotateKeys[dwp2].dwTime;
fTime2 = (float)pAnim->pRotateKeys[dwp3].dwTime;
if ((fTime2 - fTime1) ==0)
fLerpvalue = 0;
else
fLerpvalue =  (fTime - fTime1) / (fTime2 - fTime1);
D3DXQUATERNION q1,q2;
q1.x =-pAnim->pRotateKeys[dwp2].quatRotate.x;
q1.y =-pAnim->pRotateKeys[dwp2].quatRotate.y;
q1.z =-pAnim->pRotateKeys[dwp2].quatRotate.z;
q1.w =pAnim->pRotateKeys[dwp2].quatRotate.w;
q2.x =-pAnim->pRotateKeys[dwp3].quatRotate.x;
q2.y =-pAnim->pRotateKeys[dwp3].quatRotate.y;
q2.z =-pAnim->pRotateKeys[dwp3].quatRotate.z;
q2.w =pAnim->pRotateKeys[dwp3].quatRotate.w;
D3DXQuaternionSlerp(&quat,
&q1,
&q2,
fLerpvalue);
D3DXMatrixRotationQuaternion(&matTemp, &quat);
D3DXMatrixMultiply(&matResult, &matResult, &matTemp);
pAnim->pFrame->matRot = matResult;
}
else if (pAnim->pPositionKeys)
{
dwp2=dwp3=0;
fTime = (float)fmod(fGlobalTime, pAnim->pPositionKeys[pAnim->nRotateKeys-1].dwTime);
for (iKey = 0 ;iKey < pAnim->nPositionKeys ; iKey++)
{
if ((float)pAnim->pPositionKeys[iKey].dwTime > fTime)
{
dwp3 = iKey;
if (iKey > 0)
dwp2= iKey - 1;
else
dwp2 = iKey;
break;
}
}
fTime1 = (float)pAnim->pPositionKeys[dwp2].dwTime;
fTime2 = (float)pAnim->pPositionKeys[dwp3].dwTime;
if ((fTime2 - fTime1) ==0)
fLerpvalue = 0;
else
fLerpvalue =  (fTime - fTime1)/ (fTime2 - fTime1);
D3DXVec3Lerp((D3DXVECTOR3*)&vPos,
&pAnim->pPositionKeys[dwp2].vPos,                 &pAnim->pPositionKeys[dwp3].vPos,
fLerpvalue);
D3DXMatrixTranslation(&matTemp, vPos.x, vPos.y, vPos.z);
D3DXMatrixMultiply(&matResult, &matResult, &matTemp);
pAnim->pFrame->matRot = matResult;
}
}
}
HRESULT SkinMesh::DrawMeshContainer(MeshContainer* pmcMesh)
{
UINT ipattr; PBYTE pbVerticesSrc;
PBYTE pbVerticesDest;
if( pmcMesh->pSkinInfo && pmcMesh->pSkinInfo->GetNumBones())
{
D3DCAPS9 caps;
pd3dDevice->GetDeviceCaps(&caps);
DWORD cBones = pmcMesh->pSkinInfo->GetNumBones();
D3DXMATRIX* BoneMatrices = NULL;
BoneMatrices = new D3DXMATRIX[cBones];
for(DWORD iBone = 0; iBone<cBones; iBone++)
D3DXMatrixMultiply(&BoneMatrices[iBone], &pmcMesh->pBoneOffsetMat                [iBone], pmcMesh->pBoneMatrix[iBone]);
D3DXMATRIX Indentity;
D3DXMatrixIdentity(&Indentity);
pd3dDevice->SetTransform(D3DTS_WORLD, &Indentity);
pmcMesh->pMesh->LockVertexBuffer(D3DLOCK_READONLY, (LPVOID*)&pbVerticesDest);
pmcMesh->pSkinMesh->LockVertexBuffer(0, (LPVOID*)&pbVerticesSrc);
//用BoneMatrices数组影响pSkinMesh之后储存在pMesh中。这就是我们最后努力的结果了
pmcMesh->pSkinInfo->UpdateSkinnedMesh(BoneMatrices, NULL, pbVerticesSrc, pbVerticesDest);
pmcMesh->pSkinMesh->UnlockVertexBuffer();
pmcMesh->pMesh->UnlockVertexBuffer();
//画网格,没什么好说的。
for(ipattr = 0; ipattr<pmcMesh->NumMaterials; ipattr++)
{
pd3dDevice->SetMaterial(&(pmcMesh->pMaterials[ipattr]));
pd3dDevice->SetTexture(0, pmcMesh->pTextures[ipattr]);
pmcMesh->pMesh->DrawSubset(ipattr);
}
}else
{
for(ipattr = 0; ipattr<pmcMesh->NumMaterials; ipattr++)
{
pd3dDevice->SetMaterial(&(pmcMesh->pMaterials[ipattr]));
pd3dDevice->SetTexture(0, pmcMesh->pTextures[ipattr]);
pmcMesh->pMesh->DrawSubset(ipattr);
}
}
return E_NOTIMPL;
}
HRESULT SkinMesh::Render()
{
D3DXMatrixIdentity(&pFrameRoot->matRot) ;
D3DXMATRIX mat;
D3DXMatrixIdentity(&mat);
FrameMove();
//你也可以传递一个世界矩阵代替mat以改变模型
UpdateFrames(pFrameRoot, mat);
DrawFrames();
return E_NOTIMPL;
}
HRESULT SkinMesh::LoadFromXFile(TCHAR* pFileName)
{
HRESULT hr = S_OK;
LPD3DXFILE pFile = NULL;
LPD3DXFILEENUMOBJECT pFileEnumObject = NULL;
LPD3DXFILEDATA pFileData = NULL;
GUID type;
hr = D3DXFileCreate(&pFile);
if (FAILED(hr))
return hr;
hr = pFile->RegisterTemplates((LPVOID)D3DRM_XTEMPLATES ,D3DRM_XTEMPLATE_BYTES);
if(FAILED(hr))
return hr;
hr = pFile->CreateEnumObject((LPVOID)pFileName, DXFILELOAD_FROMFILE , &pFileEnumObject);
//从前面到这里一直照抄就行,按规矩办事
if(FAILED(hr))
return hr;
DWORD Size;
pFileEnumObject->GetChildren(&Size);//pFileEnumObject里面有多少个数据,并将这个数量储存在Size中。
for(DWORD i = 0 ; i < Size ; i++)
{
pFileEnumObject->GetChild(i, &pFileData);//获取对应序号的数据对象,获取的对象储存在pFileData中。
pFileData->GetType(&type);//获取pFileData的类型
if (type == TID_D3DRMFrame)
{
LoadFrame(pFileData, pFrameRoot);
}
if (type == TID_D3DRMAnimationSet)
{
LoadAnimationSet(pFileData);
}
GXRELEASE(pFileData);
}
FindBones();
pFrameRoot->matRotOrig = pFrameRoot->matRot;
GXRELEASE(pFileEnumObject);
GXRELEASE(pFile);
return E_NOTIMPL;
}
HRESULT SkinMesh::LoadMesh(LPD3DXFILEDATA pFileData, Frame* pFrameParent)
{
HRESULT hr = S_OK;
MeshContainer *pmcMesh = NULL;
LPD3DXBUFFER pMaterialsbuf = NULL;
LPD3DXBUFFER pAdjacencybuf = NULL;
DWORD NumName;
UINT cFaces;
UINT iMaterial;
pmcMesh = new MeshContainer();
pFileData->GetName(NULL, &NumName);
if(NumName>0)
{
pmcMesh->Name = new char[NumName];
pFileData->GetName(pmcMesh->Name, &NumName);
}else
pmcMesh->Name = "NoNameMesh";
D3DXLoadSkinMeshFromXof(pFileData, Options,
pd3dDevice,&pAdjacencybuf, &pMaterialsbuf,
NULL,&pmcMesh->NumMaterials,&pmcMesh->pSkinInfo, &pmcMesh-                >pSkinMesh);
pmcMesh->pSkinMesh->CloneMeshFVF(Options, pmcMesh->pSkinMesh->GetFVF(),pd3dDevice, &pmcMesh->pMesh);
DWORD NumBones = pmcMesh->pSkinInfo->GetNumBones();
if(NumBones>0)
{
pmcMesh->pBoneMatrix = new D3DXMATRIX*[NumBones];
pmcMesh->pBoneOffsetMat = new D3DXMATRIX[NumBones];
for(DWORD i = 0; i < NumBones; i++)
pmcMesh->pBoneOffsetMat[i] = (*pmcMesh->pSkinInfo->GetBoneOffsetMatrix(i));
LPDWORD pAdjacency = static_cast<LPDWORD>(pAdjacencybuf->GetBufferPointer());
cFaces = pmcMesh->pSkinMesh->GetNumFaces();
pmcMesh->pAdjacency = new DWORD[cFaces*3];
memcpy(pmcMesh->pAdjacency,pAdjacency,cFaces*3*sizeof(DWORD));
}else
{
pmcMesh->pSkinInfo = NULL;
}
if(pmcMesh->NumMaterials>0)
{
pmcMesh->pMaterials = new D3DMATERIAL9[pmcMesh->NumMaterials];
pmcMesh->pTextures = new LPDIRECT3DTEXTURE9[pmcMesh->NumMaterials];
LPD3DXMATERIAL pMaterials = (LPD3DXMATERIAL)pMaterialsbuf->GetBufferPointer();
for (iMaterial = 0; iMaterial<pmcMesh->NumMaterials; iMaterial++)
{
pmcMesh->pMaterials[iMaterial] = pMaterials[iMaterial].MatD3D;
pmcMesh->pTextures[iMaterial] = NULL;
if(pMaterials[iMaterial].pTextureFilename != NULL)
D3DXCreateTextureFromFile(pd3dDevice, pMaterials[iMaterial].pTextureFilename, &(pmcMesh->pTextures[iMaterial]));
}
}else
{
pmcMesh->pMaterials = new D3DMATERIAL9[1];
pmcMesh->pTextures = new LPDIRECT3DTEXTURE9[1];
memset(pmcMesh->pMaterials, 0, sizeof(D3DXMATERIAL));
pmcMesh->pMaterials[0].Diffuse.r = 0.5f;
pmcMesh->pMaterials[0].Diffuse.g = 0.5f;
pmcMesh->pMaterials[0].Diffuse.b = 0.5f;
pmcMesh->pMaterials[0].Specular = pmcMesh->pMaterials[0].Diffuse;
pmcMesh->pTextures[0] = NULL;
}
AddMesh(pFrameParent, pmcMesh);
pmcMesh = NULL; delete pmcMesh;
GXRELEASE(pAdjacencybuf);
GXRELEASE(pMaterialsbuf);
return E_NOTIMPL;
}
//将Frame里面的matRot累积相乘
HRESULT SkinMesh::UpdateFrames(Frame* pFrame, D3DXMATRIX& mat)
{
D3DXMatrixMultiply(&pFrame->matCombined, &pFrame->matRot, &mat);
Frame *pframeChild = pFrame->pFrameChild;
while(pframeChild != NULL)
{
UpdateFrames(pframeChild, pFrame->matCombined);
pframeChild = pframeChild->pFrameSibling;
}
return E_NOTIMPL;
}
HRESULT SkinMesh::SetAnimationName(char* AnimateName)
{
pAnimSetCur = pAnimSetHead;
while(pAnimSetCur && strcmp(pAnimSetCur->Name, AnimateName))
{
pAnimSetCur = pAnimSetCur->pAnimationSetNext;
}
return E_NOTIMPL;
}
HRESULT SkinMesh::LoadFrame(LPD3DXFILEDATA pFileData, Frame* pFrameParent)
{
LPD3DXFILEDATA pFileDataChild = NULL;
DWORD NumName = 0;
GUID type;
pFileData->GetType(&type);
if(type == TID_D3DRMMesh)//获取帧里面的网格(并非所有的帧都会包含网格,一般来说如果模型只有一个网格的话它就会包含在第一帧里面)
{
LoadMesh(pFileData, pFrameParent);
}
else if (type == TID_D3DRMFrameTransformMatrix)//获取初始矩阵
{
if(pFrameParent != NULL)
{
D3DXMATRIX *pMat;
DWORD Size;
pFileData->Lock(&Size, (LPCVOID*)&pMat);
pFrameParent->matRot = *pMat;
pFrameParent->matRotOrig = *pMat;
pFileData->Unlock();
}
}else if (type == TID_D3DRMFrame)//因为帧有包含关系(正是这种包含关系体现了父子关系),所以要在该函数里面再次判断数据类型(如果是LoadAnimationSet()则不需要再次判断数据类型,而是直接创建动画集的结构体,并将其添加到链表中等操作。而LoadFrames则需要在函数内再次判断数据类型以便执行正确的操作。注意是再次判断数据类型,请想明白这一点。)
{
Frame* pFrame = new Frame();
pFileData->GetName(NULL, &NumName);
if(NumName > 0)
{
pFrame->Name = new char[NumName];
pFileData->GetName(pFrame->Name, &NumName);
}else
pFrame->Name = "NoName";
AddFrame(pFrameParent, pFrame);
DWORD Size;
pFileData->GetChildren(&Size);
for (DWORD i = 0 ; i < Size ; i++)
{
pFileData->GetChild(i, &pFileDataChild);
LoadFrame(pFileDataChild, pFrame);
GXRELEASE(pFileDataChild);
}
}
return E_NOTIMPL;
}
HRESULT SkinMesh::LoadAnimationSet(LPD3DXFILEDATA pFileData)
{
LPD3DXFILEDATA pFileDataChild = NULL;
GUID type;
DWORD NumName;
AnimationSet*  pAnimSetCur = new AnimationSet();
pFileData->GetName(NULL, &NumName); if(NumName>0)
{
pAnimSetCur->Name = new char[NumName];
pFileData->GetName(pAnimSetCur->Name, &NumName);
} else
pAnimSetCur->Name = "NoName";
pAnimSetCur->pAnimationSetNext = pAnimSetHead;
pAnimSetHead = pAnimSetCur;
DWORD Size;
pFileData->GetChildren(&Size);
for (DWORD i = 0 ; i < Size ; i++)
{
pFileData->GetChild(i , &pFileDataChild);
pFileDataChild->GetType(&type);
if (type == TID_D3DRMAnimation)
{
LoadAnimation(pFileDataChild);
}
}
return E_NOTIMPL;
}
HRESULT SkinMesh::LoadAnimation(LPD3DXFILEDATA pFileData)
{
GUID type;
RotateKeyXFile *pFileRotateKey;
D3DKeyXFile *pFileScaleKey;
D3DKeyXFile *pFilePosKey;
MatrixKeyXFile *pFileMatrixKey;
DWORD NumName;
PBYTE pData;
DWORD dwKeyType;
DWORD cKeys;
DWORD iKey;
Animation* pAnimCur = new Animation();
pFileData->GetName(NULL, &NumName);
if (NumName>0)
{
pAnimCur->Name = new char[NumName];
pFileData->GetName(pAnimCur->Name, &NumName);
}else
pAnimCur->Name = "NoName";
pAnimCur->pAnimationNext = pAnimSetHead->pAnimation;
pAnimSetHead->pAnimation = pAnimCur;
LPD3DXFILEDATA pFileDataChild = NULL;
DWORD Size;
pFileData->GetChildren(&Size);
for (DWORD i = 0 ; i < Size ; i++)
{
pFileData->GetChild(i , &pFileDataChild);
//如果动画里面的数据类型是引用,那么它必定是帧的引用。至于这个引用做什么用,上面已经说得非常清楚了。
if(pFileDataChild->IsReference())
{
pFileDataChild->GetType(&type);
if (type == TID_D3DRMFrame)
{
DWORD Size;
pFileDataChild->GetName(NULL, &Size);
char* Name = (char*)_alloca(Size);
pFileDataChild->GetName(Name, &Size);
获取帧的名字后就去二叉树中查找,并把找到的帧的地址保存在动画结构体中。
pAnimCur->pFrame = FindFrame(pFrameRoot, Name);
}
}
if(!pFileDataChild->IsReference())
{
pFileDataChild->GetType(&type);
if (TID_D3DRMAnimationKey == type)
{
Animation *pAnimCur = pAnimSetHead->pAnimation;
DWORD Size;
pFileDataChild->Lock(&Size, (LPCVOID*)&pData);
dwKeyType = ((DWORD*)pData)[0];
cKeys = ((DWORD*)pData)[1];
if(dwKeyType == 0)
{
pAnimCur->pRotateKeys = new RotateKey[cKeys];
pAnimCur->nRotateKeys = cKeys;
pFileRotateKey = (RotateKeyXFile*)(pData+(sizeof(DWORD)*2));
for(iKey = 0; iKey < cKeys; iKey++)
{
pAnimCur->pRotateKeys[iKey].dwTime = pFileRotateKey->dwTime;
pAnimCur->pRotateKeys[iKey].quatRotate.x = pFileRotateKey->x;
pAnimCur->pRotateKeys[iKey].quatRotate.y = pFileRotateKey->y;
pAnimCur->pRotateKeys[iKey].quatRotate.z = pFileRotateKey->z;
pAnimCur->pRotateKeys[iKey].quatRotate.w = pFileRotateKey->w;
pFileRotateKey += 1;
}
}
else if (dwKeyType == 1)
{
pAnimCur->pScaleKeys = new ScaleKey[cKeys];
pAnimCur->nScaleKeys = cKeys;
pFileScaleKey = (D3DKeyXFile*)(pData + (sizeof(DWORD)*2));
for(iKey = 0; iKey<cKeys; iKey++)
{
pAnimCur->pScaleKeys[iKey].dwTime = pFileScaleKey->dwTime;
pAnimCur->pScaleKeys[iKey].vScale = pFileScaleKey->vVec;
pFileScaleKey += 1;
}
}
else if (dwKeyType == 2)
{
pAnimCur->pPositionKeys = new PositionKey[cKeys];
pAnimCur->nPositionKeys = cKeys;
pFilePosKey = (D3DKeyXFile*)(pData + (sizeof(DWORD)*2));
for (iKey = 0; iKey <cKeys; iKey++)
{
pAnimCur->pPositionKeys[iKey].dwTime = pFilePosKey->dwTime;
pAnimCur->pPositionKeys[iKey].vPos   = pFilePosKey->vVec;
pFilePosKey += 1;
}
}
else if(dwKeyType == 4)
{
pAnimCur->pMatrixKeys = new MatrixKey[cKeys];
pAnimCur->nMatrixKeys = cKeys;
pFileMatrixKey = (MatrixKeyXFile*)(pData + (sizeof(DWORD)*2));
for(iKey = 0; iKey < cKeys; iKey++)
{
pAnimCur->pMatrixKeys[iKey].dwTime = pFileMatrixKey->dwTime;
pAnimCur->pMatrixKeys[iKey].mat = pFileMatrixKey->mat;
pFileMatrixKey += 1;
}
}
}
}
GXRELEASE(pFileDataChild);
}
GXRELEASE(pFileData);
return E_NOTIMPL;
}
由于本人写代码时没有养成写注释的好习惯,所以如果你对这个类实在看不懂请尽管与我联系,我一定抽时间为你解答!
添加一句,头文件中添加的"XFile.h"是从DirectX高级动画制作中拿来用的.因为作者说直接导入rmxfguid.h和rmxftmpl.h容易出问题,所以稍微处理了一下.我现在把这两个文件也贴出来
XFile.h
#include "rmxfguid.h"
extern unsigned char D3DRM_XTEMPLATES[];
#define D3DRM_XTEMPLATE_BYTES 3278
XFile.cpp
#include "rmxftmpl.h"
我先声明一点,这个类是用于学习之用,通用性很差的,对于很多骨骼动画的X文件都不支持。(读取出来之后有问题。要么有网格没纹理,要么有网格有纹理却不动,要么就是有网格有纹理也动,却是一个骨骼在动不是网格动。总之总有一些问题,好像是3DMAX导出X文件时的一些参数设置,但具体应该怎么设置我也搞不清。但这个类读取DirectX9自带的tiny.x时是不会有问题的,这个你放心,所以如果你真的想用这个类的话请看懂后自己根据情况修改。)
类成员函数使用流程示例abc
SkinMesh   *pSkin;
pSkin = new SkinMesh(g_pd3dDevice);
pSkin->LoadFromXFile(tiny.x);
pSkin->SetAnimationName(Anim_1);//忘记tiny文件中的那个动作叫什么了,暂且用这个名字凑数吧。
pSkin->Render();

我对骨骼动画的理解(最精减的骨骼动画类)相关推荐

  1. 看动画轻松理解“递归”与“动态规划”

    作者 | 程序员小吴 来源 | 五分钟学算法 在学习「数据结构和算法」的过程中,因为人习惯了平铺直叙的思维方式,所以「递归」与「动态规划」这种带循环概念(绕来绕去)的往往是相对比较难以理解的两个抽象知 ...

  2. c 递归下降识别程序_看动画轻松理解递归与动态规划

    在学习「数据结构和算法」的过程中,因为人习惯了平铺直叙的思维方式,所以「递归」与「动态规划」这种带循环概念(绕来绕去)的往往是相对比较难以理解的两个抽象知识点. 程序员小吴打算使用动画的形式来帮助理解 ...

  3. Unity动画系统经验谈:换装系统与骨骼调节

    这里总结一下,自己使用Unity以来的心得,大部分属于随手解决但还有印象或者觉得效果不错. 状态机与状态机设计 角色的状态机以0层作为主层,然后以待机作混合树为中心进行切换. 主层中会有一些复杂一些的 ...

  4. UE4 动画重定向之使用同一套骨骼

    首先在UE4中一个完整的模型资源包含三个主要的部分,Skeletal Mesh.Skeleton.Physics Asset,如标题所说这个动画重定向的意义在于,不同Skeletal Mesh共用同一 ...

  5. 48.iOS动画和理解position与anchorPoint

    1.动画的基本概念 动画的使⽤场景:iOS中的动画是指一些视图上的过渡效果,合理利用动画能提⾼用户体验,UIView动画影响的属性 frame:视图框架 center:视图位置 alpha:视图透明度 ...

  6. 3d Max人物动画学习笔记(一) 骨骼创建

    1.从溜溜网或爱给网下载免费模型资源,并将其导入到3d Max 2016中 这里如果模型被分为不同的部分可以选中模型,右键转化为--可编辑多边形,并在修改面板中选择可编辑多边形--多边形,附加工具并选 ...

  7. unity2D动画-角色切片与2DAnimation插件做动画

    unity2D动画-角色切片做动画 写在前面的话 开发环境与准备 用角色切片做动画 终于可以Key动画了 2DAnimation插件做动画 总结 写在前面的话 更新 建议有复杂2D动画需求的话用spi ...

  8. Unity游戏动画 从入门到住院 4:动画状态机

    Unity游戏动画 从入门到住院:动画状态机 发布者: wuye | 发布时间: 2016-9-7 15:02| 评论数: 3 文/拉撒路 上次我们讲过Unity游戏动画从入门到住院,今天我们来讲一下 ...

  9. D3D角色动画学习笔记(一)——角色动画简介与初步规划

    D3D角色动画学习笔记(一)--角色动画简介与初步规划 写这个系列是基于自己学习角色动画之后,相对自己的成果做一个整理,还可以给大家做一个角色动画的预览,可能会节省大家的一点时间,同时也希望各位大神能 ...

最新文章

  1. golang网络编程基础知识:OSI网络模型、IP、端口号详解
  2. 深入理解HTTP协议
  3. python win10 连接hive_使用win10+python3.5+impyla 连接大数据平台hive表的步骤与问题解决...
  4. 使用Tomcat配置域名
  5. java工具类_非常实用的Java工具类,拿走不谢(一)
  6. python算法详解豆瓣_豆瓣评分9.0以上的编程书,了解一下?
  7. 专利进阶(一):软件专利工程师浅谈如何针对计算机软件类专利申请进行技术挖掘
  8. Visual Studio 2019 Community 离线注册教程
  9. 陈强教授《机器学习及R应用》课程第十一章作业
  10. 我的世界java版和基岩版对比_我的世界:java版和基岩版你更看好哪个?未来的发展,谁会更好...
  11. 产品经理负责制的诱惑与窘迫
  12. 元宇宙007 | 沉浸式家庭治疗,让治疗像演情景剧一样!
  13. 极光厂商通道集成指南
  14. 【NOIP模拟】我的天
  15. 一个月瘦10斤的计划
  16. 计算机逻辑部件按其结构可分为,《数字逻辑电路》期末大作业实验报告
  17. Gephi中的统计算法学习
  18. kernel 3.10代码分析--KVM相关--虚拟机运行
  19. excel打开csv文件乱码解决办法
  20. Torch的参数初始化

热门文章

  1. 微信小程序API之getLocal
  2. JavaScript:闭包
  3. linux之解决libipopt.so.1: Cannot open shared object file
  4. 计算机组成原理运算器设计,计算机组成原理2_5教学计算机运算器设计.ppt
  5. Halcon 摄像机标定流程-代码实现
  6. 实战:Windows Server 2008 活动目录 传送和争夺操作主控角色
  7. 【李宏毅2020 ML/DL】补充:Structured Learning: Structured SVM
  8. 【李宏毅2020 ML/DL】P59 Unsupervised Learning - Auto-encoder
  9. C#笔记14 LINQ
  10. h3c交换机怎么设置虚拟服务器,H3C交换机配置 | 如何实现两个网段主机与外部通信...