Скелетная анимация с Assimp и OpenGL

Недавно я работал над загрузкой и воспроизведением скелетных анимаций в OpenGL с использованием библиотеки Assimp, следуя нескольким различным учебным пособиям / источникам, таким как OglDev а также Эфенация OpenGL. Я все еще не уверен на 100%, что знаю, как это работает, поэтому просто уточнить:

  • Ключевые кадры загружаются из aiNodeAnimв mChannels член aiAnimation
  • Эти ключевые кадры используются для генерации матрицы для каждой кости, представляющей преобразование кости от ее родителя.
  • Матрица каждой кости умножается на матрицу ее родителя.
  • Эта матрица затем умножается на соответствующую матрицу смещения кости, из aiMesh->mBones[BONE]->mOffsetMatrix, Таким образом, матрица кости, сгенерированная из ключевых кадров, может заменить преобразование по умолчанию для этой кости
  • Эта финальная матрица используется в вершинном шейдере при рисовании (по одному на каждую кость)

Первая часть моего вопроса: это правильно? Насколько я знаю, это должно логически работать, предполагая, что мое понимание верно. Я еще не видел ни одного источника / поста / вопроса, который бы противостоял этому.

Я написал реализацию этого (в C ++), но она не работает, как я ожидал. Части сетки выглядят так, как они должны, и даже находятся в ожидаемой позе, но большая часть сетки кажется смятой. Большие части различимы, и я могу определить большую часть сетки, но кажется, что большинство костных матриц неверны. Когда я открываю свою модель в Blender, все ключевые кадры верны, и модель отлично анимируется. Я бы опубликовал скриншоты своего приложения, но у меня нет места, чтобы сделать это (у меня нет учетной записи imageshack или чего-то еще.)

Есть ли причина, по которой только некоторые кости будут работать, а другие нет?

Вот части моего кода, которые наиболее сомнительны. Я основал свой jawSkeleton класс на Model класс от LearnOpenGL.com. (Игнорировать jaw префикс, это личное дело, которое я делаю.)

Прототип класса, в jawSkeleton.h:

class jawSkeleton
{
public:
/*  Functions   */
// Constructor, expects a filepath to a 3D model.
jawSkeleton(GLchar* path);
jawSkeleton();

// Draws the model, and thus all its meshes
void Draw();

/*  Functions   */
// Loads a model with supported ASSIMP extensions from file and stores the resulting meshes in the meshes vector.
void Load(std::string path, bool bSmoothNormals = false, UINT unFlags = NULL);

private:

bool Loaded;

/*  Model Data  */
std::vector<jawSkeletalMesh> meshes;
std::string directory;
// Stores all the textures loaded so far, optimization to make sure textures aren't loaded more than once.
std::vector<jawSub_Texture> textures_loaded;
jawSkeletalBone* RootBone;
std::vector<jawSkeletalBone> bones;
glm::mat4 Matrices[64];
glm::mat4 GlobalInverseTransform;

std::vector<jawSkeletalAnim> Animations;

// finds a bone in the model data with the specified name
jawSkeletalBone* findBone(std::string Name);
// finds a bone in the model data with the specified name (Must be 'int' so that we can use -1 to denote no bone)
int findBoneIndex(std::string Name);

// Processes a node in a recursive fashion.
// Processes the bones identified by the meshes
void processNodeMeshBones(aiNode* node, const aiScene* scene, std::vector<std::string> *BoneNames);
// Populates the bone heirarchy structure
void populateBoneHeirarchy(const aiScene* scene, std::vector<std::string> *BoneNames);
//  Processes each individual mesh located at the node and repeats this process on its children nodes (if any).
void processNodeMeshes(aiNode* node, const aiScene* scene);

jawSkeletalMesh processMesh(aiMesh* mesh, const aiScene* scene);

// Checks all material textures of a given type and loads the textures if they're not loaded yet.
// The required info is returned as a jawSub_Texture struct.
std::vector<jawSub_Texture> loadMaterialTextures(aiMaterial* mat, aiTextureType type, std::string typeName);

// Load an animation for the model
void processAnim(aiAnimation* Anim);

// Updates the matrices for every bone
// DOES NOT pull keyframes from the animation
// Merely travels down the bone heirarchy and multiplies each child matrix by the parent matrix
void UpdateBoneMatrices();
// Updates the array of matrices that is sent to the shader
void UpdateBoneMatrixArray();

// Sets all the bone matrices to match a particular time
// Before drawing, call UpdateBoneMatrices and UpdateBoneMatrixArray after calling this function
void SetBoneMatrices(double Time);
};

