Direct-X学习笔记--骨骼动画

学了几个月DX了,终于到了骨骼动画这一步了,好激动!之前导入过一些静态的模型,还是挺帅的。不过不能动,实在是太遗憾了。今天学习了骨骼动画,终于让偶的模型动起来啦!!!

一.简介

说到动画,其实本人又想起了最早开始写小游戏的时候从网上找的那些关键帧素材,设置一个定时器,或者根据游戏循环,不断切换图片,就形成了动态的效果。不过这样实在是太麻烦了,需要美术一张一张的画图或者用PS调整。到了3D时代,骨骼动画这个神器,可以大大的缩短动画的制作周期。
3D中的动画,就是我们常说的模型动画。一共有三种:
1.关节动画:将网格拆分为多个刚体,按照关键帧插值运算,能够实现复杂的动画效果。但是,模型各部分交接的地方容易出现缝隙。
2.渐变动画:与2D游戏中的关键帧类似,每一帧的所有顶点位置等等都记录下来,效果很好,但是由于记录的数据太多,文件很大,载入很慢,还占地方。
3.骨骼蒙皮动画:这种就是现在最常用的方法,取上两者之精华,去之糟粕。占用空间小,但是表现力还很好。

骨骼蒙皮模型中含有按照一定层次组织起来的骨骼,还含有单一网格模型的蒙皮。骨骼层次仿照关节动画的组织结构将模型组织成一个整体,相邻骨骼之间通过关节相连,通过关键帧确定骨骼位置朝向等信息,然后再动画序列中相邻两个关键帧之间做差值运算,获得完整的动画信息。这样就可以很好的表现出动画,而又不至于占用过大空间。


二.X文件

.X文件,之前也有用过,LoadMeshFromFile。就是简单的把网格载入程序中,读取其中的顶点信息,纹理材质信息,然后调用DX的API将它画出来。不过,这只是普通的网格,其实X文件中还可以包含更加深层次的东东,就是骨骼动画。
这个是微软自带的例子:SkinMesh中的那个人物,自带了骨骼动画。看一下.X文件中记录了什么东东:
AnimationSet 动画的组合,包括一个或者多个Animation。
  Animation 描述一个动画,包含一个或几个AnimationKey
  AnimationKey 动画关键帧,定义具体的动作数据,包括一些列旋转、移动、放缩、矩阵变换。
  ColorRGB 定义RGB对象,包括三个Float的值,分别是R、G、B。
  ColorRGBA 定义RGBA对象。包括四个Float的值,分别是R、G、B、alpha。
  Coords2d 定义纹理坐标向量,包括两个Float值,分别是u、v。
  FloatKeys 定义浮点数组,用来定义动画键数值,包括两个部分:浮点值个数,浮点值列表。
  Material 定义材质信息,可以被应用到一个完整的Mesh对象,也可以应用到其中的一个面。包含:
           1.FaceColor环境光
           2.Power镜面反射的强度
           3.specularcolor镜面反射等等。
  Matrix4X4 定义4X4矩阵,16个浮点数值。
  Mesh 定义个Mesh对象,共有9个部分组成:
1、包含的顶点数
2、顶点列表,一个顶点包含三个浮点值
3、面数
4、面的顶点索引列表,每个面包含三个顶点
5、MeshFaceWraps 结构,暂时无用
6、MeshTextureCoords纹理坐标,可选
7、MeshNormals 法向,可选
8、MeshVertexColors 顶点颜色,默认为白色
9、MeshMaterialList 材质,不提供的话默认为白色。
  MeshFace 面索引,包含两部分:面数,定点索引构成的面数组。
  MeshTextureCoords 定义纹理坐标,包括:纹理坐标的个数,纹理坐标(每个纹理坐标有两个浮点值)。
  MeshMaterialList 定义材质的应用,包括:多少个材质被使用,材质影响面的个数,面索引。
  MeshNormals 定义Mesh的法向量,包括4部分:
