# Help with dual quaternion skinning

I'm trying to convert my code to use dual quaternion skinning instead of matrix skinning because i just can't get the skinning matrix created correctly from bones+weights using matrices.

Edit: just to clarify, if each vert has only one bone the matrix version did skin correctly.

But it's just not working and i really don't understand why, i'm using code i found here:

http://donw.io/post/dual-quaternion-skinning/

I've looked at the assorted papers often brought up in the answers to these questions, but for the most part i just don't understand them. I'm not even entirely sure how the [x, y, z, w] of a glm::quat correspond to the [1, i, j, k] of a quaternion--I think that w might be the scalar because it goes first in the actual byte order of the glm::quat struct, but i'm not sure.

Anyway, in my shader bindings i have:

void gltfShader::bindBones(const std::vector<glm::mat4> & bones)
{
std::vector<glm::fdualquat> dual_quats(std::min<int>(bones.size(), 64));

glm::quat r;
glm::vec3 t, s, sk;
glm::vec4 pr;
glm::fdualquat dq;

for(size_t i = 0; i < dual_quats.size(); ++i)
{
glm::decompose(bones[i], s, r, t, sk, pr);

dq[0] = r;
dq[1] = glm::quat(t.x, t.y, t.z, 0) * r * .5f;

dual_quats[i] = dq;
}

glUniformMatrix2x4fv(u_bones, dual_quats.size(), GL_FALSE, (float*) &dual_quats[0]);
}


mat2x4 GetBoneTransform(ivec4 joints, vec4 weights)
{
float sum_weight = weights.x + weights.y + weights.z + weights.w;

// Fetch bones
mat2x4 dq0 = u_bones[joints.x];
mat2x4 dq1 = u_bones[joints.y];
mat2x4 dq2 = u_bones[joints.z];
mat2x4 dq3 = u_bones[joints.w];

// Ensure all bone transforms are in the same neighbourhood
weights.y *= sign(dot(dq0[0], dq1[0]));
weights.z *= sign(dot(dq0[0], dq2[0]));
weights.w *= sign(dot(dq0[0], dq3[0]));

// Blend
mat2x4 result =
weights.x * dq0 +
weights.y * dq1 +
weights.z * dq2 +
weights.w * dq3;

result[0][3] += int(sum_weight < 1) * (1 - sum_weight);

// Normalise
float norm = length(result[0]);
return result / norm;
}

mat4 GetSkinMatrix()
{
mat2x4 bone = GetBoneTransform(a_joints0, a_weights0);

vec4 r = bone[0];
vec4 t = bone[1];

return mat4(
1.0 - (2.0 * r.y * r.y) - (2.0 * r.z * r.z),
(2.0 * r.x * r.y) + (2.0 * r.w * r.z),
(2.0 * r.x * r.z) - (2.0 * r.w * r.y),
0.0,

(2.0 * r.x * r.y) - (2.0 * r.w * r.z),
1.0 - (2.0 * r.x * r.x) - (2.0 * r.z * r.z),
(2.0 * r.y * r.z) + (2.0 * r.w * r.x),
0.0,

(2.0 * r.x * r.z) + (2.0 * r.w * r.y),
(2.0 * r.y * r.z) - (2.0 * r.w * r.x),
1.0 - (2.0 * r.x * r.x) - (2.0 * r.y * r.y),
0.0,

2.0 * (-t.w * r.x + t.x * r.w - t.y * r.z + t.z * r.y),
2.0 * (-t.w * r.y + t.x * r.z + t.y * r.w - t.z * r.x),
2.0 * (-t.w * r.z - t.x * r.y + t.y * r.x + t.z * r.w),
1);
}


EDIT: video of the results:

https://youtu.be/8jIt_Xhhffk

The one on the left is supposed to be a walk cycle, the one on the right is supposed to be this:

https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Monster

• "it's just not working" - what are the patient's symptoms? Are you seeing a different skinning transformation than you expect? In what way? (Pairs of images showing expected / observed behaviour can help here) Is no skinning happening at all? Do you get a runtime or compilation error? All these different symptoms can help narrow down the root cause, so give us as much information as you can. Commented Oct 10, 2018 at 14:08
• Edited, video added. Commented Oct 10, 2018 at 14:32
• Standard debug steps: does identity quaternion work? Does just translation work? Does pure rotation work? Commented Oct 10, 2018 at 14:44
• identity works, just translation and just rotation both produce odd results. Using the first bone indexed instead of combining is the same with sharper corners, i think the monster might have more problems because it's scale decomposition isn't the identity vector. Commented Oct 10, 2018 at 14:57

changing this:

for(size_t i = 0; i < dual_quats.size(); ++i)
{
glm::decompose(bones[i], s, r, t, sk, pr);

dq[0] = r;
dq[1] = glm::quat(t.x, t.y, t.z, 0) * r * .5f;
//this produces worse results:
//   dq[1] = glm::quat(0, t.x, t.y, t.z) * r * .5f;

dual_quats[i] = dq;
}


to this:

for(size_t i = 0; i < dual_quats.size(); ++i)
{
r = glm::quat_cast(bones[i]);
t = glm::vec3(bones[i][3]);

dq[0] = r;
dq[1] = glm::quat(0, t.x, t.y, t.z) * r * .5f;

dual_quats[i] = dq;
}


fixed it. I really have no idea why.

• Don't forget to mark this answer as Accepted if it worked for you. Commented Apr 22, 2021 at 10:09