最近,我一直在使用Assimp库在OpenGL中加载和播放骨骼动画,遵循一些不同的教程/来源,例如OglDev和Ephenation OpenGL。我仍然没有100%确定我知道它是如何工作的,所以只是为了澄清:
aiNodeAnim
成员mChannels
aiAnimation
加载的
aiMesh->mBones[BONE]->mOffsetMatrix
的相应骨骼偏移矩阵。这样,从关键帧生成的骨骼矩阵可以替换绑定姿势对该骨骼的默认转换我的问题的第一部分:这是正确的吗?据我所知,假设我的理解是正确的,它应该在逻辑上有效。我还没有看到任何反对这一点的来源/帖子/问题。
我已经编写了一个这样的实现(在C ++中),但它没有像我预期的那样工作。网格的一部分看起来像它们应该的那样,甚至是预期的姿势,但大多数网格看起来都是皱折的。大部分是可区分的,我可以识别大部分网格,但似乎大多数骨基质都不正确。当我在搅拌机中打开我的模型时,所有的关键帧都是正确的,模型的动画效果非常好。我会发布我的应用程序的屏幕截图,但我没有这样做的地方(我没有图片帐户或任何东西。)
为什么只有一些骨头会起作用而其他骨头不起作用?
以下是我的代码中最值得怀疑的部分。我将我的jawSkeleton
课程设置在LearnOpenGL.com的Model
课程中。 (忽略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;
}
}
我做了一些其他未显示的事情,例如通过其父矩阵递归地乘以每个骨骼的矩阵,并将它们乘以它们的偏移矩阵。当我在单个骨骼矩阵的位置使用单位矩阵时,网格以其正常的绑定姿势绘制,因此我几乎可以肯定这不是网格导入问题。正如我所说,网格的一部分看起来是正确的,所以我猜测它也不是皮肤问题。我现在已经研究了这个问题大约一个星期了。有什么线索是怎么回事?任何帮助表示赞赏。