Assimp:手动操纵装配网格的骨骼

Assimp: Manipulate bones of rigged mesh manually

我正在从事一个具有以下目标的项目:

加载绑定网格和提取骨骼信息(希望如此)没有任何问题(基于本教程:http://www.richardssoftware.net/2013/10/skinned-models-in-directx-11-with.html)。每个骨骼(class "ModelBone")包含以下信息:

Assimp.Matrix4x4 LocalTransform
Assimp.Matrix4x4 GlobalTransform
Assimp.Matrix4x4 Offset

LocalTransform直接从assimp节点中提取出来(node.Transform)。

GlobalTransform 包括自己的 LocalTransform 和所有 parent 的 LocalTransform(参见代码片段 calculateGlobalTransformation())。

Offset是直接提取assimp骨(bone.OffsetMatrix).

目前我没有实现 GPU 顶点蒙皮,但我遍历每个顶点并操纵它的位置和法向量。

        foreach (Vertex vertex in this.Vertices)
        {
            Vector3D newPosition = new Vector3D();
            Vector3D newNormal = new Vector3D();

            for (int i=0; i < vertex.boneIndices.Length; i++)
            {
                int boneIndex = vertex.boneIndices[i];
                float boneWeight = vertex.boneWeights[i];

                ModelBone bone = this.BoneHierarchy.Bones[boneIndex];

                Matrix4x4 finalTransform = bone.GlobalTransform * bone.Offset;

                // Calculate new vertex position and normal
                newPosition += boneWeight * (finalTransform * vertex.originalPosition);
                newNormal += boneWeight * (finalTransform * vertex.originalNormal);
            }

            // Apply new vertex position and normal
            vertex.position = newPosition;
            vertex.normal = newNormal;
        }

正如我已经说过的,我想用 Kinect v2 传感器操纵骨骼,这样我就不必使用动画(例如插值关键帧,...)!但一开始我希望能够手动操作骨骼(例如,将网格的躯干旋转 90 度)。因此,我通过调用 Assimp.Matrix4x4.FromRotationX(1.5708f); 创建了一个 4x4 旋转矩阵(x-axis 周围 90 度)。然后我用这个旋转矩阵替换骨骼的 LocalTransform

Assimp.Matrix4x4 rotation = Assimp.Matrix4x4.FromRotationX(1.5708f);
bone.LocalTransform = rotation;

UpdateTransformations(bone);

在骨骼操作之后,我使用以下代码计算骨骼的新 GlobalTransform 并且它是 child 骨骼:

    public void UpdateTransformations(ModelBone bone)
    {
        this.calculateGlobalTransformation(bone);

        foreach (var child in bone.Children)
        {
            UpdateTransformations(child);
        }
    }

    private void calculateGlobalTransformation(ModelBone bone)
    {
        // Global transformation includes own local transformation ...
        bone.GlobalTransform = bone.LocalTransform;

        ModelBone parent = bone.Parent;

        while (parent != null)
        {
            // ... and all local transformations of the parent bones (recursively)
            bone.GlobalTransform = parent.LocalTransform * bone.GlobalTransform;
            parent = parent.Parent;
        }
    }

这种方法导致这种 image。转换似乎正确地应用于所有 child 骨骼,但是被操纵的骨骼围绕世界 space 原点旋转,而不是围绕它自己的本地 space :( 我已经尝试包括 GlobalTransform 将(GlobalTransform 的最后一行)翻译成旋转矩阵,然后将其设置为 LocalTransform,但没有成功...

希望有人body能帮我解决这个问题!

提前致谢!

要变换骨骼,您应该使用它的偏移矩阵: http://assimp.sourceforge.net/lib_html/structai_bone.html#a9ae5293b5c937436e4b338e20221cc2e 偏移矩阵从全局 space 转换为骨骼 space。如果您想围绕原点旋转骨骼,您应该:

  1. 转换为骨骼 space
  2. 应用旋转
  3. 转换为全局space

所以骨骼的全局变换可以这样计算:

bonesGlobalTransform = parentGlobalTransform *
        bone.offset.inverse() * 
        boneLocalTransform * 
        bone.offset;

所以:

  1. 使用偏移矩阵
  2. 转换为骨骼space
  3. 应用局部变换
  4. 使用 offset.inverse() 矩阵
  5. 转换为全局 space

最后我找到了解决方案:)所有计算都是正确的,除了:

Matrix4x4 finalTransform = bone.GlobalTransform * bone.Offset;

我的正确计算是:

Matrix4x4 finalTransform = bone.GlobalTransform * bone.Offset;
finalTransform.transpose();

看来是行优先/列优先的问题。我最终的 CPU 顶点蒙皮代码是:

    public void PerformSmoothVertexSkinning()
    {
        // Precompute final transformation matrix for each bone
        List<Matrix4x4> FinalTransforms = new List<Matrix4x4>();

        foreach (ModelBone bone in this.BoneHierarchy.Bones)
        {
            // Multiplying a vector (e.g. vertex position/normal) by finalTransform will (from right to left):
            //      1. transform the vector from mesh space to bone space (by bone.Offset)
            //      2. transform the vector from bone space to world space (by bone.GlobalTransform)
            Matrix4x4 finalTransform = bone.GlobalTransform * bone.Offset;
            finalTransform.Transpose();

            FinalTransforms.Add(finalTransform);
        }

        foreach (Submesh submesh in this.Submeshes)
        {
            foreach (Vertex vertex in submesh.Vertices)
            {
                Vector3D newPosition = new Vector3D();
                Vector3D newNormal = new Vector3D();

                for (int i = 0; i < vertex.BoneIndices.Length; i++)
                {
                    int boneIndex = vertex.BoneIndices[i];
                    float boneWeight = vertex.BoneWeights[i];

                    // Get final transformation matrix to transform each vertex position
                    Matrix4x4 finalVertexTransform = FinalTransforms[boneIndex];

                    // Get final transformation matrix to transform each vertex normal (has to be inverted and transposed!)
                    Matrix4x4 finalNormalTransform = FinalTransforms[boneIndex];
                    finalNormalTransform.Inverse();
                    finalNormalTransform.Transpose();

                    // Calculate new vertex position and normal (average of influencing bones)
                    // Formula: newPosition += boneWeight * (finalVertexTransform * vertex.OriginalPosition);
                    //                      += boneWeight * (bone.GlobalTransform * bone.Offset * vertex.OriginalPosition);
                    // From right to left:
                    //      1. Transform vertex position from mesh space to bone space (by bone.Offset)
                    //      2. Transform vertex position from bone space to world space (by bone.GlobalTransform)
                    //      3. Apply bone weight
                    newPosition += boneWeight * (finalVertexTransform * vertex.OriginalPosition);
                    newNormal += boneWeight * (finalNormalTransform * vertex.OriginalNormal);
                }

                // Apply new vertex position and normal
                vertex.Position = newPosition;
                vertex.Normal = newNormal;
            }
        }
    }

希望这篇文章对其他人有所帮助。感谢谢尔盖的帮助!