38.蒙皮动画模型内容处理器

我们已经可以在X文件的导出系列5蒙皮动画模型中导出包含动画数据的模型,现在就可以自己编写内容处理器将动画数据包含在二进制的xnb文件中,然后在XNA程序中加以播放。但在进入到内容处理器的编写之前,首先需要理解XNA框架中Model类的组织方式。以下代码使用的模型都是X文件的导出系列5蒙皮动画模型导出的那个简陋的人体manSkinningX.X,它包含提右腿和提左腿两个动画。

ModelMesh和ModelBone

这个知识可参见什么是模型Bone4.8 可视化模型骨骼结构

简而言之:XNA框架的Model类表示整个模型。而Model中每个可独立运动的网格都对应一个ModelMesh,每个ModelMesh包含顶点、索引、纹理坐标等几何信息(即X文件中的Mesh、MeshNormals、MeshMaterialList、Material、MeshTextureCoords大括号中的数据),而它的位置信息(即X文件中FrameTransformMatrix大括号中的数据,其实就是一个表示相对于父节点偏移的4*4矩阵)存储在ModelBone对象中。两者通过ModelMeshBone中的ParentBone的Index属性联系在一起。

以manSkinningX.X为例,你可以使用在4.8 可视化模型骨骼结构中介绍的方法看到模型的结构:1.在XNA代码的合适地方设置中断,2.插入System.Diagnostics.Debugger.Break();代码,3.编程输出文本,我使用了第3个方法在一个文本文件中获得了这个模型的结构:

Model Bone Information
----------------------
- Name : 
  Index: 0
	- Name : Body
	  Index: 1
		- Name : null
		  Index: 2
	- Name : Spine
	  Index: 3
		- Name : Spine_Nub
		  Index: 4
		- Name : L_Leg
		  Index: 5
			- Name : null
			  Index: 6
			- Name : L_Nub
			  Index: 7
		- Name : R_Leg
		  Index: 8
			- Name : R_Nub
			  Index: 9


Model Mesh Information
----------------------
- ID  : 0
  Name: 
  Bone:  (2)

由这个文件可以看出,manSkinningX.X只有一个ModelMesh,有10个ModelBone,而ModelMesh链接到索引为2的bone上。这个结构都是由框架默认的Model - XNA Framework处理器导出的,事实上在3DSMAX制作这个模型时,只有6个bone,索引为0,1,2,6的bone都是处理器自行添加的,至于为什么这样我不是很清楚,最不能理解的是为什么L_Leg下要添加一个索引为6的额外的bone,而R_Leg下却不添加。

但如果导出manSkinningKW.X文件的结构,却发现有点不同,只有9个bone。:

Model Bone Information
----------------------
- Name : 
  Index: 0
	- Name : Body
	  Index: 1
		- Name : mesh_Body
		  Index: 2
	- Name : Spine
	  Index: 3
		- Name : Spine_Nub
		  Index: 4
		- Name : L_Leg
		  Index: 5
			- Name : L_Nub
			  Index: 6
		- Name : R_Leg
		  Index: 7
			- Name : R_Nub
			  Index: 8
Model Mesh Information
----------------------
- ID  : 0
  Name: mesh_Body
  Bone: mesh_Body (2)

如果导出manSkinningFbx.FBX文件的结构,只有8个bone,好像fbx文件的冗余数据最少:

Model Bone Information
----------------------
- Name : RootNode
  Index: 0
	- Name : Body
	  Index: 1
	- Name : Spine
	  Index: 2
		- Name : Spine_Nub
		  Index: 3
		- Name : L_Leg
		  Index: 4
			- Name : L_Nub
			  Index: 5
		- Name : R_Leg
		  Index: 6
			- Name : R_Nub
			  Index: 7
Model Mesh Information
----------------------
- ID  : 0
  Name: Body
  Bone: Body (1)

不过接下去的代码中我还是使用manSkinningX.X进行分析。

Content Pipeline类库中的类层次

ModelMesh和ModelBone位于XNA Framework Class Library中,但要理解内容管道中到底进行了什么处理,你首先需要理解Content Pipeline Class Library中的几个类的层次,如下图所示:

内容管道类层次

以下内容来自于XNA帮助文件:

1.ContentItem:提供定义内容(content)时所需的属性,这些内容是使用XNA框架中介文件格式定义的,是所有内容的基类。

2.NodeContent:它定义了本地坐标系统的图形对象的基类。

3.MeshContent:提供定义一个mesh的各种属性。一个mesh具有以下特征:

4.BoneContent:表示一个动画骨骼(Animation skeleton)。动画骨骼(Animation skeleton)是以一个BoneContent对象树表示的,这个树在Transform属性中保存了bind pose。整个骨骼的动画数据存储在根bone的Animations属性中(根bone也是一个BoneContent 对象)。

5.GeometryContent:定义一个geometry batch的各种属性。一个geometry batch是一个mesh的子组件,表示可以提交给GPU进行一次绘制调用的一组齐次几何数据。它包含了索引化的三角形列表(使用一个材质),其中所有顶点都共享相同的数据通道。 如果顶点在其数据通道中有所不同,则会被设置成唯一的(unique)。Coordinates that require unique vertices on either side of a join create unique vertices(译者:?)。

6.AnimationContent :提供表示动画所需的属性。一个动画包含一个数据channel的集合,这个集合表示一组完整的运动,这个运动可以施加在任意数量的bone或刚体上。数据channel存储在Animations dictionary中。对character skinning来说,动画数据通常链接在根bone。但是,它也可能属于任何节点。例如我们使用刚体动画的时候。

ContentItem的子类还有EffectContent,MaterialContent,BitmapContent,TextureContent,FontDescription,因为与本文无关,所以没有列出。

内容管道的调试

为了更好地理解内容管道的处理过程,你还想单步调试项目,但是因为内容管道不是在编译时运行的,所以在SkinnedModelPipeline内容管道项目中设置中断不会起任何作用,需要进行额外的操作,这个方法参考自外的操作,这个方法参考自http://www.cse.msu.edu/~cse473/step5.html。

你需要右击SkinnedModelPipeline,在弹出菜单中选择“属性”,在打开的页面中选择“调试”,点击“启动外部程序”右侧的按钮,找到你的电脑上的MSBuild.exe文件,它通常位于C:\Windows\Microsoft.NET\Framework\v3.5\MSBuild.exe,然后找到项目中的Content处理项目文件,我的电脑上位于F:\StunEngine0.4\TestGame\TestGame\Content\Content.contentproj(这个位置通常不一样,是由你的游戏项目位置决定的)。

设置项目属性

最后在SkinnedModelPipeline项目中的SkinnedModelProcessor.cs文件合适位置设置中断,右击SkinnedModelPipeline项目,在弹出菜单中选择调试→启动新项目,程序就会在你中断处停止,你可以单步调试看看到底发生了什么事。

调试内容管道

SkinnedModelWindows类库

好了,现在终于可以来研究一下XNA官网上的处理蒙皮动画模型的示例了,原文地址http://creators.xna.com/en-US/sample/skinnedmodel,经过翻译后的文章为蒙皮动画模型(Skinned Model)示例。我同时还参考了《Beginning XNA 3 0 Game Programming FromN ovice to Professional》(以下简称《BeninningXNA》)的第12章:骨骼动画,它的过程写得更加详尽,你应该先看一看11.1 动画类型对动画的概念有个大致的了解。

我想做的事情就是将manSkinningX.X文件中的动画数据经过一定的处理放置在Model类的Tag属性中,这样就可以被XNA程序调用了,此数据如下:

AnimationSet RaiseRight {
 
Animation Anim-Body {
  
  { Body }

  AnimationKey {
   4;
   10;
   0;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,3.000000,0.000000,0.000000,1.000000;;,
   160;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,3.000000,0.000000,0.000000,1.000000;;,
   320;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,3.000000,0.000000,0.000000,1.000000;;,
   480;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,3.000000,0.000000,0.000000,1.000000;;,
   640;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,3.000000,0.000000,0.000000,1.000000;;,
   800;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,3.000000,0.000000,0.000000,1.000000;;,
   960;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,3.000000,0.000000,0.000000,1.000000;;,
   1120;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,3.000000,0.000000,0.000000,1.000000;;,
   1280;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,3.000000,0.000000,0.000000,1.000000;;,
   1440;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,3.000000,0.000000,0.000000,1.000000;;;
  }
 }

 Animation Anim-Spine {
  
  { Spine }

  AnimationKey {
   4;
   10;
   0;16;0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,-1.000000,0.000000,-1.000000,0.000000,0.000000,0.000000,0.000000,10.000000,-0.000000,1.000000;;,
   160;16;0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,-1.000000,0.000000,-1.000000,0.000000,0.000000,0.000000,0.000000,10.000000,-0.000000,1.000000;;,
   320;16;0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,-1.000000,0.000000,-1.000000,0.000000,0.000000,0.000000,0.000000,10.000000,-0.000000,1.000000;;,
   480;16;0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,-1.000000,0.000000,-1.000000,0.000000,0.000000,0.000000,0.000000,10.000000,-0.000000,1.000000;;,
   640;16;0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,-1.000000,0.000000,-1.000000,0.000000,0.000000,0.000000,0.000000,10.000000,-0.000000,1.000000;;,
   800;16;0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,-1.000000,0.000000,-1.000000,0.000000,0.000000,0.000000,0.000000,10.000000,-0.000000,1.000000;;,
   960;16;0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,-1.000000,0.000000,-1.000000,0.000000,0.000000,0.000000,0.000000,10.000000,-0.000000,1.000000;;,
   1120;16;0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,-1.000000,0.000000,-1.000000,0.000000,0.000000,0.000000,0.000000,10.000000,-0.000000,1.000000;;,
   1280;16;0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,-1.000000,0.000000,-1.000000,0.000000,0.000000,0.000000,0.000000,10.000000,-0.000000,1.000000;;,
   1440;16;0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,-1.000000,0.000000,-1.000000,0.000000,0.000000,0.000000,0.000000,10.000000,-0.000000,1.000000;;;
  }
 }

 Animation Anim-Spine_Nub {
  
  { Spine_Nub }

  AnimationKey {
   4;
   10;
   0;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,8.358768,0.000000,0.000000,1.000000;;,
   160;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,8.358768,0.000000,0.000000,1.000000;;,
   320;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,8.358768,0.000000,0.000000,1.000000;;,
   480;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,8.358768,0.000000,0.000000,1.000000;;,
   640;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,8.358768,0.000000,0.000000,1.000000;;,
   800;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,8.358768,0.000000,0.000000,1.000000;;,
   960;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,8.358768,0.000000,0.000000,1.000000;;,
   1120;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,8.358768,0.000000,0.000000,1.000000;;,
   1280;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,8.358768,0.000000,0.000000,1.000000;;,
   1440;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,8.358768,0.000000,0.000000,1.000000;;;
  }
 }

 Animation Anim-L_Leg {
  
  { L_Leg }

  AnimationKey {
   4;
   10;
   0;16;-1.000000,0.000648,0.000000,0.000000,0.000648,1.000000,0.000005,0.000000,-0.000000,0.000005,-1.000000,0.000000,0.000000,0.000000,-3.000000,1.000000;;,
   160;16;-1.000000,0.000648,0.000000,0.000000,0.000648,1.000000,0.000005,0.000000,-0.000000,0.000005,-1.000000,0.000000,0.000000,0.000000,-3.000000,1.000000;;,
   320;16;-1.000000,0.000648,0.000000,0.000000,0.000648,1.000000,0.000005,0.000000,-0.000000,0.000005,-1.000000,0.000000,0.000000,0.000000,-3.000000,1.000000;;,
   480;16;-1.000000,0.000648,0.000000,0.000000,0.000648,1.000000,0.000005,0.000000,-0.000000,0.000005,-1.000000,0.000000,0.000000,0.000000,-3.000000,1.000000;;,
   640;16;-1.000000,0.000648,0.000000,0.000000,0.000648,1.000000,0.000005,0.000000,-0.000000,0.000005,-1.000000,0.000000,0.000000,0.000000,-3.000000,1.000000;;,
   800;16;-1.000000,0.000648,0.000000,0.000000,0.000648,1.000000,0.000005,0.000000,-0.000000,0.000005,-1.000000,0.000000,0.000000,0.000000,-3.000000,1.000000;;,
   960;16;-1.000000,0.000648,0.000000,0.000000,0.000648,1.000000,0.000005,0.000000,-0.000000,0.000005,-1.000000,0.000000,0.000000,0.000000,-3.000000,1.000000;;,
   1120;16;-1.000000,0.000648,0.000000,0.000000,0.000648,1.000000,0.000005,0.000000,-0.000000,0.000005,-1.000000,0.000000,0.000000,0.000000,-3.000000,1.000000;;,
   1280;16;-1.000000,0.000648,0.000000,0.000000,0.000648,1.000000,0.000005,0.000000,-0.000000,0.000005,-1.000000,0.000000,0.000000,0.000000,-3.000000,1.000000;;,
   1440;16;-1.000000,0.000648,0.000000,0.000000,0.000648,1.000000,0.000005,0.000000,-0.000000,0.000005,-1.000000,0.000000,0.000000,0.000000,-3.000000,1.000000;;;
  }
 }

 Animation Anim-L_Nub {
  
  { L_Nub }

  AnimationKey {
   4;
   10;
   0;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,-0.000000,1.000000,0.000000,9.714413,-0.000000,0.000002,1.000000;;,
   160;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,-0.000000,1.000000,0.000000,9.714413,-0.000000,0.000002,1.000000;;,
   320;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,-0.000000,1.000000,0.000000,9.714413,-0.000000,0.000002,1.000000;;,
   480;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,-0.000000,1.000000,0.000000,9.714413,-0.000000,0.000002,1.000000;;,
   640;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,-0.000000,1.000000,0.000000,9.714413,-0.000000,0.000002,1.000000;;,
   800;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,-0.000000,1.000000,0.000000,9.714413,-0.000000,0.000002,1.000000;;,
   960;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,-0.000000,1.000000,0.000000,9.714413,-0.000000,0.000002,1.000000;;,
   1120;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,-0.000000,1.000000,0.000000,9.714413,-0.000000,0.000002,1.000000;;,
   1280;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,-0.000000,1.000000,0.000000,9.714413,-0.000000,0.000002,1.000000;;,
   1440;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,-0.000000,1.000000,0.000000,9.714413,-0.000000,0.000002,1.000000;;;
  }
 }

 Animation Anim-R_Leg {
  
  { R_Leg }

  AnimationKey {
   4;
   10;
   0;16;-1.000000,-0.000073,-0.000000,0.000000,-0.000073,1.000000,0.000005,0.000000,0.000000,0.000005,-1.000000,0.000000,0.000000,-0.000001,3.000000,1.000000;;,
   160;16;-0.999639,0.026860,-0.000000,0.000000,0.026860,0.999639,0.000005,0.000000,0.000000,0.000005,-1.000000,0.000000,0.000000,-0.000001,3.000000,1.000000;;,
   320;16;-0.995098,0.098892,-0.000000,0.000000,0.098892,0.995098,0.000005,0.000000,0.000001,0.000005,-1.000000,0.000000,0.000000,-0.000001,3.000000,1.000000;;,
   480;16;-0.979352,0.202164,-0.000000,0.000000,0.202164,0.979352,0.000005,0.000000,0.000001,0.000005,-1.000000,0.000000,0.000000,-0.000001,3.000000,1.000000;;,
   640;16;-0.946857,0.321654,-0.000000,0.000000,0.321654,0.946857,0.000005,0.000000,0.000002,0.000005,-1.000000,0.000000,0.000000,-0.000001,3.000000,1.000000;;,
   800;16;-0.897005,0.442020,-0.000000,0.000000,0.442020,0.897005,0.000005,0.000000,0.000003,0.000005,-1.000000,0.000000,0.000000,-0.000001,3.000000,1.000000;;,
   960;16;-0.835498,0.549493,-0.000000,0.000000,0.549493,0.835498,0.000005,0.000000,0.000003,0.000004,-1.000000,0.000000,0.000000,-0.000001,3.000000,1.000000;;,
   1120;16;-0.773614,0.633657,-0.000000,0.000000,0.633657,0.773614,0.000005,0.000000,0.000004,0.000004,-1.000000,0.000000,0.000000,-0.000001,3.000000,1.000000;;,
   1280;16;-0.725895,0.687805,-0.000000,0.000000,0.687805,0.725895,0.000005,0.000000,0.000004,0.000004,-1.000000,0.000000,0.000000,-0.000001,3.000000,1.000000;;,
   1440;16;-0.707107,0.707107,-0.000000,0.000000,0.707107,0.707107,0.000006,0.000000,0.000004,0.000004,-1.000000,0.000000,0.000000,-0.000001,3.000000,1.000000;;;
  }
 }

 Animation Anim-R_Nub {
  
  { R_Nub }

  AnimationKey {
   4;
   10;
   0;16;1.000000,-0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,9.714412,-0.000000,0.000002,1.000000;;,
   160;16;1.000000,-0.000000,-0.000000,0.000000,-0.000000,1.000000,-0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,9.714414,-0.000000,0.000002,1.000000;;,
   320;16;1.000000,-0.000000,-0.000000,0.000000,-0.000000,1.000000,0.000000,0.000000,-0.000000,-0.000000,1.000000,0.000000,9.714414,-0.000000,0.000002,1.000000;;,
   480;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,9.714414,-0.000000,0.000003,1.000000;;,
   640;16;1.000000,-0.000000,0.000000,0.000000,-0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,9.714414,-0.000000,0.000003,1.000000;;,
   800;16;1.000000,-0.000000,0.000000,0.000000,-0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,9.714413,0.000000,0.000003,1.000000;;,
   960;16;1.000000,-0.000000,0.000000,0.000000,-0.000000,1.000000,-0.000000,0.000000,-0.000000,-0.000000,1.000000,0.000000,9.714413,0.000000,0.000003,1.000000;;,
   1120;16;1.000000,-0.000000,-0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,9.714413,-0.000000,0.000003,1.000000;;,
   1280;16;1.000000,-0.000000,0.000000,0.000000,-0.000000,1.000000,0.000000,0.000000,-0.000000,-0.000000,1.000000,0.000000,9.714414,-0.000000,0.000002,1.000000;;,
   1440;16;1.000000,-0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,-0.000000,1.000000,0.000000,9.714414,0.000000,0.000002,1.000000;;;
  }
 }
}

AnimationSet RaiseLeft {
 

 Animation Anim-Body {
  
  { Body }

  AnimationKey {
   4;
   10;
   0;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,3.000000,0.000000,0.000000,1.000000;;,
   160;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,3.000000,0.000000,0.000000,1.000000;;,
   320;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,3.000000,0.000000,0.000000,1.000000;;,
   480;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,3.000000,0.000000,0.000000,1.000000;;,
   640;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,3.000000,0.000000,0.000000,1.000000;;,
   800;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,3.000000,0.000000,0.000000,1.000000;;,
   960;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,3.000000,0.000000,0.000000,1.000000;;,
   1120;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,3.000000,0.000000,0.000000,1.000000;;,
   1280;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,3.000000,0.000000,0.000000,1.000000;;,
   1440;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,3.000000,0.000000,0.000000,1.000000;;;
  }
 }

 Animation Anim-Spine {
  
  { Spine }

  AnimationKey {
   4;
   10;
   0;16;0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,-1.000000,0.000000,-1.000000,0.000000,0.000000,0.000000,0.000000,10.000000,-0.000000,1.000000;;,
   160;16;0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,-1.000000,0.000000,-1.000000,0.000000,0.000000,0.000000,0.000000,10.000000,-0.000000,1.000000;;,
   320;16;0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,-1.000000,0.000000,-1.000000,0.000000,0.000000,0.000000,0.000000,10.000000,-0.000000,1.000000;;,
   480;16;0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,-1.000000,0.000000,-1.000000,0.000000,0.000000,0.000000,0.000000,10.000000,-0.000000,1.000000;;,
   640;16;0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,-1.000000,0.000000,-1.000000,0.000000,0.000000,0.000000,0.000000,10.000000,-0.000000,1.000000;;,
   800;16;0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,-1.000000,0.000000,-1.000000,0.000000,0.000000,0.000000,0.000000,10.000000,-0.000000,1.000000;;,
   960;16;0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,-1.000000,0.000000,-1.000000,0.000000,0.000000,0.000000,0.000000,10.000000,-0.000000,1.000000;;,
   1120;16;0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,-1.000000,0.000000,-1.000000,0.000000,0.000000,0.000000,0.000000,10.000000,-0.000000,1.000000;;,
   1280;16;0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,-1.000000,0.000000,-1.000000,0.000000,0.000000,0.000000,0.000000,10.000000,-0.000000,1.000000;;,
   1440;16;0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,-1.000000,0.000000,-1.000000,0.000000,0.000000,0.000000,0.000000,10.000000,-0.000000,1.000000;;;
  }
 }

 Animation Anim-Spine_Nub {
  
  { Spine_Nub }

  AnimationKey {
   4;
   10;
   0;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,8.358768,0.000000,0.000000,1.000000;;,
   160;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,8.358768,0.000000,0.000000,1.000000;;,
   320;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,8.358768,0.000000,0.000000,1.000000;;,
   480;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,8.358768,0.000000,0.000000,1.000000;;,
   640;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,8.358768,0.000000,0.000000,1.000000;;,
   800;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,8.358768,0.000000,0.000000,1.000000;;,
   960;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,8.358768,0.000000,0.000000,1.000000;;,
   1120;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,8.358768,0.000000,0.000000,1.000000;;,
   1280;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,8.358768,0.000000,0.000000,1.000000;;,
   1440;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,8.358768,0.000000,0.000000,1.000000;;;
  }
 }

 Animation Anim-L_Leg {
  
  { L_Leg }

  AnimationKey {
   4;
   10;
   0;16;-0.999744,0.022619,0.000000,0.000000,0.022619,0.999744,0.000005,0.000000,0.000000,0.000005,-1.000000,0.000000,0.000000,0.000000,-3.000000,1.000000;;,
   160;16;-0.996618,0.082170,0.000000,0.000000,0.082170,0.996618,0.000005,0.000000,0.000000,0.000005,-1.000000,0.000000,0.000000,0.000000,-3.000000,1.000000;;,
   320;16;-0.985559,0.169334,0.000000,0.000000,0.169334,0.985559,0.000005,0.000000,0.000001,0.000005,-1.000000,0.000000,0.000000,0.000000,-3.000000,1.000000;;,
   480;16;-0.961913,0.273356,0.000000,0.000000,0.273356,0.961913,0.000005,0.000000,0.000001,0.000005,-1.000000,0.000000,0.000000,0.000000,-3.000000,1.000000;;,
   640;16;-0.923755,0.382983,0.000000,0.000000,0.382983,0.923755,0.000005,0.000000,0.000002,0.000005,-1.000000,0.000000,0.000000,0.000000,-3.000000,1.000000;;,
   800;16;-0.873151,0.487449,0.000000,0.000000,0.487449,0.873151,0.000005,0.000000,0.000003,0.000005,-1.000000,0.000000,0.000000,0.000000,-3.000000,1.000000;;,
   960;16;-0.816258,0.577687,0.000000,0.000000,0.577687,0.816258,0.000005,0.000000,0.000003,0.000004,-1.000000,0.000000,0.000000,0.000000,-3.000000,1.000000;;,
   1120;16;-0.762399,0.647107,0.000000,0.000000,0.647107,0.762399,0.000005,0.000000,0.000003,0.000004,-1.000000,0.000000,0.000000,0.000000,-3.000000,1.000000;;,
   1280;16;-0.722472,0.691400,0.000000,0.000000,0.691400,0.722472,0.000005,0.000000,0.000004,0.000004,-1.000000,0.000000,0.000000,0.000000,-3.000000,1.000000;;,
   1440;16;-0.707107,0.707107,0.000000,0.000000,0.707107,0.707107,0.000005,0.000000,0.000004,0.000004,-1.000000,0.000000,0.000000,0.000000,-3.000000,1.000000;;;
  }
 }

 Animation Anim-L_Nub {
  
  { L_Nub }

  AnimationKey {
   4;
   10;
   0;16;1.000000,0.000000,-0.000000,0.000000,-0.000000,1.000000,-0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,9.714414,-0.000000,0.000002,1.000000;;,
   160;16;1.000000,-0.000000,-0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,-0.000000,-0.000000,1.000000,0.000000,9.714413,-0.000000,0.000002,1.000000;;,
   320;16;1.000000,0.000000,-0.000000,0.000000,-0.000000,1.000000,-0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,9.714413,0.000000,0.000002,1.000000;;,
   480;16;1.000000,0.000000,0.000000,0.000000,-0.000000,1.000000,0.000000,0.000000,-0.000000,-0.000000,1.000000,0.000000,9.714413,0.000000,0.000002,1.000000;;,
   640;16;1.000000,0.000000,0.000000,0.000000,-0.000000,1.000000,0.000000,0.000000,0.000000,-0.000000,1.000000,0.000000,9.714414,0.000000,0.000002,1.000000;;,
   800;16;1.000000,-0.000000,-0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,-0.000000,1.000000,0.000000,9.714414,-0.000000,0.000002,1.000000;;,
   960;16;1.000000,-0.000000,0.000000,0.000000,-0.000000,1.000000,0.000000,0.000000,-0.000000,-0.000000,1.000000,0.000000,9.714414,0.000000,0.000002,1.000000;;,
   1120;16;1.000000,-0.000000,-0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,-0.000000,-0.000000,1.000000,0.000000,9.714415,-0.000000,0.000002,1.000000;;,
   1280;16;1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,9.714413,0.000000,0.000002,1.000000;;,
   1440;16;1.000000,0.000000,-0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,9.714414,0.000000,0.000002,1.000000;;;
  }
 }

 Animation Anim-R_Leg {
  
  { R_Leg }

  AnimationKey {
   4;
   10;
   0;16;-1.000000,0.000916,-0.000000,0.000000,0.000916,1.000000,0.000005,0.000000,0.000000,0.000005,-1.000000,0.000000,0.000000,-0.000001,3.000000,1.000000;;,
   160;16;-1.000000,0.000916,-0.000000,0.000000,0.000916,1.000000,0.000005,0.000000,0.000000,0.000005,-1.000000,0.000000,0.000000,-0.000001,3.000000,1.000000;;,
   320;16;-1.000000,0.000916,-0.000000,0.000000,0.000916,1.000000,0.000005,0.000000,0.000000,0.000005,-1.000000,0.000000,0.000000,-0.000001,3.000000,1.000000;;,
   480;16;-1.000000,0.000916,-0.000000,0.000000,0.000916,1.000000,0.000005,0.000000,0.000000,0.000005,-1.000000,0.000000,0.000000,-0.000001,3.000000,1.000000;;,
   640;16;-1.000000,0.000916,-0.000000,0.000000,0.000916,1.000000,0.000005,0.000000,0.000000,0.000005,-1.000000,0.000000,0.000000,-0.000001,3.000000,1.000000;;,
   800;16;-1.000000,0.000916,-0.000000,0.000000,0.000916,1.000000,0.000005,0.000000,0.000000,0.000005,-1.000000,0.000000,0.000000,-0.000001,3.000000,1.000000;;,
   960;16;-1.000000,0.000916,-0.000000,0.000000,0.000916,1.000000,0.000005,0.000000,0.000000,0.000005,-1.000000,0.000000,0.000000,-0.000001,3.000000,1.000000;;,
   1120;16;-1.000000,0.000916,-0.000000,0.000000,0.000916,1.000000,0.000005,0.000000,0.000000,0.000005,-1.000000,0.000000,0.000000,-0.000001,3.000000,1.000000;;,
   1280;16;-1.000000,0.000916,-0.000000,0.000000,0.000916,1.000000,0.000005,0.000000,0.000000,0.000005,-1.000000,0.000000,0.000000,-0.000001,3.000000,1.000000;;,
   1440;16;-1.000000,0.000916,-0.000000,0.000000,0.000916,1.000000,0.000005,0.000000,0.000000,0.000005,-1.000000,0.000000,0.000000,-0.000001,3.000000,1.000000;;;
  }
 }

 Animation Anim-R_Nub {
  
  { R_Nub }

  AnimationKey {
   4;
   10;
   0;16;1.000000,-0.000000,-0.000000,0.000000,-0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,9.714413,-0.000000,0.000002,1.000000;;,
   160;16;1.000000,-0.000000,-0.000000,0.000000,-0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,9.714413,-0.000000,0.000002,1.000000;;,
   320;16;1.000000,-0.000000,-0.000000,0.000000,-0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,9.714413,-0.000000,0.000002,1.000000;;,
   480;16;1.000000,-0.000000,-0.000000,0.000000,-0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,9.714413,-0.000000,0.000002,1.000000;;,
   640;16;1.000000,-0.000000,-0.000000,0.000000,-0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,9.714413,-0.000000,0.000002,1.000000;;,
   800;16;1.000000,-0.000000,-0.000000,0.000000,-0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,9.714413,-0.000000,0.000002,1.000000;;,
   960;16;1.000000,-0.000000,-0.000000,0.000000,-0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,9.714413,-0.000000,0.000002,1.000000;;,
   1120;16;1.000000,-0.000000,-0.000000,0.000000,-0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,9.714413,-0.000000,0.000002,1.000000;;,
   1280;16;1.000000,-0.000000,-0.000000,0.000000,-0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,9.714413,-0.000000,0.000002,1.000000;;,
   1440;16;1.000000,-0.000000,-0.000000,0.000000,-0.000000,1.000000,-0.000000,0.000000,-0.000000,0.000000,1.000000,0.000000,9.714413,-0.000000,0.000002,1.000000;;;
  }
 }
}

我们需要创建三个类Keyframe,AnimationClip,SkinningData存储这些数据,具体解释可参见11.2 XNA中的骨骼动画,因为这些数据在XNA程序和内容管道中都要用到,所以把这三个类放置在一个新的类库项目中,《Beginning XNA》和官网例子区别之一:《Beginning XNA》中这个类库名为AnimatedModelContentWin,官网中名为SkinnedModelWindows

关键帧Keyframe类

关键帧是构成一个动画的最小单位,其中最关键的数据就是一个矩阵,X文件中AnimationKey中的一组16个数据即对于一个关键帧变换矩阵。因此建立以下Keyframe.cs类:

namespace SkinnedModel
{
    /// 
    /// 关键帧,表示一个bone在一个时刻的位置。
    /// 
    public class Keyframe
    {
        /// 
        /// 创建一个Keyframe对象。
        /// 
        public Keyframe(int bone, TimeSpan time, Matrix transform)
        {
            Bone = bone;
            Time = time;
            Transform = transform;
        }


        /// 
        /// 用于XNB deserializer的私用构造函数
        /// 
        private Keyframe()
        {
        }


        /// 
        /// 获取此关键帧的bone索引
        /// 
        [ContentSerializer]
        public int Bone { get; private set; }


        /// 
        /// 获取此关键帧离开所属动画片段开始时刻的时间
        /// 
        [ContentSerializer]
        public TimeSpan Time { get; private set; }


        /// 
        /// 获取此关键帧的bone变换矩阵
        /// 
        [ContentSerializer]
        public Matrix Transform { get; private set; }
    }
}

区别之二:《Beginning XNA》中关键帧按时间排序的代码就在这个类中实现,官网中在内容管道的模型处理器中实现。

动画片段AnimationClip类

而一组关键帧就构成了一个动画片段,对应X文件中AnimationSet大括号中的所有内容,因此建立以下AnimationClip.cs类:

namespace SkinnedModel
{
    /// 
    /// 一个动画片段(AnimationClip)对应Microsoft.Xna.Framework.Content.Pipeline.Graphics.AnimationContent类型。
    /// 它保存了一个动画的所有关键帧。
    /// 
    public class AnimationClip
    {
        /// 
        /// 创建一个AnimationClip对象
        /// 
        public AnimationClip(TimeSpan duration, List keyframes)
        {
            Duration = duration;
            Keyframes = keyframes;
        }


        /// 
        /// 用于XNB deserializer的私有构造函数
        /// 
        private AnimationClip()
        {
        }


        /// 
        /// 获取动画的时间长度
        /// 
        [ContentSerializer]
        public TimeSpan Duration { get; private set; }


        /// 
        /// 获取包含所有关键帧的集合,关键帧需要根据时间排序
        /// 
        [ContentSerializer]
        public List Keyframes { get; private set; }
    }
}

区别之三:《Beginning XNA》中这个类叫做AnimationData,并多了个表示此动画片段名称的Name属性,Keyframes使用的是数组形式。官网中在这个名称属性在SkinningData类中,放在dictionary的key中,Keyframes使用的是泛型集合

动画数据SkinningData类

最后是保存绘制一个蒙皮动画对象所需的所有数据的SkinningData.cs类,我们自定义处理器的最终目的就是得到这个类,并将它存储在模型的Tag属性中,它对应X文件中所有AnimationSet之下的内容(当然还需经过一些数据的组织)。代码如下:

namespace SkinnedModel
{
    /// 
    /// 保存绘制一个蒙皮动画对象所需的所有数据,它存储在Model的Tag属性中。
    /// 
    public class SkinningData
    {
        /// 
        /// 创建一个SkinningData对象
        /// 
        public SkinningData(Dictionary animationClips,List bindPose, List inverseBindPose,List skeletonHierarchy)
        {
            AnimationClips = animationClips;
            BindPose = bindPose;
            InverseBindPose = inverseBindPose;
            SkeletonHierarchy = skeletonHierarchy;
        }

        /// 
        /// 用于XNB deserializer的私有构造函数
        /// 
        private SkinningData()
        {
        }

        /// 
        /// 获取动画片段的集合。这些动画片段存储在一个dictionary中,动画片段的名称,如"Walk", "Run","JumpReallyHigh"等作为dictionary的键。
        /// 
        [ContentSerializer]
        public Dictionary AnimationClips { get; private set; }

        /// 
        /// 骨骼中每个bone的Bindpose矩阵,与父bone相联系
        /// 
        [ContentSerializer]
        public List BindPose { get; private set; }

        /// 
        /// 骨骼中每个bone的顶点至bone空间的转换矩阵
        /// 
        [ContentSerializer]
        public List InverseBindPose { get; private set; }

        /// 
        /// 对骨骼中的每个bone的父bone索引
        /// 
        [ContentSerializer]
        public List SkeletonHierarchy { get; private set; }
    }
}

区别之四:《Beginning XNA》中这个类叫做AnimatedModelData,动画片段的集合使用了数组,父bone索引的名称为bonesParent。官网中在动画片段的集合作为一个dictionary的vlaue,它的key为对应动画片段的名称,父bone索引的名称为SkeletonHierarchy

在SkinnedModelWindows项目中还包含一个AnimationPlayer.cs类,但是这个类与本教程无关,将在下一个教程中讨论。

准备好数据类后,下面就可以讨论内容管道处理器了。

SkinnedModelPipeline处理器

《Beginning XNA》和XNA官网的例子本质上是相同的,只不过官网的例子多做了几个检查工作,更加严密点而已,所以以下代码以官网的例子为基础,但你也可以参见11.3 Animated Model Processor获取详尽的解释。以下是代码:

