Reputation: 21
I am developing a new video game and I've been blocked for about 5 weeks on skeletal animation. I believe I've narrowed down the problem, but can't figure out what I'm actually doing wrong.
I have a simple 12-vertex rectangular object with four bones inside. This image shows what the object looks like in its bind pose, and what the object should like with the top bone rotated ~90 degrees about the Y-axis. To test bone weights in my application, this is the simple example I'm using. In my application, I programatically turn the top bone ~90 degrees and have the shader render it.
Unfortunately, my application does not produce the same result. The bind pose displays properly, but when the top bone transform is applied, the transform is exaggerated and the top part of the rectangle simply stretches in the direction I rotate the top bone.
I have verified the following:
So I've reduced my problem to this single sanity check. Referring to the first screenshot above, I've chosen one vertex to transform using my bone method. One little vertex: -0.5, -0.5, 4.0. Assuming I apply everything properly, the bones should transform this vertex to -0.95638, -0.5, 2.63086. To make debugging easier, I've taken my vertex shader...
#version 330 core
layout (location = 0) in vec3 position; // The position variable has attribute position 0
layout (location = 1) in vec3 normal; // This is currently unused
layout (location = 2) in vec2 texture;
layout (location = 3) in ivec4 boneIDs;
layout (location = 4) in vec4 boneWeights;
out vec2 fragTexture;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform mat4 bones[ 16 ];
void main()
{
mat4 boneTransform =
( bones[ boneIDs[ 0 ] ] * boneWeights[ 0 ] ) +
( bones[ boneIDs[ 1 ] ] * boneWeights[ 1 ] ) +
( bones[ boneIDs[ 2 ] ] * boneWeights[ 2 ] ) +
( bones[ boneIDs[ 3 ] ] * boneWeights[ 3 ] );
mat4 mvp = projection * view * model;
gl_Position = mvp * boneTransform * vec4( position, 1.0f );
fragTexture = texture;
}
...and put it into this simple unit test-style function below, made just to transform my test vertex.
glm::mat4 id( 1.0f ); // ID 0
glm::mat4 bone( 1.0f ); // ID 1
glm::mat4 bone002( 1.0f ); // ID 2
glm::mat4 bone003( 1.0f ); // ID 3
// Keyframe is set to rotate bone003 -89.113 degrees along Y
bone003 *= glm::toMat4( glm::angleAxis( (float)glm::radians( -89.113 ), glm::vec3( 0.0f, 1.0f, 0.0f ) ) );
glm::mat4 xform =
( bone002 * 0.087f ) +
( bone003 * 0.911f ) +
( id * 0 ) +
( id * 0 );
glm::vec4 point = xform * glm::vec4( glm::vec3( -0.5f, -0.5f, 4.0f ), 1.0f );
This code simulates the state of my vertex shader, where the four mat4s above are bones[0] through bones[3]. bone003 is what is sent to my shader after transforming Bone.003 and removing its inverse bind. Despite being exactly in line with my current understanding of skeletal animation, and matching all relevant weights/values from Blender, the vertex (-0.5, -0.5, 4.0) is transformed to the nonsense value of (-3.694115, -0.499000, -0.051035). The math is right, the values match up, but the answer is all wrong.
So, here is where I come to my actual question: What am I doing wrong in transforming my mesh vertices by influence of bone transforms? Where is my understanding of skeletal animation incorrect here?
Upvotes: 2
Views: 1283
Reputation: 3078
This seems wrong to me:
mat4 boneTransform =
( bones[ boneIDs[ 0 ] ] * boneWeights[ 0 ] ) +
( bones[ boneIDs[ 1 ] ] * boneWeights[ 1 ] ) +
( bones[ boneIDs[ 2 ] ] * boneWeights[ 2 ] ) +
( bones[ boneIDs[ 3 ] ] * boneWeights[ 3 ] );
You should multiply the vertex (in bone space) with every bone matrix, and add the resulting vectors together taking care of weights, like so:
vec4 temp = vec4(0.0f, 0.0f, 0.0f, 0.0f);
vec4 v = vec4(position, 1.0f);
temp += (bones[boneIDs[0]] * v) * boneWeights[0];
temp += (bones[boneIDs[1]] * v) * boneWeights[1];
temp += (bones[boneIDs[2]] * v) * boneWeights[2];
temp += (bones[boneIDs[3]] * v) * boneWeights[3];
// temp is now the vector in local space that you
// transform with MVP to clip space, or whatever
Let me know if this works!
EDIT: I guess not then. Alright:
the vertex (-0.5, -0.5, 4.0) is transformed to the nonsense value of (-3.694115, -0.499000, -0.051035)
Is that really nonsense? Rotating clockwise ~90 degrees around the Y-axis gives about that value if I just eye-ball it. Your test is "correct". At this point I'm starting to think that there's a problem with the bone hierarchy, or a problem with the interpolation of the keyframes.
Upvotes: 2