1
\$\begingroup\$

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://www.chinedufn.com/dual-quaternion-shader-explained/

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]);
}

In the vertex shader:

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

\$\endgroup\$
4
  • 3
    \$\begingroup\$ "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. \$\endgroup\$
    – DMGregory
    Oct 10, 2018 at 14:08
  • \$\begingroup\$ Edited, video added. \$\endgroup\$ Oct 10, 2018 at 14:32
  • \$\begingroup\$ Standard debug steps: does identity quaternion work? Does just translation work? Does pure rotation work? \$\endgroup\$ Oct 10, 2018 at 14:44
  • \$\begingroup\$ 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. \$\endgroup\$ Oct 10, 2018 at 14:57

1 Answer 1

1
\$\begingroup\$

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.

\$\endgroup\$
1
  • \$\begingroup\$ Don't forget to mark this answer as Accepted if it worked for you. \$\endgroup\$
    – DMGregory
    Apr 22, 2021 at 10:09

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .