11.3 Animated Model Processor
现在你需要创建一个新的模型处理器(model processor)扩展XNA默认的模型处理器。你将使用这个新模型处理器处理动画模型,提取骨骼和动画,并将它们存储为一个AnimatedModelData对象。
要创建一个新模型处理器你应创建一个叫做AnimatedModelProcessorWin的新的素材管道扩展库(Content Pipeline Extension Library)项目。这个库项目来自于一个新素材处理器类(content processor class),并会自动将素材管道组件(Content Pipeline assembly,Microsoft.Xna. Framework.Content. Pipeline)添加到项目中。因为你将使用AnimatedModelContentWin库(上一部分建立的)存储动画数据,所以你还需要将这个库添加到项目中。代码如下:
[ContentProcessor] public class ContentProcessor1 : ContentProcessor<TInput, TOutput> { public override TOutput Process(TInput input, ContentProcessorContext context) { // TODO throw new NotImplementedException(); }
默认素材处理器类扩展了ContentProcessor类,它作为任何素材管道处理器的基类,用来处理类型为Tinput的对象并输出为类型为TOutput的新对象。因为你对创建一个新的素材处理器不感兴趣,只想扩展某些功能,所以你应扩展一个已有的素材处理器而不是ContentProcessor类。在这里,你将扩展ModelProcessor类,它是默认的模型处理器类。你还要把你的新素材处理器类重命名为AnimatedModelProcessor。以下是代码:
[ContentProcessor] public class AnimatedModelProcessor : ModelProcessor { public static string TEXTURES_PATH = "Textures/"; public static string EFFECTS_PATH = "Effects/"; public static string EFFECT_FILENAME = "AnimatedModel.fx"; public override ModelContent Process(NodeContent input, ContentProcessorContext context) { protected override MaterialContent ConvertMaterial( MaterialContent material, ContentProcessorContext context) { ... } } }
在ModelProcessor类中有很多方法可以重写,但处理动画模型只需重写Process和ConvertMaterial方法。主要方法是Process方法,这个方法需要将一个NodeContent对象——包含网格、骨骼和动画——转换为ModelContent对象——存储XNA模型对象的数据。除了Process方法,还要调用ConvertMaterial方法处理模型材质。
重写默认的处理方法
这部分你将重写ModelProcessor类的Process方法,这个方法用以处理模型。你还将创建两个新方法提取模型骨骼和动画:ExtractSkeletonAndAnimations方法和ExtractAnimations方法,ExtractAnimations在ExtractSkeletonAndAnimations方法中调用。以下是代码:
public override ModelContent Process(NodeContent input, ContentProcessorContext context) { // Process the model with the default processor ModelContent model = base.Process(input, context); // Now extract the model skeleton and all its animations AnimatedModelData animatedModelData = ExtractSkeletonAndAnimations(input, context); // Stores the skeletal animation data in the model Dictionary<string, object> dictionary = new Dictionary<string, object>(); dictionary.Add("AnimatedModelData", animatedModelData); model.Tag = dictionary; return model; }
在Process方法开始,你调用它的基类ModelProcessor,接着,调用ExtractSkeletonAndAnimations方法处理输入的NodeContent并返回一个包含模型骨骼和动画的AnimatedModelData对象。最后,创建一个dictionary将一个字符映射到对象,将AnimatedModelData添加到这个dictionary,将这个dictionary设置在ModelContent对象的Tag属性中。XNA的Model类有一个Tag属性可以将自定义的数据添加到模型中。使用dictionary作为Tag属性,你可以将不同的自定义对象添加到Model类中,并可以实时通过使用string查询到这些对象。
注意你设置在ModelContent对象Tag属性中的数据会一起存储在二进制的XNB文件中,当使用content manager载入模型时这些数据会重新还原。
提取模型骨骼
ExtractSkeletonAndAnimations方法将一个root NodeContent对象作为输入,这个对象可能包含MeshContent和BoneContent作为它的子节点(children)。要提取模型骨骼,你首先要在root NodeContent中找到骨骼的root bone,任何执行深度搜寻depth traverse,创建bone的集合。XNA的MeshHelper类提供了一些方法帮你完成这个处理过程:
// Find the root bone node BoneContent skeleton = MeshHelper.FindSkeleton(input); // Transform the hierarchy in a list (depth traversal) IList<BoneContent> boneList = MeshHelper.FlattenSkeleton(skeleton);
你可以使用MeshHelper类的FindSkeleton方法找到骨骼的root bone。然后你需要使用深度搜索将骨骼树转换为集合。可以使用MeshHelper类的FindSkeleton方法做这件事。结果是bone的集合,每个bone是BoneContent类的一个对象。注意在集合中的bone的顺序和网格顶点的索引顺序是相同的。
对集合中的每个bone,你将它的本地配置(local configuration)、反绝对配置(inverse absolute configuration)和父索引存储在bind pose中。你可以从BoneContent对象的Transform 和AbsoluteTransform属性中读取本地配置和绝对配置,你可以使用XNA中Matrix类的Invert方法计算反绝对配置。
bonesBindPose[i] = boneList[i].Transform; bonesInverseBindPose[i] = Matrix.Invert(boneList[i].AbsoluteTransform);
以下是ExtractSkeletonAndAnimations 方法的完整代码:
private AnimatedModelData ExtractSkeletonAndAnimations(NodeContent input, ContentProcessorContext context) { // Find the root bone node BoneContent skeleton = MeshHelper.FindSkeleton(input); // Transform the hierarchy in a list (depth traversal) IList<BoneContent> boneList =MeshHelper.FlattenSkeleton(skeleton); context.Logger.LogImportantMessage("{0} bones found.", boneList.Count); // Create skeleton bind pose, inverse bind pose, and parent array Matrix[] bonesBindPose = new Matrix[boneList.Count]; Matrix[] bonesInverseBindPose = new Matrix[boneList.Count]; int[] bonesParentIndex = new int[boneList.Count]; List<string> boneNameList = new List(boneList.Count); // Extract and store the data needed from the bone list for (int i= 0; i< boneList.Count; i++) { bonesBindPose[i] = boneList[i].Transform; bonesInverseBindPose[i] = Matrix.Invert(boneList[i].AbsoluteTransform); int parentIndex =boneNameList.IndexOf(boneList[i].Parent.Name); bonesParentIndex[i] = parentIndex; boneNameList.Add(boneList[i].Name); } // Extract all animations AnimationData[] animations = ExtractAnimations( skeleton.Animations,boneNameList, context); return new AnimatedModelData(bonesBindPose, bonesInverseBindPose, bonesParentIndex, animations); }
在提取模型骨骼后,你需要调用ExtractAnimations方法提取模型动画。
提取模型动画
所有模型动画存储在一个动画dictionary中,这个dictionary映射到一个包含指向一个AnimationContent对象的动画名称、动画数据的字符串。你可以从模型骨骼的root node(类型为BoneContent)的Animations属性中获得动画dictionary。注意素材管道有自己的类存储模型动画数据:AnimationContent、AnimationChannel和AnimationKeyframe类。AnimationContent类存储以一个AnimationChannel对象的数组的形式存储一个完整的模型动画,每个AnimationChannel对象以AnimationKeyframe对象数组的形式存储单个bone的动画。当你将bone一起存储在单个数组中时,XNA的AnimationContent类也会单独存储每个bone的动画。
你可以遍历动画dictionary的AnimationContent对象提取模型动画,对每个找到的动画你需要遍历它们的bone channels(可以从Channels属性中获得),提取所有动画关键帧(可以从Keyframes属性中获得)。下面是ExtractAnimations方法的代码:
private AnimationData[] ExtractAnimations( AnimationContentDictionary animationDictionary, List<string> boneNameList, ContentProcessorContext context) { context.Logger.LogImportantMessage("{0} animations found.",animationDictionary.Count); AnimationData[] animations = new AnimationData[animationDictionary.Count]; int count = 0; foreach (AnimationContent animationContent in animationDictionary.Values) { // Store all keyframes of the animation List<Keyframe> keyframes = new List<Keyframe>(); // Go through all animation channels // Each bone has its own channel foreach (string animationKey in animationContent.Channels.Keys) { AnimationChannel animationChannel =animationContent.Channels[animationKey]; int boneIndex = boneNameList.IndexOf(animationKey); foreach (AnimationKeyframe keyframe in animationChannel) keyframes.Add(new Keyframe( keyframe.Time, boneIndex, keyframe.Transform)); } // Sort all animation frames by time keyframes.Sort(); animations[count++] = new AnimationData(animationContent.Name, animationContent.Duration, keyframes.ToArray()); } return animations; }
当存储了动画的所有关键帧后,你需要将它们排序。因为关键帧是存储在一个List中的,所以你可以使用Sort方法。别忘了你前面在Keyframe类中实现了Icomparable接口,可以使用它们的time属性对关键帧进行排序。
现在你提取了模型骨骼和动画并将它们存储在一个友好的格式中,下面准备写入XNB文件。注意因为List泛型类和Icomparable接口是由.NET Framework而不是XNA提供的,所以你可以在C# 帮助文件中它们的相关信息。
读取和写入自定义数据
你创建的用来存储模型骨骼动画数据的AnimatedModelProcessor使用自定义的对象(AnimatedModelData,AnimationData和Keyframe类)。素材管道需要从二进制文件中读取和写入这些对象,但素材管道不知道如何读取和写入这些自定义对象。
要定义如何读取骨骼动画数据和写入至二进制文件中,你需要为每个用来存储骨骼动画数据的类创建一个content type reader和一个content type writer。这里,你需要为AnimatedModelData,AnimationData和Keyframe类创建一个content type reader和一个content type writer,你可以通过扩展XNA的ContentTypeReader和ContentTypeWriter类创建content type reader和content type writer。
Content Type Writer
要创建content type writer你需要在AnimatedModelProcessorWin项目中添加一个名为AnimatedModelDataWriter的新Content Type Writer。content type writer类只需添加到model processor项目中。你要在content type writer中添加三个新类:KeyframeWriter,AnimationDataWriter和AnimatedModelDataWriter类,这三个类用来为Keyframe,AnimationData和AnimatedModelData类写入数据。每个类都需要扩展ContentTypeWriter类并重写Write方法。
ContentTypeWriter类的Write方法接受两个参数。第一个是ContentWriter,用来将对象的数据写入二进制文件,第二个参数是要写入的对象。在Write方法内,你应使用ContentWriter对象写入所有类中的属性。注意对象的写入顺序是很重要的,它们必须和读取的顺序相同。以下是KeyframeWriter,AnimationDataWriter和AnimatedModelDataWriter类的代码:
[ContentTypeWriter] public class KeyframeWriter : ContentTypeWriter<Keyframe> { protected override void Write(ContentWriter output, Keyframe value) { output.WriteObject(value.Time); output.Write(value.Bone); output.Write(value.Transform); } public override string GetRuntimeReader(TargetPlatform targetPlatform) { return typeof(KeyframeReader).AssemblyQualifiedName; } } [ContentTypeWriter] public class AnimationDataWriter : ContentTypeWriter<AnimationData> { protected override void Write(ContentWriter output, AnimationData value) { output.Write(value.Name); output.WriteObject(value.Duration); output.WriteObject(value.Keyframes); public override string GetRuntimeReader(TargetPlatform targetPlatform) { return typeof(AnimationDataReader).AssemblyQualifiedName; } } } [ContentTypeWriter] public class AnimatedModelDataWriter : ContentTypeWriter<AnimatedModelData> { protected override void Write(ContentWriter output, AnimatedModelData value) { output.WriteObject(value.BonesBindPose); output.WriteObject(value.BonesInverseBindPose); output.WriteObject(value.BonesParent); output.WriteObject(value.Animations); } public override string GetRuntimeReader(TargetPlatform targetPlatform) { return typeof(AnimatedModelDataReader).AssemblyQualifiedName; } }
ContentType Reader
要创建content type reader你需要在AnimatedModelProcessorWin项目中添加一个名为AnimatedModelDataReader的新Content Type Reader。与content type writer类不同,游戏程序需要只需content type reader实时加载动画数据。 你需要创建三个新的类-KeyframeReader,AnimationDataReader和AnimatedModelDataReader-它们用来读取Keyframe,AnimationData和AnimatedModelData类的数据。每个类都需要扩展ContentTypeReader 类并重写Read方法。
ContentTypeReader类的Read方法接受两个参数。第一个是ContentReader,用来从二进制文件读取对象数据,第二个参数是指向对象示例的引用。因为你还没创建对象,所以第二个参数总是null。再次注意读取对象的顺序应与写入的顺序相同。下面是KeyframeReader,AnimationDataReader和AnimatedModelDataReader类的代码:
public class KeyframeReader : ContentTypeReader<Keyframe> { protected override Keyframe Read(ContentReader input, Keyframe existingInstance) { TimeSpan time = input.ReadObject<TimeSpan>(); int boneIndex = input.ReadInt32(); Matrix transform = input.ReadMatrix(); return new Keyframe(time, boneIndex, transform); } } public class AnimationDataReader : ContentTypeReader<AnimationData> { protected override AnimationData Read(ContentReader input, AnimationData existingInstance) { string name = input.ReadString(); TimeSpan duration = input.ReadObject<TimeSpan>(); Keyframe[] keyframes = input.ReadObject<Keyframe[]>(); return new AnimationData(name, duration, keyframes); } } public class AnimatedModelDataReader :ContentTypeReader<AnimatedModelData> { protected override AnimatedModelData Read(ContentReader input, AnimatedModelData existingInstance) { Matrix[] bonesBindPose = input.ReadObject<Matrix[]>(); Matrix[] bonesInverseBindPose = input.ReadObject<Matrix[]>(); int[] bonesParent = input.ReadObject<int[]>(); AnimationData[] animations =input.ReadObject<AnimationData[]>(); return new AnimatedModelData(bonesBindPose, bonesInverseBindPose, bonesParent, animations); } }
发布时间:2009/4/28 下午3:01:43 阅读次数:6947