模型的骨骼动画技术讲解
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
模型的骨骼动画技术讲解
骨骼动画实际上是两部分的过程。第一个由美术执行,第二个由程序员(或者你写的引擎)执行。第一部分发生在建模软件中,称为建模。这里发生的是术定义了网格下面骨骼的骨架。
网格代表物体(无论是人类,怪物还是其他物体)的皮肤,骨骼用于移动网格物体,以模拟现实世界中的实际运动,这通过将每个顶点分配给一个或多个骨头来完成。当顶点被分配给
骨骼时,定义了权重,该权重确定骨骼在移动时对顶点的影响量。通常的做法是使所有权重
的总和1 (每个顶点)。例如,如果一个顶点位于两个骨骼之间,我们可能希望将每个骨骼的权重分配为
0.5 ,因为我们希望骨骼在顶点上的影响相等。然而,如果顶点完全在单个骨骼的影响之内,那么权重将为
1 (这意味着骨骼自主地控制顶点的运动)。
这是一个在混合器中创建的骨骼结构的例子:
我们上面看到的是动画的重要组成部分,美术将骨骼结构组合在一起,并为每个动画类型
(“步行”,“跑步”,“死亡”等)定义了一组关键帧。关键帧包含沿着动画路径的关键点的
所有骨骼的变换。图形引擎在关键帧的变换之间进行插值,并在它们之间创建平滑的运动。
用于骨骼动画的骨骼结构通常是继承的,这意味着骨骼有一个孩子/父母关系,所以创
建了一根骨头。除了根骨之外,每个骨骼都有一个父母。例如,在人体的情况下,您可以
将后骨分配为具有诸如手臂和腿部以及手指骨的儿童骨骼的根部。当父骨骼移动时,它也
移动其所有的孩子,但是当孩子的骨骼移动时,它不会移动它的父母(我们的手指可以移动
而不移动手,但是当手移动它移动所有的手指)。从实践的角度来看,这意味着当我们处理骨骼的变换时,我们需要将它与从它引导到根的所有父骨骼的转换结合起来。
我们不会再进一步讨论装备,它是一个复杂的主题,并且在图形程序员的领域之外。建模软件有先进的工具来帮助美术做这项工作,你需要成为一个很好的美术来创造一个好看的
网格和骨架。让我们看看图形引擎需要做什么才能制作骨架动画。
第一阶段是用顶点骨骼信息来提取顶点缓冲区。有几个选项可用,但我们将要做的很
简单。对于每个顶点,我们将添加一个插槽阵列,其中每个插槽包含骨骼ID和权重。为了使我们的生活更简单,我们将使用具有四个插槽的数组,这意味着没有顶点可以受到四个
以上的骨骼的影响。如果您要加载更多骨骼的模型,则需要调整阵列大小,但是对于作为
本博文一部分的Doom 3模型,四个骨骼就足够了。所以我们的新顶点结构将如下所示:POSitiOn
TeXtUre COOrdinateS
NOrmal
BOne IDO ∣ Weight 0
BOne IDI ∣ Weight 1
BOne ID2 [ Weight 2
BOne ID3 I Weight 3
骨骼ID是骨转换数组的索引,这些变换将被应用在WVP矩阵之前的位置和正常(即它们
将顶点从“骨空间”转换成局部空间)。权重将用于将几个骨骼的变换组合成单个变换,并且在任何情况下,总权重必须正好为 1 (建模软件的事情)。通常,我们将在动画关键帧之
间进行插值,并在每个帧中更新骨骼变换数组。
骨骼转换阵列的创建方式通常是棘手的部分。变换被设置在一个历史结构(即树)中,
通常的做法是在树中的每个节点中具有缩放向量,旋转四元数和平移向量。实际上,每个节点都包含这些项目的数组。数组中的每个条目都必须有一个时间戳。应用时间与其中一个时间戳完全匹配的情况可能很少,因此我们的代码必须能够插值缩放/旋转/转换,以便在应用程序的时间点获得正确的转换。我们对每个节点从当前骨到根进行相同的过程,并将
这个变换链相加在一起以获得最终结果。我们为每个骨骼做这些,然后更新着色器。
到目前为止,我们谈到的一切都是非常通用的。但是这是一个关于使用ASSimP的骨骼动画的博文,所以我们需要再次进入该库,读者可以自行下载一个ASSimP库,看看如何使用它进行皮肤化。ASSimP的好处是它支持从多种格式加载骨骼信息。不好的是,您仍然需要对其创建的数据结构进行相当多的工作,以生成您为着色器所需的骨骼转换。
让我们从根的骨骼信息开始吧, 以下是ASSimP数据结构中的相关内容:
后面给读者介绍一下关于ASSimP类的加载,一切都包含在aiScene类中(当我们导入网格
文件时我们得到的对象),aiScene包含一组aiMesh对象。aiMesh是模型的一部分,并在顶点级别包含位置,法线,纹理坐标等内容。现在我们看到aiMesh还包含一个aiBone对象
的数组。毫无疑问,aiBOne代表网格骨架中的一个骨骼,每个骨骼都有一个名字,通过它可以在骨骼层级(见下文),顶点权重数组和4x4偏移矩阵中找到,我们需要这个矩阵的原因是因为顶点存储在通常的本地空间中,这意味着即使没有骨架动画,我们现有的代码库也
可以加载模型并正确渲染。但是,骨干变化在骨骼空间中发挥作用(每个骨骼都有自己的空
间,这就是为什么我们需要将变换加在一起)。因此,偏移矩阵的工作将顶点位置从网格的
局部空间移动到该特定骨骼的骨空间。
顶点权重数组是事物开始变得有趣的地方,该数组中的每个条目都包含aiMesh中顶点
数组的索引(请注意,顶点分布在几个长度相同的数组中)和权重。所有顶点权重的总和
必须为1 ,但是要找到它们,您需要遍历所有骨骼,并将权重累加到每个特定顶点的列表中。
在我们的顶点级别构建骨骼信息之后,我们需要处理骨骼变换层级并生成将加载到着色
器中的最终转换,下图显示相关数据结构:
aiNode
Stnng NamE
mat4x4 TranSfOrmatiOn
aiNode* Parent
a⅛Node* ChiIdrenO aiAnimation
double DUratiOn
ClOUbIe TiCkSPerSeCOnd
ChannetSo
String Nafne
VeCtor3 D POSttionSn
quaternion RotationsQ
VeCtOr3D Sca )iπgs∏
再次,我们从aiScene 开始,aiScene 对象包含一个指向 aiNode 类对象的指针,该对象是一 个节点层级的根(换句话说-一棵树),树中的每个节点都有一个指向其父项的指针以及指 向其子节点的数组, 这样我们可以方便地来回遍历树。 另外,节点执行从节点空间变换到
其父节点空间的变换矩阵。 最后,节点可能有也可能没有一个名字。 如果一个节点表示父 进制中的骨骼,则节点名称必须与骨骼名称相匹配。 但是有时节点没有名称(这意味着没 有相应的骨骼),而且他们的工作只是帮助模型分解模型并且沿着一些中间变换。
最后一块拼图是 aiAnimation 数组,它也存储在
aiScene 对象中, 单个aiAnimation 对 象表示一系列动画帧,例如" WaIk ”,“ run ”,“ shoot ”等。通过在帧之间进行内插,我们得
到与动画名称相匹配的所需视觉效果。 动画的持续时间为每秒钟的秒数(例如每秒 100个
刻度和25个刻度,代表4秒动画),这有助于我们对进程进行时间调整, 以使动画在每个硬
件上看起来相同。 另外,动画还有一个名为通道的 aiNodeAnim 对象的数组。
每个通道实 际上都是骨骼,全部是它的转变。
该通道包含一个名称,该名称必须与其他一个节点在层
级和三个转换数组中匹配。 为了计算特定时间点的最终骨骼变换,
我们需要在这三个阵列中的每一个中找到与时间 匹配的两个入口,并在它们之间插值。
那么我们需要将转换组合成一个矩阵。 做完之后, 我们需要在根中找到相应的节点。 然后我们需要相应的通道为父,
并进行相同的插值过程。 我们把这两个变化相乘合起来,直到我们达到根的层级。
加载模型的源代码实现如下:
ROOlNOde —
Animationsf]