皮肤矩阵中glTF的Inverse(GlobalTransform)的含义

问题描述

我想用骨架动画渲染网格。在制作动画之前,我只想使用动画的第一个关键帧渲染网格,即渲染具有适当骨骼层次变换的网格。我忽略了 glTF 中的场景结构;我只是使用 meshes[0] 获取网格并使用 skins[0] 获取其骨架。

我知道最终的 skin 矩阵是作为uniforms 提供给顶点着色器的计算

for (bone in bones) {
  bone.skin_xform = inverse(global_xform) * bone.global_xform * bone.inv_bind_xform;
}

当我完全这样做时,我看到我的模型在地面以下 11.4 (5.7 + 5.7) 个单位(Z = 0 处的平面;世界上有 +Z)。当我只渲染没有任何蒙皮的网格时,即只有位置、法线和纹理坐标,它位于地面上。我还能够推断出为什么在蒙皮时会发生这种情况。

这是 gltf

的相关部分
    "skins" : [
        {
            "inverseBindMatrices" : 6,"joints" : [
                0,...
        }
    ],"nodes" : [
        {
            "name" : "Root","rotation" : [
                0,1,0
            ],"translation" : [
                0,-5.709875583648682
            ]
        },{
            "mesh" : 0,"name" : "Body","skin" : 0
        },{
            "children" : [
                0,1
            ],"name" : "Armature",5.709875583648682
            ]
        }
    ]

我读过 glTF 的 documentationtutorialreference guide (PDF)。虽然文档根本没有提及它,但以下是教程和参考指南对 inverse(global_xform) 的说明:

顶点必须使用网格所附加节点的全​​局变换的进行变换,因为这种变换已经使用模型-视图-矩阵完成,因此必须从蒙皮计算中取消。

据此,Bodyglobal 转换必须反转并使用。这导致 translateZ(-5.7)。 Root 已经有 translateZ(-5.7) 的局部变换,所以我理解网格到地面的 -11.4 偏移。但是,如果我按原样使用 Body 的全局变换,没有反转,则在上述公式中没有问题。

为什么参考指南要求我们反转根骨骼父级的全局变换?我错过了什么?当我从 Blender 导入这个模型时,我注意到 armature 对象上的变换确实是 translateZ(5.7)

解决方法

你说

我忽略了 glTF 中的场景结构;我只是使用 meshes[0] 来获取网格和 skins[0] 来获取它的骨架。

然而,(相关部分)标准说(强调我的)

顶点必须使用网格所附加节点的全​​局变换的逆变换,因为这种变换已经使用模型-视图-矩阵完成

既然您说要从 glTF 中去除网格和骨架,则不需要场景结构 inverse(global_xform)。这是因为您的网格的 model-view-matrix 没有用于 inverse(global_xform) 的非标识变换来抵消偏移。使用three.js 可以正常工作,因为它渲染整个场景及其所有节点层次结构,这与您的不同。

但是,如果我按原样使用 Body 的全局变换,没有反转,则在上述公式中没有问题。

这是正确的用法,因为 Root 的全局变换是其所有父变换与 Root 的局部变换的串联

root.global_xform = armature.local_xform * body.local_xform * root.local_xform

根据您的评论,我看到骨架对象的位置具有非同一性变换。通常作为身份更好; reference: a tutorial on skeletal animation by TheThinMatrix


这里有更详细的计算说明。我们看到 Root 节点具有 TranslateZ(-5.7) 的局部变换;它的父节点,Armature 节点,具有 TranslateZ(5.7) 的局部变换;骨架没有其他父母。因此 Root 的全局变换实际上是身份。这是蒙皮顶点着色器中的方程

point’ = P * V * Mesh *              Skin                  * point
point’ = P * V * Mesh * (InvMeshGlobal * Global * InvBind) * point
                  5.7 * (-5.7 * 5.7 * -5.7 * InvBind)
                  5.7 * (-5.7 * I * InvBind)

所以 InvGlobalXform(上面写为 InvMeshGlobal)仅在渲染整个场景层次结构时才需要,而您刚刚获得网格及其骨架,忽略节点之外的祖先节点 {{1 }} 和 mesh 存在。我可以想出两个解决方案。

解决方案 1

  • 在导出之前,确保网格和骨架在 Blender 中应用了 LocationRotationScale
  • skin 及其父 Root 本地变换都将成为身份,即 root 的全局变换将成为身份
  • 忽略 ArmatureMesh 转换
  • 整个等式只需要 InvMeshGlobal

解决方案 2

  • 存储InvBind的祖先变换
  • 使用祖先变换来达到Root的适当全局变换;通常成为身份
  • 忽略 RootMesh 转换
  • InvMeshGlobalGlobal 需要在等式中

解决方案(1)仅在输入网格和骨架没有全局旋转或平移时有效;解决方案 (2) 也适用于此。与解决方案(1)不同,解决方案(2)需要存储祖先变换。