/// 
/// 重写Process方法,此方法将内容管道的NodeContent树中间格式转换为包含动画数据的Content对象
/// 
public override ModelContent Process(NodeContent input,ContentProcessorContext context)
{
    // 确认这个mesh包含我们知道如何处理的数据
    ValidateMesh(input, context, null);

    // 获取骨骼的根bone
    BoneContent skeleton = MeshHelper.FindSkeleton(input);

    if (skeleton == null)
        throw new InvalidContentException("未找到输入的skeleton。");

    // We don't want to have to worry about different parts of the model being
    // in different local coordinate systems, so let's just bake everything.
    // 原文使用了Bake一词,译为烘焙,为3D程序中的一个专有名词,这里的操作就是计算每个bone的绝对变换
    FlattenTransforms(input, skeleton);

    // 将skeleton的层次结构转换为一个BoneContent集合
    IList bones = MeshHelper.FlattenSkeleton(skeleton);

    if (bones.Count > MaxBones)
    {
        throw new InvalidContentException(string.Format("骨骼包含{0}个bone,但最大只支持{1}个。",bones.Count, MaxBones));
    }

    List bindPose = new List();
    List inverseBindPose = new List();
    List skeletonHierarchy = new List();
	
    // 读取bind pose和骨骼层次数据
    foreach (BoneContent bone in bones)
    {
        bindPose.Add(bone.Transform);
        inverseBindPose.Add(Matrix.Invert(bone.AbsoluteTransform));
        skeletonHierarchy.Add(bones.IndexOf(bone.Parent as BoneContent));
    }

    // 将动画数据转换为可实时运行的AnimationClip格式
    Dictionary animationClips;
    animationClips = ProcessAnimations(skeleton.Animations, bones);

    // 调用基类的Process方法转换模型数据
    ModelContent model = base.Process(input, context);

    // 将自定义动画数据存储在模型的Tag属性中
    model.Tag = new SkinningData(animationClips, bindPose,inverseBindPose, skeletonHierarchy);

    return model;
}

在重写的Process方法一开始调用ValidateMesh方法检查模型是否符合需要,在ValidateMesh方法中还调用了MeshHasSkinning方法,这两个方法主要检查模型制作时是否有几何体链接到骨骼的情况,如果有就会被删除,在X文件的导出系列5蒙皮动画模型中怪物的眼球就属于这种情况,被这个处理器处理后就不会被显示。第二是模型必须包含蒙皮数据。

/// 
/// 确认这个mesh包含我们知道如何处理的数据
/// 
static void ValidateMesh(NodeContent node, ContentProcessorContext context, string parentBoneName)
{
    MeshContent mesh = node as MeshContent;

    if (mesh != null)
    {
        // 确认mesh.
        if (parentBoneName != null)
        {
            context.Logger.LogWarning(null, null, "Mesh {0}是bone {1}的一个子节点,而SkinnedModelProcessor " +
                "无法正确处理作为bone子节点的mesh。", mesh.Name, parentBoneName);
        }

        if (!MeshHasSkinning(mesh))
        {
            context.Logger.LogWarning(null, null,"Mesh {0} 不包含skinning信息,所以会被删除",mesh.Name);
            mesh.Parent.Children.Remove(mesh);
            return;
        }
    }
    else if (node is BoneContent)
    {
        // 如果此节点是一个BoneContent,则我们需要记下它的名称用于进一步检查。
        parentBoneName = node.Name;
    }

    // 递归检测子节点 (需要遍历子节点集合的副本,因为有可能在ValidateMesh过程中会删除某些子节点)
    foreach (NodeContent child in new List(node.Children))
        ValidateMesh(child, context, parentBoneName);
}


/// 
/// 检测mesh是否包含skininng信息
/// 
static bool MeshHasSkinning(MeshContent mesh)
{
    foreach (GeometryContent geometry in mesh.Geometry)
    {
        if (!geometry.Vertices.Channels.Contains(VertexChannelNames.Weights()))
            return false;
    }

    return true;
}

然后使用内容管道自带的MeshHelper.FindSkeleton方法获取根bone,它是一个BoneContent对象。

接着调用FlattenTransforms方法重新计算模型每个bone的绝对变换,在FlattenTransforms方法中调用了XNA自带的MeshHelper.TransformScene 方法,FlattenTransforms方法代码如下:

/// 
/// 烘焙模型几何体的变换矩阵,这样所有对象都在同一个坐标系中。
/// 
static void FlattenTransforms(NodeContent node, BoneContent skeleton)
{
    foreach (NodeContent child in node.Children)
    {
        // 不要处理根节点,因为它是特殊的
        if (child == skeleton)
            continue;

        // 烘焙几何体的本地变换
        MeshHelper.TransformScene(child, child.Transform);

        // 烘焙完成后,我们就可以将本地坐标系设置为单位矩阵
        child.Transform = Matrix.Identity;

        // 递归处理子节点
        FlattenTransforms(child, skeleton);
    }
}

区别之五:《Beginning XNA》中并不包含ValidateMesh和FlattenTransforms方法。

然后调用XNA自带的MeshHelper.FlattenSkeleton方法将Skeleton中层次结构的BoneContent转换为一个BoneContent集合。

还记得我们最终要获取的SkinningData有4个属性吗?前三个属性很容易获取,由以下代码实现:

List bindPose = new List();
List inverseBindPose = new List();
List skeletonHierarchy = new List();

// 读取bind pose和骨骼层次数据
foreach (BoneContent bone in bones)
{
    bindPose.Add(bone.Transform);
    inverseBindPose.Add(Matrix.Invert(bone.AbsoluteTransform));
skeletonHierarchy.Add(bones.IndexOf(bone.Parent as BoneContent));
}

难的数据是第4个,调用了ProcessAnimations处理所有动画,而ProcessAnimations又调用了ProcessAnimation处理每一个动画,代码如下:

