骨骼动画歪斜

问题描述

我遵循了 Thin Matrix 的有关骨骼动画的流行教程和另一个代码示例 GitHub

网格渲染发现没有任何动画。但是一旦应用了动画,它就会出现偏差。

如果我将单位矩阵作为骨骼变换传递,它会起作用。在没有任何动画的情况下仍然可以正常渲染。

我还注意到我使用的 collada 文件使用 Z 作为向上,而我使用 Y 作为向上。但是我导出所有数据而不更改任何内容以确保所有转换和顶点数据按预期工作。我稍后计划在导出时调整它,以便数据也使用 Y。

这是我的骨骼动画代码

标题

class SkeletalAnimation {
        
        typedef struct Bone{
            int id;
            std::string name;
            glm::mat4 offset;
            std::vector<Bone> children;
        } Bone;

        typedef struct  {
            std::vector<float> translationTimestamps;
            std::vector<float> rotationTiMetamps;
            std::vector<float> scalingTiMetamps;

            std::vector<glm::vec3> translations;
            std::vector<glm::quat> rotations;
            std::vector<glm::vec3> scalings;
        } BoneTransforms;

        typedef struct Animation {
            float duration;
            float ticksPerSecond;
            std::unordered_map<std::string,BoneTransforms> boneTransforms;
            Animation(float pDuration,float ticksPerSecond) :
                duration(pDuration),ticksPerSecond(ticksPerSecond),boneTransforms({})
            {}
            Animation() {}
        } Animation;

        typedef std::unordered_map<std::string,std::pair<int,glm::mat4>> BoneData;
        typedef std::unordered_map<std::string,Animation> AnimationMap;
        typedef std::vector<glm::mat4> Pose;

        typedef struct{
            unsigned int segment;
            float fracture;
        } Segment;

        typedef struct {
            Pose pose;
            BoneData boneData;
            unsigned int boneCount;
            std::string name;
            Bone skeleton;
        } MeshEntry;

        typedef std::unordered_map<std::string,MeshEntry> MeshBoneMap;

    private:

        const std::string mPath;
        SDL_Renderer* mRenderer;
        
        std::vector<MeshEntry> mMeshEntries;
        std::vector<SkeletalMesh*> mMeshes;
        std::vector<ImageTexture*> mTextures;
        std::vector<unsigned int> mMeshToTexture;

        std::string* mCurrentAnimation;
        std::vector<std::string> mAnimations;

        AnimationMap mAnimationMap;
        Segment mCurrentSegment;
        glm::mat4 mGlobalInverseTransform;
        MeshBoneMap mMeshBoneMap;

        static glm::mat4 sIdentityMatrix;

        void LoadNode(aiNode* pNode,const aiScene* pScene);
        void LoadSkeletalMesh(aimesh* pMesh,const aiScene* pScene);
        bool LoadBones(Bone& pBone,aiNode* pNode,BoneData& pBoneData);
        void LoadAnimations(const aiScene* pScene);
        void LoadMaterials(const aiScene* pScene);
        void Animate(float pDeltaTime,Bone& pSkeleton,Pose& pPose,glm::mat4& pParentTransform);

        static inline glm::mat4 aiToGlmMat4(const aimatrix4x4& paimat);
        static inline glm::vec3 aiToGlmVec3(const aiVector3D& pAiVec);
        static inline glm::quat aiToGlmQuat(const aiQuaternion& pAiVec);
        static inline void GetSegment(Segment* pSegment,const std::vector<float>& pTimestamps,const float pDeltaTime);

    public:
        SkeletalAnimation(const std::string pPath,SDL_Renderer* pRenderer);
        ~SkeletalAnimation();

        void LoadAnimation();
        void GetAllAnimations(std::vector<std::string>* pAnimations);
        float GetAnimationDuration();
        void SetAnimationTime(float pTime);
        void SetAnimation(std::string pAnimation);
        void RenderAnimation(float pDeltaTime,SkeletalAnimationShader* pSkeletalAnimationShader);
        void RenderStill(SkeletalAnimationShader* pSkeletalAnimationShader);
        void ClearModel();
    };

来源:

