0

我已经加载了顶点、材质、法线、权重、关节 ID、关节本身和父子信息(层次结构),我还设法将它们全部渲染,当我旋转或平移其中一个关节时,孩子旋转父母。我的问题是,父母在错误的点或偏移量上旋转(希望你明白我的意思),这意味着我的初始偏移量是错误的,对吧?为了获得起始t-pose,我猜我不需要旋转或平移,只需要关节位置的偏移量,但我不知道如何获得它,被卡住了很长时间。在 Collada 文件中,每个关节都有一个变换,我也加载了那个,但我不知道如何正确实现它,我的 3d 模型变形并且看起来不对。如果你回答这个问题,请把它当作你在向猴子(我)解释它,如果可能的话,一步一步来,我不熟悉这些绑定和反向绑定术语,并且非常困惑。我想如果我能做到这一点,我最终会自己弄清楚骨骼动画的其余部分,所以这只是这件小事。

4

1 回答 1

2

我最近得到了骨骼、关节和节点的工作,所以我将尝试解释我是如何实现它的。请注意,我是使用Assimp来导入我的DAE文件的,但据我所知,Assimp并没有对数据做任何处理,所以这个解释应该直接与Collada文件中的数据相关。

我只是自己学习所有这些,所以我可能会弄错。如果我这样做,任何人,请告诉我,我会相应地更新这个答案。

语义

网格是一组顶点、法线、纹理坐标和面。存储在网格中的点处于绑定姿势或静止姿势。这通常是(但不总是)T 形姿势。

皮肤是控制器。它指的是单个网格,并包含将修改该网格的骨骼列表(这是存储骨骼的位置)。您可以将皮肤元素视为将要渲染的实际模型(或模型的一部分)。

骨骼是名称和相关矩阵的平面列表。这里没有分层数据,它只是一个平面列表。层次结构由引用骨骼的节点提供。

节点关节是分层数据元素。它们存储在层次结构中,父节点具有零个或多个子节点。一个节点可以链接到零个或多个骨骼,并且可以链接到零个或多个皮肤。应该只有一个根节点。关节与节点相同,因此我将连接称为节点。

请注意,节点和骨骼是分开的。您无需修改​​骨骼来为模型设置动画。相反,您修改了一个节点,该节点在渲染模型时应用于骨骼。

皮肤

皮肤是你要渲染的东西。皮肤总是指一个单一的网格。作为同一模型(或场景)的一部分,您可以在一个 DAE 文件中拥有多个皮肤。有时,模型会通过变换网格来重用网格。例如,您可能有一个用于单个手臂的网格,并在身体的另一侧重复使用该手臂,镜像。我相信这就是bind_shape_matrix皮肤的价值所在。到目前为止,我还没有使用过这个,而且我的矩阵总是恒等的,所以我不能说它的用法。

骨骼是将变换应用于您的模型。您不修改骨骼。相反,您修改控制骨骼的节点。稍后再谈。

骨骼由以下部分组成:

  • 一个名称,用于查找控制此骨骼的节点 ( Name_array)
  • 绑定姿势矩阵,有时称为“逆绑定矩阵”或“偏移矩阵”(bind_poses数组)
  • 骨骼将影响的顶点索引列表(vertex_weights元素)
  • 上面相同长度的权重列表,说明骨骼对该顶点的影响程度。(weights数组)

节点

节点是分层数据元素,描述模型在渲染时如何转换。您将始终从一个根节点开始,然后沿着节点树向上移动,按顺序应用变换。我为此使用了深度优先算法。

该节点说明在渲染或动画时应如何转换模型、皮肤和骨骼。

节点可以指代皮肤。这意味着皮肤将用作此模型渲染的一部分。如果您看到一个节点引用了一个皮肤,它会在渲染时包含在内。

一个节点由以下部分组成:

  • 一个名字(sid属性)
  • 一个变换矩阵(transform元素)
  • 子节点(node元素)

GlobalInverseTransform 矩阵

GlobalInverseTransform矩阵是通过获取Transform第一个节点的矩阵并将其反转来计算的。就那么简单。

算法

