Advertisement

OpenGL rendering animation skeleton with cylinders

Started by November 30, 2023 09:27 PM
1 comment, last by JoeJ 1 year ago

I am trying to get into learning animations and I have a BVH from mixamo, specifying joints and I am trying to simply render a skeleton using cylinders in OpenGL but I can't seem to get this to work correctly and my cylinders representing the skeleton just end up spread out.

Mixamo BVH: https://pastebin.com/4w2Qv5TD

CDN media
void Skeleton::drawJoint(Mat4& viewMat, mat4 parentMatrix, Joint* joint, float scale, int frame)
{
	vec3 jointPos = parentMatrix * vec3(joint->JointOffsetX, joint->JointOffsetY, jJointOffsetZ);
	
	for(auto& child : joint->Children)
	{
		vec3 childPos = vec3(child.JointOffsetX, child.JointOffsetY, child.jointOffsetZ);
		auto matrix = parentMatrix * Mat4::Translate(jointPos);

		drawCylinder(viewMat, jointPos, childPos);
		drawJoint(viewMat, matrix, &child, scale, frame);
	}
} 

void Skeleton::drawCylinder(Mat4& viewMat, vec3 start, vec3 end)
{ 

    vec3 diff = end - start;

    // Calculate the length of the cylinder
    float length = diff.length();

    glPushMatrix();

    // // Move the cylinder to the start position
    glTranslatef(start.x, start.y, start.z);

    RenderCylinder(viewMat, 0.1, length, 10);

    glPopMatrix();

You ignore rotations.
If you rotate the upper arm, you want to rotate the lower arm, hands etc. with it.
But currently you only propagate translation to child bones:

auto matrix = parentMatrix * Mat4::Translate(jointPos);

jointPos tells where the bone has its center of rotation, but you also need the rotation the bone should have.
Usually you need something like this:

Mat4 transform;
transform.SetTranslation(jointPos);
transform.SetRotation(joint->rotation); // rotation can be given as quaternions ideally, or eventually euler angles
auto matrix = parentMatrix * transform;

That's surely the culprit and what you want to work on first.

Besides, a recursive function is not needed to transform a character skeleton.
Usually bones are ordered, so parents come always first if we simply iterate over all bones.
For ragdolls i have this hard coded to give an example:

	enum bone_names
	{

		HIP,
		BELLY,
		CHEST,
		NECK,
		HEAD,

		L_THIGH,
		L_SHIN,
		L_FOOT,
		L_TOE,

		R_THIGH,
		R_SHIN,
		R_FOOT,
		R_TOE,

		L_SHOULDER,
		L_BICEP,
		L_FOREARM,
		L_HAND,

		R_SHOULDER,
		R_BICEP,
		R_FOREARM,
		R_HAND,

		NUM_BONES,
	};

Iterating in such order ensures all hierarchical dependencies are already known for any bone. But ofc. you need to store relevant data, in this case the actual transformation matrix per bone.

Code to create a skeleton pose from given joint angles then looks like this for me:

void PoseFromJointTargets (
		BodyPose *bodyPose, // transfrom for each bone
		JointState *jointStates) // animation angles for each joint
	{
		for (int i=1; i<NUM_BONES; i++)
		{
			RagdollJoint &joint = joints[i];
			JointState &state = jointStates[i];
			int i0 = joint.bodyIndex[0];
			int i1 = joint.bodyIndex[1];
			Mat4 bodyMatrix = (i0 == i // (working with physics data structures, i need to branch depending on which of the two bodies per joint represents the parent in the hierarchy. That's not needed with animation data structures
.)
				? joint.PredictBodyPose0 (bodyPose[i1].GetBodyMatrix(), state.targetOrn) // due to iterating in proper sequence, it's guaranteed the parent bodyPose has been already calculated.
				: joint.PredictBodyPose1 (bodyPose[i0].GetBodyMatrix(), state.targetOrn));
			bodyPose[i].SetBodyMatrix(bodyMatrix); // calculating and storing the current bodyPose
		}
	}

Notice it's a simple loop, and i neither need recursion nor a stack.

Now imagine to write a file exporter for some animation editor, to generate the bvh file.
You might use recursion because memory order of bones can change as artists edit the model.
You will start with the root node, which usually is the hip or some ground bone parenting the hip. Then you traverse its children as usual and you write to file each bone in this same to down order you traverse them.
After that, the sequence of bones in the file is already properly ordered hierarchically.
And thus, for a game runtime there should be never a need for expensive traversals to animate skeletons. ; )


This topic is closed to new replies.

Advertisement