Animation questions (many questions :P)[SOLVED]

Started by
9 comments, last by Misantes 9 years, 4 months ago

Edit** To be of use to someone later, the mistake in the below code is within the SetBoneTransforms() function. There is no need to convert the assimp matrix (especially not to an array of floats). Who knows what I was thinking. But, simply pass in the matrix as is and use the [0][0] value of the matrix in the glUniform call. You'll of course need to change the parameters of the SetBoneTransforms() to accommodate this change.

Alright, so I've struggled with loading and animating models using assimp for a couple weeks now, and it's by far one of the more difficult things I've come across since I started programming (basically, any of my programming deficiencies seem to be rearing their ugly heads in trying to implement this).

I'm mostly going from the tutorial here, though I've taken ideas and information from various resources and have adapted it to some extent to try to get in implemented into my framework (basically, into a format I'm comfortable working with). The author of the tutorial uses quite a lot of his own custom math headers and custom function calls for various 3D frameworks I've had to adapt, and it's possible I've done it poorly. There's certain a lot of code below that probably isn't the ideal way to do things or have an alternative in the glm library I'm unaware of (I plan on cleaning it up once it's functional). Please, feel free to point these out though, even if it doesn't solve the larger problems.

To note, depending on various things, I don't receive an error, though the model does not animate at all (best case scenario) or doesn't render (worst case scenario) or segfaults (just a clock error, I'm fairly sure, easily fixed, i think).

Since this is a complex problem and I'm likely doing several things wrong, I'll attempt to walk you through my process and explain how I understand what I'm doing. That may be the best way to illuminate what I'm doing wrong. Apologies in advance for the wall of code. To note, I'm using ASSIMP for the model loading.

For reference, here is the header (it's a little haphazard, since I've been mutilating this code for awhile now:


#ifndef CHESSPIECE_H
#define CHESSPIECE_H
#include "Entity.h"
#define GLM_FORCE_RADIANS

//Assimp////
#include <assimp/Importer.hpp>     
#include <assimp/scene.h>          
#include <assimp/postprocess.h>     

#define ARRAY_SIZE_IN_ELEMENTS(a) (sizeof(a)/sizeof(a[0]))
#define NUM_BONES_PER_VERTEX 8 
#define BONE_ID_LOCATION     5
#define BONE_WEIGHT_LOCATION 6
#define INVALID_MATERIAL 0xFFFFFFFF

class ChessPiece : public Entity
{
    public:
        ChessPiece();
        ChessPiece(glm::vec3 locationIN, GLuint &progamIDIN, std::vector<glm::vec3> &verticesIN, std::vector<glm::vec2> &uvsIN,
                        std::vector<glm::vec3> &normalsIN, glm::vec3 &imageIndexIN, int tileTypeIN, glm::vec3 scaleIN);
        void Render(GLuint &texturearray, Controls &control);
        virtual ~ChessPiece();
        void CleanUp();
        void Enable(GLuint &texturearray);
        void Disable();
        bool LoadAssimpModel( const std::string& pFile);
        void ProcessAssimpModel();
        void DoTheSceneProcessing(const aiScene* &sceneIN);
        void BoneTransform(float TimeInSeconds, std::vector<aiMatrix4x4>& Transforms);
        void ReadNodeHeirarchy(float AnimationTime, const aiNode* pNode, const aiMatrix4x4& ParentTransform);
        const aiNodeAnim* FindNodeAnim(const aiAnimation* pAnimation, const std::string NodeName);
        void CalcInterpolatedRotation(aiQuaternion& Out, float AnimationTime, const aiNodeAnim* pNodeAnim);
        void CalcInterpolatedPosition(aiVector3D& Out, float AnimationTime, const aiNodeAnim* pNodeAnim);
        void CalcInterpolatedScaling(aiVector3D& Out, float AnimationTime, const aiNodeAnim* pNodeAnim);
        uint FindRotation(float AnimationTime, const aiNodeAnim* pNodeAnim);
        uint FindPosition(float AnimationTime, const aiNodeAnim* pNodeAnim);
        uint FindScaling(float AnimationTime, const aiNodeAnim* pNodeAnim);
        void SetBoneTransform(uint Index, const float &Transform);
    protected:
    private:

        sf::Clock clocky;
        
        //assimp variables////
        Assimp::Importer importer; // Create an instance of the Importer class
        const aiScene* scene;

        std::vector<aiVector3D >vertices;
        std::vector<aiVector3D >uv;
        std::vector<aiVector3D >normal;

        aiMatrix4x4 m_GlobalInverseTransform;

        struct BoneInfo
        {
            aiMatrix4x4 BoneOffset;
            aiMatrix4x4 FinalTransformation;

        };

        struct VertexBoneData
        {
            uint IDs[NUM_BONES_PER_VERTEX];
            float Weights[NUM_BONES_PER_VERTEX];

            VertexBoneData()
            {
                Reset();
            };

            void Reset()
            {
               // std::cout<<"resetting"<<std::endl;
                for(int i = 0; i < NUM_BONES_PER_VERTEX; i++)
                {
                    Weights[i] = 0;
                    IDs[i]=0;
                }
            }

            void AddBoneData(uint BoneID, float Weight);
        };

        struct MeshEntry
        {
            MeshEntry()
            {
                NumIndices    = 0;
                BaseVertex    = 0;
                BaseIndex     = 0;
                MaterialIndex = INVALID_MATERIAL;
            }

            unsigned int NumIndices;
            unsigned int BaseVertex;
            unsigned int BaseIndex;
            unsigned int MaterialIndex;
        };

        std::vector<VertexBoneData> Bones;
        GLuint boneBuffer;
        GLuint weightBuffer;
        GLuint boneLocBuffer;

        void LoadBones(uint MeshIndex, const aiMesh* pMesh, std::vector<VertexBoneData> &Bones);
        std::map<std::string,uint> m_BoneMapping; // maps a bone name to its index
        uint m_NumBones = 0;
        std::vector<BoneInfo> m_BoneInfo;
        std::vector<MeshEntry> m_Entries;

        GLuint gBones;
        static const uint MAX_BONES = 100;
        GLuint m_boneLocation[MAX_BONES];
};

#endif // CHESSPIECE_H

I start by loading the model:


bool ChessPiece::LoadAssimpModel( const std::string& fileIN)
{
    scene = importer.ReadFile( fileIN,
        aiProcess_CalcTangentSpace       |
        aiProcess_GenSmoothNormals       |
        aiProcess_Triangulate            |
        aiProcess_JoinIdenticalVertices  |
        aiProcess_SortByPType| aiProcess_FlipUVs );

    if( !scene)
    {
        std::cout<<importer.GetErrorString()<<std::endl;;
        return false;
    }
    else
    {
        m_GlobalInverseTransform = scene->mRootNode->mTransformation;
        m_GlobalInverseTransform.Inverse();
    }
    return true;
}

and then pull the vertices, uvs and normals from the Assimp scene:


void ChessPiece::ProcessAssimpModel()
{
    uint NumVertices = 0;
    uint NumIndices = 0;
    m_Entries.resize(scene->mNumMeshes);
    for (uint i = 0 ; i < m_Entries.size() ; i++)
    {
        NumVertices += scene->mMeshes[i]->mNumVertices;
    }

    Bones.resize(NumVertices);

    for(int i = 0; i < scene->mNumMeshes; i++)
    {
        const aiMesh* mesh = scene->mMeshes[i];

        for(int j = 0; j < mesh->mNumFaces; j++)
        {
            const aiFace& face = mesh->mFaces[j];
            for(int k = 0; k < 3; k++)
            {
                vertices.push_back(mesh->mVertices[face.mIndices[k]]);
                uv.push_back(mesh->mTextureCoords[0][face.mIndices[k]]);

                if(mesh->HasNormals())
                {
                    normal.push_back(mesh->mNormals[face.mIndices[k]]);
                }
                else
                {
                    normal.push_back( aiVector3D(1.0f, 1.0f, 1.0f));
                }
            }
        }
        LoadBones(i, mesh, Bones);
    }
}

So far, so good, I suppose. The model thus far does load and render from the collada file.

Then (within the above loop) load the bones for each mesh, filling a map with the index number and weight for each bone:


void ChessPiece::LoadBones(uint MeshIndex, const aiMesh* pMesh, std::vector<VertexBoneData> &Bones)
{

    for (uint i = 0 ; i < pMesh->mNumBones ; i++) {
        uint BoneIndex = 0;
        std::string BoneName(pMesh->mBones[i]->mName.data);
        if (m_BoneMapping.find(BoneName) == m_BoneMapping.end()) {
            BoneIndex = m_NumBones;
            m_NumBones++;
            BoneInfo bi;
            m_BoneInfo.push_back(bi);
        }
        else {
            BoneIndex = m_BoneMapping[BoneName];
        }

        m_BoneMapping[BoneName] = BoneIndex;
        m_BoneInfo[BoneIndex].BoneOffset = pMesh->mBones[i]->mOffsetMatrix;

        for (uint j = 0 ; j < pMesh->mBones[i]->mNumWeights ; j++) {
            uint VertexID = m_Entries[MeshIndex].BaseVertex + pMesh->mBones[i]->mWeights[j].mVertexId;

            float Weight = pMesh->mBones[i]->mWeights[j].mWeight;

            Bones[VertexID].AddBoneData(BoneIndex, Weight);
        }
    }
}

void ChessPiece::VertexBoneData::AddBoneData(uint BoneID, float Weight)
{
    for (uint i = 0 ; i < ARRAY_SIZE_IN_ELEMENTS(IDs) ; i++)
    {
        if (Weights[i] == 0.0)
        {

            IDs[i] = BoneID;
            Weights[i] = Weight;
            return;
        }
    }
    std::cout<<"too many bones"<<std::endl;
    assert(0);
} 

Then to finalize the object loading, I index a VBO with the infomation:

I worry a little that I'm setting up layout 5 and 6 (the bone information) incorrectly. I've never set the buffer data using a map, and it's possible I'm going about this incorrectly.


    MatrixID = glGetUniformLocation(programID, "MVP");
    ViewMatrixID = glGetUniformLocation(programID, "V");
    ModelMatrixID = glGetUniformLocation(programID, "M");
    ModelView3x3MatrixID = glGetUniformLocation(programID, "MV3x3");

    DiffuseTextureID  = glGetUniformLocation(programID, "DiffuseTextureSampler");
    NormalTextureID  = glGetUniformLocation(programID, "NormalTextureSampler");
    SpecularTextureID  = glGetUniformLocation(programID, "SpecularTextureSampler");

    std::vector<glm::vec3> tangents;
    std::vector<glm::vec3> bitangents;
    computeTangentBasis2(
    vertices, uv, normal, // input
    tangents, bitangents    // output
    );

    indexVBO_TBN2(
    vertices, uv, normal, tangents, bitangents,
    indices, indexed_vertices, indexed_uvs, indexed_normals, indexed_tangents, indexed_bitangents
    );

    glGenBuffers(1, &vertexbuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
    glBufferData(GL_ARRAY_BUFFER, indexed_vertices.size() * sizeof(glm::vec3), &indexed_vertices[0], GL_STATIC_DRAW);

    glGenBuffers(1, &uvbuffer);
    glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
    glBufferData(GL_ARRAY_BUFFER, indexed_uvs.size() * sizeof(glm::vec2), &indexed_uvs[0], GL_STATIC_DRAW);

    glGenBuffers(1, &normalbuffer);
    glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
    glBufferData(GL_ARRAY_BUFFER, indexed_normals.size() *sizeof(glm::vec3), &indexed_normals[0], GL_STATIC_DRAW);

    glGenBuffers(1, &tangentbuffer);
    glBindBuffer(GL_ARRAY_BUFFER, tangentbuffer);
    glBufferData(GL_ARRAY_BUFFER, indexed_tangents.size() * sizeof(glm::vec3), &indexed_tangents[0], GL_STATIC_DRAW);

    glGenBuffers(1, &bitangentbuffer);
    glBindBuffer(GL_ARRAY_BUFFER, bitangentbuffer);
    glBufferData(GL_ARRAY_BUFFER, indexed_bitangents.size() * sizeof(glm::vec3), &indexed_bitangents[0], GL_STATIC_DRAW);

    //bones////
    glEnableVertexAttribArray(5);
    glBindBuffer(GL_ARRAY_BUFFER, boneBuffer);
	glBufferData(GL_ARRAY_BUFFER, sizeof(Bones[0]) * Bones.size(), &Bones[0], GL_STATIC_DRAW);
    glVertexAttribIPointer(5, 4, GL_INT, sizeof(VertexBoneData), (const GLvoid*)0);

    glEnableVertexAttribArray(6);
    glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(VertexBoneData), (const GLvoid*)16);
    
    ///////////////////////////////
    for (unsigned int i = 0 ; i < ARRAY_SIZE_IN_ELEMENTS(m_boneLocation) ; i++) {
        char Name[128];
        sprintf(Name, "gBones[%d]", i);
        GLuint Location = glGetUniformLocation(programID, Name);
        m_boneLocation[i] = Location;
    }
    ///////////////////////////////////

    //indices buffer
    glGenBuffers(1, &elementbuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned short), &indices[0], GL_STATIC_DRAW);

    //gen uniforms
    glUseProgram(programID);
    LightID = glGetUniformLocation(programID, "LightPosition_worldspace");
    LayerNumID1 = glGetUniformLocation(programID, "layer_index1");
    LayerNumID2 = glGetUniformLocation(programID, "layer_index2");
    LayerNumID3 = glGetUniformLocation(programID, "layer_index3");
    gBones = glGetUniformLocation(programID, "gBones");

Before rendering, I call BoneTransform, creating a blank identity matrix to send to the ReadNodeHeirarchy() function, calculating the new vertices via scaling, translation and rotation based on time and nodes, and then filling the 4x4matrix Transforms with the final vertex location:


std::vector<aiMatrix4x4> Transforms;
BoneTransform(clock1.getElapsedTime().asSeconds(), Transforms);

void ChessPiece::BoneTransform(float TimeInSeconds, std::vector<aiMatrix4x4>& Transforms)
{
    aiMatrix4x4 Identity;

    Identity[0][0] = 1.0f; Identity[0][1] = 0.0f; Identity[0][2] = 0.0f; Identity[0][3] = 0.0f;
    Identity[1][0] = 0.0f; Identity[1][1] = 1.0f; Identity[1][2] = 0.0f; Identity[1][3] = 0.0f;
    Identity[2][0] = 0.0f; Identity[2][1] = 0.0f; Identity[2][2] = 1.0f; Identity[2][3] = 0.0f;
    Identity[3][0] = 0.0f; Identity[3][1] = 0.0f; Identity[3][2] = 0.0f; Identity[3][3] = 1.0f;

    float TicksPerSecond = scene->mAnimations[0]->mTicksPerSecond != 0 ?
                            scene->mAnimations[0]->mTicksPerSecond : 25.0f;
    float TimeInTicks = TimeInSeconds * TicksPerSecond;
    float AnimationTime = fmod(TimeInTicks, scene->mAnimations[0]->mDuration);

    ReadNodeHeirarchy(AnimationTime, scene->mRootNode, Identity);

    Transforms.resize(m_NumBones);

    for (uint i = 0 ; i < m_NumBones ; i++) {
        Transforms[i] = m_BoneInfo[i].FinalTransformation;
    }
}


void ChessPiece::ReadNodeHeirarchy(float AnimationTime, const aiNode* pNode, const aiMatrix4x4& ParentTransform)
{
    std::string NodeName(pNode->mName.data);

    const aiAnimation* pAnimation = scene->mAnimations[0];

    aiMatrix4x4 NodeTransformation(pNode->mTransformation);

    const aiNodeAnim* pNodeAnim = FindNodeAnim(pAnimation, NodeName);

    if (pNodeAnim) {
        // Interpolate scaling and generate scaling transformation matrix
        aiVector3D Scaling;
        CalcInterpolatedScaling(Scaling, AnimationTime, pNodeAnim);
        aiMatrix4x4 ScalingM;
       // ScalingM.InitScaleTransform(Scaling.x, Scaling.y, Scaling.z);
        ScalingM[0][0] = Scaling.x; ScalingM[0][1] = 0.0f;   ScalingM[0][2] = 0.0f;   ScalingM[0][3] = 0.0f;
        ScalingM[1][0] = 0.0f;   ScalingM[1][1] = Scaling.y; ScalingM[1][2] = 0.0f;   ScalingM[1][3] = 0.0f;
        ScalingM[2][0] = 0.0f;   ScalingM[2][1] = 0.0f;   ScalingM[2][2] = Scaling.z; ScalingM[2][3] = 0.0f;
        ScalingM[3][0] = 0.0f;   ScalingM[3][1] = 0.0f;   ScalingM[3][2] = 0.0f;   ScalingM[3][3] = 1.0f;
        // Interpolate rotation and generate rotation transformation matrix
        aiQuaternion RotationQ;
        CalcInterpolatedRotation(RotationQ, AnimationTime, pNodeAnim);
        aiMatrix4x4 RotationM = aiMatrix4x4(RotationQ.GetMatrix());

        // Interpolate translation and generate translation transformation matrix
        aiVector3D Translation;
        CalcInterpolatedPosition(Translation, AnimationTime, pNodeAnim);
        aiMatrix4x4 TranslationM;
        //TranslationM.InitTranslationTransform(Translation.x, Translation.y, Translation.z);
        TranslationM[0][0] = 1.0f; TranslationM[0][1] = 0.0f; TranslationM[0][2] = 0.0f; TranslationM[0][3] = Translation.x;
        TranslationM[1][0] = 0.0f; TranslationM[1][1] = 1.0f; TranslationM[1][2] = 0.0f; TranslationM[1][3] = Translation.y;
        TranslationM[2][0] = 0.0f; TranslationM[2][1] = 0.0f; TranslationM[2][2] = 1.0f; TranslationM[2][3] = Translation.z;
        TranslationM[3][0] = 0.0f; TranslationM[3][1] = 0.0f; TranslationM[3][2] = 0.0f; TranslationM[3][3] = 1.0f;
        // Combine the above transformations
        NodeTransformation = TranslationM * RotationM * ScalingM;
        std::cout<<"node matrix: "<<std::endl;
        for(int i = 0; i < 4; i++)
        {
            for(int j = 0; j < 4; j++)
            {
                //for debugging purposes////
                //std::cout<<NodeTransformation[i][j]<<std::endl;
            }
        }
    }

    aiMatrix4x4 GlobalTransformation = ParentTransform * NodeTransformation;

    if (m_BoneMapping.find(NodeName) != m_BoneMapping.end()) {
        uint BoneIndex = m_BoneMapping[NodeName];
        m_BoneInfo[BoneIndex].FinalTransformation = m_GlobalInverseTransform * GlobalTransformation *
                                                    m_BoneInfo[BoneIndex].BoneOffset;
    }

    for (uint i = 0 ; i < pNode->mNumChildren ; i++) {
        ReadNodeHeirarchy(AnimationTime, pNode->mChildren[i], GlobalTransformation);
    }
}


void ChessPiece::CalcInterpolatedRotation(aiQuaternion& Out, float AnimationTime, const aiNodeAnim* pNodeAnim)
{
    if (pNodeAnim->mNumRotationKeys == 1) {
        Out = pNodeAnim->mRotationKeys[0].mValue;
        return;
    }

    uint RotationIndex = FindRotation(AnimationTime, pNodeAnim);
    uint NextRotationIndex = (RotationIndex + 1);
    assert(NextRotationIndex < pNodeAnim->mNumRotationKeys);
    float DeltaTime = pNodeAnim->mRotationKeys[NextRotationIndex].mTime - pNodeAnim->mRotationKeys[RotationIndex].mTime;
    float Factor = (AnimationTime- (float)pNodeAnim->mRotationKeys[RotationIndex].mTime) / DeltaTime;
    assert(Factor >= 0.0f && Factor <= 1.0f);
    const aiQuaternion& StartRotationQ = pNodeAnim->mRotationKeys[RotationIndex].mValue;
    const aiQuaternion& EndRotationQ = pNodeAnim->mRotationKeys[NextRotationIndex].mValue;
    aiQuaternion::Interpolate(Out, StartRotationQ, EndRotationQ, Factor);
    Out = Out.Normalize();
}
uint ChessPiece::FindRotation(float AnimationTime, const aiNodeAnim* pNodeAnim)
{
    assert(pNodeAnim->mNumRotationKeys > 0);

    for (uint i = 0 ; i < pNodeAnim->mNumRotationKeys - 1 ; i++) {
        if (AnimationTime < (float)pNodeAnim->mRotationKeys[i + 1].mTime) {
            return i;
        }
    }

    assert(0);
}
uint ChessPiece::FindPosition(float AnimationTime, const aiNodeAnim* pNodeAnim)
{
    for (uint i = 0 ; i < pNodeAnim->mNumPositionKeys - 1 ; i++) {
        if (AnimationTime < (float)pNodeAnim->mPositionKeys[i + 1].mTime) {
            return i;
        }
    }

    assert(0);

    return 0;
}
uint ChessPiece::FindScaling(float AnimationTime, const aiNodeAnim* pNodeAnim)
{
    assert(pNodeAnim->mNumScalingKeys > 0);

    for (uint i = 0 ; i < pNodeAnim->mNumScalingKeys - 1 ; i++) {
        if (AnimationTime < (float)pNodeAnim->mScalingKeys[i + 1].mTime) {
            return i;
        }
    }

    assert(0);

    return 0;
}


void ChessPiece::CalcInterpolatedPosition(aiVector3D& Out, float AnimationTime, const aiNodeAnim* pNodeAnim)
{
    if (pNodeAnim->mNumPositionKeys == 1) {
        Out = pNodeAnim->mPositionKeys[0].mValue;
        return;
    }

    uint PositionIndex = FindPosition(AnimationTime, pNodeAnim);
    uint NextPositionIndex = (PositionIndex + 1);
    assert(NextPositionIndex < pNodeAnim->mNumPositionKeys);
    float DeltaTime = (float)(pNodeAnim->mPositionKeys[NextPositionIndex].mTime - pNodeAnim->mPositionKeys[PositionIndex].mTime);
    float Factor = (AnimationTime- (float)pNodeAnim->mRotationKeys[RotationIndex].mTime) / DeltaTime;
    assert(Factor >= 0.0f && Factor <= 1.0f);
    const aiVector3D& Start = pNodeAnim->mPositionKeys[PositionIndex].mValue;
    const aiVector3D& End = pNodeAnim->mPositionKeys[NextPositionIndex].mValue;
    aiVector3D Delta = End - Start;
    Out = Start + Factor * Delta;
}
void ChessPiece::CalcInterpolatedScaling(aiVector3D& Out, float AnimationTime, const aiNodeAnim* pNodeAnim)
{
    if (pNodeAnim->mNumScalingKeys == 1) {
        Out = pNodeAnim->mScalingKeys[0].mValue;
        return;
    }

    uint ScalingIndex = FindScaling(AnimationTime, pNodeAnim);
    uint NextScalingIndex = (ScalingIndex + 1);
    assert(NextScalingIndex < pNodeAnim->mNumScalingKeys);
    float DeltaTime = (float)(pNodeAnim->mScalingKeys[NextScalingIndex].mTime - pNodeAnim->mScalingKeys[ScalingIndex].mTime);

    float Factor = (AnimationTime- (float)pNodeAnim->mRotationKeys[RotationIndex].mTime) / DeltaTime;

    assert(Factor >= 0.0f && Factor <= 10.0f);
    const aiVector3D& Start = pNodeAnim->mScalingKeys[ScalingIndex].mValue;
    const aiVector3D& End   = pNodeAnim->mScalingKeys[NextScalingIndex].mValue;
    aiVector3D Delta = End - Start;
    Out = Start + Factor * Delta;
}

I worry I'm doing something wrong in the above functions. If i print out the transformations, I do get occasional outputs such as "-9.53674e-07"

Then (and I'm unsure about this part as well, it's a workaround. I'm not entirely certain how to pass an aiMatrix4x4 to glUniformMatrix4fv without converting it like this:

With that done, I start the rendering process:

EDIT***For reference, this is where the mistakes begin. Don't try to convert these and simply pass in the aiMatrix Transforms and use the [0][0] value of Transforms within the SetBones() function.


std::vector<float> floatTransforms;
for(int i = 0; i < Transforms.size(); i++)
{
    for(int j = 0; j < 4; j++)
    {
        for(int k = 0; k < 4; k++)
        {
            floatTransforms.emplace_back(Transforms[i][j][k]);
        }
    }
}
for(int m = 0; m < floatTransforms.size(); m++)
{
    SetBoneTransform(m, floatTransforms[m]);
}

void ChessPiece::SetBoneTransform(uint Index, const float &Transform)
{
    glUniformMatrix4fv(m_boneLocation[Index], 1, GL_TRUE, &Transform);
}

Then, to render:


void ChessPiece::Enable(GLuint &texturearray)
{
    glUseProgram(programID);

    //diffuse texture////
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D_ARRAY, texturearray);
    glUniform1i(LayerNumID1, imageIndex.x);

    //normal texture////
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D_ARRAY, texturearray);
    glUniform1i(LayerNumID2, imageIndex.y);

    //specular texture////
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D_ARRAY, texturearray);
    glUniform1i(LayerNumID3, imageIndex.z);

    //vertices
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,(void*)0);

    //UVs
    glEnableVertexAttribArray(1);
    glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
    glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,0,(void*)0);

    //normals
    glEnableVertexAttribArray(2);
    glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
    glVertexAttribPointer(2,3,GL_FLOAT,GL_FALSE,0,(void*)0);

    //tangents
    glEnableVertexAttribArray(3);
    glBindBuffer(GL_ARRAY_BUFFER, tangentbuffer);
    glVertexAttribPointer(3,3,GL_FLOAT,GL_FALSE,0,(void*)0);

    //bitangents
    glEnableVertexAttribArray(4);
    glBindBuffer(GL_ARRAY_BUFFER, bitangentbuffer);
    glVertexAttribPointer(4,3,GL_FLOAT,GL_FALSE,0,(void*)0);
    
    //Bones. And I'm not 100% certain this is correct////
    glEnableVertexAttribArray(5);
    glBindBuffer(GL_ARRAY_BUFFER, boneBuffer);
	glBufferData(GL_ARRAY_BUFFER, sizeof(Bones[0]) * Bones.size(), &Bones[0], GL_STATIC_DRAW);
    glVertexAttribIPointer(5, 4, GL_INT, sizeof(VertexBoneData), (const GLvoid*)0);

    glEnableVertexAttribArray(6);
    glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(VertexBoneData), (const GLvoid*)16);

    //Index buffer
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);
}

