使用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
相关产品推荐
相关产品推荐