/// 
/// 将内容管道的AnimationContentDictionary对象转换为可实时运行的AnimationClip格式
/// 
static Dictionary ProcessAnimations(AnimationContentDictionary animations, IList bones)
{
    // 创建一个dictionary将bone名称对应bone索引
    Dictionary boneMap = new Dictionary();

    // 填充这个dictionary
    for (int i = 0; i < bones.Count; i++)
    {
        string boneName = bones[i].Name;

        if (!string.IsNullOrEmpty(boneName))
            boneMap.Add(boneName, i);
    }

    // 依次转换每个动画片段
    Dictionary animationClips;
    animationClips = new Dictionary();

    foreach (KeyValuePair animation in animations)
    {
        AnimationClip processed = ProcessAnimation(animation.Value, boneMap);
        
        animationClips.Add(animation.Key, processed);
    }

    if (animationClips.Count == 0)
    {
        throw new InvalidContentException("输入文件不包含任何动画。");
    }

    return animationClips;
}


/// 
/// 将内容管道的AnimationContentDictionary对象转换为可实时运行的AnimationClip格式
/// 
static AnimationClip ProcessAnimation(AnimationContent animation,Dictionary boneMap)
{
    List keyframes = new List();

    // 遍历每个动画通道
    foreach (KeyValuePair channel in
        animation.Channels)
    {
        // 获取当前通道控制的bone索引
        int boneIndex;

        if (!boneMap.TryGetValue(channel.Key, out boneIndex))
        {
            throw new InvalidContentException(string.Format(
                "未在bone'{0}'中找到动画数据, " +
                "它不是骨骼的一部分。", channel.Key));
        }

        // 转换关键帧数据
        foreach (AnimationKeyframe keyframe in channel.Value)
        {
            keyframes.Add(new Keyframe(boneIndex, keyframe.Time,keyframe.Transform));
        }
    }

    // 根据时间对关键帧排序
    keyframes.Sort(CompareKeyframeTimes);

    if (keyframes.Count == 0)
        throw new InvalidContentException("动画不包含关键帧");

    if (animation.Duration <= TimeSpan.Zero)
        throw new InvalidContentException("动画持续时间为0");

    return new AnimationClip(animation.Duration, keyframes);
}


/// 
/// 将关键帧根据时间升序排序
/// 
static int CompareKeyframeTimes(Keyframe a, Keyframe b)
{
    return a.Time.CompareTo(b.Time);
}

要理解上述过程,你一定要对动画数据的组织结构有所了解,强烈建议设置中断看看动画数据到底在哪里,它们是如何组织的。

中断截图

由上述截图可以看出,动画数据位于skeleton下的Animations(http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.content.pipeline.graphics.nodecontent.animations(v=XNAGameStudio.31).aspx)中,Animations的类型是AnimationContentDictionary(http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.content.pipeline.graphics.animationcontentdictionary.aspx),而AnimationContentDictionary的Key为动画片段名称,在本例中有两个动画片段:RaiseLeft和RaiseRight,AnimationContentDictionary的Value即动画片段,对应内容管道中的AnimationContent(http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.content.pipeline.graphics.animationcontent(v=XNAGameStudio.31).aspx)类。

在AnimationContent类之下有一个Channels属性,对应AnimationChannelDictionary(http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.content.pipeline.graphics.animationchanneldictionary(v=XNAGameStudio.31).aspx)类,AnimationChannelDictionary类的Key对应bone,本例中为6个:Spine,Spine_Nub,L_Leg,L_Nub,R_Leg,R_Nub,AnimationChannelDictionary类的Value是一个AnimationChannel(http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.content.pipeline.graphics.animationchannel(v=XNAGameStudio.31).aspx)对象,此对象包含了类型为AnimationKeyframe(http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.content.pipeline.graphics.animationkeyframe(v=XNAGameStudio.31).aspx)的关键帧集合。

在AnimationContent类之下还有一个Duration(http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.content.pipeline.graphics.animationcontent.duration(v=XNAGameStudio.31).aspx)的属性表示动画片段的时间长度。

只有理解了以上知识,你才会真正了解提取动画进行的操作。

提取完动画后,使用以下代码将处理好的SkinningData数据存储在模型的Tag属性中:

// 将自定义动画数据存储在模型的Tag属性中
model.Tag = new SkinningData(animationClips, bindPose, inverseBindPose, skeletonHierarchy);

区别之六:《Beginning XNA》中还要编写Content Type Writers和Content Type Readers,而在XNA3.1中可以通过反射实现,无需此代码。

最后,在官网的例子中还编写代码实现了自定义Effect,这个代码在我的引擎中并不需要,因为使用自定义Effect我是在引擎中的实现的。只要不使用BasicEffect而使用自定义effect,XNA官网的很多例子都用到了类似的代码:

/// 
/// 改变材质使之可以使用蒙皮模型的effect.
/// 
protected override MaterialContent ConvertMaterial(MaterialContent material,ContentProcessorContext context)
{
    BasicMaterialContent basicMaterial = material as BasicMaterialContent;

    if (basicMaterial == null)
    {
        throw new InvalidContentException(string.Format(
            "SkinnedModelProcessor只支持BasicMaterialContent," +
            "但输入的网格使用了{0}.", material.GetType()));
    }

    EffectMaterialContent effectMaterial = new EffectMaterialContent();

    // 存储effect的引用
    string effectPath = Path.GetFullPath("SkinnedModel.fx");

    effectMaterial.Effect = new ExternalReference(effectPath);

    // 将纹理设置从输入的BasicMaterialContent对象复制到新材质中
    if (basicMaterial.Texture != null)
        effectMaterial.Textures.Add("Texture", basicMaterial.Texture);

    // 调用基类的ConvertMaterial方法
    return base.ConvertMaterial(effectMaterial, context);
}

好了,至此已经使用自定义模型处理器完成了动画数据的处理,但工作只完成了一半,下一个教程将讨论如何在XNA代码中使用这些动画数据。

文件下载(已下载 1740 次)

发布时间:2010/6/7 13:11:16  阅读次数:10582

2006 - 2024,推荐分辨率1024*768以上,推荐浏览器Chrome、Edge等现代浏览器,截止2021年12月5日的访问次数:1872万9823 站长邮箱

沪ICP备18037240号-1

沪公网安备 31011002002865号