void ChessPiece::Render(GLuint &texturearray, Controls &control)
{
    glm::mat4 ViewMatrix = control.ViewMatrix;

    glm::mat4 ModelMatrix =  glm::translate(location)* glm::scale(modelScale);
    glm::mat3 ModelView3x3Matrix = glm::mat3(ViewMatrix * ModelMatrix);
    glm::mat4 MVP = control.ProjectionMatrix * ViewMatrix * ModelMatrix;

    glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);
    glUniformMatrix4fv(ModelMatrixID, 1, GL_FALSE, &ModelMatrix[0][0]);
    glUniformMatrix4fv(ViewMatrixID, 1, GL_FALSE, &ViewMatrix[0][0]);
    glUniformMatrix3fv(ModelView3x3MatrixID, 1, GL_FALSE, &ModelView3x3Matrix[0][0]);

    glm::vec3 lightPosF =  location + lightPos;
    glUniform3f(LightID, lightPosF.x, lightPosF.y, lightPosF.z);

    glDrawElements(GL_TRIANGLES,indices.size(),GL_UNSIGNED_SHORT,(void*)0);
}

My shader i'm not entirely certain is correct either. If i calculate gl_Position against the bone transform nothing renders at all (if I'm calculating the vertices incorrectly above, this isn't surprising and should be expected). Additionally. I'm not certain how to go about calculating the normals against it either, though I don't think that's necessary to simply render it (or am I wrong here? I'd imagine it would just look funny in the lighting until I fix that).


#version 330 core

layout(location = 0) in vec3 vertexPosition_modelspace;
layout(location = 1) in vec2 vertexUV;
layout(location = 2) in vec3 vertexNormal_modelspace;
layout(location = 3) in vec3 vertexTangent_modelspace;
layout(location = 4) in vec3 vertexBitangent_modelspace;
layout (location = 5) in ivec4 BoneIDs;
layout (location = 6) in vec4 Weights;

out vec2 UV;
out vec3 Position_worldspace;
out vec3 EyeDirection_cameraspace;
out vec3 LightDirection_cameraspace;

out vec3 LightDirection_tangentspace;
out vec3 EyeDirection_tangentspace;

const int MAX_BONES = 100;
uniform mat4 MVP;
uniform mat4 V;
uniform mat4 M;
uniform mat3 MV3x3;
uniform vec3 LightPosition_worldspace;
uniform mat4 gBones[MAX_BONES];

void main(){

    mat4 BoneTransform = gBones[BoneIDs[0]] * Weights[0];
    BoneTransform += gBones[BoneIDs[1]] * Weights[1];
    BoneTransform += gBones[BoneIDs[2]] * Weights[2];
    BoneTransform += gBones[BoneIDs[3]] * Weights[3];

    vec4 PosL    = BoneTransform * vec4(vertexPosition_modelspace, 1.0);

	//this one renders nothing////
	//gl_Position =  MVP * PosL;
        
        gl_Position =  MVP * vec4(vertexPosition_modelspace,1);

	Position_worldspace = (M * PosL).xyz;

	vec3 vertexPosition_cameraspace = ( V * M * vec4(vertexPosition_modelspace,1)).xyz;
	EyeDirection_cameraspace = vec3(0,0,0) - vertexPosition_cameraspace;

	vec3 LightPosition_cameraspace = ( V * vec4(LightPosition_worldspace,1)).xyz;
	LightDirection_cameraspace = LightPosition_cameraspace + EyeDirection_cameraspace;

	UV = vertexUV;

	vec3 vertexTangent_cameraspace = MV3x3 * vertexTangent_modelspace;
	vec3 vertexBitangent_cameraspace = MV3x3 * vertexBitangent_modelspace;
	vec3 vertexNormal_cameraspace = MV3x3 * vertexNormal_modelspace;

	mat3 TBN = transpose(mat3(
		vertexTangent_cameraspace,
		vertexBitangent_cameraspace,
		vertexNormal_cameraspace
	));

	LightDirection_tangentspace = TBN * LightDirection_cameraspace;
	EyeDirection_tangentspace =  TBN * EyeDirection_cameraspace;
}

Well, that's it tongue.png I appreciate anyone who has read this far, you're truly a remarkable specimen and a benefit to the human race, and I owe you a giant debt of gratitude, a huge thank you, and a pony smile.png If Haegar answers this, I think I owe him my first-born child at this point tongue.png

Final notes:

There's a good chance I'm converting some of these values incorrectly. I'm not quite certain how to interface Assimp with glm with OpenGL, so have simply defaulted to doing things by hand in a few places. I'll certainly dig into that at some point before implementing this code into my project, but if you have any suggestions, I would appreciate them.

Again, there are likely dozens of little things I'm doing wrong, and I genuinely appreciate those pointed out if you're feeling generous. If I'm going about things the wrong way on a larger scale, please feel free to note that as well. Hopefully it isn't a small minor typo or something. I can deal with the embarrassment of just not quite grasping the larger picture, but I've made one too many posts where I was simply making a stupid mistake.

If I've left out any vital functions or information, please let me know and I'll update it. It's almost 4am for me, so I've probably left out something vital tongue.png.

Thanks again to anyone who can shed insight on this. I've really given it a thorough try on my own, but I'm out of ideas and resources and feeling rather desperate tongue.png I haven't actually done any "game" programming in almost 3 weeks trying to move my project from OBJ models to animated collada ones.

Beginner here <- please take any opinions with grain of salt

Advertisement
... If Haegar answers this, I think I owe him my first-born child at this point

Although this is an attractive offer for sure, I have to confess that the amount of code in the OP is a bit ... discouraging. Rumpelstiltskin's deal was easier ;)

For now, being obvious due to its bolded typeface, this ...

If i print out the transformations, I do get occasional outputs such as "-9.53674e-07"

... does not hint at a problem. It is just a kind of writing the number -9.53674*10-7 = 1.0 / ( -9.53674*107 ) = 1.0 / -95367400.0 which is simply close to zero.


    float Factor = (AnimationTime- (float)pNodeAnim->mRotationKeys[RotationIndex].mTime) / DeltaTime;

One thing that is at least confusing is that inside the position interpolation code as well as the scaling interpolation code you use the mTime of the rotation keyframe. Well, maybe that the keyframes of position, rotation and scaling are all at the same points in time, but even then you should use mPositionKeys[PositionIndex].mTime and mScalingKeys[ScalingIndex].mTime, resp., for the sake of clarity. If, however, the keyframes are not equally timed, your routines are definitely wrong.


I worry a little that I'm setting up layout 5 and 6 (the bone information) incorrectly. I've never set the buffer data using a map, and it's possible I'm going about this incorrectly.

You are probably right with this assumption ;(

1.) Your struct VertexBoneData uses uint as type for IDs, and you rely on the uint being 4 bytes in size.

2.) The constant NUM_BONES_PER_VERTEX is set to 8. If tightly packed and sizeof(uint) is actually 4, then the block of IDs occupies 8*4=32 bytes, as well as the bock of Weights occupies 32 bytes. However, the offset specified for Weights within the call to the belonging glVertexAttribPointer is just 16, making the second half of IDs being interpreted as weights. This hints at NUM_BONES_PER_VERTEX should be 4 instead. Also the vertex shader script is written so that NUM_BONES_PER_VERTEX should be 4.

