You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Assimp加载模型时骨骼动画出现拉伸与部件丢失问题

问题描述
  • 自制C++游戏引擎实现了骨骼动画功能,多数场景运行正常,简单带动画的模型可正常播放;但从Blender导出的某FBX模型播放动画时,部分网格会莫名被拉伸至原点,且头发完全消失。
  • 该模型导入Blender后动画播放正常,但在引擎中仅当不启用动画时才无异常。
  • 通过RenderDoc观察发现,模型中绑定到单根骨骼的独立网格(头发和舌头)被拉伸至原点,头发甚至完全无法被识别。
相关代码

Model类代码

class Model
{
  public:
    // model data
    std::vector<Texture>
        textures_loaded; // stores all the textures loaded so far,
                         // optimization to make sure textures aren't
                         // loaded more than once.
    std::vector<Mesh>               meshes;
    std::string                     directory;
    std::vector<float>              colliderVertices;
    std::vector<uint32_t>           colliderIndices;
    bool                            gammaCorrection;
    bool                            extractTexture = true;
    std::map<std::string, BoneInfo> boneInfoMap;
    int                             boneCounter = 0;

    // constructor, expects a filepath to a 3D model.
    Model(std::string const& path, bool gamma = false,
          bool extractTexture = true)
       : gammaCorrection(gamma), extractTexture(extractTexture)
    {
        LoadModel(path);
    }

    Model() {}

    // draws the model, and thus all its meshes
    void Draw(Shader& shader)
    {
        for (unsigned int i = 0; i < meshes.size(); i++)
            meshes[i].Draw(shader);
    }

  private:
    // loads a model with supported ASSIMP extensions from file and
    // stores the resulting meshes in the meshes vector.
    void LoadModel(std::string const& path)
    {
        // read file via ASSIMP
        Assimp::Importer importer;
        const aiScene*   scene = importer.ReadFile(
            path, aiProcess_Triangulate | aiProcess_GenSmoothNormals |
                      aiProcess_FlipUVs | aiProcess_CalcTangentSpace |
                      aiProcess_LimitBoneWeights);
        // check for errors
        if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE ||
            !scene->mRootNode) // if is Not Zero
        {
            std::cout << "ERROR::ASSIMP:: "
                      << importer.GetErrorString() << std::endl;
            return;
        }
        // retrieve the directory path of the filepath
        directory = path.substr(0, path.find_last_of('/'));

