我正在写一个.smd导入器,而且我被困在骨骼动画部分。问题是我不确切知道它是如何工作的。我正在使用此this来编写导出器,但它没有显示如何使用存储在文件中的信息。
我想所有具有相同骨骼id的顶点都应该被分组,平移和旋转,因为你无法旋转每个顶点。但我不知道我是否正确,即使我是,我仍然不知道如何通过脚本来做到这一点......
所以问题是:我如何使用存储在文件中的骨架动画信息?
答案 0 :(得分:12)
我并不熟悉SMD格式,但这里有......
注意:此答案假设您知道如何为对象/节点构建复合变换。这是结合其平移,旋转和缩放的矩阵(虽然看起来在SMD中没有使用比例)。此外,使用矩阵乘法,矩阵求逆和矩阵*向量乘法。
模型的骨骼节点形成一棵树;每个骨骼都有一个父骨骼,除了根骨骼(nodes
部分)。每个节点都有自己的局部变换(位置和旋转)。
局部节点变换:节点的局部变换是从其位置和旋转构造的4×4矩阵,它将点从其局部空间转换到其父节点的空间:如果表示节点空间中的位置的向量,则将其与矩阵相乘导致父对象空间中的该向量。有关如何执行此操作的详细信息,请参阅this link。 Google a bit了解更多信息。
在SMD中,骨骼变换仅在动画中的关键帧中定义 (skeleton
部分)。 A"参考" SMD文件有一帧动画;模型的参考位置中每个骨骼节点的位置和旋转。
动画SMD文件具有包含多个帧的动画序列,每个帧指定(某些)骨骼的不同变换。在播放动画时,您可以根据帧的时间和当前场景/游戏时间在帧之间进行插值,并为每个骨骼进行变换(位置+旋转)。
在预处理中(当加载网格时),你需要计算所谓的" at-rest"骨转换。这些是每个骨骼的模型到骨骼空间变换,当它位于参考位置时。这就是原因:
所有顶点位置都在模型空间中定义,但最终必须从骨骼空间开始进行顶点变换,因为您希望顶点随单个骨骼移动。因此,必须首先将顶点位置转换为骨骼空间。这是静止骨骼变换的用武之地。
因此,我们正在寻找的静止变换将顶点从模型空间转换为骨骼空间。将所有骨骼放在参考位置。从根节点开始遍历树,并连接变换矩阵。因此,例如,对于上臂节点,您将获得变换:
transform = root * spine * shoulderR * upperArmR
然而,这是从上臂空间到模型空间的转换。因此,只需反转矩阵即可获得静止骨骼变换。为每个骨骼执行此操作并存储这些矩阵。
请注意,静止变换不会随时间而变化;它们根据模型的参考位置进行修复。
每个顶点与一个或多个骨骼节点相关联。对于每个这样的关联,顶点具有相应的权重。通常,所有权重总和为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
上的条目基本上描述了我在这里发布的内容。
此外,尝试理解使用 at-rest 骨骼变换将顶点从模型转换为骨骼空间的概念,然后再使用其当前变换返回。这是整个事情的关键。
祝你好运!