3.) I don't see a glGenBuffers(1, &boneBuffer) anywhere.

4.) I'm also missing a VAO. Although some implementation work without it, the specification for OpenGL 3.x has made it mandatory. You should use it to avoid spurious mistakes.

I completely understand about the wall of code.

1: good catch on the timing. I had changed all those values from their original, as they throw the assert(Factor >=0 && Factor <=1) on the very first iteration. When debugging, it shows that the animation time is less than the mRotationKeys[RotationIndex].mTime (same for scaling and transform). I had toyed with a few different things and when changing it back to the original version to post here, I'm guilty of copy/pasting. I'll fix those. Out of curiosity, is there a reason that ought to be throwing the assert? Should I just initialize the clock earlier in the program so there's a larger value passed in on the first iteration? This was one of the quirks of the tutorial I was struggling with.

I dislike the clock just reading the game running time (as, unless I'm misunderstanding things, it will just run the animation once at start-up), and want to change it to start a clock on a keypress or event, but I'll run in to the same assert throw, I believe.

2: for the number of bones per vertex. I had changed this from 4, and hadn't realized or caught the consequences of that. The model I'm using has a few more than 4 at certain points. I'll test out with a simpler model and change things back to 4 for debugging purposes. If I want to use models with more bones per vertex, do I just change the block of IDs for weights to a larger type variable? Also, should I simply decouple that from a map, bind two separate buffers, and use larger sized types of variables to store the information?

3: Also a good call on glGenBuffers(1, &boneBuffer). I scoured the tutorial source files and never found this. I did add it myself at one point but must have reverted back to an older version of the code and missed that it was gone. I'll get that back in there.

4: I bind the VAO earlier in the program when generating the terrain. Is there any reason to use a second one or rebind anything, or should the first call be fine? Apologies for not including that in the shown code.

I have some work to do this afternoon. but I'll implement all these changes this evening and update the post here. If you find time to look at this post again, to narrow down the problematic areas, will you look at the vertex shader and what values I'm passing to it in the Enable() and Render() functions? I probably ought to have bolded those portions as well, as I'm much less certain of things there. So, there's a good chance it's wrong.

And thanks as always Haegar, you're the guardian angel of the OpenGL forums smile.png and I can't thank you enough.

Beginner here <- please take any opinions with grain of salt

Ok, to update my...progress(?) I'll post here:

I fixed the key's mTime to correspond correctly. They now look like:


float Factor = (AnimationTime - (float)pNodeAnim->mScalingKeys[ScalingIndex].mTime) / DeltaTime;

float Factor = (AnimationTime - (float)pNodeAnim->mPositionKeys[PositionIndex].mTime) / DeltaTime;

float Factor = (AnimationTime -(float)pNodeAnim->mRotationKeys[RotationIndex].mTime) / DeltaTime;

for the scaling, position and rotation keys, respectively.

I added glGenBuffers:


    //bones////
    glGenBuffers(1, &boneBuffer);
    glEnableVertexAttribArray(5);
    glBindBuffer(GL_ARRAY_BUFFER, boneBuffer);
	glBufferData(GL_ARRAY_BUFFER, sizeof(Bones[0]) * Bones.size(), &Bones[0], GL_STATIC_DRAW);
    glVertexAttribIPointer(5, 4, GL_INT, sizeof(VertexBoneData), (const GLvoid*)0);

    glEnableVertexAttribArray(6);
    glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(VertexBoneData), (const GLvoid*)32);