glm::mat4 SkeletalAnimation::sIdentityMatrix = glm::mat4();
    
        SkeletalAnimation::SkeletalAnimation(const std::string pPath,SDL_Renderer* pRenderer) :
            mPath(pPath),mRenderer(pRenderer),mCurrentAnimation(new std::string()),mAnimations({}),mAnimationMap({}),mMeshBoneMap({})
        {}

        SkeletalAnimation::~SkeletalAnimation() {
        
        }

        void SkeletalAnimation::LoadAnimation() {
            Assimp::Importer _importer;
            const aiScene* _scene = _importer.ReadFile(mPath,aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_GenSmoothnormals | aiProcess_JoinIdenticalVertices);

            if (!_scene) {
                SDL_Log("Assimp Error Loading Animation at path: %s \n Error: %s .",mPath.c_str(),_importer.GetErrorString());
                return;
            }

            
            mGlobalInverseTransform = glm::inverse(aiToGlmMat4(_scene->mRootNode->mTransformation));

            LoadNode(_scene->mRootNode,_scene);
            
            LoadAnimations(_scene);
            
            LoadMaterials(_scene);
            
        }

        void SkeletalAnimation::LoadNode(aiNode* pNode,const aiScene* pScene) {
            for (size_t i = 0; i < pNode->mNumMeshes; i++) {
                LoadSkeletalMesh(pScene->mMeshes[pNode->mMeshes[i]],pScene);
            }

            for (size_t i = 0; i < pNode->mNumChildren; i++) {
                LoadNode(pNode->mChildren[i],pScene);
            }
        }

        void SkeletalAnimation::LoadSkeletalMesh(aimesh* pMesh,const aiScene* pScene) {

            MeshEntry _meshEntry;
            _meshEntry.boneCount = pMesh->mNumBones;
            _meshEntry.name = std::string(pMesh->mName.C_str());
            _meshEntry.pose = {};
            _meshEntry.pose.resize(pMesh->mNumBones,sIdentityMatrix);
            _meshEntry.boneData = {};
            SkeletalMeshData _meshData;

            for (size_t i = 0; i < pMesh->mNumVertices; i++) {
                _meshData.vertices.insert(_meshData.vertices.end(),{
                        pMesh->mVertices[i].x,pMesh->mVertices[i].y,//Swaped Y and Z since Blender uses Z as up and I use Y as up.
                        pMesh->mVertices[i].z });

                if (pMesh->mTextureCoords[0]) {
                    _meshData.uvs.insert(_meshData.uvs.end(),{
                            pMesh->mTextureCoords[0][i].x,pMesh->mTextureCoords[0][i].y
                        });
                }
                else {
                    _meshData.uvs.insert(_meshData.uvs.end(),{
                            0.0f,0.0f
                        });
                }
                _meshData.normals.insert(_meshData.normals.end(),{
                        pMesh->mnormals[i].x,pMesh->mnormals[i].y,pMesh->mnormals[i].z });

                _meshData.boneIDs.insert(_meshData.boneIDs.end(),{
                    0,0});

                _meshData.weights.insert(_meshData.weights.end(),{
                    0.0f,0.0f,0.0f});

            }

            for (size_t i = 0; i < pMesh->mNumFaces; i++) {
                aiface _face = pMesh->mFaces[i];
                for (size_t j = 0; j < _face.mNumIndices; j++) {
                    _meshData.indices.push_back(_face.mIndices[j]);
                }
            }
            
            for (size_t i = 0; i < pMesh->mNumBones; i++) {
                aiBone* _bone = pMesh->mBones[i];
                glm::mat4 _offset = aiToGlmMat4(_bone->mOffsetMatrix);
                _meshEntry.boneData[_bone->mName.C_str()] = std::make_pair(i,_offset);

                for (size_t j = 0; j < _bone->mNumWeights; j++) {
                    aiVertexWeight _weight = _bone->mWeights[j];
                    unsigned int _vertexID = _weight.mVertexId * 4;

                    for (size_t k = 0; k < 4; k++) {
                        if (_meshData.weights[_vertexID + k] == 0.0f) {
                            _meshData.weights[_vertexID + k] = _weight.mWeight;
                            _meshData.boneIDs[_vertexID + k] = i;
                            break;
                        }
                    }
                }

                
            }

            for (size_t i = 0; i < _meshData.weights.size(); i+=4) {
                float _totalWeight = 
                    _meshData.weights[i]    + 
                    _meshData.weights[i+1]  + 
                    _meshData.weights[i+2]  +
                    _meshData.weights[i+3];
                if (_totalWeight > 0.0f) {
                    _meshData.weights[i] /= _totalWeight;
                    _meshData.weights[i+1] /= _totalWeight;
                    _meshData.weights[i+2] /= _totalWeight;
                    _meshData.weights[i+3] /= _totalWeight;
                }
            }

            SkeletalMesh* _newMesh = new SkeletalMesh();
            _newMesh->BuildMesh(_meshData);
            mMeshes.push_back(_newMesh);
            mMeshToTexture.push_back(pMesh->mMaterialIndex);

            LoadBones(_meshEntry.skeleton,pScene->mRootNode,_meshEntry.boneData);
            mMeshEntries.push_back(_meshEntry);
        }

        bool SkeletalAnimation::LoadBones(Bone& pBone,BoneData& pBoneData) {
            if (pBoneData.find(pNode->mName.C_str()) != pBoneData.end()) {
                pBone.name = pNode->mName.C_str();
                pBone.id = pBoneData[pBone.name].first;
                pBone.offset = pBoneData[pBone.name].second;

                for (size_t i = 0; i < pNode->mNumChildren; i++) {
                    Bone _child;
                    LoadBones(_child,pNode->mChildren[i],pBoneData);
                    pBone.children.push_back(_child);
                }
                return true;
            }
            else { 
                for (size_t i = 0; i < pNode->mNumChildren; i++) {
                    if (LoadBones(pBone,pBoneData)) {
                        return true;
                    }

                }
            }
            return false;
        }

        void SkeletalAnimation::LoadAnimations(const aiScene* pScene) {
            for (size_t i = 0; i < pScene->mNumAnimations; i++) {
                
                Animation _currentInternalAnimation(0.0f,1.0f);
                aiAnimation* _currentAiAnimation = pScene->mAnimations[i];

                mAnimations.push_back(std::string(_currentAiAnimation->mName.C_str()));
                if (i == 0) {
                    *mCurrentAnimation = mAnimations[0];
                }

                if (_currentAiAnimation->mTicksPerSecond != 0.0f) {
                    _currentInternalAnimation.ticksPerSecond = _currentAiAnimation->mTicksPerSecond;
                }
                else {
                    _currentInternalAnimation.ticksPerSecond = 1;
                }

                _currentInternalAnimation.duration = _currentAiAnimation->mDuration * _currentAiAnimation->mTicksPerSecond;
                _currentInternalAnimation.boneTransforms = {};

                BoneTransforms _transforms;

                for (size_t j = 0; j < _currentAiAnimation->mNumChannels; j++) {

                    aiNodeAnim* _channel = _currentAiAnimation->mChannels[j];

                    for (size_t k = 0; k < _channel->mNumPositionKeys; k++) {

                        _transforms.translations.push_back(aiToGlmVec3(_channel->mPositionKeys[k].mValue));
                        _transforms.translationTimestamps.push_back(_channel->mPositionKeys[k].mTime); 
                    }
                    for (size_t k = 0; k < _channel->mNumRotationKeys; k++) {
                        _transforms.rotations.push_back(aiToGlmQuat(_channel->mRotationKeys[k].mValue));
                        _transforms.rotationTiMetamps.push_back(_channel->mRotationKeys[k].mTime);

                    }
                    for (size_t k = 0; k < _channel->mNumScalingKeys; k++) {
                        _transforms.scalings.push_back(aiToGlmVec3(_channel->mScalingKeys[k].mValue));
                        _transforms.scalingTiMetamps.push_back(_channel->mScalingKeys[k].mTime);
                    }

                    _currentInternalAnimation.boneTransforms[_channel->mNodeName.C_str()] = _transforms;
                }
                mAnimationMap[_currentAiAnimation->mName.C_str()] = _currentInternalAnimation;
            }
        }

        void SkeletalAnimation::LoadMaterials(const aiScene* pScene) {
            mTextures.resize(pScene->mNumMaterials);
            for (size_t i = 0; i < pScene->mNumMaterials; i++) {
                aimaterial* _material = pScene->mMaterials[i];
                mTextures[i] = nullptr;

                if (_material->GetTextureCount(aiTextureType_DIFFUSE)) {
                    aiString _path;
                    if (_material->GetTexture(aiTextureType_DIFFUSE,&_path) == AI_SUCCESS) {
                        int _idx = std::string(_path.data).rfind("\\");
                        std::string _fileName = std::string(_path.data).substr(_idx + 1);
                        std::string _texturePath = std::string("assets/") + _fileName;
                        SDL_Log("Model Loading Texture at path: %s .",_texturePath.c_str());
                        mTextures[i] = new ImageTexture(_texturePath,mRenderer);
                        mTextures[i]->Load();

                        if (!mTextures[i]->IsLoaded()) {
                            delete mTextures[i];
                            mTextures[i] = nullptr;
                            SDL_Log("Model Error Loading Texture at path: %s .",_texturePath.c_str());
                        }

                    }
                }
            }

        }

        float SkeletalAnimation::GetAnimationDuration() {
            return mAnimationMap[*mCurrentAnimation].duration;
        }

        void SkeletalAnimation::SetAnimationTime(float pTime) {
            for (size_t i = 0; i < mMeshes.size(); i++) {
                MeshEntry& _entry = mMeshEntries[i];
                Animate(pTime,_entry.skeleton,_entry.pose,sIdentityMatrix);
            }
        }

        void SkeletalAnimation::Animate(float pDeltaTime,glm::mat4& pParentTransform) {
            Animation& _currentAnimation = mAnimationMap[*mCurrentAnimation];
            
            BoneTransforms& _boneTransforms = _currentAnimation.boneTransforms[pSkeleton.name];
            pDeltaTime = fmod(pDeltaTime,_currentAnimation.duration);
            
            //Calculate translations
            GetSegment(&mCurrentSegment,_boneTransforms.translationTimestamps,pDeltaTime);

            glm::vec3 _translation = glm::mix(
                _boneTransforms.translations[mCurrentSegment.segment - 1],_boneTransforms.translations[mCurrentSegment.segment],mCurrentSegment.fracture);

            //Calculate rotations
            GetSegment(&mCurrentSegment,_boneTransforms.rotationTiMetamps,pDeltaTime);

            glm::quat _rotation = glm::slerp(
                _boneTransforms.rotations[mCurrentSegment.segment - 1],_boneTransforms.rotations[mCurrentSegment.segment],mCurrentSegment.fracture);

            //Calculate scalings
            GetSegment(&mCurrentSegment,_boneTransforms.scalingTiMetamps,pDeltaTime);

            glm::vec3 _scaling = glm::mix(
                _boneTransforms.scalings[mCurrentSegment.segment - 1],_boneTransforms.scalings[mCurrentSegment.segment],mCurrentSegment.fracture);

            glm::mat4 _translationMatrix = glm::translate(glm::mat4(1.0f),_translation);
            glm::mat4 _rotationMatrix = glm::toMat4(_rotation);
            glm::mat4 _scalingMatrix = glm::scale(glm::mat4(1.0f),_scaling); glm::mat4(1.0f);
            
            glm::mat4 _localTransform = _translationMatrix * _rotationMatrix * _scalingMatrix;
            glm::mat4 _globalTransform = pParentTransform * _localTransform;

            pPose[pSkeleton.id] = mGlobalInverseTransform * _globalTransform * pSkeleton.offset;

            for (Bone& _child : pSkeleton.children) {
                Animate(pDeltaTime,_child,pPose,_globalTransform);
            }
        }

        void SkeletalAnimation::GetAllAnimations(std::vector<std::string>* pAnimations) {
            pAnimations->clear();
            *pAnimations = mAnimations;
        }

        void SkeletalAnimation::SetAnimation(std::string pAnimation) {
            assert(std::find(mAnimations.begin(),mAnimations.end(),pAnimation) != mAnimations.end(),"Animation does not exist.");
            *mCurrentAnimation = pAnimation;
        }

        void SkeletalAnimation::RenderAnimation(float pDeltaTime,SkeletalAnimationShader* pSkeletalAnimationShader) {

            for (size_t i = 0; i < mMeshes.size(); i++) {
                unsigned int _materialIndex = mMeshToTexture[i];

                if (_materialIndex < mTextures.size() && mTextures[_materialIndex]) {
                    mTextures[_materialIndex]->Enable();
                }

                MeshEntry& _entry = mMeshEntries[i];
                Animate(pDeltaTime,sIdentityMatrix);
                pSkeletalAnimationShader->SetBoneTransforms(_entry.boneCount,_entry.pose);
                mMeshes[i]->Render();
            }
        }

        void SkeletalAnimation::RenderStill(SkeletalAnimationShader* pSkeletalAnimationShader) {
            for (size_t i = 0; i < mMeshes.size(); i++) {
                unsigned int _materialIndex = mMeshToTexture[i];

                if (_materialIndex < mTextures.size() && mTextures[_materialIndex]) {
                    mTextures[_materialIndex]->Enable();
                }

                MeshEntry& _entry = mMeshEntries[i];
                pSkeletalAnimationShader->SetBoneTransforms(_entry.boneCount,_entry.pose);
                mMeshes[i]->Render();
            }
        }

        void SkeletalAnimation::ClearModel() {
            for (size_t i = 0; i < mMeshes.size(); i++) {
                if (mMeshes[i]) {
                    delete mMeshes[i];
                    mMeshes[i] = nullptr;
                }
            }

            for (size_t i = 0; i < mTextures.size(); i++) {
                if (mTextures[i]) {
                    delete mTextures[i];
                    mTextures[i] = nullptr;
                }
            }
        }

        void SkeletalAnimation::GetSegment(Segment* pSegment,const float pDeltaTime) {
            unsigned int _segment = 1;
            while (pDeltaTime > pTimestamps[_segment]) {
                _segment++;
            }
            float _start = pTimestamps[_segment - 1];
            float _end = pTimestamps[_segment];
            float _fracture = (pDeltaTime - _start) / (_end - _start);
            pSegment->segment = _segment;
            pSegment->fracture = _fracture;
        }

        glm::mat4 SkeletalAnimation::aiToGlmMat4(const aimatrix4x4& paimat) {
            glm::mat4 _glmMat;
            for (int y = 0; y < 4; y++)
            {
                for (int x = 0; x < 4; x++)
                {
                    _glmMat[x][y] = paimat[y][x];
                }
            }
            return _glmMat;
        }

        glm::vec3 SkeletalAnimation::aiToGlmVec3(const aiVector3D& pAiVec) {
            return glm::vec3(pAiVec.x,pAiVec.y,pAiVec.z); //Swapped Y and Z to correct Blender ups.
        }

        glm::quat SkeletalAnimation::aiToGlmQuat(const aiQuaternion& pAiQuat) {
            return glm::quat(pAiQuat.w,pAiQuat.x,pAiQuat.y,pAiQuat.z);
        }

我逐行阅读我的代码多次,看看我做错了什么,但我什么也想不出来。我不认为我的着色器是问题,但这是顶点着色器:

layout (location = 0) in vec3 position;
layout (location = 1) in vec2 uv;
layout (location = 2) in vec3 normal;
layout (location = 3) in ivec4 boneIds; 
layout (location = 4) in vec4 boneWeights; 

out vec2 textureUV;
out vec3 lightnormal;
out vec4 worldPosition;

uniform mat4 model;
uniform mat4 projectionView;
uniform mat4 boneTransforms[50];

void main()
{
    mat4 boneTransform  =  mat4(0.0f);
    for(int i = 0; i < 4; i++){
        boneTransform  += boneTransforms[boneIds[i]] * boneWeights[i];
    }
    worldPosition = boneTransform * vec4(position,1.0f);
    worldPosition = model * worldPosition;
    gl_Position = projectionView * worldPosition;
    textureUV = uv;
    lightnormal = mat3(transpose(inverse(model * boneTransform))) * normal;
}

结果:

Skewed image

解决方法

我想通了。 BoneTransforms 需要在 for 循环内移动。每个循环周期都覆盖同一个实例。