1.nNormals法向量的个数=顶点数
2.Normals顶点法向量列表
3.nFaceNormals面的个数
4.FaceNormals面对应的法向量。
  MeshVertexColors  指定顶点的颜色代替原来的材质,包含:顶点数目,颜色索引
  TextureFilename  纹理的名称,字符串类型。
  VertexDuplicationIndices  保留副本,用于精简Mesh的操作,包含:顶点数,原始顶点数,实际顶点数。
  XSkinMeshHeader  描述被导出的SkinMesh相关信息,影响一个顶点的最多变换数目,影响每个面三个顶点的最大变换数目,影响一个顶点的骨骼数。
  TimedFloatKeys 时间值,用于Animaterkey中定义时间间隔。
  Vector 三维向量,三个浮点值。
  SkinWeights 定义骨骼影响权重。包括以下几个部分:骨骼的名字,有多少个权重值,顶点的索引列表等等

三.封装一个容易使用的骨骼动画类

DX这个.X文件也看不太懂,不过,我们也没必要看懂这个,关键还是怎么用。给我们一个带动画的.X文件,我们能够把它读取,并播放出来才是最重要的。这个骨骼动画相关的内容貌似还是比较难,不过好在网上有一些教程,最重要的是DX官方文档给的那个例子,其实已经封装了一个可以使用的骨骼动画类。不过他还需要几个全局函数和全局变量才能读取并绘制出骨骼动画,还是没有彻底封装。所以,我们进一步把这几个函数和全局变量加上DX给的那个类,进行再次封装,写一个简单粗暴的骨骼动画类。只用几步就可以播放骨骼动画。
AllocateHierarchy.h文件(DX官方给出的类):
/*!
 * \file AllocateHierarchy.h
 *
 * \author puppet_master
 * \date 九月 2015
 *
 * \微软SDK自带的关于骨骼动画的类,用于骨骼动画的创建以及绘制更新
 * \注:程序不直接使用该类,而是将此类二次封装后再使用。
 */

#ifndef __ALLOCATEHIERARCHY_H_
#define __ALLOCATEHIERARCHY_H_

//--------------------------------------------------------------------------------------
// Name: struct D3DXFRAME_DERIVED
// Desc: 
//--------------------------------------------------------------------------------------
struct D3DXFRAME_DERIVED : public D3DXFRAME
{
	D3DXMATRIXA16 CombinedTransformationMatrix;
};


//--------------------------------------------------------------------------------------
// Name: struct D3DXMESHCONTAINER_DERIVED
// Desc: Structure derived from D3DXMESHCONTAINER so we can add some app-specific
//       info that will be stored with each mesh
//--------------------------------------------------------------------------------------
struct D3DXMESHCONTAINER_DERIVED : public D3DXMESHCONTAINER
{
	//纹理信息
	LPDIRECT3DTEXTURE9* ppTextures;       //纹理数组  

	//网格信息             
	LPD3DXMESH pOrigMesh;					//原始网格
	LPD3DXATTRIBUTERANGE pAttributeTable;	//属性表
	DWORD NumAttributeGroups;				//属性组数量(子网格数量)
	DWORD NumInfl;							//每个顶点最多受几个骨骼影响
	LPD3DXBUFFER pBoneCombinationBuf;		//骨骼结合缓存
	D3DXMATRIX** ppBoneMatrixPtrs;			//骨骼组合变换矩阵
	D3DXMATRIX* pBoneOffsetMatrices;		//骨骼初始变换矩阵
	DWORD NumPaletteEntries;				//骨骼数量上限
	bool UseSoftwareVP;						//是否使用软件顶点处理
	DWORD iAttributeSW;						// used to denote the split between SW and HW if necessary for non-indexed skinning
};

//--------------------------------------------------------------------------------------
// Name: class CAllocateHierarchy
// Desc: Custom version of ID3DXAllocateHierarchy with custom methods to create
//       frames and meshcontainers.
//用来从.X文件中加载网格以及动画数据
//--------------------------------------------------------------------------------------
class CAllocateHierarchy : public ID3DXAllocateHierarchy
{
private:
	HRESULT AllocateName( LPCSTR Name, LPSTR* pNewName );
	HRESULT GenerateSkinnedMesh( IDirect3DDevice9* pd3dDevice, D3DXMESHCONTAINER_DERIVED* pMeshContainer );
public:
	STDMETHOD( CreateFrame )( THIS_ LPCSTR Name, LPD3DXFRAME *ppNewFrame );
	STDMETHOD( CreateMeshContainer )( THIS_
		LPCSTR Name,
		CONST D3DXMESHDATA *pMeshData,
		CONST D3DXMATERIAL *pMaterials,
		CONST D3DXEFFECTINSTANCE *pEffectInstances,
		DWORD NumMaterials,
		CONST DWORD *pAdjacency,
		LPD3DXSKININFO pSkinInfo,
		LPD3DXMESHCONTAINER *ppNewMeshContainer );
	STDMETHOD( DestroyFrame )( THIS_ LPD3DXFRAME pFrameToFree );
	STDMETHOD( DestroyMeshContainer )( THIS_ LPD3DXMESHCONTAINER pMeshContainerBase );

