关于c#:了解OpenGL引擎的GLTF2.0文件的外观部分

Understanding the skinning part of a GLTF2.0 file for OpenGL engine

我有一个简单的混合器模型,该模型由三个网格和三个控制每个网格的骨骼组成。动画只是骨骼绕y轴旋转一点,然后向后旋转。中心骨骼是两个外部骨骼的父对象。

Blender screenshot

然后,我使用GLTF2.0(文本版本)导出插件导出该场景,现在正尝试将其导入到我新制作的opengl引擎(C#xamarin android)中。

由于我想完全了解OpenGL中的GLTF2.0格式和骨骼动画,因此我尝试实现自己读取GLTF2.0。

我读:

  • https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_020_Skins.md
  • https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/README.md
  • https://kcoley.github.io/glTF/specification/2.0/figures/gltfOverview-2.0.0a.png

显示网格物体很容易,但是现在我仍然无法制作动画。
在我的gltf文件中,我看到了三种外观:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
"skins" : [
    {
       "inverseBindMatrices" : 21,
       "joints" : [
            4,
            5,
            6
        ],
       "skeleton" : 0
    },
    {
       "inverseBindMatrices" : 22,
       "joints" : [
            4,
            5,
            6
        ],
       "skeleton" : 0
    },
    {
       "inverseBindMatrices" : 23,
       "joints" : [
            4,
            5,
            6
        ],
       "skeleton" : 0
    }
]

这让我感到困惑,因为我对所有网格都拥有一个骨骼结构,而对于每个网格却没有三个骨骼。
我以为我会在类实例(例如Bone.cs)中收集所有骨骼,每个骨骼都有子骨骼列表和其父骨骼字段。然后,我将在实例(Animation.cs类)中收集动画,每个动画实例将具有一个关键帧列表,其中包含给定时间戳的旋转,缩放和平移。
然后将动画时间戳记设置为2.5秒,然后查找该时间戳记的最近两个关键帧,并插入这些关键帧的旋转,缩放,平移。

实际问题

  • 为什么会有三张皮肤?为什么将inverseBindMatrices绑定到皮肤而不是关节?
  • 从关键帧(每个骨骼)进行正确的旋转,缩放,平移后,如何计算需要传递给顶点着色器的每个骨骼的矩阵?
  • 文件中的每个骨骼节点都有自己的旋转,平移,缩放值,但没有矩阵。这是为什么?没有缺少什么吗?
  • gltf文件将骨骼(关节)作为节点ID引用,但是作为属性传递给着色器的weights / jointId-Arrays与这些骨骼ID不匹配:jointIds-Array包含即骨骼的0,1,2 id,但骨骼位于节点4,5,6中-如何为传递给着色器的每个jointId找到正确的骨骼?

我希望你能帮助我。亲切的问候!!
如果需要,我可以提供更多代码,但是我认为,如果我整体上理解该主题,那么我自己可以做到。

编辑

GLTF示例模型下载

编辑#2

好吧,我想我慢慢地掌握了它。

  • 对于每个受电枢控制的网格,文件中都有一个蒙皮。我认为每个网格都需要有一个反向绑定矩阵,以便能够将网格转换为骨骼空间(如果需要,还可以转换为骨骼空间)。

  • 我仍然不知道如何正确地将最终转换传递给着色器。

  • 这一点仍然使我难以理解。

  • 由于每个蒙皮都有三个(或最多4个)关节的列表,因此这些关节需要将最终的变换传递到顶点着色器。如果您有8个关节,但是当前要绘制的网格仅受到其中4个的影响,那么为什么要传递所有8个矩阵而不是只传递所需的4个矩阵。

这一切仍然令人怀疑。也许这可以帮助其他人。


我正在尝试一个接一个地解决您的问题

  • Why are there three skins? Why is the inverseBindMatrices bound to a skin and not to a joint?

您已经发现,每个网格只有一个蒙皮。在您的特定情况下,您可以将所有三个网格合并为一个,这实际上并没有限制此一般原理。然而

I think there needs to be an inverse bind matrix for each mesh in order to be able to transform the mesh to bone space (and - if need be - back).

每个网格的每个关节都有一个反向绑定矩阵。由于某种原因,该属性的名称为inverseBindMatrices复数,并且它引用了bufferview,而后者又引用了buffer中的某些数据。

在这里更改问题的顺序,因为这样更有意义:

Every bone node in the file has its own rotation, translation, scale values but no matrix. Why is that? Isn't there something missing?

你还需要什么?每个仿射变换都可以分解为平移,旋转和缩放,因此数据是完整的。 glTF规范定义了所得矩阵应按T*R*S顺序进行计算。

  • When I have the right rotation, scaling, translation from a key frame (per bone), how do I calculate the matrices for each bone that I need to pass to my vertex shader?

对于每个骨骼节点i,您可以将局部变换计算为M_local(i) = T(i)*R(i)*S(i)。您将通过应用完整的层次结构获得联合矩阵,因此基本上是M_global(i) = M_global(parent(i)) * M_local(i),然后可以将联合矩阵构造为M_joint(i) = inverse(globalTransform) * M_global(i) * inverseBindMatrix(i)

  • The gltf file referse to a bone (joint) as a node id, but the weights/jointId-Arrays that get passed as attributes to the shader do not match these bone ids: jointIds-Array contains i.e. 0,1,2 for the bone ids, but the bones are in nodes 4,5,6 - how do I find the right bone for each jointId passed to the shader?

jointIds数组包含对关节的引用,而不是骨骼(因此得名)的引用。蒙皮着色器根本不关心骨骼,骨骼所做的一切只是在此处定义了关节的层次,因此它们会影响M_global矩阵的实际值,从而也影响M_joint矩阵的实际值。第i个条目仅引用相应皮肤的joints数组中的第i个关节,因此需要M_joint(i)

  • Since every Skin has a list of three (or max. 4) Joints, these are the Joints of which the final transformations need to be passed to the vertex shader.

为什么将skin限制为4个关节。皮肤可以拥有一个喜欢的关节。

If you have 8 Joints but the current to-be-drawn Mesh only gets affected by 4 of them, why should you pass all 8 matrices instead of only the 4 you need.

为什么要为只需要四个的网格定义8个骨骼的外观? glTF数据格式不会阻止您存储无关信息或以低效方式存储信息。

这里要注意的一点是,关节之间的层次仍然由skeleton的骨骼节点层次定义。因此,您可以在单个skin中忽略任意关节,但是这些骨骼节点(以及它们的潜在动画效果)仍然可以影响最终的关节矩阵-对于由"遗漏"骨骼下方的骨骼定义的任何关节在骨骼骨骼层次上。