        // process ASSIMP's root node recursively
        ProcessNode(scene->mRootNode, scene);
    }

    // processes a node in a recursive fashion. Processes each
    // individual mesh located at the node and repeats this process on
    // its children nodes (if any).
    void ProcessNode(aiNode* node, const aiScene* scene,
                     glm::mat4 parentTransform = glm::mat4(1.0f))
    {
        glm::mat4 nodeTransform =
            parentTransform *
            ConvertMatrixToGLMFormat(node->mTransformation);

        for (unsigned int i = 0; i < node->mNumMeshes; i++)
        {
            aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
            meshes.push_back(processMesh(mesh, scene, nodeTransform));
        }
        for (unsigned int i = 0; i < node->mNumChildren; i++)
        {
            ProcessNode(node->mChildren[i], scene, nodeTransform);
        }
    }

    void SetVertexBoneDataToDefault(Vertex& vertex)
    {
        for (int i = 0; i < MAX_BONE_INFLUENCE; i++)
        {
            vertex.BoneIDs[i] = -1;
            vertex.Weights[i] = 0.0f;
        }
    }

    Mesh processMesh(aiMesh* mesh, const aiScene* scene, 
            const glm::mat4& nodeTransform)
    {
        std::vector<Vertex>       vertices;
        std::vector<unsigned int> indices;
        std::vector<Texture>      textures;

        for (unsigned int i = 0; i < mesh->mNumVertices; i++)
        {
            Vertex vertex;
            SetVertexBoneDataToDefault(vertex);

            glm::vec4 transformedPos = nodeTransform * glm::vec4(
                mesh->mVertices[i].x, mesh->mVertices[i].y, 
                mesh->mVertices[i].z, 1.0f);
            vertex.Position = glm::vec3(transformedPos);
            
            glm::mat3 normalMatrix = 
                glm::transpose(glm::inverse(glm::mat3(nodeTransform)));
            vertex.Normal = glm::normalize(normalMatrix * 
                    glm::vec3(mesh->mNormals[i].x, mesh->mNormals[i].y, 
                        mesh->mNormals[i].z));

            if (mesh->mTextureCoords[0])
            {
                glm::vec2 vec;
                vec.x = mesh->mTextureCoords[0][i].x;
                vec.y = mesh->mTextureCoords[0][i].y;
                vertex.TexCoords = vec;
            }
            else
            {
                vertex.TexCoords = glm::vec2(0.0f, 0.0f);
            }

            if (mesh->mTangents)
            {
                vertex.Tangent = glm::vec3(mesh->mTangents[i].x,
                                           mesh->mTangents[i].y,
                                           mesh->mTangents[i].z);
                vertex.Bitangent = glm::vec3(mesh->mBitangents[i].x,
                                             mesh->mBitangents[i].y,
                                             mesh->mBitangents[i].z);
            }

            vertices.push_back(vertex);
        }

        for (unsigned int i = 0; i < mesh->mNumFaces; i++)
        {
            aiFace face = mesh->mFaces[i];
            for (unsigned int j = 0; j < face.mNumIndices; j++)
                indices.push_back(face.mIndices[j]);
        }

        ExtractBoneWeightForVertices(vertices, mesh, scene);

        return Mesh(vertices, indices, textures);
    }

    void SetVertexBoneData(Vertex& vertex, int boneID, float weight)
    {
        for (int i = 0; i < MAX_BONE_INFLUENCE; ++i)
        {
            if (vertex.BoneIDs[i] < 0)
            {
                vertex.Weights[i] = weight;
                vertex.BoneIDs[i] = boneID;
                break;
            }
        }
    }

    void ExtractBoneWeightForVertices(std::vector<Vertex>& vertices,
                                      aiMesh*              mesh,
                                      const aiScene*       scene)
    {
        assert(mesh->mNumVertices == vertices.size());

        for (int boneIndex = 0; boneIndex < (int)mesh->mNumBones;
             ++boneIndex)
        {
            int         boneID = -1;
            std::string boneName =
                mesh->mBones[boneIndex]->mName.C_Str();

            // Check if the bone is already in the map
            if (boneInfoMap.find(boneName) == boneInfoMap.end())
            {
                BoneInfo newBoneInfo;
                newBoneInfo.id = boneCounter;
                newBoneInfo.offset = ConvertMatrixToGLMFormat(
                    mesh->mBones[boneIndex]->mOffsetMatrix);
                boneInfoMap[boneName] = newBoneInfo;
                boneID = boneCounter;
                boneCounter++;
            }
            else
            {
                boneID = boneInfoMap[boneName].id;
            }

            assert(boneID != -1);

            auto weights = mesh->mBones[boneIndex]->mWeights;
            int  numWeights = mesh->mBones[boneIndex]->mNumWeights;

            for (int weightIndex = 0; weightIndex < numWeights;
                 ++weightIndex)
            {
                assert(weightIndex != -1);

                int   vertexId = weights[weightIndex].mVertexId;
                float weight = weights[weightIndex].mWeight;

                // Ensure the vertexId is valid
                assert(vertexId <= vertices.size());
                SetVertexBoneData(vertices[vertexId], boneID, weight);
            }
        }
    }

    std::vector<Texture> loadMaterialTextures(aiMaterial*    mat,
                                              aiTextureType  type,
                                              std::string    typeName,
                                              const aiScene* scene);
};

Animation类代码

struct AssimpNodeData
{
    glm::mat4                   transformation;
    std::string                 name;
    int                         childrenCount;
    std::vector<AssimpNodeData> children;
};

