骨骼动画倾斜
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 循环中移动。每个循环周期都覆盖相同的实例。
我遵循了 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 循环中移动。每个循环周期都覆盖相同的实例。