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。
發佈了5 篇原創文章 · 獲贊 27 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章