I'm using the MD5 format for animated meshes, so until recently I've been skinning on the CPU with quaternions and weight vectors. However, I want to move the skinning to the GPU, so I want to work with matrices instead. I've been trying to make the change via the methods discussed in this thread, but I can't get things to work properly.
I think I must be creating the matrices incorrectly. The MD5 format gives joints as a quaternion and a position vector, so I assumed it was just VQS without any scaling. Therefore, I'm using the following code to convert a joint to a transformation matrix:
float x = j.orient.x, y = j.orient.y, z = j.orient.z, w = j.orient.w; float mat[] = { 1-2*y*y-2*z*z, 2*x*y-2*w*z, 2*x*z+2*w*y, j.pos.x, 2*x*y+2*w*z, 1-2*x*x-2*z*z, 2*y*z-2*w*x, j.pos.y, 2*x*z-2*w*y, 2*y*z+2*w*x, 1-2*x*x-2*y*y, j.pos.z, 0, 0, 0, 1 };
j.orient is the quaternion, and j.pos is the position vector. This produces incorrect results for both translations and rotations, but if I invert pos.y it seems to translate properly. I've also tried a different approach, using the joint's position rather than the origin as the center for rotation:
float sqw = j.orient.w*j.orient.w; float sqx = j.orient.x*j.orient.x; float sqy = j.orient.y*j.orient.y; float sqz = j.orient.z*j.orient.z; mat[0] = sqx - sqy - sqz + sqw; // since sqw + sqx + sqy + sqz =1 mat[5] = -sqx + sqy - sqz + sqw; mat[10] = -sqx - sqy + sqz + sqw; double tmp1 = j.orient.x*j.orient.y; double tmp2 = j.orient.z*j.orient.w; mat[4] = 2.0 * (tmp1 + tmp2); mat[1] = 2.0 * (tmp1 - tmp2); tmp1 = j.orient.x*j.orient.z; tmp2 = j.orient.y*j.orient.w; mat[8] = 2.0 * (tmp1 - tmp2); mat[2] = 2.0 * (tmp1 + tmp2); tmp1 = j.orient.y*j.orient.z; tmp2 = j.orient.x*j.orient.w; mat[9] = 2.0 * (tmp1 + tmp2); mat[6] = 2.0 * (tmp1 - tmp2); float a1,a2,a3; a1 = j.pos.x; a2 = j.pos.y; a3 = j.pos.z; mat[3] = a1 - a1 * mat[0] - a2 * mat[1] - a3 * mat[2]; mat[7] = a2 - a1 * mat[4] - a2 * mat[5] - a3 * mat[6]; mat[11] = a3 - a1 * mat[8] - a2 * mat[9] - a3 * mat[10]; mat[12] = mat[13] = mat[14] = 0.0; mat[15] = 1.0;
(That code is taken from here.) Using this method, I get correct rotation, but translation is either inverted or completely negated depending on the axis.
At initialization, I create a matrix from each joint in its bind position using the code above, and then invert it; I also create a matrix from each joint in its animated position in each frame. During animation, for each vertex, I:
- multiply the inverted bind position matrix of a joint affecting this vertex by its animated matrix,
- multiply the vertex's bind position vector by the resulting matrix,
- multiply the resulting vector by the weight associated with this joint for the vertex,
- and increment the final vertex position (which was 0, 0, 0 at the beginning of the frame) by the product.
I do this for every joint affecting each vertex. I think that covers it. I'm testing with very simple animations, too - either translating or rotating, and only on one axis at a time, with joints that have no parents. So, what am I doing horribly wrong? I would be very grateful if anyone could tell me, and hopefully I haven't been too long-winded.
[Edited by - consequence on July 12, 2010 2:06:14 AM]