11.2 XNA中的骨骼动画

XNA有个定义得很好的素材管道(Content Pipeline),它被分成了不同层次和导入器、处理器、编译器(content writers)和读取器(content readers)。因为XNA的素材管道对骨骼动画并不完全支持,你需要自己扩展素材管道添加对骨骼动画的支持。注意素材管道部分支持骨骼动画,因为它可以导入带有骨骼动画数据的X和FBX文件,但输出时并不处理所有的骨骼动画数据。图11-4显示了用于导入、处理、编译并读取模型文件的素材管道类的简化图示。

首先,根据相应的素材导入器(content importer)导入模型,每个素材导入器将模型数据转换为XNA document object model (DOM)格式。通过这种方式,模型在导入后成为相同格式并可以被相应的素材处理器(content processor)-ModelProcessor处理。模型导入器的输出是一个root NodeContent对象,这个对象描述了一个有自己坐标和子节点的图形类型。有两个类可以扩展NodeContent类:MeshContent和BoneContent。这样,从模型导入器输出的root NodeContent对象可以拥有一些子NodeContent,MeshContent和BoneContent。

图11-4

图11-4. XNA ContentPipeline-用来导入、处理、编译和读取模型的类

ModelProcessor将模型导入器(model importer)输出的root NodeContent 对象作为一个参数,返回一个ModelContent对象。ModelContent对象返回object returned by the ModelProcessor has the processed model data, which needs to be stored into an XNB binary file. 要将ModelContent 对象存储为XNB文件,ModelContent和在它之中的所有对象必须要有自己的ContentTypeWriter。ContentTypeWriter定义了对象的输入以何种方式写入到XNB文件中。最后,在实时方式中,ContentManager使用ContentTypeReader从XNB文件读取对象的数据并返回模型对象。

要在XNA中添加对骨骼动画的支持,你需要扩展默认的模型模型处理器(model processor),创建一个新的处理过程并存储模型的骨骼和动画。除此以外,你还要创建一些新的类存储骨骼动画数据和一些ContentTypeWriter和ContentTypeReader类写入和读取这些数据。

图11-5显示了扩展素材管道所需创建的类,添加了对骨骼动画模型的支持。这些类用红色标注(译者注:图中的灰色)。

图11-5

图11-5.素材管道的扩展,支持骨骼动画模型。

因为存储骨骼动画数据的类将由程序实时调用,所以你将在一个独立的库中创建这些类。要存储这些类,要创建一个新的叫做AnimationModelContentWin的Windows Game Library。在Windows 平台上model processor将使用这些类存储骨骼动画数据。如果你的游戏是运行在Windows 平台上的,那么这个库还会实时载入骨骼动画数据。

如果是在Xbox 360平台,你还要多创建一个项目:叫做AnimationModelContentXbox的Xbox 360 Game Library。这个库包含与AnimationModelContentWin相同的文件,但Xbox 360程序通过这个库实时载入数据。即使你只运行在Xbox 360平台,你仍需要AnimationModelContentWin,因为原始模型文件是在Windows平台导入并处理的,需要一个Windows库存储这些模型数据。

你将创建不同的类存储骨骼动画数据:Keyframe,AnimationData和AnimatedModelData。Keyframe类存储骨骼动画的动画帧,每个动画帧存储骨骼中bone的一个新配置。AnimationData类存储一个keyframes的数组,组成完整的动画(比如跑、跳等)。最后,AnimatedModelData存储模型骨骼(bone和hierarchy)和一个AnimatedModelData类型的数组,包含所有的模型动画。

Keyframe类

Keyframe类用来存储bone的一个动画帧。一个动画帧必须指向一个动画bone的引用,被引用的bone的新配置(位置和朝向)和新配置应用的时间。注意你使用keyframe设置初始bone配置,将当前配置变成一个新配置。你使用XNA的Matrix类一矩阵形式存储bone的配置,使用TimeSpan存储动画时间(关键帧作用的时间)。

在AnimatedModelData类中你以bone数组的形式存储模型骨骼,它是由模型骨骼不同深度的traverse构成的。这样,对bone的引用可以以一个代表AnimatedModelData类中bone数组中的一个bone的索引整数表示。Keyframe类的代码如下:

public class Keyframe : IComparable
{
    int boneIndex;
    TimeSpan time;
    Matrix transform; 
    
    // Properties... 
    
