Assimp:手动操纵绑定网格的骨骼

Posted

技术标签:

【中文标题】Assimp:手动操纵绑定网格的骨骼【英文标题】:Assimp: Manipulate bones of rigged mesh manually 【发布时间】:2016-04-18 14:19:09 【问题描述】:

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

使用 Assimp.NET 加载装配好的 3D 网格(例如人体骨骼) 处理网格骨骼,使其适合您自己的身体(使用 Microsoft Kinect v2) 执行顶点蒙皮

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

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

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

GlobalTransform 包括自己的LocalTransform 和所有父母的LocalTransform(见代码截断calculateGlobalTransformation())。

Offset 直接从 assimp bone (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 轴 90 度)。然后我用这个旋转矩阵替换骨骼的LocalTransform

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

UpdateTransformations(bone);

骨骼操作后,我使用以下代码计算骨骼的新 GlobalTransform 及其子骨骼:

    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。转换似乎正确地应用于所有子骨骼,但被操纵的骨骼围绕世界空间原点而不是围绕其自己的局部空间旋转:(我已经尝试将GlobalTransform 翻译(GlobalTransform 的最后一行)包含到之前的旋转矩阵设置为LocalTransform,但是没有成功...

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

提前致谢!

【问题讨论】:

【参考方案1】:

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

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;
            
        
    

希望这个帖子对其他人有所帮助。感谢您的帮助谢尔盖!

【讨论】:

【参考方案2】:

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

    转换到骨骼空间 应用轮换 转换到全局空间

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

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

所以:

    使用偏移矩阵转换到骨骼空间 应用局部变换 使用 offset.inverse() 矩阵转换到全局空间

【讨论】:

嗨。我认为 calculateGlobalTransformation 和顶点蒙皮都不正确您应该在 calculateGlobalTransformation 中使用偏移矩阵,并且不应该在顶点蒙皮中使用它 嗨,谢尔盖,我很高兴你能回复我。几乎放弃了这个话题。自从我上一篇文章以来,我改变了两件小事(矩阵乘法顺序)。所以我已经更新了我的初始帖子(参见calculateGlobalTransformation 和顶点蒙皮代码),它现在反映了我当前的代码。我很确定GlobalTransform 的计算是正确的,因为当在我的视口中将每个骨骼的GlobalTransform 的平移部分显示为点时,它会产生这个图像:goo.gl/ncBSF4。所以我认为问题在于剥皮(例如错误的FinalTransform)。 在我的回答中你可以看到骨骼的 GlobalTransform 的计算。你不需要 FinalTransform。您应该使用 GlobalTransform 来转换顶点。像这样:resultVertexTransformation += m_bonesGlobalTransforms[boneIdx] * boneWeight; 不幸的是,这是我第一次阅读。例如,在mathinfo.univ-reims.fr/image/dxMesh/extra/d3dx_skinnedmesh.pdf 的第 13 页上,您可以看到FinalTransform = OffsetMatrix * CombinedMatrix,其中CombinedMatrixGlobalTransform。并且graphics.ucsd.edu/courses/cse169_w05/3-Skin.htm 在formula (3.3) 中显示蒙皮涉及骨骼的 GlobalTransform 和“反向绑定姿势矩阵”(= 偏移矩阵)。所以我现在有点困惑。您是否有包含代码的参考项目以便更好地理解? 如果您有兴趣,我也想与您分享我的代码 (1drv.ms/1NFMKWt)。兴趣点是: 1) 类“/Helpers/BoneHierarchy.cs”中的方法calculateGlobalTransformation 2) 类“/Helpers/SkinnedModel”中的方法PerformVertexSkinning 3) 要更改骨骼的旋转,请参见类的构造函数“ /ViewModels/MonitoringViewModel.cs"

以上是关于Assimp:手动操纵绑定网格的骨骼的主要内容,如果未能解决你的问题,请参考以下文章

带有骨骼的 Assimp + COLLADA 模型 = 不正确的顶点位置

如何使用 assimp 在 C++ 中旋转蒙皮模型的骨骼?

5笔涂出一只3D猫咪模型,可跑可跳无需手动绑定骨骼,新鬼畜素材get丨浙大&开源...

渲染在 OpenGL 中使用 Assimp 加载的动画模型时折叠的网格

Assimp 动画骨骼变换

Skeleton with Assimp 骨骼动画解析