Branchless Antipodality Handling
1 minute read.
Wonderland Engine uses dual quaternions for the scenegraph and in the vertex shaders. As a result, chosing Dual Quaternion skinning over matrix interpolations was obvious, as all the Dual Quaternion math was already implemented in GLSL anyway.
Following the Implementation by the Authors of “Geometric Skinning with Approximate Dual Quaternion Blending” 1 , I made a simple optimization to their “most robust, but not the most efficient way”.
They list:
/* [dq0-3 are the 4 joint transforms per vertex * dq is a float2x4 (which has no glsl equivalent) [0] is the real (rotation) part and [1] is the dual (translation) part.] */ if (dot(dq0[0], dq1[0]) < 0.0) dq1 *= -1.0; if (dot(dq0[0], dq2[0]) < 0.0) dq2 *= -1.0; if (dot(dq0[0], dq3[0]) < 0.0) dq3 *= -1.0;
which can be transformed into the following GLSL:
float copysign(float from, float to) { const uint firstBit = (1u << 31u); const uint otherBits = ~(1u << 31u); return uintBitsToFloat( /* Multiply the signs only */ ((floatBitsToUint(from) ^ floatBitsToUint(to)) & firstBit) | /* Keep non-sign bits the same */ (floatBitsToUint(to) & otherBits)); } /* jointTransforms is an array of the following struct */ struct Quat2 { vec4 rot; vec4 loc; }; /* Prepare the weights instead of multiplying with the dual quaternion directly */ float w[4] = float[]( weights[0], copysign(dot(jointTransforms[0].rot, jointTransforms[1].rot), weights[1]), copysign(dot(jointTransforms[0].rot, jointTransforms[2].rot), weights[2]), copysign(dot(jointTransforms[0].rot, jointTransforms[3].rot), weights[3]) );
This saves the Dual Quaternion multiplications and branches. w can now be used as weights for the skinning as per usual.
This assumes sign is implemented branchless, of course, which is up to the driver.
- 1
- Kavan et al., 2008, Geometric Skinning with Approximate Dual Quaternion Blending