当前位置:   article > 正文

Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十三章:角色动画_dx12角色动画

dx12角色动画


学习目标

  1. 熟悉蒙皮动画的术语;
  2. 学习网格层级变换在数学理论,以及如何遍历基于树结构的网格层级;
  3. 理解顶点混合的想法以及数学理论;
  4. 学习如何从文件加载动画数据;
  5. 学习如何在D3D中实现角色动画。


1 框架的层级结构

在这里插入图片描述


1.1 数学公式

例如,有下面的结构:
在这里插入图片描述
每根子骨骼的坐标系都可以跟父骨骼关联,第一根骨骼与世界坐标系关联:
在这里插入图片描述
如果矩阵A0是第一根骨骼的世界变换矩阵,A1是第二根骨骼变换到第一根骨骼的矩阵,往后依次类推,那么第i根骨骼变换到世界坐标系的变换矩阵就是:
在这里插入图片描述
在我们上述的例子中,M2 = A2A1A0, M1 = A1A0 and M0 = A0,就是每根骨骼对于的世界坐标系变换矩阵:
在这里插入图片描述



2 蒙皮网格


2.1 定义

在这里插入图片描述
高光的那整条骨骼链叫做骨架(skeleton)。3D几何模型叫做皮肤(skin)。皮肤顶点与绑定空间相关联(整个皮肤相关联的局部坐标系)。每个骨骼影响一系列子皮肤的位置和形状。


2.2 重置骨骼到根空间的变换公式

和上述不同的地方是,把各个骨骼变换到世界坐标系的矩阵拆解开,先找到变换到根空间的矩阵,然后变换到世界坐标系;第二个不同点是从下往上,这样比从上往下更高效。第n根骨骼的变换如下:
在这里插入图片描述
这里p是骨骼i的父骨骼的编号,toRootp从p的局部坐标系映射到根局部坐标系。


2.3 抵消变换(Offset Transform)

有一个小问题是,被骨骼影响的顶点并不在骨骼坐标系统中,而是在绑定空间中。所以在应用公式对顶点进行变换之前,我们先要将顶点从绑定空间变换到影响它的骨骼的空间中,所以叫抵消变换(offset transformation)。
在这里插入图片描述
所以现在可以定义一个最终变换:
在这里插入图片描述


2.4 对骨架进行动画

我们定义了一个骨架动画的类SkinnedData.h/.cpp在Skinned Mesh Demo中。
我们首先对每个骨骼单独在局部坐标系移动,然后考虑其父节点的移动,然后变换到根空间。
我们定义一些列动画的动画片段(animation clip):

///<summary>
/// Examples of AnimationClips are "Walk", "Run", "Attack", "Defend".
/// An AnimationClip requires a BoneAnimation for every bone to form
/// the animation clip.
///</summary>
struct AnimationClip
{
	// Smallest end time over all bones in this clip.
	float GetClipStartTime()const;
	
	// Largest end time over all bones in this clip.
	float GetClipEndTime()const;
	
	// Loops over each BoneAnimation in the clip and interpolates
	// the animation.
	void Interpolate(float t, std::vector<XMFLOAT4X4>& boneTransforms)const;
	
	// Animation for each bone.
	std::vector<BoneAnimation> BoneAnimations;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

我们可以使用unordered_map保存这些片段:

std::unordered_map<std::string, AnimationClip> mAnimations;
AnimationClip& clip = mAnimations["attack"];
  • 1
  • 2

最终,每个骨骼需要抵消变换矩阵,并且需要一个数据结构表示骨架结构。所以我们骨骼动画最终数据结构如下:

class SkinnedData
{ 
public:
	UINT BoneCount()const;
	
	float GetClipStartTime(const std::string& clipName)const;
	float GetClipEndTime(const std::string& clipName)const;
	
	void Set(std::vector<int>& boneHierarchy,
		std::vector<DirectX::XMFLOAT4X4>& boneOffsets,
		std::unordered_map<std::string, AnimationClip>& animations);
		
	// In a real project, you’d want to cache the result if there was a
	// chance that you were calling this several times with the same
	// clipName at the same timePos.
	void GetFinalTransforms(const std::string& clipName, float timePos,
		std::vector<DirectX::XMFLOAT4X4>& finalTransforms)const;
		
private:
	// Gives parentIndex of ith bone.
	std::vector<int> mBoneHierarchy;
	std::vector<DirectX::XMFLOAT4X4> mBoneOffsets;
	std::unordered_map<std::string, AnimationClip> mAnimations;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/883775
推荐阅读
  

闽ICP备14008679号