蒙皮动画-权重破坏网格

时间:2020-06-20 16:54:41

标签: c++ animation opengl graphics 3d

我正在用自己的Collada解析器编写动画系统,并且遇到了一个无法解决的问题。

我收集了网格/皮肤信息(顶点,法线,jointIds,权重等),我的骨骼信息(关节,它们的局部变换,反向绑定位置,层次结构) 和我的动画(每个关节的关键帧变换位置,时间戳)

我的问题是计算并随后在着色器中实现了所有内容(权重的总和乘以联合变换和顶点位置)-我得到以下信息:

enter image description here

当我删除权重乘法时,网格保持完整无缺-但是皮肤实际上并没有跟随动画。我感到好像数学是正确的一样迷茫,但是很显然我在某个地方出错了。有人可以照亮我误解的方面吗?

这是我目前的理解和实施方式:

  1. 收集了关节的所有localTransforms和层次结构之后,我计算了它们的逆绑定转换矩阵。为此,我将每个关节的localTransform与其parentLocalTransform进行倍增,以获得bindTransform。反转bindTransform会导致其inverseBindTransform。下面是我的代码:
    // Recursively collect each Joints InverseBindTransform - 
    // root joint's local position is an identity matrix. 
    // Function is only called once after data collection.
    void Joint::CalcInverseBindTransform(glm::mat4 parentLocalPosition)
    {
        glm::mat4 bindTransform = parentLocalPosition * m_LocalTransform;
        m_InverseBindPoseMatrix = glm::inverse(bindTransform);

        for (Joint child : Children) {
            child.CalcInverseBindTransform(bindTransform);
        }
    }
  1. 在动画制作过程中,在动画师内部,对于每个关节,我为两个帧的currentTime介于两者之间取两个JointTransforms,并计算插值的JointTransform。 (JointTransform仅具有用于位置的vec3和用于旋转的四元数)。我对每个关节都执行此操作,然后通过将其插值再次通过其parentLocalTransform递归地将新的frameLocalTransform多重化,将其应用于每个关节。我将那个bindTransform乘以invBindTransform,然后转置矩阵。以下是该代码:
    std::unordered_map<int, glm::mat4> Animator::InterpolatePoses(float time) {

        std::unordered_map<int, glm::mat4> poses;
        if (IsPlaying()) {

            for (std::pair<int, JointTransform> keyframe : m_PreviousFrame.GetJointKeyFrames()) {

                JointTransform previousFrame = m_PreviousFrame.GetJointKeyFrames()[keyframe.first];
                JointTransform nextFrame = m_NextFrame.GetJointKeyFrames()[keyframe.first];
                JointTransform interpolated = JointTransform::Interpolate(previousFrame, nextFrame, time);
                poses[keyframe.first] = interpolated.getLocalTransform();
            }
        }
        return poses;
    }

    void Animator::ApplyPosesToJoints(std::unordered_map<int, glm::mat4> newPose, Joint* j, glm::mat4 parentTransform) 
    {
        if (IsPlaying()) {

            glm::mat4 currentPose = newPose[j->GetJointId()];
            glm::mat4 modelSpaceJoint = parentTransform * currentPose;

            for (Joint child : j->GetChildren()) {
                ApplyPosesToJoints(newPose, &child, modelSpaceJoint);
            }

            modelSpaceJoint = glm::transpose(j->GetInvBindPosition() * modelSpaceJoint);
            j->SetAnimationTransform(modelSpaceJoint);
        }
    }
  1. 然后我为每个关节收集所有新的AnimatedTransforms并将它们发送到着色器:
    void AnimationModel::Render(bool& pass)
    {
                    [...]
        std::vector<glm::mat4> transforms = GetJointTransforms();
        for (int i = 0; i < transforms.size(); ++i) {
            m_Shader->SetMat4f(transforms[i], ("JointTransforms[" + std::to_string(i) + "]").c_str());
        }
                    [...]
    }

    void AnimationModel::AddJointsToArray(Joint current, std::vector<glm::mat4>& matrix)
    {
        glm::mat4 finalMatrix = current.GetAnimatedTransform();
        matrix.push_back(finalMatrix);
        for (Joint child : current.GetChildren()) {
            AddJointsToArray(child, matrix);
        }
    }
  1. 在着色器中,我只是遵循在研究此主题时可以在网上找到的求和公式:
        for (int i = 0; i < total_weight_amnt; ++i) {
            mat4 jointTransform = JointTransforms[jointIds[i]];

            vec4 newVertexPos = jointTransform * vec4(pos, 1.0);
            total_pos += newVertexPos * weights[i];
                        [...]

----------回复标准化权重------------

在1以上有一些权重之和,但是在解决了我的代码中的错误之后,该模型如下所示:

enter image description here

用于计算权重-我遍历向量中所有预加的权重,如果发现的权重小于要添加的权重,则在该位置替换该权重。否则,我将权重附加到向量的末尾。如果向量中的权重小于指定的max_weights(即4),则我将剩余的权重/关节ID填充为0。

1 个答案:

答案 0 :(得分:0)

我了解蒙皮动画出现问题时,可能在不同区域出现 alot 问题。因此,对于将来遇到相同问题的Google员工-可以将其更多地看作是您可能做错了而不是绝对做错了。 / p>

对于我的问题-在许多小领域,我有正确的主意,但方法错误。距离我很近,但正如他们所说,没有雪茄。

  1. 我不需要自己计算逆绑定姿势,Collada的逆绑定姿势(有时/通常被声明为“ offsetMatrix”)非常完美。这不是问题,因为我只是在进行不必要的计算。

  2. 在Collada文件中,它们通常为您提供的层次结构中的“关节”或“节点”比动画所需的更多。在开始实际的动画“关节”之前,有场景和初始骨架“节点”类型。场景通常是一个身份矩阵,在读取Collada文件时会根据您的“上轴”对其进行操作。 Node类型将确定骨骼中每个关节的整体大小-因此,如果不调整大小,则可能是单位矩阵。确保您的层次结构仍然包含层次结构中列出的 ALL 个节点/关节。我非常不这样做,这极大地扭曲了我的globalPosition(BindPose)。

    enter image description here

  3. 如果要通过四元数表示“关节”的变换旋转(强烈建议使用),请确保在两个旋转位置之间进行插值后将生成的四元数归一化。 同样,将“旋转”和“变换”合并到最终矩阵中时,请确保乘法顺序和最终输出正确。

  4. 最后-您的最后一个外观矩阵由关节InvBindMatrix * GlobalPosition * GlobalInverseRootTransform组成(<-这是(1)中提到的“场景”节点的局部变换的逆函数,还记得吗?)。 根据到目前为止的先前矩阵乘法,您可能需要转置最终矩阵。

并以此-我能够成功为模型制作动画!

最后一个注意事项-我的网格物体和动画文件是分别添加的。如果动画与网格物体位于不同的文件中,请确保从具有动画的文件而不是具有网格物体的文件中收集蒙皮/关节信息。我列出了加载模型的步骤,然后通过不同的文件为它提供了多个动画:

  1. 在网格中加载(其中包含顶点,法线,TexCoords,JointIds,Weights)
  2. 加载动画文件(这提供了Skeleton,InverseBindPositions和其他将骨骼绑定到网格所需的信息)-收集骨骼和绑定信息后,还要从该文件中收集第一个动画信息。
  3. 对于另一个动画,上述骨架应该可以在同一网格/模型上的其他任何动画正常工作-只需读取动画信息并存储在您选择的数据结构中即可。重复步骤3,直到开心为止。