我正在编写一个 .smd 导入器,但我被困在骨骼动画部分。问题是我不知道它是如何工作的。我正在使用它来编写导出器,但它没有显示如何使用存储在文件中的信息。
我想所有具有相同骨骼 id 的顶点都应该被分组、平移和旋转,因为你不能旋转每个顶点。但我不知道我是否正确,即使我是,我仍然不知道如何通过脚本来做到这一点......
所以问题是:我如何使用存储在文件中的骨骼动画信息?
我不熟悉 SMD 格式,但这里有......
注意:此答案假设您知道如何为对象/节点构造复合变换。这是结合了它的平移、旋转和缩放的矩阵(虽然 SMD 中似乎没有使用缩放)。此外,还使用了矩阵乘法、矩阵求逆和矩阵 * 向量乘法。
模型的骨骼节点形成一棵树;每个骨骼都有一个父骨骼,除了根骨骼(nodes
截面)。每个节点都有自己的局部变换(位置和旋转)。
局部节点变换:节点的局部变换是一个由其位置和旋转构成的 4x4 矩阵,它将点从其局部空间变换到其父节点空间:如果一个向量表示节点空间中的一个位置,则将其与矩阵相乘得到父空间中的那个向量。有关如何执行此操作的详细信息,请参阅此链接。谷歌更多。
在 SMD 中,骨骼变换仅在动画的关键帧中定义(该skeleton
部分)。“参考”SMD 文件具有单帧动画;模型参考位置中每个骨骼节点的位置和旋转。
动画 SMD 文件具有包含多个帧的动画序列,每个帧为(某些)骨骼指定不同的变换。播放动画时,您根据帧时间和当前场景/游戏时间在帧之间进行插值,并为每个骨骼提供变换(位置 + 旋转)。
在预处理中(加载网格时左右),您需要计算所谓的“静止”骨骼变换。这些是每个骨骼在参考位置时的模型到骨骼空间变换。原因如下:
所有顶点位置都在模型空间中定义,但最终,顶点变换必须从骨骼空间开始,因为您希望顶点与单个骨骼一起移动。因此,必须首先将顶点位置转换为骨骼空间。这就是静止骨骼变换的用武之地。
所以我们正在寻找的静止变换将顶点从模型空间变换到骨骼空间。将所有骨骼放在参考位置。从根节点开始遍历树,并连接变换矩阵。因此,例如,对于上臂节点,您将获得转换:
transform = root * spine * shoulderR * upperArmR
然而,这是从上臂空间到模型空间的变换。因此,只需反转矩阵即可获得静止骨骼变换。对每个骨骼执行此操作并存储这些矩阵。
请注意,静止变换不会随时间变化;它们是根据模型的参考位置固定的。
每个顶点与 1 个或多个骨骼节点相关联。对于每个这样的关联,顶点都有相应的权重。通常,所有权重总和为1
。在 SMD 中,这些关联是在triangles
段中定义的。根据您链接到的页面,格式为:
triangles
my_material
bone_id x y z nx ny nz u v bone_links
这定义了一个顶点(x, y, z)
并最初将其与骨骼相关联bone_id
(1
我假设的权重)。该bone_links
部分可以(某种程度上)覆盖它并指定多个关联,如下所示:
bone_links = num_links bone_id[0] weight[0] bone_id[1] weight[1] ... etc.
如果权重加起来不等于 1,则剩余权重将归于与原始 的关联bone_id
。
因此,与骨骼 0、1 和 2 关联的示例顶点将是:
0 x y z nx ny nz u v 3 0 0.15 1 0.35 2 0.5
这是我们最终根据当前骨骼变换确定顶点位置的地方。如前所述;根据当前时间和动画,您确定(插值)当前骨骼变换。对于每个骨骼,计算骨骼到世界的变换。示例(我们之前看到过):
boneToWorld = root * spine * shoulderR * upperArmR
现在,对于与单个骨骼关联的顶点,以下给出其动画/蒙皮位置:
vertexPosAnimated = boneToWorld * boneAtRest * vertexPosModel
这首先将顶点位置从模型空间 ( vertexPosModel
) 转换为与之关联的骨骼空间(此转换不会随时间改变)。然后,使用骨骼的当前位置,再次将顶点从骨骼转换到模型空间。这允许它随着骨骼的变换变化而移动。
观察到,当骨骼当前处于静止位置时,boneAtRest
是 的倒数boneToWorld
,boneToWorld * boneAtRest
单位矩阵也是,所以顶点位置保持不变,这是正确的!
最后,由于顶点可以与多个骨骼相关联,因此我们为每个相关联的骨骼计算上述的加权和,而不是上面的。例如,对于与 3 个骨骼关联的顶点:
vertexPosAnimated =
boneToWorld[0] * boneAtRest[0] * vertexPosModel * weight[0] +
boneToWorld[1] * boneAtRest[1] * vertexPosModel * weight[1] +
boneToWorld[2] * boneAtRest[2] * vertexPosModel * weight[2];
这些是一些广泛的笔触,我什至没有讨论着色器的实现(如果这就是你要做的),但我想我已经涵盖了所有原则,而且它已经是一个很长的答案了。
过去我发现对 3D 引擎开发很有帮助的一件事是 M3G 文档(旧的 Java Mobile 3D API)。上的条目SkinnedMesh
基本上也描述了我在这里发布的内容。
此外,尝试理解使用静止骨骼变换将顶点从模型空间变换到骨骼空间的概念,然后使用当前变换再次返回。这是整个事情的关键。
祝你好运!