	CAllocateHierarchy()
	{
	}
};

#endif
AllocateHierarchy.cpp文件:
#include "stdafx.h"
#include "AllocateHierarchy.h"


HRESULT CAllocateHierarchy::AllocateName( LPCSTR Name, LPSTR* pNewName )
{
	UINT cbLength;

	if( Name != NULL )
	{
		cbLength = ( UINT )strlen( Name ) + 1;
		*pNewName = new CHAR[cbLength];
		if( *pNewName == NULL )
			return E_OUTOFMEMORY;
		memcpy( *pNewName, Name, cbLength * sizeof( CHAR ) );
	}
	else
	{
		*pNewName = NULL;
	}

	return S_OK;
}

//--------------------------------------------------------------------------------------
// Called either by CreateMeshContainer when loading a skin mesh, or when 
// changing methods.  This function uses the pSkinInfo of the mesh 
// container to generate the desired drawable mesh and bone combination 
// table.
//--------------------------------------------------------------------------------------
HRESULT CAllocateHierarchy::GenerateSkinnedMesh( IDirect3DDevice9* pd3dDevice, D3DXMESHCONTAINER_DERIVED* pMeshContainer )
{
	D3DCAPS9 d3dCaps;
	pd3dDevice->GetDeviceCaps( &d3dCaps );

	if( pMeshContainer->pSkinInfo == NULL )
		return S_OK;

	SAFE_RELEASE( pMeshContainer->MeshData.pMesh );
	SAFE_RELEASE( pMeshContainer->pBoneCombinationBuf );


	if (FAILED(pMeshContainer->pSkinInfo->ConvertToBlendedMesh(
		pMeshContainer->pOrigMesh, 
		D3DXMESH_MANAGED | D3DXMESHOPT_VERTEXCACHE,
		pMeshContainer->pAdjacency,
		NULL, NULL, NULL,
		&pMeshContainer->NumInfl,
		&pMeshContainer->NumAttributeGroups,
		&pMeshContainer->pBoneCombinationBuf,
		&pMeshContainer->MeshData.pMesh)))
		return E_FAIL;
	return S_OK;
}

//--------------------------------------------------------------------------------------
// Name: CAllocateHierarchy::CreateFrame()
// Desc: 创建框架,分配内存&初始化
//--------------------------------------------------------------------------------------
HRESULT CAllocateHierarchy::CreateFrame( LPCSTR Name, LPD3DXFRAME* ppNewFrame )
{
	HRESULT hr = S_OK;
	D3DXFRAME_DERIVED* pFrame;

	*ppNewFrame = NULL;

	pFrame = new D3DXFRAME_DERIVED;
	if( pFrame == NULL )
	{
		hr = E_OUTOFMEMORY;
		goto e_Exit;
	}

	hr = AllocateName( Name, &pFrame->Name );
	if( FAILED( hr ) )
		goto e_Exit;

	// initialize other data members of the frame
	D3DXMatrixIdentity( &pFrame->TransformationMatrix );
	D3DXMatrixIdentity( &pFrame->CombinedTransformationMatrix );

	pFrame->pMeshContainer = NULL;
	pFrame->pFrameSibling = NULL;
	pFrame->pFrameFirstChild = NULL;

	*ppNewFrame = pFrame;
	pFrame = NULL;

e_Exit:
	delete pFrame;
	return hr;
}




