Importing Animated Models

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。
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值