class Animation
{
  public:
    Animation() = default;

    Animation(const std::string& animationPath, ModelRenderer* model);

    ~Animation();

    Bone* FindBone(const std::string& name);

    float GetTicksPerSecond();

    float GetDuration();

    const AssimpNodeData& GetRootNode();

    const std::map<std::string, BoneInfo>& GetBoneIDMap();
    
    glm::mat4                       globalInverseTransform;

  private:
    void ReadMissingBones(const aiAnimation* animation, Model& model);

    void ReadHierarchyData(AssimpNodeData& dest, const aiNode* src);

    float                           duration;
    int                             ticksPerSecond;
    std::vector<Bone>               bones;
    AssimpNodeData                  rootNode;
    std::map<std::string, BoneInfo> boneInfoMap;
};

Animation::Animation(const std::string& animationPath,
                     ModelRenderer*     model)
{
    Assimp::Importer importer;
    const aiScene*   scene = importer.ReadFile(
        animationPath, aiProcess_Triangulate | aiProcess_FlipUVs);
    assert(scene && scene->mRootNode);
    auto animation = scene->mAnimations[0];
    duration = (float)animation->mDuration;
    ticksPerSecond = (int)animation->mTicksPerSecond;
    aiMatrix4x4 globalTransformation =
        scene->mRootNode->mTransformation;
    globalTransformation = globalTransformation.Inverse();
    globalInverseTransform =
        ConvertMatrixToGLMFormat(globalTransformation);
    ReadHierarchyData(rootNode, scene->mRootNode);
    ReadMissingBones(animation, model->model);
    boneInfoMap = model->model.boneInfoMap;
}

Animation::~Animation()
{
}

Bone* Animation::FindBone(const std::string& name)
{
    auto iter =
        std::find_if(bones.begin(), bones.end(), [&](const Bone& Bone)
                     { return Bone.GetBoneName() == name; });
    if (iter == bones.end())
        return nullptr;
    else
        return &(*iter);
}

float Animation::GetTicksPerSecond()
{
    return (float)ticksPerSecond;
}

float Animation::GetDuration()
{
    return duration;
}

const AssimpNodeData& Animation::GetRootNode()
{
    return rootNode;
}

const std::map<std::string, BoneInfo>& Animation::GetBoneIDMap()
{
    return boneInfoMap;
}

void Animation::ReadMissingBones(const aiAnimation* animation,
                                 Model&             model)
{
    int size = animation->mNumChannels;

    auto& lboneInfoMap =
        model.boneInfoMap; // getting boneInfoMap from Model class

    int& boneCount = model.boneCounter; // getting the m_BoneCounter
                                        // from Model class

    // reading channels(bones engaged in an animation and their
    // keyframes)
    for (int i = 0; i < size; i++)
    {
        auto        channel = animation->mChannels[i];
        std::string boneName = channel->mNodeName.data;

        if (lboneInfoMap.find(boneName) == lboneInfoMap.end())
        {
            lboneInfoMap[boneName].id = boneCount;
            lboneInfoMap[boneName].offset = glm::mat4(1.0f);
            boneCount++;
        }
        bones.push_back(Bone(channel->mNodeName.data,
                             lboneInfoMap[channel->mNodeName.data].id,
                             channel));
    }

    boneInfoMap = lboneInfoMap;
}

void Animation::ReadHierarchyData(AssimpNodeData& dest,
                                  const aiNode*   src)
{
    assert(src);

    dest.name = src->mName.data;
    dest.transformation =
        ConvertMatrixToGLMFormat(src->mTransformation);
    dest.childrenCount = src->mNumChildren;

    for (int i = 0; i < (int)src->mNumChildren; i++)
    {
        AssimpNodeData newData;
        ReadHierarchyData(newData, src->mChildren[i]);
        dest.children.push_back(newData);
    }
}

内容的提问来源于stack exchange,提问作者Slugarius Maximur

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.03 17:15:53