//--------------------------------------------------------------------------------------
// Name: CAllocateHierarchy::CreateMeshContainer()
// Desc: 创建网格容器对象,保存网格模型数据
//--------------------------------------------------------------------------------------
HRESULT CAllocateHierarchy::CreateMeshContainer(
	LPCSTR Name,
	CONST D3DXMESHDATA *pMeshData,
	CONST D3DXMATERIAL *pMaterials,
	CONST D3DXEFFECTINSTANCE *pEffectInstances,
	DWORD NumMaterials,
	CONST DWORD *pAdjacency,
	LPD3DXSKININFO pSkinInfo,
	LPD3DXMESHCONTAINER *ppNewMeshContainer )
{
	HRESULT hr;
	D3DXMESHCONTAINER_DERIVED *pMeshContainer = NULL;
	UINT NumFaces;
	UINT iMaterial;
	UINT iBone, cBones;
	LPDIRECT3DDEVICE9 pd3dDevice = NULL;

	LPD3DXMESH pMesh = NULL;

	*ppNewMeshContainer = NULL;

	// this sample does not handle patch meshes, so fail when one is found
	if( pMeshData->Type != D3DXMESHTYPE_MESH )
	{
		hr = E_FAIL;
		goto e_Exit;
	}

	// get the pMesh interface pointer out of the mesh data structure
	pMesh = pMeshData->pMesh;

	// this sample does not FVF compatible meshes, so fail when one is found
	if( pMesh->GetFVF() == 0 )
	{
		hr = E_FAIL;
		goto e_Exit;
	}

	// allocate the overloaded structure to return as a D3DXMESHCONTAINER
	pMeshContainer = new D3DXMESHCONTAINER_DERIVED;
	if( pMeshContainer == NULL )
	{
		hr = E_OUTOFMEMORY;
		goto e_Exit;
	}
	memset( pMeshContainer, 0, sizeof( D3DXMESHCONTAINER_DERIVED ) );

	// make sure and copy the name.  All memory as input belongs to caller, interfaces can be addref'd though
	hr = AllocateName( Name, &pMeshContainer->Name );
	if( FAILED( hr ) )
		goto e_Exit;

	pMesh->GetDevice( &pd3dDevice );
	NumFaces = pMesh->GetNumFaces();

	// if no normals are in the mesh, add them
	if( !( pMesh->GetFVF() & D3DFVF_NORMAL ) )
	{
		pMeshContainer->MeshData.Type = D3DXMESHTYPE_MESH;

		// clone the mesh to make room for the normals
		hr = pMesh->CloneMeshFVF( pMesh->GetOptions(),
			pMesh->GetFVF() | D3DFVF_NORMAL,
			pd3dDevice, &pMeshContainer->MeshData.pMesh );
		if( FAILED( hr ) )
			goto e_Exit;

		// get the new pMesh pointer back out of the mesh container to use
		// NOTE: we do not release pMesh because we do not have a reference to it yet
		pMesh = pMeshContainer->MeshData.pMesh;

		// now generate the normals for the pmesh
		D3DXComputeNormals( pMesh, NULL );
	}
	else  // if no normals, just add a reference to the mesh for the mesh container
	{
		pMeshContainer->MeshData.pMesh = pMesh;
		pMeshContainer->MeshData.Type = D3DXMESHTYPE_MESH;

		pMesh->AddRef();
	}

	// allocate memory to contain the material information.  This sample uses
	//   the D3D9 materials and texture names instead of the EffectInstance style materials
	pMeshContainer->NumMaterials = max( 1, NumMaterials );
	pMeshContainer->pMaterials = new D3DXMATERIAL[pMeshContainer->NumMaterials];
	pMeshContainer->ppTextures = new LPDIRECT3DTEXTURE9[pMeshContainer->NumMaterials];
	pMeshContainer->pAdjacency = new DWORD[NumFaces*3];
	if( ( pMeshContainer->pAdjacency == NULL ) || ( pMeshContainer->pMaterials == NULL ) )
	{
		hr = E_OUTOFMEMORY;
		goto e_Exit;
	}

	memcpy( pMeshContainer->pAdjacency, pAdjacency, sizeof( DWORD ) * NumFaces*3 );
	memset( pMeshContainer->ppTextures, 0, sizeof( LPDIRECT3DTEXTURE9 ) * pMeshContainer->NumMaterials );

	// if materials provided, copy them
	if( NumMaterials > 0 )
	{
		memcpy( pMeshContainer->pMaterials, pMaterials, sizeof( D3DXMATERIAL ) * NumMaterials );

		for( iMaterial = 0; iMaterial < NumMaterials; iMaterial++ )
		{
			if( pMeshContainer->pMaterials[iMaterial].pTextureFilename != NULL )
			{
				if( FAILED( D3DXCreateTextureFromFile( pd3dDevice, pMeshContainer->pMaterials[iMaterial].pTextureFilename,
					&pMeshContainer->ppTextures[iMaterial] ) ) )
					pMeshContainer->ppTextures[iMaterial] = NULL;

				// don't remember a pointer into the dynamic memory, just forget the name after loading
				pMeshContainer->pMaterials[iMaterial].pTextureFilename = NULL;
			}
		}
	}
	else // if no materials provided, use a default one
	{
		pMeshContainer->pMaterials[0].pTextureFilename = NULL;
		memset( &pMeshContainer->pMaterials[0].MatD3D, 0, sizeof( D3DMATERIAL9 ) );
		pMeshContainer->pMaterials[0].MatD3D.Diffuse.r = 0.5f;
		pMeshContainer->pMaterials[0].MatD3D.Diffuse.g = 0.5f;
		pMeshContainer->pMaterials[0].MatD3D.Diffuse.b = 0.5f;
		pMeshContainer->pMaterials[0].MatD3D.Specular = pMeshContainer->pMaterials[0].MatD3D.Diffuse;
	}

	// if there is skinning information, save off the required data and then setup for HW skinning
	if( pSkinInfo != NULL )
	{
		// first save off the SkinInfo and original mesh data
		pMeshContainer->pSkinInfo = pSkinInfo;
		pSkinInfo->AddRef();

		pMeshContainer->pOrigMesh = pMesh;
		pMesh->AddRef();

		// Will need an array of offset matrices to move the vertices from the figure space to the bone's space
		cBones = pSkinInfo->GetNumBones();
		pMeshContainer->pBoneOffsetMatrices = new D3DXMATRIX[cBones];
		if( pMeshContainer->pBoneOffsetMatrices == NULL )
		{
			hr = E_OUTOFMEMORY;
			goto e_Exit;
		}

		// get each of the bone offset matrices so that we don't need to get them later
		for( iBone = 0; iBone < cBones; iBone++ )
		{
			pMeshContainer->pBoneOffsetMatrices[iBone] = *( pMeshContainer->pSkinInfo->GetBoneOffsetMatrix( iBone ) );
		}

		// GenerateSkinnedMesh will take the general skinning information and transform it to a HW friendly version
		hr = GenerateSkinnedMesh( pd3dDevice, pMeshContainer );
		if( FAILED( hr ) )
			goto e_Exit;
	}

	*ppNewMeshContainer = pMeshContainer;
	pMeshContainer = NULL;

e_Exit:
	SAFE_RELEASE( pd3dDevice );

	// call Destroy function to properly clean up the memory allocated 
	if( pMeshContainer != NULL )
	{
		DestroyMeshContainer( pMeshContainer );
	}

	return hr;
}




