1

最近,我一直在使用 Assimp 库在 OpenGL 中加载和播放骨骼动画,遵循一些不同的教程/资源,例如OglDevEphenation OpenGL。我仍然不是 100% 确定我知道它是如何工作的,所以澄清一下:

  • 关键帧从aiNodeAnim's 的mChannels成员中加载aiAnimation
  • 这些关键帧用于生成每个骨骼矩阵,表示骨骼从其父骨骼的转换
  • 每个骨骼的矩阵乘以它的父矩阵
  • 然后将该矩阵乘以相应骨骼的偏移矩阵,从aiMesh->mBones[BONE]->mOffsetMatrix。这样,从关键帧生成的骨骼矩阵可以替换该骨骼的绑定姿势的默认变换
  • 绘制时在顶点着色器中使用这个最终矩阵(每个骨骼一个)

我的问题的第一部分:这是正确的吗?据我所知,假设我的理解是正确的,它应该在逻辑上起作用。我还没有看到任何反对这一点的来源/帖子/问题。

我已经编写了一个实现(在 C++ 中),但它并没有像我预期的那样工作。部分网格看起来像他们应该的样子,甚至处于预期的姿势,但大部分网格看起来都皱巴巴的。大部分是可区分的,我可以识别大部分网格,但似乎大多数骨骼矩阵都不正确。当我在搅拌机中打开我的模型时,所有关键帧都是正确的,并且模型动画完美。我会发布我的应用程序的屏幕截图,但我没有地方可以这样做(我没有 imageshack 帐户或任何东西。)

有什么理由为什么只有一些骨头会起作用而其他骨头不起作用?

以下是我的代码中最有问题的部分。我的jawSkeleton课程基于ModelLearnOpenGL.com 的课程。(忽略jaw前缀,这是我做的私人事情。)

类原型,在jawSkeleton.h

class jawSkeleton
{
public:
        /*  Functions   */
        // Constructor, expects a filepath to a 3D model.
    jawSkeleton(GLchar* path);
    jawSkeleton();

        // Draws the model, and thus all its meshes
    void Draw();

        /*  Functions   */
        // Loads a model with supported ASSIMP extensions from file and stores the resulting meshes in the meshes vector.
    void Load(std::string path, bool bSmoothNormals = false, UINT unFlags = NULL);

private:

    bool Loaded;

        /*  Model Data  */
    std::vector<jawSkeletalMesh> meshes;
    std::string directory;
        // Stores all the textures loaded so far, optimization to make sure textures aren't loaded more than once.
    std::vector<jawSub_Texture> textures_loaded;
    jawSkeletalBone* RootBone;
    std::vector<jawSkeletalBone> bones;
    glm::mat4 Matrices[64];
    glm::mat4 GlobalInverseTransform;

    std::vector<jawSkeletalAnim> Animations;

        // finds a bone in the model data with the specified name
    jawSkeletalBone* findBone(std::string Name);
        // finds a bone in the model data with the specified name (Must be 'int' so that we can use -1 to denote no bone)
    int findBoneIndex(std::string Name);

        // Processes a node in a recursive fashion.
        // Processes the bones identified by the meshes
    void processNodeMeshBones(aiNode* node, const aiScene* scene, std::vector<std::string> *BoneNames);
        // Populates the bone heirarchy structure
    void populateBoneHeirarchy(const aiScene* scene, std::vector<std::string> *BoneNames);
        //  Processes each individual mesh located at the node and repeats this process on its children nodes (if any).
    void processNodeMeshes(aiNode* node, const aiScene* scene);

    jawSkeletalMesh processMesh(aiMesh* mesh, const aiScene* scene);

        // Checks all material textures of a given type and loads the textures if they're not loaded yet.
        // The required info is returned as a jawSub_Texture struct.
    std::vector<jawSub_Texture> loadMaterialTextures(aiMaterial* mat, aiTextureType type, std::string typeName);

        // Load an animation for the model
    void processAnim(aiAnimation* Anim);

        // Updates the matrices for every bone
        // DOES NOT pull keyframes from the animation
        // Merely travels down the bone heirarchy and multiplies each child matrix by the parent matrix
    void UpdateBoneMatrices();
        // Updates the array of matrices that is sent to the shader
    void UpdateBoneMatrixArray();

