Advertisement

Skeletal animation not working

Started by January 17, 2019 07:09 PM
6 comments, last by natte 6 years ago

I am trying to understand skeletal animation in 3D.

Somehow I am getting a stretched out model when I try to render it.
I am unsure what is causing this.

I have parsed the local bind transform from the COLLADA file for each bone I then transpose the matrix to shift the columns to rows, then multiply each bone with its parents local bind transform (recursivly starting at the parent bone (where I use identity matrix for the parent). After that I inverse the local bind transform for each bone and I then send it to the shader.

I know I haven't applied a pose to it or anything but my understanding is that if I send the list of inverse matrices up to the shader it should render successfully in the initial pose.

Do I have to do anything else to each bones matrix before I can send it to the shader?
 

Natte, r u following an online tutorial?

If yes which one ?

If no then try searching youtube for opengl character animation. There's one with the cowboy character wearing a hat doing a simple run in blue trousers. Uses collada. Follow that. Can't get it wrong.

Otherwise, share your cpp code and vertex shader here

Advertisement

Sorry to disappoint, that is the one I tried to follow but I haven't gotten the result I expected, now it might be that I haven't implemented the animator yet and I have to transform the inverse matrices with the
model pose to get it to render correctly. But I thought It would render correctly even without the animator since I assumed the values I parse from the COLLADA file is the idle pose (pose[0]).

so the code is very similar to that one.
My shader code is :


#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormals;
layout (location = 2) in vec2 aTexture;
layout (location = 3) in ivec3 aJointIndices;
layout (location = 4) in vec3 aWeights;

const int MAX_JOINTS = 50;
const int MAX_WEIGHTS = 3;

out vec3 normals;
out vec2 tex_coords;

uniform mat4 projection_view_matrix;
uniform mat4 joint_transforms[MAX_JOINTS];

void main()
{
    vec4 total_local_position = vec4(0.0);
    vec4 total_normal = vec4(0.0);

    for(int i = 0; i < MAX_WEIGHTS; i++)
    {
        mat4 joint_transform = joint_transforms[aJointIndices[i]];
        vec4 posePosition = joint_transform * vec4(aPos, 1.0);
        total_local_position += posePosition * aWeights[i];
    
        vec4 world_normal = joint_transform * vec4(aNormals,0.0);
        total_normal += world_normal * aWeights[i];
    }
    gl_Position = projection_view_matrix * total_local_position;
    normals = total_normal.xyz;
    
    tex_coords = aTexture;
}


 

and again, I am uploading the inverse bind transform matrices for all bones to this shader. It is quite a large code base so not sure what else I can show that would be helpful.

this is the code for calculating the inverse bind transform,


void calculate_inverse_bind_transform(glm::mat4 parent_bind_transform)
{
    glm::mat4 bind_transform = parent_bind_transform * bind_local_transform;
    inverse_bind_transform = glm::inverse(bind_transform);
    for(int i = 0; i < children.size(); i++)
        children[i].calculate_inverse_bind_transform(bind_transform);
}

Here is a picture of the monstrosity


 

guy.png

lol - don't u just love it :-D

correct, u don't need the animator code to render cowboy at bind pose. U haven't shown how u calculate your joint_transforms array:


uniform mat4 joint_transforms[MAX_JOINTS]

I suspect incorrect matrices in this array. If by assumption, all matrices are correct then aJointIndices has mixed up or incorrect bones indices.

 

19 hours ago, ddlox said:

lol - don't u just love it ?

correct, u don't need the animator code to render cowboy at bind pose. U haven't shown how u calculate your joint_transforms array:



uniform mat4 joint_transforms[MAX_JOINTS]

I suspect incorrect matrices in this array. If by assumption, all matrices are correct then aJointIndices has mixed up or incorrect bones indices.

 

Caused me to laugh at first, now I it's more closer to tears :)

This is where I parse the matrix and after this I do the above calculate_inverse_bind_transform method.


	std::string matrix_data = el->FirstChildElement("matrix")->GetText();;
	std::vector<float> f = split_string(matrix_data);
	float tmp_data[16];
	for(int i = 0; i < f.size(); i++)
	{
		tmp_data[i] = f.at(i);
	}
	
	
	glm::mat4 matrix = glm::make_mat4x4(tmp_data);
	glm::mat4 matrix_transposed = glm::transpose(matrix); 
	if(is_root)
	{
		// correct matrix blender use Z as up-axis we use Y
		
		glm::mat4 up_axis_correcion = glm::mat4(1.0f);
		up_axis_correcion = glm::rotate(up_axis_correcion, glm::radians(-90.0f), glm::vec3(1,0,0));
		matrix_transposed = matrix_transposed * up_axis_correcion;
		
	}

I then send it to the shader like this:


glm::mat4 projection_view_matrix = projection * view;
shader_use(entities[i].shader_id);
shader_setMat4(entities[i].shader_id, "projection_view_matrix", projection_view_matrix); 
for(int j = 0; j < 16; j++)
{
	std::string name = "joint_transforms[" + std::to_string(j) + "]";
	shader_setMat4(entities[i].shader_id, name,
	entities[i].model.meshes[0].local_transforms[j]);
}
                      
                      
void shader_setMat4(unsigned int shader_id, const std::string &name, const glm::mat4 &mat)
{
	glUniformMatrix4fv(glGetUniformLocation(shader_id, name.c_str()),
					   1, GL_FALSE, &mat[0][0]);
}

I tried to make all matrices the identity matrix and send those to the shader, it then renders correctly, not sure if that proves that it has to be the matrices or that the weights or bones can still be wrong?

Advertisement

U need to call calc_inv_bind_transform( ) once at load time and in this function your bind_local_transform matrix needs to be set to your matrix_transposed (i.e. for each joint). I'm assuming that matrix_transposed is correct when u create it from file read.

This way each joint will have a correctly calculated inverse_bind_transform matrix.

So now, your entities_blabla.local_transforms should really be called blabla.final_transforms . It is these matrices that u send to the vshader (joint_transforms),  they r the model space transforms of your joints which will transform vertices from their bind pose (where no animation is applied) to the desired/current/final animated pose.

To calculate these final transforms, u need this:


void apply_pose (joint, mat4 parent_m)
{
	mat4 curr_m = parent_m * joint.local_m; // calculates the joint's model space transform
	foreach (child : joint.children)
	{
		apply_pose  (child, curr_m);
	}
	_blabla.final_transform  [joint.index] = joint.inverse_bind_transform * curr_m;
	
}

call it:
apply_pose (root.joint, glm::mat4 (1));

joint.local_m == your bind_local_transform == your matrix_transposed (all the same thing). However when u create the animator class joint.local_m will be the new (rotation * translation) transform obtained/calculated in the interpolation function. This will be the bone space transform of your joint in relation to its parent.

Yes when u force final_transforms to identity matrices, what u see rendered is cowboy in its bind pose when no animation is applied. This apply_pose () function will do the same thing as well without having u to force identity matrices yr self. When no animation is applied and when joint.local_m is set to the matrix that was loaded from file (your matrix_transposed) then apply_pose () will set final_transforms[ ] to identity matrices. The rendering is then the bind pose.

Note also that in some case where the bind pose's root joint is a child of another global scene node then final_transforms becomes:

Balblab.final_transforms = global_scene_node_m * joint.inverse_bind_m * curr_m;

If global_scene_node_m is not in world or model space, then u will have to also invert it:

.... = inverse (global_scene_node_m) * joint.inverse_bind_m * curr_m;

If I remember well, this video tutorial did not have a global node attaching cowboy. So first try without this global node.

U can try all this without the animator class.

When u then get to animate poses, u then want a new function to calculate_current_pose ( ) then u want to call apply_pose ( ) again right after it. And this time, joint.local_m should be set to the new R * T transform.

I can't remember the exact name for calculate_current_pose () but it's all in the video tutorials 2 or 3. Or just check his accompanying code.

That's it. All the best.

Peace!

Thank you! 
I finally got it to work thanks to your method (apply_pose()).
Everything else was correct, I guess my fault was that I thought the inverse_bind_transform was everything I needed to render it correctly
but since I didn't implement the animation part of the tutorial yet I missed that I needed to do another step first.

No wonder I couldn't find anything wrong with what I had, I just didn't have enough.
I need to try and educate myself more on how exactly each transform manipulate space, since it's all still abstruse to me it's hard to know where the problem lies!

I can now focus on the animation part, thanks for sticking with me!
Appreciate your elaborate explanations.

This topic is closed to new replies.

Advertisement