Matrix palette skinning, blending matrices

Started by
6 comments, last by JohnnyCode 5 years, 2 months ago

I have a quick question on matrix palette skinning. I have the following shared which handles my skinning as expected. It skins the vertex to each bone, then multiplies the skinned position by weight:


#version 330 core

uniform mat4 view;
uniform mat4 model;
uniform mat4 projection;
uniform mat4 skeleton[120];

in vec3 position;
in vec2 texcoord;

in ivec4 bones;
in vec4 weights;

out vec2 uv;

void main() {
	vec4 pos = vec4(position.x, position.y, position.z, 1.0);

	vec4 vertex = vec4(0.0f, 0.0f, 0.0f, 0.0f);
	vertex += (skeleton[bones.x] * pos) * weights.x;
	vertex += (skeleton[bones.y] * pos) * weights.y;
	vertex += (skeleton[bones.z] * pos) * weights.z;
	vertex += (skeleton[bones.w] * pos) * weights.w;
	vertex.w = 1.0f;
	gl_Position = projection * view * model * vertex;

	uv = texcoord;
}

Whats confusing me is i can change this to scale & add the matrices, which yields the exact same results?!?


#version 330 core

uniform mat4 view;
uniform mat4 model;
uniform mat4 projection;
uniform mat4 skeleton[120]; // matrix pallete for skinning

in vec3 position;
in vec2 texcoord;

in ivec4 bones;
in vec4 weights;

out vec2 uv;

void main() {
	vec4 pos = vec4(position.x, position.y, position.z, 1.0);

	mat4 pallete = skeleton[bones.x] * weights.x;
	pallete += skeleton[bones.y] * weights.y;
	pallete += skeleton[bones.z] * weights.z;
	pallete += skeleton[bones.w] * weights.w;
	gl_Position = projection * view * model * pallete * pos;

	uv = texcoord;
}

I don't understand why the second version woks at all. I assumed since the upper 3x3 matrix contains both scale and rotation, doing this would introduce artifacts, but it doesn't....

Does anyone have any insight into why this might be? is blending matrices in this fashion safe / acceptable?

Advertisement

Welcome to the wonderful world of linear transformations. For the usual weighted skinning approach, this is indeed a valid way to do it.

The short version is that the matrices in this case are linear transforms, which have the helpful properties that, for any particular linear function F(), values u, v, and scalar c, the following hold true:

F(c * u) = c * F(u) and F(u + v) = F(u) + F(v)

Assuming matrices bone0, bone1, bone2, bone3, weight0..3, and shrinking down to only looking at the x value of the result:


float result = 0.0;
result += (bone0 * pos).x * weight0;
result += (bone1 * pos).x * weight1;
result += (bone2 * pos).x * weight2;
result += (bone3 * pos).x * weight3;

(bone0 * pos).x expands out to something like (bone0._11 * pos.x + bone0._21 * pos.y + bone0._31 * pos.z + bone0._41), and similar for the rest, (apologies for playing very fast and loose with column vs row major, it doesn't particularly matter for the linearity of things)

result += (bone0._11 * pos.x + bone0._21 * pos.y + bone0._31 * pos.z + bone0._41) * weight0;
result += (bone1._11 * pos.x + bone1._21 * pos.y + bone1._31 * pos.z + bone1._41) * weight1;
result += (bone2._11 * pos.x + bone2._21 * pos.y + bone2._31 * pos.z + bone2._41) * weight2;
result += (bone3._11 * pos.x + bone3._21 * pos.y + bone3._31 * pos.z + bone3._41) * weight3;

if you distribute the weight# multiplies through, roll all the sums together, and then pull pos.x, pos.y, and pos.z out accordingly, you get something like:

result = pos.x * (bone0._11 * weight0 + bone1._11 * weight1 + bone2._11 * weight2 + bone3._11 * weight3) + pos.y * (etc...) + pos.z * (etc...)

and get exactly the second formulation

 

Wow, that's pretty interesting, i didn't expect it to work like that. Thank you for the thorough example!

I assume this means if i have two unrelated skeleton poses (bunch of matrices in world space), i could multiply them together like this to blend them. Would there be any downside to doing that compared to blending the position / rotation / scale components that make up the matrices in joint local space?

14 hours ago, uglybdavis said:

Wow, that's pretty interesting, i didn't expect it to work like that. Thank you for the thorough example!

I assume this means if i have two unrelated skeleton poses (bunch of matrices in world space), i could multiply them together like this to blend them. Would there be any downside to doing that compared to blending the position / rotation / scale components that make up the matrices in joint local space?

No, you cannot linearily combine rotation matricies to blend poses. They can linearily combine in upper case becouse weights linearily combine (add up) as scalars for transformed vector.

I mean, that's what blending is, a linear combination. So, to blend two poses, it would just be linearly combining the matrices... So, to blend two poses we'd have two arrays of matrices in world space, one that's walking, one that's idling. Then we'd do a linear blend between them. The resulting blended matrix would then be passed to the shader...


// inBlend1 is in the range of 0 to 1
// inBlend2 = (1.0f - inBlend1)
void BlendPoses(vector<mat4> &outPose, vector<mat4> &inPose1, vetor<mat4> &inPose2, float inBlend1, float inBlend2) {
	for (int i = 0, size = outPose.size(); i < size; ++i) {
		outPose[i] = (inPose1[i] * inBlend1) + (inPose2[i] * inBlend2);
	}
}

So, i did just that, potato quality gif attached.

I'm not really understanding the problem, maybe we're considering poses different things? So long as the pose is an array of world space matrices which contains only linear transforms (position, rotation, scale), this should work right? I think blending animations should just be a problem of averaging some number of matrices together for each joint....

I guess next steps are going to be to make a sample that blends poses both this way and at the joint component level (before they are converted to matrices) and see if they are different or the same. I don't have any examples of complex animations that do both rotation, translation an scale tough, most of the samples i have are orientation only...

blend9.gif

@JohnnyCode you where right. It looked correct, but examining the skeleton, it sure wasn't. Image attached.

Green skeleton is linearly blending the final matrices.

Red skeleton is blending the joints components before they get turned into matrices.

blend9.gif

Thanks for sharing, in an animation where key poses do not differ a lot, you may not spot such issues immediatelly.

This topic is closed to new replies.

Advertisement