//--------------------------------------------------------------------------------------
// Name: CAllocateHierarchy::DestroyFrame()
// Desc: 释放框架
//--------------------------------------------------------------------------------------
HRESULT CAllocateHierarchy::DestroyFrame( LPD3DXFRAME pFrameToFree )
{
	SAFE_DELETE_ARRAY( pFrameToFree->Name );
	SAFE_DELETE( pFrameToFree );
	return S_OK;
}




//--------------------------------------------------------------------------------------
// Name: CAllocateHierarchy::DestroyMeshContainer()
// Desc: 释放网格容器
//--------------------------------------------------------------------------------------
HRESULT CAllocateHierarchy::DestroyMeshContainer( LPD3DXMESHCONTAINER pMeshContainerBase )
{
	UINT iMaterial;
	D3DXMESHCONTAINER_DERIVED* pMeshContainer = ( D3DXMESHCONTAINER_DERIVED* )pMeshContainerBase;

	SAFE_DELETE_ARRAY( pMeshContainer->Name );
	SAFE_DELETE_ARRAY( pMeshContainer->pAdjacency );
	SAFE_DELETE_ARRAY( pMeshContainer->pMaterials );
	SAFE_DELETE_ARRAY( pMeshContainer->pBoneOffsetMatrices );

	// release all the allocated textures
	if( pMeshContainer->ppTextures != NULL )
	{
		for( iMaterial = 0; iMaterial < pMeshContainer->NumMaterials; iMaterial++ )
		{
			SAFE_RELEASE( pMeshContainer->ppTextures[iMaterial] );
		}
	}
	SAFE_DELETE_ARRAY( pMeshContainer->ppTextures );
	SAFE_DELETE_ARRAY( pMeshContainer->ppBoneMatrixPtrs );
	SAFE_RELEASE( pMeshContainer->pBoneCombinationBuf );
	SAFE_RELEASE( pMeshContainer->MeshData.pMesh );
	SAFE_RELEASE( pMeshContainer->pSkinInfo );
	SAFE_RELEASE( pMeshContainer->pOrigMesh );
	SAFE_DELETE( pMeshContainer );
	return S_OK;
}

