骨骼动画倾斜

Skeletal animation is skewed

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

网格渲染查找时没有任何动画。但是一旦应用了动画,它就会倾斜。

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

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

这是我的骨骼动画代码:

Header:

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,
                    0,
                    0});

                _meshData.weights.insert(_meshData.weights.end(), {
                    0.0f,
                    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 ,aiNode* pNode, 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, pNode->mChildren[i], 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, 0, &_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, Bone& pSkeleton, Pose& pPose, 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, _entry.skeleton, _entry.pose, 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 std::vector<float>& pTimestamps, 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;
}

结果:

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