Загрузка данных ключевого кадра, в jawSkeleton.cpp:

void jawSkeleton::processAnim(aiAnimation* Anim) {
this->Animations.push_back(jawSkeletalAnim());
jawSkeletalAnim &_Anim = this->Animations.back();

_Anim.Name = Anim->mName.C_Str();

double TicksPerSecond = (Anim->mTicksPerSecond == 0.0) ? 1.0 : Anim->mTicksPerSecond;
_Anim.Duration = Anim->mDuration / TicksPerSecond;

for (GLuint i = 0; i < Anim->mNumChannels; i++) {
aiNodeAnim* ThisAnim = Anim->mChannels[i];
jawSkeletalBoneAnim BoneAnim;
BoneAnim.Index = this->findBoneIndex(ThisAnim->mNodeName.C_Str());
if (BoneAnim.Index > this->bones.size() || BoneAnim.Index < 0)
continue;

// Translation
BoneAnim.TranslateKeys.reserve(ThisAnim->mNumPositionKeys);
for (GLuint j = 0; j < ThisAnim->mNumPositionKeys; j++) {
aiVector3D v = (ThisAnim->mPositionKeys + j)->mValue;
BoneAnim.TranslateKeys.push_back(jawTranslateKey{
glm::vec3(v.x, v.y, v.z),
ThisAnim->mPositionKeys[j].mTime / TicksPerSecond
});
}

// Rotation
BoneAnim.RotateKeys.reserve(ThisAnim->mNumRotationKeys);
for (GLuint j = 0; j < ThisAnim->mNumRotationKeys; j++) {
aiQuaternion v = (ThisAnim->mRotationKeys + j)->mValue;
BoneAnim.RotateKeys.push_back(jawRotateKey{
glm::quat(v.w, v.x, v.y, v.z),
ThisAnim->mRotationKeys[j].mTime / TicksPerSecond
});
}

// Scaling
BoneAnim.ScaleKeys.reserve(ThisAnim->mNumScalingKeys);
for (GLuint j = 0; j < ThisAnim->mNumScalingKeys; j++) {
aiVector3D v = (ThisAnim->mScalingKeys + j)->mValue;
BoneAnim.ScaleKeys.push_back(jawScaleKey{
glm::vec3(v.x, v.y, v.z),
ThisAnim->mScalingKeys[j].mTime / TicksPerSecond
});
}

// The BoneAnims member is an std::unordered_map
_Anim.BoneAnims.insert(std::pair<GLuint, jawSkeletalBoneAnim>(BoneAnim.Index, BoneAnim));
}
}

Подготовка отдельных матриц, в jawSkeletal.cpp:

void jawSkeleton::SetBoneMatrices(double Time) {

// This is the function to edit

jawSkeletalAnim& Anim = this->Animations[0];

for (GLuint i = 0; i < this->bones.size(); i++) {
auto Result = Anim.BoneAnims.find(i);
if (Result == Anim.BoneAnims.end())
continue;
jawSkeletalBoneAnim &BoneAnim = Result->second;

glm::mat4 T = glm::translate(glm::mat4(1), BoneAnim.TranslateKeys[70].Value);glm::mat4 R = glm::toMat4(BoneAnim.RotateKeys[70].Value);
glm::mat4 S = glm::scale(glm::mat4(1), BoneAnim.ScaleKeys[70].Value);

this->bones[i].CurrentMatrix = T * R * S;
}
}

Я делаю несколько других не показанных вещей, таких как рекурсивное умножение матрицы каждой кости на матрицу ее родителя и умножение их на их матрицу смещения. Когда я использую матрицу идентичности вместо отдельных костных матриц, сетка рисуется в своей обычной позе связывания, так что я почти уверен, что это не проблема импорта сетки. И, как я уже сказал, части сетки выглядят правильно, так что я думаю, это не проблема скинов. Я изучал этот вопрос около недели. Любая подсказка, что происходит? Любая помощь приветствуется.

0

Решение

Задача ещё не решена.

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]