模型中的骨骼动画

来源:互联网 发布:控制算法与单片机 编辑:程序博客网 时间:2024/04/30 03:40

现在你需要创建一个新的模型处理器(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);     }}
原创粉丝点击