and lastly, changed the size in glVertexAttribPointer to 32, to account for the 8 bones (or should this be 64? I would think it's still 32, since I don't want to pass the entire size of the VertexBoneData, only the half. I really need to devote a few weeks to just trying to nail down some of these details to understand things a little better. To note: I've tried both values.) Also, should that second value be 8 as well, or is the "size" of the int still 4?:


glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(VertexBoneData), (const GLvoid*)32);

This last one I'm not certain is the correct way to go about it, but sadly, I don't have a simpler model, and believe it or not, simple animated collada files with textures are rather difficult to find (I'll keep looking in the meantime, or make my own simple one. My only worry is that I'm also rather new to 3D modeling, so I would worry I was going wrong in the exporting of the weights/animation and wouldn't know whether any problems were code related or model-creation related).

So, now "things" are rendering...and there's some sort of animation going on in there on a 5 second loop (which is the length of my animation), but it definitely isn't looking right tongue.png And, i'm fairly certain it's now down to the vertex shader. I can cut out the calculations and simply pass a blank identity matrix through the transform function without multiplying it against any rotations/scaling/translations (which in my head should just multiply the vertexPosition_modelspace by 1, but it still comes out looking this way, so, I'm guessing the issue isn't within the calculations but how I'm handling things in the shader. I've also tried cutting out scaling, rotations, etc one by one, to just see if perhaps one of them were off. I could be off on this, but it's where I'm going to focus my efforts in the meantime.

Here's an image of what it's looking like. The vertices are all over the place, and also centered in the center of view space, which should probably tell me something, but I'm not quite sure what tongue.png To note, it should look like a little red statue just moving its leg. So, the clouds in the image are just the skybox and not the animated object. The statue is the dark red and gray stuff splattered all over the rest of the screen tongue.png

Edit**

Ugh, I just realized how not smart I am >.< I need to update the vertex shader to account for the increase in the number of bones. Simply adding more bones in the shader doesn't work since it's a vec4, so I'll need to adapt that...somehow. I have some work to do in the meantime (real work sadly) but when I'm done I'll try to update that to account for it. Hopefully that will solve the issues. If anyone has an idea how to go about that, I'll likely try to pass in an array of 8 or a 4x4 matrix and pass in 16 bones. Unless there's a nifty vec8 (glsl doesn't seem to think so tongue.png). Since it's reading...uniquely...from that map, where it pulls the integer then the float, I'm a little flustered with how to get the data. Simply trying to adapt the shader to read:


layout (location = 5) in int BoneIDs[8];
layout (location = 6) in float Weights[8]; 

results in the error: "warning: two vertex attribute variables (named Weights[0] and BoneIDs[1]) were assigned to the same generic vertex attribute." I'm hoping I dont' have to suss that map into two separate data sets, though part of my brain would like that very much.

Beginner here <- please take any opinions with grain of salt

1: good catch on the timing. [...] Out of curiosity, is there a reason that ought to be throwing the assert? Should I just initialize the clock earlier in the program so there's a larger value passed in on the first iteration? This was one of the quirks of the tutorial I was struggling with.

Assertions are a runtime mechanism to confirm that the state of the program is as expected. WIth assertions inserted, you don't rely on things running well, but check for it. Not all misbehavior of the code can be seen on screen. For example, some mistakes may have a subtle effect that sums up over time. You can distinguish assertions for at least 3 purposes: To check requirements on function arguments at the very beginning of a function, to ensure return values of functions to meet the definition, and to check the general state. So, if an assertion fires, and the implementation of the assertion matches its intention, there is something wrong.

With respect to that particular assertion: You have to investigate why it is triggered. Put a if conditional with the inverted condition of the assertion just above the assertion, put a dummy statement into the if-body, and set a breakpoint onto the dummy statement. Then start the debugger and look what value Factor has. It may be that due to numerical imprecision the value is just a tiny bit outside the allowed interval, or else it is much outside. In the former case you suffer from a typical problem when using floating point numbers, while in the latter case you have a mistake. How to continue depends on what it is.

I dislike the clock just reading the game running time (as, unless I'm misunderstanding things, it will just run the animation once at start-up), and want to change it to start a clock on a keypress or event, but I'll run in to the same assert throw, I believe.
The game running time is a good source for timing animations because it is independent on the frame rate, of course. On every iteration of the main game loop, the game running time is fetched at one point and used as update time during that loop iteration. By this way all animations see the same moment in time / the same delta time. When an animation is started, the current update time is stored as start time for that animation instance. Further, the difference of current update time minus start time gives the local animation time, i.e. one that has value 0 at the beginning of the particular animation playback. This is in principal the time to be used for looking up keyframes.
Things get more complicated when animation loops are used. In this case the wrap at the animation loop's end can be used to adapt the start time to get a new one for each iteration. Notice that this adaption is not done by storing the current game running time; you have to consider the offset coming from exceeding the end of the loop.
Thing get even more complicated when animation blending comes into play, because animation blending often requires time acceleration. But let this aside for a later inspection.
4: I bind the VAO earlier in the program when generating the terrain. Is there any reason to use a second one or rebind anything, or should the first call be fine? Apologies for not including that in the shown code.
It is sufficient to have a single VAO active all the time if you deal with the VBOs in the way existing before the OpenGL 3 era, or anyway if you have just a single VBO. In fact, VAOs are intended to simplify switching of VBOs by having one VAO per configuration, because only 1 OpenGL call is then necessary for switching. This should give some performance boost. Unfortunately, in the past some drivers performed bad on this as sources on the internet have reported. I don't know how it is nowadays.

and lastly, changed the size in glVertexAttribPointer to 32, to account for the 8 bones (or should this be 64? I would think it's still 32, since I don't want to pass the entire size of the VertexBoneData, only the half. I really need to devote a few weeks to just trying to nail down some of these details to understand things a little better. To note: I've tried both values.) [...]
What you want to pass is the byte offset from the beginning of the structure to the occurrence of the array of Weights. This is the byte-size of IDs[NUM_BONES_PER_VERTEX], which calculates to NUM_BONES_PER_VERTEX * sizeof(uint). Your best choice is to replace the literal constant by using the offsetof-operator, or at least by using the expression NUM_BONES_PER_VERTEX * sizeof(uint).
As said, you rely on uint being 4 bytes in size. That should be considered to by changed, e.g. by using uint32_t, although that isn't a guarantee either. But it gives at least a hint of what you want.
[...] Also, should that second value be 8 as well, or is the "size" of the int still 4?:
The value does not denote the size of the int but the count of elements in the vector passed in. That value is allowed to be 1, 2, 3, or else 4, because the vertex stage inputs of the GPU are 4 element vectors (elements not supplied are automatically completed with standard values). So "8" is not an option.

I need to update the vertex shader to account for the increase in the number of bones. Simply adding more bones in the shader doesn't work since it's a vec4, so I'll need to adapt that...somehow. I have some work to do in the meantime (real work sadly) but when I'm done I'll try to update that to account for it. Hopefully that will solve the issues. If anyone has an idea how to go about that, I'll likely try to pass in an array of 8 or a 4x4 matrix and pass in 16 bones. Unless there's a nifty vec8 (glsl doesn't seem to think so ). Since it's reading...uniquely...from that map, where it pulls the integer then the float, I'm a little flustered with how to get the data. Simply trying to adapt the shader to read:
layout (location = 5) in int BoneIDs[8];
layout (location = 6) in float Weights[8];
results in the error: "warning: two vertex attribute variables (named Weights[0] and BoneIDs[1]) were assigned to the same generic vertex attribute." I'm hoping I dont' have to suss that map into two separate data sets, though part of my brain would like that very much.

As said above, a vertex input is 4 elements wide. A "location" defines the index of the vertex stage input to use. So each "location" stands for a 4 element vector. When you write


    layout (location = 5) in int BoneIDs[8];

you ask the GPU to reserve 8 inputs beginning at location 5. So this means locations 5, 6, 7, ..., 12, each one used to transfer one element on an int value (the remaining 3 elements being filled up with 0, 0, and 1). Then you specify the Weights in a similar way, asking for locations 6, 7, 8, ... 13. This cannot work and is hence denied by the shader compiler.

What you actually want to use is something like


    layout (location = 5) in ivec4 BoneIDs[2];
    layout (location = 7) in vec4 BoneIDs[2];

This means "allocate the vertex stage inputs at locations 5 and 6, each one for a 4 element int vector (giving you 8 integers in total), as well as the vertex stage inputs 7 and 8, each one for a 4 element float vector (giving you 8 floats in total)". This has an impact on the glVertexAttribPointer calls, of course.

You're too generous Haegar, and I thank you for it.

I understood the assert, i was just confused why the tutorial was set up where it would catch on the first iterations. I checked the numbers, and it's because the animation time sent in (the running time) was too small on the first iteration, thus subtracting the key mtime would make it smaller than 0. To get around, i just pass in a clock that's initialized a little sooner (one in the main program loop, rather than part of the Object class. This probably isn't ideal ( i can imagine on a faster system it would be problematic again). But, this is certainly a problem I can deal with on my own, and likely isn't the source of the main problem with rendering. I'll likely try to overhaul that whole function (many of these functions to be honest) before integrating them into my main project.


It is sufficient to have a single VAO active all the time if you deal with the VBOs in the way existing before the OpenGL 3 era, or anyway if you have just a single VBO. In fact, VAOs are intended to simplify switching of VBOs by having one VAO per configuration, because only 1 OpenGL call is then necessary for switching. This should give some performance boost. Unfortunately, in the past some drivers performed bad on this as sources on the internet have reported. I don't know how it is nowadays.

Don't feel obligated to elaborate here, this is certainly something I could look up, and certainly ought to. I recently switched from having multiple VAOs to a single one, as I was under the impression that it was the preferred method. Oddly enough, it didn't change much. Rather than bind multiple, I just called it once and forgot about it >.<. I'll look into this a bit more.

It shouldn't be difficult to adapt the code either way (i hope).


As said above, a vertex input is 4 elements wide. A "location" defines the index of the vertex stage input to use. So each "location" stands for a 4 element vector. When you write
layout (location = 5) in int BoneIDs[8];
you ask the GPU to reserve 8 inputs beginning at location 5. So this means locations 5, 6, 7, ..., 12, each one used to transfer one element on an int value (the remaining 3 elements being filled up with 0, 0, and 1). Then you specify the Weights in a similar way, asking for locations 6, 7, 8, ... 13. This cannot work and is hence denied by the shader compiler.

What you actually want to use is something like
layout (location = 5) in ivec4 BoneIDs[2];
layout (location = 7) in vec4 BoneIDs[2];
This means "allocate the vertex stage inputs at locations 5 and 6, each one for a 4 element int vector (giving you 8 integers in total), as well as the vertex stage inputs 7 and 8, each one for a 4 element float vector (giving you 8 floats in total)". This has an impact on the glVertexAttribPointer calls, of course.

Ah, brilliant. It's incredibly late for me, but I will attempt to try to implement this tomorrow. If I run into further troubles, I'll give myself the better part of a week to work through them, as you've already been too kind and helpful. But, I'm pretty sure you've given me enough to be able to sort through the remainder of the details myself.

As always, thank you from the bottom of my heart, Haegarr. You're tremendously helpful, and I genuinely appreciate it. I can't imagine learning this stuff without the occasional insight and help from people like you :) (this stuff can be really difficult :P).

Beginner here <- please take any opinions with grain of salt

A thought about the proceeding: I would go with at most 4 bones per vertex for now. If that works, going for more is a question of refactoring. I would also consider to build up a test scene manually, so having a chance to simplify the model to the bare needs. While having an overview is is fine for knowing where to go, a non-incremental developing makes failure tracking difficult.

For now: Good luck! :)

Hm. I took your advice and went back to a 4 boned code. I made a quick, easy two boned model (it's just a 3d rectangle that bends in half), but the vertices still dont' seem to be in the right place. It renders, and there is definitely animation going on, but the model seems inverted upon itself where it should be animating back and forth. I'm now thinking the problem is either in how I'm setting the export options in blender for the collada model, or my vertex shader is still wrong, or perhaps the "factor" variable created by the key times is somehow being calculated incorrectly. I'll try to debugg each of those options, but it will probably take some time (there's a lot of things that could be going wrong for each of those, with a lot of options for some of them).

Beginner here <- please take any opinions with grain of salt


[...] I'll try to debugg each of those options, but it will probably take some time (there's a lot of things that could be going wrong for each of those, with a lot of options for some of them).

Yeah, that's the reason why I spoke of a very simple manually made (i.e. hardcoded data) model, if possible. If you have a knowingly well defined model, any exporter / importer problem would be excluded. If that simple model renders well (without animation), I would test some different bone settings (still without animation) to ensure that the skinning works well. If also this is okay, then I'd add some simple animation. At the very end, I'd enable the importer and test with more complex set up.

This topic is closed to new replies.

Advertisement