I'm rotating the bones of a skeleton inside a mesh for a low poly 3D figure. On the vertex shader its applied like this.
glsl:
vec4 vert1 = (bone_matrix[index1]*vertex_in)*weight; vec4 vert2 = (bone_matrix[index2]*vertex_in)*(1-weight); gl_Position = vert1+vert2;
bone_matrix[index1]
is the matrix of one bone and bone_matrix[index2]
is the matrix of the other. weight
designates vertex_in
's membership to these bones. The problem is the closer the weight is to .5, the more the diameter of the elbow shrinks when a rotation is applied. I've tested it with around a 10,000 vertex cylinder shape (with a gradient of weights). The result looked like bending a garden hose.
I got my weighting method from these sources. Its actually the only way I could find:
http://www.opengl.org/wiki/Skeletal_Animation
http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html
http://blenderecia.orgfree.com/blender/skinning_proposal.pdf
The left is how the shape starts, the middle is how the above equation rotates it, and the right is my goal. The mid points are weighted 0.5
. It only gets worse the more bent it is, at 180 degrees it has zero diameter.
So considering the options, or other options that I may not have considered, How have others avoid this pinching effect?
EDIT: I've gotten SLERP to work using quaternions but I opted not to use it as GLSL does not natively support it. I couldn't get the geometric SLERP to work as described by Tom. I got NLERP working for the first 90 degrees, so I added an extra "bone" between each joint. So to bend the forearm 40 degrees I bend the elbow and the forearm by 20 degrees each. This eliminates the pinching effect at the expense of doubling the quantity of bones which is not an ideal solution.
Abstract. Skeletal weight and/or weight of the different bones of the human skeleton are currently used in a wide range of applications such as archaeological cremations and forensics. Still, few reference values are available that compare the mean weights for the different skeletal parts.
Find the coordinates of the vertices of the image ΔXYZ with X(1,2),Y(3,5) and Z(−3,4) after it is rotated 180° counterclockwise about the origin. Write the ordered pairs as a vertex matrix. To rotate the ΔXYZ 180° counterclockwise about the origin, multiply the vertex matrix by the rotation matrix, [−100−1] .
Since matrix multiplication has no effect on the zero vector (the coordinates of the origin), rotation matrices describe rotations about the origin. Rotation matrices provide an algebraic description of such rotations, and are used extensively for computations in geometry, physics, and computer graphics.
Linear blend skinning is the idea of transforming vertices inside a single mesh by a (blend) of multiple transforms. Each transform is the concatenation of a "bind matrix" that takes the vertex into the local space of a given "bone" and a transformation matrix that moves from that bone's local space to a new position.
Disclaimer : I'm not a lot of a 3D guy, so I'll just suggest you a mathematical approach that may help you.
First of all, let me put this little schema, this way we'll be sure we are all talking about the same thing :
The blue and green figures are the original bones, rotated completely with either bone_matrix[index1]
or bone_matrix[index2]
. The red dot is the center of rotation, the orange figure is what you want and the black one is what you have.
So, you figure being build as a weighted average of the blue and green ones, on this drawing we see (thanks to the gray lines), why it shrinks like that.
You need to somehow compensate for this shrinking, I would suggest scaling back the points from your center of rotation, we need a scaling of value 2 at the junction between the bones, and of value 1 at the extremities.
Let scale_matrix
be a pre-computed matrix : a scaling of amplitude 2 centered at your center of rotation (red dot).
You end up with this shader :
vec4 vert1 = (bone_matrix[index1]*vertex_in)*weight; vec4 vert2 = (bone_matrix[index2]*vertex_in)*(1-weight); vec4 inter = vert1+vert2; vec4 scaled1 = inter*(1-2*min(weight, 1-weight)); vec4 scaled2 = (scale_matrix*inter)*(2*min(weight, 1-weight)); gl_Position = scaled1+scaled2;
I'm afraid I can't test it right now (I don't know a lot about GLSL), but I think you'll be able to adapt it to your case if something doesn't fit.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With