Assimp动画骨骼变换
Assimp animation bone transformation
最近我在做骨骼动画导入,所以我用一些 IK 技术制作了一个类似 minecraft 的 3d 模型来测试 Assimp 动画导入。输出格式为COLLADA(*.dae),我使用的工具是Blender。在编程方面,我的环境是opengl/glm/assimp。我认为这些信息对我的问题是 enough.One 东西,模型的动画,我只是记录了 7 unmove 关键帧用于测试 assimp 动画。
首先,我猜除了局部变换部分我的变换是正确的,所以只让函数 return glm::mat4(1.0f)
,结果显示绑定姿势(不确定)模型。 (见下图)
其次,将数值glm::mat4(1.0f)
调回bone->localTransform = transform * scaling * glm::mat4(1.0f);
,模型变形。 (见下图)
在搅拌机中测试图像和模型:
(bone->localTransform = glm::mat4(1.0f) * scaling * rotate;
: 此图像在地下 :( )
代码在这里:
void MeshModel::UpdateAnimations(float time, std::vector<Bone*>& bones)
{
for each (Bone* bone in bones)
{
glm::mat4 rotate = GetInterpolateRotation(time, bone->rotationKeys);
glm::mat4 transform = GetInterpolateTransform(time, bone->transformKeys);
glm::mat4 scaling = GetInterpolateScaling(time, bone->scalingKeys);
//bone->localTransform = transform * scaling * glm::mat4(1.0f);
//bone->localTransform = glm::mat4(1.0f) * scaling * rotate;
//bone->localTransform = glm::translate(glm::mat4(1.0f), glm::vec3(0.5f));
bone->localTransform = glm::mat4(1.0f);
}
}
void MeshModel::UpdateBone(Bone * bone)
{
glm::mat4 parentTransform = bone->getParentTransform();
bone->nodeTransform = parentTransform
* bone->transform // assimp_node->mTransformation
* bone->localTransform; // T S R matrix
bone->finalTransform = globalInverse
* bone->nodeTransform
* bone->inverseBindPoseMatrix; // ai_mesh->mBones[i]->mOffsetMatrix
for (int i = 0; i < (int)bone->children.size(); i++) {
UpdateBone(bone->children[i]);
}
}
glm::mat4 Bone::getParentTransform()
{
if (this->parent != nullptr)
return parent->nodeTransform;
else
return glm::mat4(1.0f);
}
glm::mat4 MeshModel::GetInterpolateRotation(float time, std::vector<BoneKey>& keys)
{
// we need at least two values to interpolate...
if ((int)keys.size() == 0) {
return glm::mat4(1.0f);
}
if ((int)keys.size() == 1) {
return glm::mat4_cast(keys[0].rotation);
}
int rotationIndex = FindBestTimeIndex(time, keys);
int nextRotationIndex = (rotationIndex + 1);
assert(nextRotationIndex < (int)keys.size());
float DeltaTime = (float)(keys[nextRotationIndex].time - keys[rotationIndex].time);
float Factor = (time - (float)keys[rotationIndex].time) / DeltaTime;
if (Factor < 0.0f)
Factor = 0.0f;
if (Factor > 1.0f)
Factor = 1.0f;
assert(Factor >= 0.0f && Factor <= 1.0f);
const glm::quat& startRotationQ = keys[rotationIndex].rotation;
const glm::quat& endRotationQ = keys[nextRotationIndex].rotation;
glm::quat interpolateQ = glm::lerp(endRotationQ, startRotationQ, Factor);
interpolateQ = glm::normalize(interpolateQ);
return glm::mat4_cast(interpolateQ);
}
glm::mat4 MeshModel::GetInterpolateTransform(float time, std::vector<BoneKey>& keys)
{
// we need at least two values to interpolate...
if ((int)keys.size() == 0) {
return glm::mat4(1.0f);
}
if ((int)keys.size() == 1) {
return glm::translate(glm::mat4(1.0f), keys[0].vector);
}
int translateIndex = FindBestTimeIndex(time, keys);
int nextTranslateIndex = (translateIndex + 1);
assert(nextTranslateIndex < (int)keys.size());
float DeltaTime = (float)(keys[nextTranslateIndex].time - keys[translateIndex].time);
float Factor = (time - (float)keys[translateIndex].time) / DeltaTime;
if (Factor < 0.0f)
Factor = 0.0f;
if (Factor > 1.0f)
Factor = 1.0f;
assert(Factor >= 0.0f && Factor <= 1.0f);
const glm::vec3& startTranslate = keys[translateIndex].vector;
const glm::vec3& endTrabslate = keys[nextTranslateIndex].vector;
glm::vec3 delta = endTrabslate - startTranslate;
glm::vec3 resultVec = startTranslate + delta * Factor;
return glm::translate(glm::mat4(1.0f), resultVec);
}
代码思路参考自 and Skeletal Animation With Assimp.
总的来说,我把assimp的所有信息都fectch到MeshModel中,保存到骨骼结构中,所以我觉得这些信息还可以吗?
最后一件事,我的顶点着色器代码:
#version 330 core
#define MAX_BONES_PER_VERTEX 4
in vec3 position;
in vec2 texCoord;
in vec3 normal;
in ivec4 boneID;
in vec4 boneWeight;
const int MAX_BONES = 100;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform mat4 boneTransform[MAX_BONES];
out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;
out float Visibility;
const float density = 0.007f;
const float gradient = 1.5f;
void main()
{
mat4 boneTransformation = boneTransform[boneID[0]] * boneWeight[0];
boneTransformation += boneTransform[boneID[1]] * boneWeight[1];
boneTransformation += boneTransform[boneID[2]] * boneWeight[2];
boneTransformation += boneTransform[boneID[3]] * boneWeight[3];
vec3 usingPosition = (boneTransformation * vec4(position, 1.0)).xyz;
vec3 usingNormal = (boneTransformation * vec4(normal, 1.0)).xyz;
vec4 viewPos = view * model * vec4(usingPosition, 1.0);
gl_Position = projection * viewPos;
FragPos = vec3(model * vec4(usingPosition, 1.0f));
Normal = mat3(transpose(inverse(model))) * usingNormal;
TexCoords = texCoord;
float distance = length(viewPos.xyz);
Visibility = exp(-pow(distance * density, gradient));
Visibility = clamp(Visibility, 0.0f, 1.0f);
}
如果我的上述问题,缺少代码或描述模糊,请告诉我,谢谢!
编辑:(1)
另外,我的骨骼信息是这样的(取码部分):
for (int i = 0; i < (int)nodeAnim->mNumPositionKeys; i++)
{
BoneKey key;
key.time = nodeAnim->mPositionKeys[i].mTime;
aiVector3D vec = nodeAnim->mPositionKeys[i].mValue;
key.vector = glm::vec3(vec.x, vec.y, vec.z);
currentBone->transformKeys.push_back(key);
}
有一些变换向量,所以我上面的代码 glm::mat4 transform = GetInterpolateTransform(time, bone->transformKeys);
,当然,从中得到 相同的值 。我不确定我是否制作了提供变换值的 nomove 关键帧动画是否为真(当然它有 7 个关键帧)。
关键帧内容如下(头部骨骼调试):
7 个不同的关键帧,相同的矢量值。
编辑:(2)
如果你想测试我的dae文件,我把它放在jsfiddle里了,快来拿吧:)。另一件事,在 Unity 中我的文件工作正常,所以我认为可能不是我的本地转换出现问题,似乎问题可能是其他的,比如 parentTransform 或 bone->transform...等等?我还添加了所有骨骼的局部变换矩阵,但无法弄清楚为什么 COLLADA 为我的取消移动动画包含这些值...
多方测试,最终发现问题出在UpdateBone()
部分。
在我指出我的问题之前,我需要说矩阵乘法的系列让我感到困惑,但是当我找到解决方案时,它让我完全(也许只有 90%)意识到所有矩阵的作用。
问题来自文章。我假设答案代码是绝对正确的,并且不再考虑应该使用矩阵。因此,滥用矩阵将我的视线转移到局部变换矩阵中。回到我的问题部分的结果图像是当我将局部变换矩阵更改为 return glm::mat4(1.0f)
时的绑定姿势。
所以问题是为什么改变了绑定姿势?我认为问题一定是骨骼 space 中的局部变换,但我错了。在我给出答案之前,请看下面的代码:
void MeshModel::UpdateBone(Bone * bone)
{
glm::mat4 parentTransform = bone->getParentTransform();
bone->nodeTransform = parentTransform
* bone->transform // assimp_node->mTransformation
* bone->localTransform; // T S R matrix
bone->finalTransform = globalInverse
* bone->nodeTransform
* bone->inverseBindPoseMatrix; // ai_mesh->mBones[i]->mOffsetMatrix
for (int i = 0; i < (int)bone->children.size(); i++) {
UpdateBone(bone->children[i]);
}
}
然后我做了如下修改:
void MeshModel::UpdateBone(Bone * bone)
{
glm::mat4 parentTransform = bone->getParentTransform();
if (boneName == "Scene" || boneName == "Armature")
{
bone->nodeTransform = parentTransform
* bone->transform // when isn't bone node, using assimp_node->mTransformation
* bone->localTransform; //this is your T * R matrix
}
else
{
bone->nodeTransform = parentTransform // This retrieve the transformation one level above in the tree
* bone->localTransform; //this is your T * R matrix
}
bone->finalTransform = globalInverse // scene->mRootNode->mTransformation
* bone->nodeTransform //defined above
* bone->inverseBindPoseMatrix; // ai_mesh->mBones[i]->mOffsetMatrix
for (int i = 0; i < (int)bone->children.size(); i++) {
UpdateBone(bone->children[i]);
}
}
我不知道assimp_node->mTransformation
之前给我的是什么,只有assimp文档中的描述"The transformation relative to the node's parent"。对于一些测试,我发现 mTransformation
是绑定姿势矩阵,如果我在骨骼节点上使用它们,则当前节点相对于父节点。给个头骨矩阵的图吧。
左边的部分是从assimp_node->mTransformation
中获取的transform
。右边的部分是我的unmove动画的localTransform
计算出来的通过 nodeAnim->mPositionKeys
、nodeAnim->mRotationKeys
和 nodeAnim->mScalingKeys
.
中的键
回顾一下我所做的,我做了两次绑定姿势变换,所以我的问题部分中的图像看起来只是单独的而不是意大利面条:)
最后,让我展示一下我在unmove 动画测试和正确的动画结果之前做了什么。
(给大家,如果我的概念有误,请指出我!谢谢。)
最近我在做骨骼动画导入,所以我用一些 IK 技术制作了一个类似 minecraft 的 3d 模型来测试 Assimp 动画导入。输出格式为COLLADA(*.dae),我使用的工具是Blender。在编程方面,我的环境是opengl/glm/assimp。我认为这些信息对我的问题是 enough.One 东西,模型的动画,我只是记录了 7 unmove 关键帧用于测试 assimp 动画。
首先,我猜除了局部变换部分我的变换是正确的,所以只让函数 return glm::mat4(1.0f)
,结果显示绑定姿势(不确定)模型。 (见下图)
其次,将数值glm::mat4(1.0f)
调回bone->localTransform = transform * scaling * glm::mat4(1.0f);
,模型变形。 (见下图)
在搅拌机中测试图像和模型:
bone->localTransform = glm::mat4(1.0f) * scaling * rotate;
: 此图像在地下 :( )
代码在这里:
void MeshModel::UpdateAnimations(float time, std::vector<Bone*>& bones)
{
for each (Bone* bone in bones)
{
glm::mat4 rotate = GetInterpolateRotation(time, bone->rotationKeys);
glm::mat4 transform = GetInterpolateTransform(time, bone->transformKeys);
glm::mat4 scaling = GetInterpolateScaling(time, bone->scalingKeys);
//bone->localTransform = transform * scaling * glm::mat4(1.0f);
//bone->localTransform = glm::mat4(1.0f) * scaling * rotate;
//bone->localTransform = glm::translate(glm::mat4(1.0f), glm::vec3(0.5f));
bone->localTransform = glm::mat4(1.0f);
}
}
void MeshModel::UpdateBone(Bone * bone)
{
glm::mat4 parentTransform = bone->getParentTransform();
bone->nodeTransform = parentTransform
* bone->transform // assimp_node->mTransformation
* bone->localTransform; // T S R matrix
bone->finalTransform = globalInverse
* bone->nodeTransform
* bone->inverseBindPoseMatrix; // ai_mesh->mBones[i]->mOffsetMatrix
for (int i = 0; i < (int)bone->children.size(); i++) {
UpdateBone(bone->children[i]);
}
}
glm::mat4 Bone::getParentTransform()
{
if (this->parent != nullptr)
return parent->nodeTransform;
else
return glm::mat4(1.0f);
}
glm::mat4 MeshModel::GetInterpolateRotation(float time, std::vector<BoneKey>& keys)
{
// we need at least two values to interpolate...
if ((int)keys.size() == 0) {
return glm::mat4(1.0f);
}
if ((int)keys.size() == 1) {
return glm::mat4_cast(keys[0].rotation);
}
int rotationIndex = FindBestTimeIndex(time, keys);
int nextRotationIndex = (rotationIndex + 1);
assert(nextRotationIndex < (int)keys.size());
float DeltaTime = (float)(keys[nextRotationIndex].time - keys[rotationIndex].time);
float Factor = (time - (float)keys[rotationIndex].time) / DeltaTime;
if (Factor < 0.0f)
Factor = 0.0f;
if (Factor > 1.0f)
Factor = 1.0f;
assert(Factor >= 0.0f && Factor <= 1.0f);
const glm::quat& startRotationQ = keys[rotationIndex].rotation;
const glm::quat& endRotationQ = keys[nextRotationIndex].rotation;
glm::quat interpolateQ = glm::lerp(endRotationQ, startRotationQ, Factor);
interpolateQ = glm::normalize(interpolateQ);
return glm::mat4_cast(interpolateQ);
}
glm::mat4 MeshModel::GetInterpolateTransform(float time, std::vector<BoneKey>& keys)
{
// we need at least two values to interpolate...
if ((int)keys.size() == 0) {
return glm::mat4(1.0f);
}
if ((int)keys.size() == 1) {
return glm::translate(glm::mat4(1.0f), keys[0].vector);
}
int translateIndex = FindBestTimeIndex(time, keys);
int nextTranslateIndex = (translateIndex + 1);
assert(nextTranslateIndex < (int)keys.size());
float DeltaTime = (float)(keys[nextTranslateIndex].time - keys[translateIndex].time);
float Factor = (time - (float)keys[translateIndex].time) / DeltaTime;
if (Factor < 0.0f)
Factor = 0.0f;
if (Factor > 1.0f)
Factor = 1.0f;
assert(Factor >= 0.0f && Factor <= 1.0f);
const glm::vec3& startTranslate = keys[translateIndex].vector;
const glm::vec3& endTrabslate = keys[nextTranslateIndex].vector;
glm::vec3 delta = endTrabslate - startTranslate;
glm::vec3 resultVec = startTranslate + delta * Factor;
return glm::translate(glm::mat4(1.0f), resultVec);
}
代码思路参考自
总的来说,我把assimp的所有信息都fectch到MeshModel中,保存到骨骼结构中,所以我觉得这些信息还可以吗?
最后一件事,我的顶点着色器代码:
#version 330 core
#define MAX_BONES_PER_VERTEX 4
in vec3 position;
in vec2 texCoord;
in vec3 normal;
in ivec4 boneID;
in vec4 boneWeight;
const int MAX_BONES = 100;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform mat4 boneTransform[MAX_BONES];
out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;
out float Visibility;
const float density = 0.007f;
const float gradient = 1.5f;
void main()
{
mat4 boneTransformation = boneTransform[boneID[0]] * boneWeight[0];
boneTransformation += boneTransform[boneID[1]] * boneWeight[1];
boneTransformation += boneTransform[boneID[2]] * boneWeight[2];
boneTransformation += boneTransform[boneID[3]] * boneWeight[3];
vec3 usingPosition = (boneTransformation * vec4(position, 1.0)).xyz;
vec3 usingNormal = (boneTransformation * vec4(normal, 1.0)).xyz;
vec4 viewPos = view * model * vec4(usingPosition, 1.0);
gl_Position = projection * viewPos;
FragPos = vec3(model * vec4(usingPosition, 1.0f));
Normal = mat3(transpose(inverse(model))) * usingNormal;
TexCoords = texCoord;
float distance = length(viewPos.xyz);
Visibility = exp(-pow(distance * density, gradient));
Visibility = clamp(Visibility, 0.0f, 1.0f);
}
如果我的上述问题,缺少代码或描述模糊,请告诉我,谢谢!
编辑:(1)
另外,我的骨骼信息是这样的(取码部分):
for (int i = 0; i < (int)nodeAnim->mNumPositionKeys; i++)
{
BoneKey key;
key.time = nodeAnim->mPositionKeys[i].mTime;
aiVector3D vec = nodeAnim->mPositionKeys[i].mValue;
key.vector = glm::vec3(vec.x, vec.y, vec.z);
currentBone->transformKeys.push_back(key);
}
有一些变换向量,所以我上面的代码 glm::mat4 transform = GetInterpolateTransform(time, bone->transformKeys);
,当然,从中得到 相同的值 。我不确定我是否制作了提供变换值的 nomove 关键帧动画是否为真(当然它有 7 个关键帧)。
关键帧内容如下(头部骨骼调试):
编辑:(2)
如果你想测试我的dae文件,我把它放在jsfiddle里了,快来拿吧:)。另一件事,在 Unity 中我的文件工作正常,所以我认为可能不是我的本地转换出现问题,似乎问题可能是其他的,比如 parentTransform 或 bone->transform...等等?我还添加了所有骨骼的局部变换矩阵,但无法弄清楚为什么 COLLADA 为我的取消移动动画包含这些值...
多方测试,最终发现问题出在UpdateBone()
部分。
在我指出我的问题之前,我需要说矩阵乘法的系列让我感到困惑,但是当我找到解决方案时,它让我完全(也许只有 90%)意识到所有矩阵的作用。
问题来自文章glm::mat4(1.0f)
时的绑定姿势。
所以问题是为什么改变了绑定姿势?我认为问题一定是骨骼 space 中的局部变换,但我错了。在我给出答案之前,请看下面的代码:
void MeshModel::UpdateBone(Bone * bone)
{
glm::mat4 parentTransform = bone->getParentTransform();
bone->nodeTransform = parentTransform
* bone->transform // assimp_node->mTransformation
* bone->localTransform; // T S R matrix
bone->finalTransform = globalInverse
* bone->nodeTransform
* bone->inverseBindPoseMatrix; // ai_mesh->mBones[i]->mOffsetMatrix
for (int i = 0; i < (int)bone->children.size(); i++) {
UpdateBone(bone->children[i]);
}
}
然后我做了如下修改:
void MeshModel::UpdateBone(Bone * bone)
{
glm::mat4 parentTransform = bone->getParentTransform();
if (boneName == "Scene" || boneName == "Armature")
{
bone->nodeTransform = parentTransform
* bone->transform // when isn't bone node, using assimp_node->mTransformation
* bone->localTransform; //this is your T * R matrix
}
else
{
bone->nodeTransform = parentTransform // This retrieve the transformation one level above in the tree
* bone->localTransform; //this is your T * R matrix
}
bone->finalTransform = globalInverse // scene->mRootNode->mTransformation
* bone->nodeTransform //defined above
* bone->inverseBindPoseMatrix; // ai_mesh->mBones[i]->mOffsetMatrix
for (int i = 0; i < (int)bone->children.size(); i++) {
UpdateBone(bone->children[i]);
}
}
我不知道assimp_node->mTransformation
之前给我的是什么,只有assimp文档中的描述"The transformation relative to the node's parent"。对于一些测试,我发现 mTransformation
是绑定姿势矩阵,如果我在骨骼节点上使用它们,则当前节点相对于父节点。给个头骨矩阵的图吧。
左边的部分是从assimp_node->mTransformation
中获取的transform
。右边的部分是我的unmove动画的localTransform
计算出来的通过 nodeAnim->mPositionKeys
、nodeAnim->mRotationKeys
和 nodeAnim->mScalingKeys
.
回顾一下我所做的,我做了两次绑定姿势变换,所以我的问题部分中的图像看起来只是单独的而不是意大利面条:)
最后,让我展示一下我在unmove 动画测试和正确的动画结果之前做了什么。
(给大家,如果我的概念有误,请指出我!谢谢。)