        // Sets all the bone matrices to match a particular time
        // Before drawing, call UpdateBoneMatrices and UpdateBoneMatrixArray after calling this function
    void SetBoneMatrices(double Time);
};

加载关键帧数据,在jawSkeleton.cpp

void jawSkeleton::processAnim(aiAnimation* Anim) {
    this->Animations.push_back(jawSkeletalAnim());
    jawSkeletalAnim &_Anim = this->Animations.back();

    _Anim.Name = Anim->mName.C_Str();

    double TicksPerSecond = (Anim->mTicksPerSecond == 0.0) ? 1.0 : Anim->mTicksPerSecond;
    _Anim.Duration = Anim->mDuration / TicksPerSecond;

    for (GLuint i = 0; i < Anim->mNumChannels; i++) {
        aiNodeAnim* ThisAnim = Anim->mChannels[i];
        jawSkeletalBoneAnim BoneAnim;
        BoneAnim.Index = this->findBoneIndex(ThisAnim->mNodeName.C_Str());
        if (BoneAnim.Index > this->bones.size() || BoneAnim.Index < 0)
            continue;

        // Translation
        BoneAnim.TranslateKeys.reserve(ThisAnim->mNumPositionKeys);
        for (GLuint j = 0; j < ThisAnim->mNumPositionKeys; j++) {
            aiVector3D v = (ThisAnim->mPositionKeys + j)->mValue;
            BoneAnim.TranslateKeys.push_back(jawTranslateKey{
                glm::vec3(v.x, v.y, v.z),
                ThisAnim->mPositionKeys[j].mTime / TicksPerSecond
                });
        }

        // Rotation
        BoneAnim.RotateKeys.reserve(ThisAnim->mNumRotationKeys);
        for (GLuint j = 0; j < ThisAnim->mNumRotationKeys; j++) {
            aiQuaternion v = (ThisAnim->mRotationKeys + j)->mValue;
            BoneAnim.RotateKeys.push_back(jawRotateKey{
                glm::quat(v.w, v.x, v.y, v.z),
                ThisAnim->mRotationKeys[j].mTime / TicksPerSecond
            });
        }

        // Scaling
        BoneAnim.ScaleKeys.reserve(ThisAnim->mNumScalingKeys);
        for (GLuint j = 0; j < ThisAnim->mNumScalingKeys; j++) {
            aiVector3D v = (ThisAnim->mScalingKeys + j)->mValue;
            BoneAnim.ScaleKeys.push_back(jawScaleKey{
                glm::vec3(v.x, v.y, v.z),
                ThisAnim->mScalingKeys[j].mTime / TicksPerSecond
            });
        }

        // The BoneAnims member is an std::unordered_map
        _Anim.BoneAnims.insert(std::pair<GLuint, jawSkeletalBoneAnim>(BoneAnim.Index, BoneAnim));
    }
}

准备各个矩阵,在jawSkeletal.cpp

void jawSkeleton::SetBoneMatrices(double Time) {

    // This is the function to edit

    jawSkeletalAnim& Anim = this->Animations[0];

    for (GLuint i = 0; i < this->bones.size(); i++) {
        auto Result = Anim.BoneAnims.find(i);
        if (Result == Anim.BoneAnims.end())
            continue;
        jawSkeletalBoneAnim &BoneAnim = Result->second;

        glm::mat4 T = glm::translate(glm::mat4(1), BoneAnim.TranslateKeys[70].Value);


        glm::mat4 R = glm::toMat4(BoneAnim.RotateKeys[70].Value);
        glm::mat4 S = glm::scale(glm::mat4(1), BoneAnim.ScaleKeys[70].Value);

        this->bones[i].CurrentMatrix = T * R * S;
    }
}

我做了一些其他未显示的事情,例如递归地将每个骨骼的矩阵乘以其父矩阵,然后将它们乘以它们的偏移矩阵。当我使用单位矩阵代替单个骨骼矩阵时,网格是以其正常绑定姿势绘制的,所以我几乎可以肯定这不是网格导入问题。正如我所说,部分网格看起来是正确的,所以我猜这也不是蒙皮问题。我已经研究这个问题大约一个星期了。有什么线索吗?任何帮助表示赞赏。

4

0 回答 0