现在我们可以得到好的部分——实际的蒙皮和渲染。

计算节点的 LocalTransform

每个节点都应该有一个矩阵,称为LocalTransform矩阵。此矩阵不在 DAE 文件中,而是由您的软件计算得出。它基本上是Transform节点及其所有父节点的矩阵的累积。

第一步是遍历节点层次结构。

从第一个节点开始LocalTransform,使用节点的Transform矩阵和LocalTransform父节点的矩阵计算节点的 。如果节点没有父节点,则使用单位矩阵作为父节点的LocalTransform矩阵。

Node.LocalTransform = ParentNode.LocalTransform * Node.Transform

对这个节点中的每个子节点递归地重复这个过程。

计算骨骼的 FinalTransform 矩阵

就像节点一样,骨骼也应该有一个FinalTransform矩阵。同样,这不会存储在 DAE 文件中,它是由您的软件作为渲染过程的一部分进行计算的。

对于使用的每个网格,对于该网格中的每个骨骼,应用以下算法:

For each mesh used:
    For each bone in mesh:
        If a node with the same name exists:
            Bone.FinalTransform = Bone.InverseBind * Node.LocalTransform * GlobalInverseTransform
        Otherwise:
            Bone.FinalTransform = Bone.InverseBind * GlobalInverseTransform

我们现在有了FinalTransform模型中每个骨骼的矩阵。

计算顶点的位置

一旦我们计算了所有骨骼,我们就可以将网格的点转换为它们的最终渲染位置。这是我使用的算法。这不是执行此操作的“正确”方法,因为它应该由顶点着色器即时计算,但它可以演示正在发生的事情。

From the root node:
    For each mesh referred to by node:
        Create an array to hold the transformed vertices, the same size as your source vertices array.
        Create an array to hold the transformed normals, the same size as your source vertices array (normals and vertices arrays should be the same length at the beginning.

        If the mesh has no bones:
            Copy source vertices and source normals to output arrays - mesh is not skinned
        Otherwise:
            For every bone in the mesh:
                For every weight in the bone:
                    OutputVertexArray(Weight.VertexIndex) = Mesh.InputVertexArray(Weight.VertexIndex) * Bone.FinalTransform * Weight.TransformWeight
                    OutputNormalArray(Weight.VertexIndex) = Normalize(Mesh.InputNormalArray(Weight.VertexIndex) * Bone.FinalTransform * Weight.TransformWeight)
        
        Render the mesh, using OutputVertexArray, OutputNormalArray, Mesh.InputTexCoordsArray and the mesh's face indices.

    Recursively call this process for each child node.

这应该可以为您提供正确渲染的输出。

请注意,使用此系统,可以多次重复使用网格。

动画

只是关于动画的快速说明。我对此并没有做太多,Assimp 隐藏了 Collada 的许多血腥细节(并引入了它自己的血腥形式),但是要使用文件中的预定义动画,你需要对平移、旋转和缩放进行一些插值用一个矩阵表示节点在单个时间点的动画状态。

请记住,矩阵构造遵循 TRS(平移、旋转、缩放)约定,其中首先发生平移,然后是旋转,然后是缩放。

AnimatedNodeTransform = TranslationMatrix * RotationMatrix * ScaleMatrix

生成的矩阵完全替代了节点的变换矩阵——它没有与矩阵结合。

我仍在尝试弄清楚如何正确执行动态动画(想想逆运动学)。对于我尝试的某些模型,效果很好。我可以将四元数应用于节点的变换矩阵,它会起作用。但是,其他一些模型会做一些奇怪的事情,比如围绕原点旋转节点,所以我认为我仍然缺少一些东西。如果我最终解决了这个问题,我将更新此部分以反映我的发现。

希望这可以帮助。如果我遗漏了什么,或者有什么不对的地方,任何人都请随时纠正我。我自己只是在学习这些东西。如果我发现任何错误,我将编辑答案。

另外,请注意我使用的是 Direct3D,因此我的矩阵乘法顺序可能与您的相反。您可能需要在我的答案中翻转某些操作的乘法顺序。

于 2020-07-26T07:59:32.050 回答