    public TimeSpan Time { get { return time; } set { time = value; } } 
    public int Bone { get { return boneIndex; } set { boneIndex = value; } } 
    public Matrix Transform { get { return transform; } set { transform = value; } } 
    
    public Keyframe(TimeSpan time, int boneIndex, Matrix transform) 
    {
        this.time = time; 
        this.boneIndex = boneIndex; 
        this.transform = transform; 
    } 
        
    public int CompareTo(object obj) 
    {
        Keyframe keyframe = obj as Keyframe; 
        if (obj == null)
            throw new ArgumentException("Object is not a Keyframe."); 
        return time.CompareTo(keyframe.Time);
    } 
}

在Keyframe中,你实现了一个Icomparable接口用来比较Keyframe对象。Keyframe对象是基于时间来属性比较的。你以后将使用这个比较根据时间帧对关键帧排序。

AnimationData类

AnimationData类用来存储一个完整的模型动画(比如跑、跳等)。你以一个关键帧数组的形式存储每个动画,除了关键帧,你还有存储其他诸如动画名称和持续时间等数据。代码如下:

public class AnimationData { string name; TimeSpan duration; Keyframe[] keyframes; 
public string Name { get { return name; } set { name = value; } } 
public TimeSpan Duration { get { return duration; } set { duration = value; } } 
public Keyframe[] Keyframes { get { return keyframes; } set { keyframes = value; } } 

public AnimationData(string name, TimeSpan duration, Keyframe[] keyframes)
{ 
    this.name = name;
    this.duration = duration;
    this.keyframes = keyframes; 
}

AnimatedModelData类

AnimatedModelData类用来存储模型骨骼和动画。你以bone数组的形式存储模型骨骼,每个bone都以矩阵表示。你通过模型骨骼不同depth traverse构建bone数组。depth traversal开始于骨骼的root bone终止于最深的bone。当找到最深的bone后,这个traversal将返回并查找另一条可能的路径,再次找到最深的bone。

例如,如图11-6中的depth traverse of the hierarchy返回包含Root Bone,Neck,Left Shoulder,Left Forearm,Left Hand,Left End Bone,Right Shoulder,Right Forearm,Right Hand和Right End Bone的数组。

图11-6

图11-6. 骨骼层次hierarchy的例子

你将骨骼bone存储在它的bind pose配置中。bind pose是指哪个bone连接在模型网格上和是否是动画的开始pose。如果模型没有动画或当动画开始时,所有的模型bone都在bind pose中找到。在AnimatedModelData类,你需要创建两个Matrix数组类型用来存储骨骼bone,一个整数数组存储骨骼bone的层次,一个AnimationData类型的数组存储模型动画。代码如下:

public class AnimatedModelData
{
    Matrix[] bonesBindPose; 
    Matrix[] bonesInverseBindPose;
    int[] bonesParent; 
    AnimationData[] animations; 
    
    // Properties ... 
    public int[] BonesParent { get { return bonesParent; } set { bonesParent = value; } }
    public Matrix[] BonesBindPose { get { return bonesBindPose; } set { bonesBindPose = value; } 
    public Matrix[] BonesInverseBindPose { get { return bonesInverseBindPose; } set { bonesInverseBindPose = value; } } 
    public AnimationData[] Animations { get { return animations; } set { animations = value; } } 
    
    public AnimatedModelData(Matrix[] bonesBindPose, Matrix[] bonesInverseBindPose, int[] bonesParent, AnimationData[] animations) 
    {
        this.bonesParent = bonesParent; 
        this.bonesBindPose = bonesBindPose;
        this.bonesInverseBindPose = bonesInverseBindPose; 
        this.animations = animations;
    } 

在AnimatedModelData类中,bonesBindPose属性存储一个包含本地配置(local configuration)(相对于它的ancestor)的数组中,bonesInverseBindPose属性存储反绝对配置(inverse absolute configuration)(与它的ancestor无关),bonesParent存储每个bone的parent的索引。最后,animations属性存储模型动画。

你使用bone的inverse absolute configuration将bone的顶点的默认坐标系(模型坐标系)转换为这个bone的坐标系,我们会在“Skeletal Animation Equations”部分详细介绍这个过程。


发布时间:2009/4/27 10:37:43  阅读次数:9706

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

沪ICP备18037240号-1

沪公网安备 31011002002865号