Importing Animated Models
在正式使用skinned model shader之前,需要先导入一个具体模型的动画数据。这是一个说起来容易做起来很难的任务,而且需要额外的plumbing(管道工程,对应于第15章导入模型时所讲的content pipeline)。值得庆幸的是,Open Asset Import Library(见第15章“Models”)支持动画模型,并且可以用于从多种不同格式的文件中获取动画数据。但是,要把该数据转换成渲染引擎能直接使用的格式并使引擎代码独立于Open Asset Import Library,需要做更多的工作(主要是因为在编译期执行数据转换操作比在应用程序执行时更好)。实现动画系统要创建的第一个类是SceneNode类(见列表20.2)。
列表20.2 Declaration of the SceneNode Class
#pragma once
#include "Common.h"
namespace Library
{
class SceneNode : public RTTI
{
RTTI_DECLARATIONS(SceneNode, RTTI)
public:
const std::string& Name() const;
SceneNode* Parent();
std::vector<SceneNode*>& Children();
const XMFLOAT4X4& Transform() const;
XMMATRIX TransformMatrix() const;
void SetParent(SceneNode* parent);
void SetTransform(XMFLOAT4X4& transform);
void SetTransform(CXMMATRIX transform);
SceneNode(const std::string& name);
SceneNode(const std::string& name, const XMFLOAT4X4& transform);
protected:
std::string mName;
SceneNode* mParent;
std::vector<SceneNode*> mChildren;
XMFLOAT4X4 mTransform;
private:
SceneNode();
SceneNode(const SceneNode& rhs);
SceneNode& operator=(const SceneNode& rhs);
};
}
SceneNode类主要建立了用于skeletal animation的hierarchical transformation structure(分层变换体系结构)。每一个SceneNode对象都包含一个名称,变换矩阵,父结点,一组子结点。如果父结点为NULL,表示该SceneNode对象为层次结构中的根结点。
接下来创建一个由SceneNode类派生的Bone类,该类的声明代码如列表20.3所示。
列表20.3 Declaration of the Bone Class
class Bone : public SceneNode
{
RTTI_DECLARATIONS(Bone, SceneNode)
public:
UINT Index() const;
void SetIndex(UINT index);
const XMFLOAT4X4& OffsetTransform() const;
XMMATRIX OffsetTransformMatrix() const;
Bone(const std::string& name, UINT index, const XMFLOAT4X4& offsetTransform);
private:
Bone();
Bone(const Bone& rhs);
Bone& operator=(const Bone& rhs);
UINT mIndex; // Index into the model's bone container
XMFLOAT4X4 mOffsetTransform; // Transforms from mesh space to bone space
};
一个Bone类对象表示模型骨骼中的一个特定的组成部分。由于Bone类继承自SceneNode类,一个Bone对象也是一个SceneNode对象,因此具有与SceneNode对象同样的分层体系结构。每一个Bone和SceneNode对象都是独立的,支持在某个层次范围内执行变换操作,所有的层次都会影响模型的骨骼结构,但没有与之关联的vertices。第一次看到这种结构可以会觉得很奇怪,但实际上却是非常普通的做法。Bone类在SceneNode类的基础上增加一个成员变量index索引值,用于访问模型的骨骼,以及一个用于offset tranform(偏移计算的变换)矩阵变量mOffsetTransform。其中index值用于把vertices与shader中的bone变换矩阵数组关联起来(限shader输入结构体中的BoneIndices成员)。 变量mOffsetTransform用于把一个object从mesh space变换到bone space。之前要使用变换矩阵,是因为与该bone关联的vertices位于mesh space中(也就是local/model space),但是bone的变换运算都是在 bone space中(相对于父结点bone,bone结点自身也有一个坐标系)。因此,要使用一个bone变换矩阵对vertex进行变换,首先要使用mOffsetTransform变量把bone从mesh space变换到bone space。
完成了Bone类的定义之后,就可以在Model类中增加一组Bones变量。Model类中具体地实现代码如下:
std::vector<Bone*> mBones;
std::map<std::string, UINT> mBoneIndexMapping;
SceneNode* mRootNode;
其中成员变量mBones表示一组bones的集合,不考虑这些bones所处的层次。变量mBoneIndexMapping把一个bone的名称与bone在mBones数组中索引值进行一一对应。Bone类中的成员变量Bone::mIndex具有同样的映射,该变量用于表示在shader中访问bone transformations数组的索引值。这些独立的成员结构是使用Open Asset Import Library,并用于存储bone数据的方法产生的副作用。如果在编译期执行动画数据的转换操作,就可以去掉这些成员变量。 成员变量mRootNode表示变换分层结构中的根结点,并在模型中的所有bones都获取完成之后生成根结点。在Open Asset Import Library中以每一个mesh存储bone数据,因此需要在Mesh中增加获取bone数据的相关成员。具体包括两部分数据:bones本身以及bone vertex weights(权重)。列表20.4中列出BoneVertexWeight类的声明代码。
列表20.4 Declaration of the BoneVertexWeight Class
class BoneVertexWeights
{
public:
typedef struct _VertexWeight
{
float Weight;
UINT BoneIndex;
_VertexWeight(float weight, UINT boneIndex)
: Weight(weight), BoneIndex(boneIndex) { }
} VertexWeight;
const std::vector<VertexWeight>& Weights();
void AddWeight(float weight, UINT boneIndex);
static const UINT MaxBoneWeightsPerVertex = 4U;
private:
std::vector<VertexWeight> mWeights;
};
使用Mesh类的成员变量Mesh::mBoneWeights(变量类型为std::vector<BoneVertexWeight>)可以把一个BoneVertexWeight类型的对象实例与mesh中每一个vertex进行关联。在Mesh类的构造函数中(使用Open Asset Import Library处理mesh结构的过程在该函数中完成)需要增加如列表20.5所示的代码。
列表20.5 Bone Processing with the Mesh Class Constructor
if (mesh.HasBones())
{
mBoneWeights.resize(mesh.mNumVertices);
for (UINT i = 0; i < mesh.mNumBones; i++)
{
aiBone* meshBone = mesh.mBones[i];
// Look up the bone in the model's hierarchy, or add it if not found.
UINT boneIndex = 0U;
std::string boneName = meshBone->mName.C_Str();
auto boneMappingIterator = mModel.mBoneIndexMapping.find(boneName);
if (boneMappingIterator != mModel.mBoneIndexMapping.end())
{
boneIndex = boneMappingIterator->second;
}
else
{
boneIndex = mModel.mBones.size();
XMMATRIX offsetMatrix = XMLoadFloat4x4(&(XMFLOAT4X4(reinterpret_cast<const float*>(meshBone->mOffsetMatrix[0]))));
XMFLOAT4X4 offset;
XMStoreFloat4x4(&offset, XMMatrixTranspose(offsetMatrix));
Bone* modelBone = new Bone(boneName, boneIndex, offset);
mModel.mBones.push_back(modelBone);
mModel.mBoneIndexMapping[boneName] = boneIndex;
}
for (UINT i = 0; i < meshBone->mNumWeights; i++)
{
aiVertexWeight vertexWeight = meshBone->mWeights[i];
mBoneWeights[vertexWeight.mVertexId].AddWeight(vertexWeight.mWeight, boneIndex);
}
}
}
在Open Asset Import Library中使用一个aiBone类对象的数组存储bone数据。一个aiBone对象中存储了bone的名称,offset transform矩阵以及vertex weights列表。在列表20.5所示的代码中,通过遍历aiBone对象的数组,并根据boner的名称在model的mBoneIndexMapping容器中查找每一个bone。如果没有找到指定名称的bone,就在mBoneIndexMapping容器中添加以该名称创建的bone(创建bone时需要把offset矩阵进行转置,因为存储的变换矩阵是列优先矩阵,而我们需要的是行优先矩阵)。之所以需要把Bone对象都添加到mBoneIndexMapping容器中,是因为bones都存储在model中,但是需要在所有的aiMesh对象之间共享数据。最后,使用aiVertexWeight::mVertexId变量把vertex weights都拷贝到Mesh::mBoneWeights容器中。 使用上面的方法,可以在mesh中找到大部分bone数据,但是骨骼层次结构是属于aiScene对象的一部分,而aiScene对象是在Model类的构造函数中处理的。更具体地说,aiScene对象中含有一个aiNode类型的成员变量mRootNode,其中aiNode结构体类似于SceneNode类,因此使用aiScene::mRootNode变量表示transformation hierarchy(变换层次结构)的根结点。可以使用递归函数Model::BuildSkeleton()处理这些分层结点,该函数代码如列表20.6所示。
列表20.6 Processing the Skeletal Hierarchy
SceneNode* Model::BuildSkeleton(aiNode& node, SceneNode* parentSceneNode)
{
SceneNode* sceneNode = nullptr;
auto boneMapping = mBoneIndexMapping.find(node.mName.C_Str());
if (boneMapping == mBoneIndexMapping.end())
{
sceneNode = new SceneNode(node.mName.C_Str());
}
else
{
sceneNode = mBones[boneMapping->second];
}
XMMATRIX transform = XMLoadFloat4x4(&(XMFLOAT4X4(reinterpret_cast<const float*>(node.mTransformation[0]))));
sceneNode->SetTransform(XMMatrixTranspose(transform));
sceneNode->SetParent(parentSceneNode);
for (UINT i = 0; i < node.mNumChildren; i++)
{
SceneNode* childSceneNode = BuildSkeleton(*(node.mChildren[i]), sceneNode);
sceneNode->Children().push_back(childSceneNode);
}
return sceneNode;
}
首先调用BuildSkeleton()函数创建根结点aiSceneNode::mRootNode,然后通过递归创建根结点的每一个子结点。需要注意的是该结点层次并不需要组成全部的Bone对象。如果结点的名称与model中的一个Bone对象匹配,表示bone对象已经被使用了。否则,该结点表示一个没有任意vertices关联的变换,但是该变换却会影响到transformation hierarchy(变换层次结构)。在这种情况下,需要把以该结点名称创建一个新的SceneNode对象并添加到hierarchy结构中,而不能跳过这些结点。 现在,我们已经完成了导入模型骨骼结构所需要的全部数据,以及对应的skinning信息。最后一步是导入一些用于表示skeleton随着时间的变化如何进行变换的animations(动画数据)。Animations由一组 keyframes(关键侦)组成,一个keyframe表示一个时间点,并记录了在该时间点一个bone所要执行的变换。一个animation的帧率可能会创建成与游戏所期望的帧率保持一致。比如,如果游戏的运行帧率为60帧/秒,那么一个1秒时长的animation可能包含60个keyframes。但是,如果一个animation的关键帧数少于游戏的帧率,为了产生一个更平滑的animation可以在两个keybrames之间进行插值。
在Open Asset Import Library中,使用aiScene::mAnimations成员变量存储animations数据,该变量是一个aiAnimation类对象组成的数组。每一个aiAnimation对象实例都包括animation的名称,duration(持续时间,以ticks为单位),以及一秒钟的ticks数量。此外,还包含了多个keyframes集合,每一组keyframes对应了animation中的一个bone。AnimationClip类与aiAnimation数据结构类似(只是对aiAnimation进行了封装,以消除在示例程序中对Open Asset Import Library的公共依赖)。列表20.7中列出了AnimationClip类的声明代码。
列表20.7 Declaration of the AnimationClip Class
#pragma once
#include "Common.h"
struct aiAnimation;
namespace Library
{
class Bone;
class BoneAnimation;
class AnimationClip
{
friend class Model;
public:
~AnimationClip();
const std::string& Name() const;
float Duration() const;
float TicksPerSecond() const;
const std::vector<BoneAnimation*>& BoneAnimations() const;
const std::map<Bone*, BoneAnimation*>& BoneAnimationsByBone() const;
const UINT KeyframeCount() const;
UINT GetTransform(float time, Bone& bone, XMFLOAT4X4& transform) const;
void GetTransforms(float time, std::vector<XMFLOAT4X4>& boneTransforms) const;
void GetTransformAtKeyframe(UINT keyframe, Bone& bone, XMFLOAT4X4& transform) const;
void GetTransformsAtKeyframe(UINT keyframe, std::vector<XMFLOAT4X4>& boneTransforms) const;
void GetInteropolatedTransform(float time, Bone& bone, XMFLOAT4X4& transform) const;
void GetInteropolatedTransforms(float time, std::vector<XMFLOAT4X4>& boneTransforms) const;
private:
AnimationClip(Model& model, aiAnimation& animation);
AnimationClip();
AnimationClip(const AnimationClip& rhs);
AnimationClip& operator=(const AnimationClip& rhs);
std::string mName;
float mDuration;
float mTicksPerSecond;
std::vector<BoneAnimation*> mBoneAnimations;
std::map<Bone*, BoneAnimation*> mBoneAnimationsByBone;
UINT mKeyframeCount;
};
}
用于表示animation中每一个bone的keyframes集合都存储在BoneAnimation类型的mBoneAnimations容器中。BoneAnimation类的声明代码非常简短,只包含一个给定bone对象的keyframes集合。这些keyframes数据在AnimationClip类的私有构造函数中进行处理,该函数的实现代码如列表20.8所示。
列表20.8 Processing Animation Data
AnimationClip::AnimationClip(Model& model, aiAnimation& animation)
: mName(animation.mName.C_Str()), mDuration(static_cast<float>(animation.mDuration)), mTicksPerSecond(static_cast<float>(animation.mTicksPerSecond)),
mBoneAnimations(), mBoneAnimationsByBone(), mKeyframeCount(0)
{
assert(animation.mNumChannels > 0);
if (mTicksPerSecond <= 0.0f)
{
mTicksPerSecond = 1.0f;
}
for (UINT i = 0; i < animation.mNumChannels; i++)
{
BoneAnimation* boneAnimation = new BoneAnimation(model, *(animation.mChannels[i]));
mBoneAnimations.push_back(boneAnimation);
assert(mBoneAnimationsByBone.find(&(boneAnimation->GetBone())) == mBoneAnimationsByBone.end());
mBoneAnimationsByBone[&(boneAnimation->GetBone())] = boneAnimation;
}
for (BoneAnimation* boneAnimation : mBoneAnimations)
{
if (boneAnimation->Keyframes().size() > mKeyframeCount)
{
mKeyframeCount = boneAnimation->Keyframes().size();
}
}
}
在AnimationClip类的成员变量mBoneAnimationByBone容器中,也引用了BoneAnimation对象,该map容器类型的变量主要用于快速查找获取一个指定bone的animation数据。此外,在AnimationClip的构造函数中还计算了成员变量mKeyframeCount。该变量是用于表示访问bone transformations数组的索引值(通过数组系列的索引位置而不是时间点),直到索引值达到最大的keyframe数量。但是,每一个BoneAnimation实例对象可以包含不同数量的keyframes。通过在BoneAnimation对象数组中选择最大的keyframe,就可以使用索引值获取到所有可用的keyframes。当BoneAnimation对象中的keyframes数量小于指定的索引值,需要clamp最后一个keyframe数量(也就是索引值不能超过最后的keyframe)。
使用索引值检索bone transformations,主要是在函数AnimationClip::GetTransform*()(单数版本)和AnimationClip::GetTransforms*()(复数版本)中完成。根据不同的索引值类型参数,这些函数的单数版本用于获取指定bone的transformation,而复数版本获取所有bones的transformations集合。而且这些函数需要依赖BoneAnimation类中对应的函数调用,BoneAnimation类如列表20.9所示。
列表20.9 Declaration of the BoneAnimation Class
#pragma once
#include "Common.h"
struct aiNodeAnim;
namespace Library
{
class Model;
class Bone;
class Keyframe;
class BoneAnimation
{
friend class AnimationClip;
public:
~BoneAnimation();
Bone& GetBone();
const std::vector<Keyframe*> Keyframes() const;
UINT GetTransform(float time, XMFLOAT4X4& transform) const;
void GetTransformAtKeyframe(UINT keyframeIndex, XMFLOAT4X4& transform) const;
void GetInteropolatedTransform(float time, XMFLOAT4X4& transform) const;
private:
BoneAnimation(Model& model, aiNodeAnim& nodeAnim);
BoneAnimation();
BoneAnimation(const BoneAnimation& rhs);
BoneAnimation& operator=(const BoneAnimation& rhs);
UINT FindKeyframeIndex(float time) const;
Model* mModel;
Bone* mBone;
std::vector<Keyframe*> mKeyframes;
};
}
在BoneAnimation类的构造函数中获取所有的keyframe数据,如列表20.10所示。
列表20.10 Processing Keyframe Data
BoneAnimation::BoneAnimation(Model& model, aiNodeAnim& nodeAnim)
: mModel(&model), mBone(nullptr), mKeyframes()
{
UINT boneIndex = model.BoneIndexMapping().at(nodeAnim.mNodeName.C_Str());
mBone = model.Bones().at(boneIndex);
assert(nodeAnim.mNumPositionKeys == nodeAnim.mNumRotationKeys);
assert(nodeAnim.mNumPositionKeys == nodeAnim.mNumScalingKeys);
for (UINT i = 0; i < nodeAnim.mNumPositionKeys; i++)
{
aiVectorKey positionKey = nodeAnim.mPositionKeys[i];
aiQuatKey rotationKey = nodeAnim.mRotationKeys[i];
aiVectorKey scaleKey = nodeAnim.mScalingKeys[i];
assert(positionKey.mTime == rotationKey.mTime);
assert(positionKey.mTime == scaleKey.mTime);
Keyframe* keyframe = new Keyframe(static_cast<float>(positionKey.mTime), XMFLOAT3(positionKey.mValue.x, positionKey.mValue.y, positionKey.mValue.z),
XMFLOAT4(rotationKey.mValue.x, rotationKey.mValue.y, rotationKey.mValue.z, rotationKey.mValue.w), XMFLOAT3(scaleKey.mValue.x, scaleKey.mValue.y, scaleKey.mValue.z));
mKeyframes.push_back(keyframe);
}
}
在该构造函数中,通过遍历aiNodeAnim对象的数组,把对象中的相关数据拷贝到Keyframe对象实例中。Keyframe类的声明代码如列表20.11所示。
列表20.11 Declaration of the Keyframe Class
#pragma once
#include "Common.h"
namespace Library
{
class Keyframe
{
friend class BoneAnimation;
public:
float Time() const;
const XMFLOAT3& Translation() const;
const XMFLOAT4& RotationQuaternion() const;
const XMFLOAT3& Scale() const;
XMVECTOR TranslationVector() const;
XMVECTOR RotationQuaternionVector() const;
XMVECTOR ScaleVector() const;
XMMATRIX Transform() const;
private:
Keyframe(float time, const XMFLOAT3& translation, const XMFLOAT4& rotationQuaternion, const XMFLOAT3& scale);
Keyframe();
Keyframe(const Keyframe& rhs);
Keyframe& operator=(const Keyframe& rhs);
float mTime;
XMFLOAT3 mTranslation;
XMFLOAT4 mRotationQuaternion;
XMFLOAT3 mScale;
};
}
每一个keyframe对象中包含一个translation(平移),rotation(旋转)以及scale(缩放)变量,对应于animation中一个特定的时间点。其中rotaion变量使用一个 quaternion(四元数)表示,一个四元数是对复数的四维表示,用于更方便的执行三维旋转运算。在本书中使用Euler angles(欧拉角度)描述3D空间的旋转。这种旋转方法由一个单位向量(旋转轴axis)和一个旋转角度(θ angle)的组合表示。Quaternions提供了一种简单的方法把向量轴和旋转角度编码到四个数中,可以使用一个position vector(点向量)表示这四个数。使用quaternions还可以避免Euler angles导致的 gimbal lock(万向节死锁)问题,gimbal lock是指当Euler角度中的某两个轴变得平行时,就会失去其中一个的自由度。
Keyframe类的实现代码非常简单:仅仅是使用成员变量保存对应的数据,并对外提供访问这些数量的函数接口。但是,Keyframe::Transform()函数值得特别说明一下,在该函数中把translation,rotation以及scale组合到一个transformation(变换)矩阵中。
XMMATRIX Keyframe::Transform() const
{
static XMVECTOR rotationOrigin = XMLoadFloat4(&Vector4Helper::Zero);
return XMMatrixAffineTransformation(ScaleVector(), rotationOrigin, RotationQuaternionVector(), TranslationVector());
}
Keyframe::Transform()函数主要用于BoneAnimation类以及该类中的GetTransform*()相关函数。通过调用BoneAnimation::GetTransform()函数可以获取一个指定时间点的未经过插值的bone transformations。在该函数中通过调用BoneAnimation::FindKeyframeIndex()函数查找最后一个时间点小于指定时间点的keyframe。BoneAnimation::GetInterpolatedTransform()函数则是用于获取在给定时间点一前一后的两个keyframes,并对这两个frames的translation,rotation以及scale变量进行插值计算得到当前时间点的keyframe值。BoneAnimation::GetTransformAtKeyframe()函数返回指定keyframe的transformation,如果指定的索引值大于bone的keyframe数组大小,需要把索引值clamp为最后一个keyframe的索引值(之前已经讨论过,在一个animation中并不需要所有bones具有相同数量的keyframes)。列表20.12中这几函数中实现代码。
列表20.12 Bone Transformation Retrieval
UINT BoneAnimation::GetTransform(float time, XMFLOAT4X4& transform) const
{
UINT keyframeIndex = FindKeyframeIndex(time);
Keyframe* keyframe = mKeyframes[keyframeIndex];
XMStoreFloat4x4(&transform, keyframe->Transform());
return keyframeIndex;
}
void BoneAnimation::GetTransformAtKeyframe(UINT keyframeIndex, XMFLOAT4X4& transform) const
{
// Clamp the keyframe
if (keyframeIndex >= mKeyframes.size())
{
keyframeIndex = mKeyframes.size() - 1;
}
Keyframe* keyframe = mKeyframes[keyframeIndex];
XMStoreFloat4x4(&transform, keyframe->Transform());
}
void BoneAnimation::GetInteropolatedTransform(float time, XMFLOAT4X4& transform) const
{
Keyframe* firstKeyframe = mKeyframes.front();
Keyframe* lastKeyframe = mKeyframes.back();
if (time <= firstKeyframe->Time())
{
// Specified time is before the start time of the animation, so return the first keyframe
XMStoreFloat4x4(&transform, firstKeyframe->Transform());
}
else if (time >= lastKeyframe->Time())
{
// Specified time is after the end time of the animation, so return the last keyframe
XMStoreFloat4x4(&transform, lastKeyframe->Transform());
}
else
{
// Interpolate the transform between keyframes
UINT keyframeIndex = FindKeyframeIndex(time);
Keyframe* keyframeOne = mKeyframes[keyframeIndex];
Keyframe* keyframeTwo = mKeyframes[keyframeIndex + 1];
XMVECTOR translationOne = keyframeOne->TranslationVector();
XMVECTOR rotationQuaternionOne = keyframeOne->RotationQuaternionVector();
XMVECTOR scaleOne = keyframeOne->ScaleVector();
XMVECTOR translationTwo = keyframeTwo->TranslationVector();
XMVECTOR rotationQuaternionTwo = keyframeTwo->RotationQuaternionVector();
XMVECTOR scaleTwo = keyframeTwo->ScaleVector();
float lerpValue = ((time - keyframeOne->Time()) / (keyframeTwo->Time() - keyframeOne->Time()));
XMVECTOR translation = XMVectorLerp(translationOne, translationTwo, lerpValue);
XMVECTOR rotationQuaternion = XMQuaternionSlerp(rotationQuaternionOne, rotationQuaternionTwo, lerpValue);
XMVECTOR scale = XMVectorLerp(scaleOne, scaleTwo, lerpValue);
static XMVECTOR rotationOrigin = XMLoadFloat4(&Vector4Helper::Zero);
XMStoreFloat4x4(&transform, XMMatrixAffineTransformation(scale, rotationOrigin, rotationQuaternion, translation));
}
}
UINT BoneAnimation::FindKeyframeIndex(float time) const
{
Keyframe* firstKeyframe = mKeyframes.front();
if (time <= firstKeyframe->Time())
{
return 0;
}
Keyframe* lastKeyframe = mKeyframes.back();
if (time >= lastKeyframe->Time())
{
return mKeyframes.size() - 1;
}
UINT keyframeIndex = 1;
for (; keyframeIndex < mKeyframes.size() - 1 && time >= mKeyframes[keyframeIndex]->Time(); keyframeIndex++);
return keyframeIndex - 1;
}
接下来,我们仔细分析一下BoneAnimation::GetInterpolatedTransform()函数的实现原理。首先,查找指定时间点的前后两个keyframes并获取对应的数据。然后使用如下的公式进行插值计算:
以下方法演示了该公式的计算过程:
lerp (x,y,s) = x * (1 - s) + (y * s)
因此,在上面的示例中,lerp value(插值因子)为0.2表示在最终的计算结果中keyframe1所占的比例为80%,而keyframe2所占的比例为20%。
在BoneAnimation::GetInterpolatedTransform()函数中使用这种方法插值计算translation,rotation以及scale值,并使用这些值计算指定时间点的bone transformation。