D3DXAnimation.h文件(我们封装的类):
/*!
 * \file D3DXAnimation.h
 *
 * \author puppet_master
 * \date 九月 2015
 *
 * \封装了微软自带的骨骼动画相关功能,提供一个简单的接口供使用
 */

#ifndef __D3DXANIMATION_H_
#define __D3DXANIMATION_H_

#include "AllocateHierarchy.h"

class CD3DXAnimation
{
private:
	IDirect3DDevice9*	m_pDevice;					//D3D设备对象
	CAllocateHierarchy* m_pAllocateHier;			//骨骼动画网格模型指针
	LPD3DXFRAME			m_pFrameRoot;				//帧
	LPD3DXANIMATIONCONTROLLER m_pAnimController;	//动画控制器
	D3DXMATRIX*			m_pBoneMatrix;				//骨骼矩阵
private:
	//一些微软自带函数,关于骨骼动画加载与绘制更新的函数,将其封装,不使用这些接口

	void DrawMeshContainer( IDirect3DDevice9* pd3dDevice, LPD3DXMESHCONTAINER pMeshContainerBase, LPD3DXFRAME pFrameBase );
	void DrawFrame( IDirect3DDevice9* pd3dDevice, LPD3DXFRAME pFrame );
	HRESULT SetupBoneMatrixPointers( LPD3DXFRAME pFrameBase, LPD3DXFRAME pFrameRoot );
	void UpdateFrameMatrices( LPD3DXFRAME pFrameBase, LPD3DXMATRIX pParentMatrix );
public:
	CD3DXAnimation(IDirect3DDevice9* device);
	~CD3DXAnimation(void);

	//提供给外界的接口

	//创建骨骼动画
	bool Init(LPCTSTR filename);

	//通过名字设置要播放的骨骼动画
	void SetAnimationByName(LPCTSTR name);

	//更新位置
	void SetMatrix(LPD3DXMATRIX mtrix);

	//更新动画
	void UpdateAnimation(double timeDelay);

	//绘制骨骼动画
	void Render();
};

#endif

D3DXAnimation.cpp文件:
#include "stdafx.h"
#include "D3DXAnimation.h"


CD3DXAnimation::CD3DXAnimation(IDirect3DDevice9* device)
	:m_pDevice(device),
	 m_pAllocateHier(NULL),
	 m_pAnimController(NULL),
	 m_pFrameRoot(NULL),
	 m_pBoneMatrix(NULL)
{
}


CD3DXAnimation::~CD3DXAnimation(void)
{
	D3DXFrameDestroy(m_pFrameRoot, m_pAllocateHier);
	SAFE_RELEASE(m_pAnimController);
	SAFE_DELETE(m_pAllocateHier);
}

//--------------------------------------------------------------------------------------
// Name: SetupBoneMatrixPointers()
// Desc: 设置好各级框架的组合变换矩阵。
//--------------------------------------------------------------------------------------
HRESULT CD3DXAnimation::SetupBoneMatrixPointers( LPD3DXFRAME pFrameBase, LPD3DXFRAME pFrameRoot )
{
	if( pFrameBase->pMeshContainer != NULL )
	{
		D3DXFRAME_DERIVED* pFrame = NULL;
		D3DXMESHCONTAINER_DERIVED* pMeshContainer = (D3DXMESHCONTAINER_DERIVED*)pFrameBase->pMeshContainer;

		// if there is a skinmesh, then setup the bone matrices
		if (pMeshContainer->pSkinInfo != NULL)
		{
			UINT cBones = pMeshContainer->pSkinInfo->GetNumBones();
			pMeshContainer->ppBoneMatrixPtrs = new D3DXMATRIX*[cBones];
			for (UINT iBone = 0; iBone < cBones; iBone++)
			{
				pFrame = (D3DXFRAME_DERIVED*)D3DXFrameFind(pFrameRoot, pMeshContainer->pSkinInfo->GetBoneName(iBone));
				if (pFrame == NULL) return E_FAIL;

				pMeshContainer->ppBoneMatrixPtrs[iBone] = &pFrame->CombinedTransformationMatrix;
			}
		}
	}

	if (pFrameBase->pFrameSibling != NULL)
	{
		if (FAILED(SetupBoneMatrixPointers(pFrameBase->pFrameSibling, pFrameRoot)))
			return E_FAIL;
	}

	if (pFrameBase->pFrameFirstChild != NULL)
	{
		if (FAILED(SetupBoneMatrixPointers(pFrameBase->pFrameFirstChild, pFrameRoot)))
			return E_FAIL;
	}

	return S_OK;
}

