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

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

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号