//--------------------------------------------------------------------------------------
// Name: DrawFrame()
// Desc: 绘制骨骼
//--------------------------------------------------------------------------------------
void CD3DXAnimation::DrawFrame( IDirect3DDevice9* pd3dDevice, LPD3DXFRAME pFrame )
{
	if (pFrame == NULL) return;
	LPD3DXMESHCONTAINER pMeshContainer;
	pMeshContainer = pFrame->pMeshContainer;                    // 取得网格容器
	while( pMeshContainer != NULL )                      
	{
		DrawMeshContainer(pd3dDevice, pMeshContainer, pFrame);  // 绘制非空蒙皮网格
		pMeshContainer = pMeshContainer->pNextMeshContainer;    // 遍历所有网格容器
	}

	DrawFrame(pd3dDevice, pFrame->pFrameSibling);               // 绘制兄弟框架
	DrawFrame(pd3dDevice, pFrame->pFrameFirstChild);            // 绘制子框架
}

//--------------------------------------------------------------------------------------
// Name: DrawMeshContainer()
// Desc: 绘制蒙皮容器中的蒙皮网格
//--------------------------------------------------------------------------------------
void CD3DXAnimation::DrawMeshContainer( IDirect3DDevice9* pd3dDevice, LPD3DXMESHCONTAINER pMeshContainerBase, LPD3DXFRAME pFrameBase )
{
	D3DXMESHCONTAINER_DERIVED* pMeshContainer = ( D3DXMESHCONTAINER_DERIVED* )pMeshContainerBase;
	D3DXFRAME_DERIVED* pFrame = ( D3DXFRAME_DERIVED* )pFrameBase;
	UINT iMaterial;
	UINT NumBlend;
	UINT iAttrib;
	DWORD AttribIdPrev;
	LPD3DXBONECOMBINATION pBoneComb;

	UINT iMatrixIndex;
	D3DXMATRIXA16 matTemp;
	D3DCAPS9 d3dCaps;
	pd3dDevice->GetDeviceCaps( &d3dCaps );

	// first check for skinning
	if( pMeshContainer->pSkinInfo != NULL )
	{
		AttribIdPrev = UNUSED32;
		pBoneComb = reinterpret_cast<LPD3DXBONECOMBINATION>( pMeshContainer->pBoneCombinationBuf->GetBufferPointer() );

		// Draw using default vtx processing of the device (typically HW)
		for( iAttrib = 0; iAttrib < pMeshContainer->NumAttributeGroups; iAttrib++ )
		{
			NumBlend = 0;
			for( DWORD i = 0; i < pMeshContainer->NumInfl; ++i )
			{
				if( pBoneComb[iAttrib].BoneId[i] != UINT_MAX )
				{
					NumBlend = i;
				}
			}

			if( d3dCaps.MaxVertexBlendMatrices >= NumBlend + 1 )
			{
				// first calculate the world matrices for the current set of blend weights and get the accurate count of the number of blends
				for( DWORD i = 0; i < pMeshContainer->NumInfl; ++i )
				{
					iMatrixIndex = pBoneComb[iAttrib].BoneId[i];
					if( iMatrixIndex != UINT_MAX )
					{
						D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatrices[iMatrixIndex],
							pMeshContainer->ppBoneMatrixPtrs[iMatrixIndex] );
						pd3dDevice->SetTransform( D3DTS_WORLDMATRIX( i ), &matTemp );
					}
				}

				pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, NumBlend );

				// lookup the material used for this subset of faces
				if( ( AttribIdPrev != pBoneComb[iAttrib].AttribId ) || ( AttribIdPrev == UNUSED32 ) )
				{
					pd3dDevice->SetMaterial( &pMeshContainer->pMaterials[pBoneComb[iAttrib].AttribId].MatD3D );
					pd3dDevice->SetTexture( 0, pMeshContainer->ppTextures[pBoneComb[iAttrib].AttribId] );
					AttribIdPrev = pBoneComb[iAttrib].AttribId;
				}

				// draw the subset now that the correct material and matrices are loaded
				pMeshContainer->MeshData.pMesh->DrawSubset( iAttrib );
			}
		}
		pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, 0 );
	}
	else  // standard mesh, just draw it after setting material properties
	{
		pd3dDevice->SetTransform( D3DTS_WORLD, &pFrame->CombinedTransformationMatrix );

		for( iMaterial = 0; iMaterial < pMeshContainer->NumMaterials; iMaterial++ )
		{
			pd3dDevice->SetMaterial( &pMeshContainer->pMaterials[iMaterial].MatD3D );
			pd3dDevice->SetTexture( 0, pMeshContainer->ppTextures[iMaterial] );
			pMeshContainer->MeshData.pMesh->DrawSubset( iMaterial );
		}
	}
}

//--------------------------------------------------------------------------------------
// Name: UpdateFrameMatrics()
// Desc: 更新框架中的变换矩阵
//--------------------------------------------------------------------------------------
void CD3DXAnimation::UpdateFrameMatrices( LPD3DXFRAME pFrameBase, LPD3DXMATRIX pParentMatrix )
{
	if (pFrameBase == NULL || pParentMatrix == NULL) return;
	D3DXFRAME_DERIVED* pFrame = ( D3DXFRAME_DERIVED* )pFrameBase;

	// 将当前骨骼的相对于父骨骼的偏移矩阵作累积运算
	D3DXMatrixMultiply(&pFrame->CombinedTransformationMatrix, &pFrame->TransformationMatrix, pParentMatrix);

	UpdateFrameMatrices(pFrame->pFrameSibling, pParentMatrix);                              // 更新兄弟骨骼
	UpdateFrameMatrices(pFrame->pFrameFirstChild, &pFrame->CombinedTransformationMatrix);   // 更新子骨骼
}

//---------------------------------------------------------
//Name:真正暴露给外部调用的函数
//Desc:关于动画的创建,更新,绘制
//---------------------------------------------------------

bool CD3DXAnimation::Init(LPCTSTR filename)
{
	m_pAllocateHier = new CAllocateHierarchy();
	D3DXLoadMeshHierarchyFromX(filename, D3DXMESH_MANAGED, m_pDevice, m_pAllocateHier, NULL, &m_pFrameRoot, &m_pAnimController);
	SetupBoneMatrixPointers(m_pFrameRoot, m_pFrameRoot);

	return true;
}

void CD3DXAnimation::SetAnimationByName(LPCTSTR name)
{
	LPD3DXANIMATIONSET pAnimationSet = NULL;
	m_pAnimController->GetAnimationSetByName(name, &pAnimationSet);
	m_pAnimController->SetTrackAnimationSet((UINT)1.0, pAnimationSet);
}

void CD3DXAnimation::SetMatrix(LPD3DXMATRIX mtrix)
{
	UpdateFrameMatrices(m_pFrameRoot, mtrix);
}

void CD3DXAnimation::UpdateAnimation(double timeDelay)
{
	m_pAnimController->AdvanceTime(timeDelay, NULL);
}

void CD3DXAnimation::Render()
{
	DrawFrame(m_pDevice, m_pFrameRoot);
}

使用骨骼动画类:
1.创建骨骼动画:
<span style="white-space:pre">	</span>g_pAnimation1 = new CD3DXAnimation(g_pDevice);
	g_pAnimation1->Init("tiny.x");
2.逻辑更新&绘制(两步暂时放在了一起):
	g_pAnimation->SetMatrix(&matWorld1);
	g_pAnimation->UpdateAnimation(fElasedTime * 1);
	g_pAnimation->Render();
好了,这样就大功告成了。封装好了的骨骼动画类使用起来灰常方便,只要导入一个骨骼动画.X文件,就可以创建模型并让它动起来啦。
run一下,模型使用的还是DX官方给出的那个长得很丑的小人:

Direct-X学习笔记--骨骼动画_第1张图片



这样,这个小人就走起来啦,终于看到模型动起来了,好激动。。。


你可能感兴趣的:(C++,动画,3D,骨骼动画,dx)