Skip to content

Instantly share code, notes, and snippets.

@gszauer
Created March 31, 2019 19:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gszauer/3ff5001f4df5b86d2a74c74239eadea3 to your computer and use it in GitHub Desktop.
Save gszauer/3ff5001f4df5b86d2a74c74239eadea3 to your computer and use it in GitHub Desktop.
Dual Quaternion With Scaling
// Dual quaternion vertex shader with arbitrary scaling support
// Shown here: https://twitter.com/gszauer/status/1108618057637724160
/* Data that ends up getting passed to this shader:
void CombineDualQuaternions(
glm::quat &real_r, glm::quat &dual_r, // output
glm::quat &real_a, glm::quat &dual_a, // input left
glm::quat &real_b, glm::quat &dual_b) { // input right
glm::quat real = real_a * real_b;
glm::quat dual = (real_a * dual_b) + (dual_a * real_b);
real_r = real;
dual_r = dual;
}
for (int i = 0, iSize = skeleton.bonesCount; i < iSize; ++i) {
vec3 position = skeleton.bones[i].position;
quat rotation = skeleton.bones[i].rotation;
vec3 scale = skeleton.bones[i].scale;
position = SampeVectorTrack(time, clip.position[i], position);
rotation = SampeQuaternionTrack(time, clip.rotation[i], rotation);
scale = SampeVectorTrack(time, clip.scale[i], scale);
quat world_real = normalize(rotation);
quat world_dual = quat(0.0f, position.x, position.y, position.z) * world_real * 0.5f;
vec3 world_scale = scale;
int p = skeleton.bones[i].parentBone;
while (p >= 0) {
quat parent_real = normalize(skeleton.bones[p].rotation);
quat parent_dual = quat(0.0f, skeleton.bones[p].position.x, skeleton.bones[p].position.y, skeleton.bones[p].position.z) * parent_real * 0.5f;
world_scale = skeleton.bones[p].scale * world_scale;
CombineDualQuaternions(world_real, world_dual, parent_real, parent_dual, world_real, world_dual);
p = skeleton.bones[p].parentBone;
}
result.skeletonDqReal[i] = world_real;
result.skeletonDqDual[i] = world_dual;
result.skeletonScaleAndSkin[i] = mat4(
world_scale.x, 0, 0, 0,
0, world_scale.y, 0, 0,
0, 0, world_scale.z, 0,
0, 0, 0, 1) * skeleton.bones[i].skin;
}
*/
#version 330 core
uniform mat4 view;
uniform mat4 model;
uniform mat4 projection;
// Sending the dual quats in as two seperate streams
// should probably send as a mat4x2, but this works for me
uniform vec4 skeletonDqReal[56];
uniform vec4 skeletonDqDual[56];
// Scale of vertex * skin of bone
uniform mat4 skeletonScaleAndSkin[56];
// incoming vertices have position and normal only.
in vec3 position;
in vec3 normal;
// They also contain pretty standard skinning info
in ivec4 bones;
in vec4 weights;
// Output for lighting
out vec3 norm;
out vec3 fragPos;
// Rotate a vector by a quaternion simplification of q * v' * q
vec3 QuatRotateVector(vec4 q, vec3 v) {
vec3 u = vec3(q.x, q.y, q.z);
float s = q.w;
return 2.0 * dot(u, v) * u + (s * s - dot(u, u)) * v + 2.0 * s * cross(u, v);
}
// Multiply two quaternions I wrote this pretty quick and never double checked,
// if the math is right. I'm prety sure the actual math is correct, but i might
// have gotten the arguments in reverse order.
vec4 QMul(vec4 p, vec4 q) {
vec3 q_v = vec3(q.x, q.y, q.z);
vec3 p_v = vec3(p.x, p.y, p.z);
float q_r = q.w;
float p_r = p.w;
float scalar = q_r * p_r - dot(q_v, p_v);
vec3 vector = (p_v * q_r) + (q_v * p_r) + cross(p_v, q_v);
return vec4(vector.x, vector.y, vector.z, scalar);
}
// Transform a pint by a dual quaternion. sounds fancy, but we just extract
// the position of the dq, and add that to the input vector being rotated
// the the rotation of the dq. Pretty standard transform accumulation
vec3 DualQuatTransformPoint(vec4 Qr, vec4 Qd, vec3 p) {
// Important to remember that we're doing quaternion multiplication
vec4 t = QMul(2.0 * Qd, vec4(-Qr.x, -Qr.y, -Qr.z, Qr.w));
return QuatRotateVector(Qr, p) + t.xyz;
}
void main() {
// Scale the real part of the input DQ and accumulate it for every bone
vec4 weightedRotQuat =
skeletonDqReal[bones.x] * weights.x +
skeletonDqReal[bones.y] * weights.y +
skeletonDqReal[bones.z] * weights.z +
skeletonDqReal[bones.w] * weights.w;
// Sale the dual part of the input DQ and accumulate it for every bone
vec4 weightedTransQuat =
skeletonDqDual[bones.x] * weights.x +
skeletonDqDual[bones.y] * weights.y +
skeletonDqDual[bones.z] * weights.z +
skeletonDqDual[bones.w] * weights.w;
// Scale the scale/skin matrix appropriatele and accumulate it for every bone
mat4 weightedScales =
skeletonScaleAndSkin[bones.x] * weights.x +
skeletonScaleAndSkin[bones.y] * weights.y +
skeletonScaleAndSkin[bones.z] * weights.z +
skeletonScaleAndSkin[bones.w] * weights.w;
// Normalize the scaled dual quaternion (could just use the lenght function here i guess)
float rotQuatMagnitude = sqrt(weightedRotQuat[0] * weightedRotQuat[0] + weightedRotQuat[1] * weightedRotQuat[1] + weightedRotQuat[2] * weightedRotQuat[2] + weightedRotQuat[3] * weightedRotQuat[3]);
weightedRotQuat = weightedRotQuat / rotQuatMagnitude;
weightedTransQuat = weightedTransQuat / rotQuatMagnitude;
// Take the input vertex and move it to model space
// First, apply skinning, and scale the vertex out
vec4 vertex = weightedScales * vec4(position, 1);
// Then apply rotation and translation using the dual quaternion
vertex.xyz = DualQuatTransformPoint(weightedRotQuat, weightedTransQuat, vertex.xyz);
vertex.w = 1.0; // Probably not needed, but i havent done the math to confirm that
// Figure out fragment position in world space for lighting
fragPos = vec3(model * vertex);
// Figure out vertex psition in NDC space
gl_Position = projection * view * model * vertex;
// Adjust normal, first by skin / scale matrix
vec3 normalPass1 = normalize(inverse(transpose(mat3(weightedScales))) * normal);
// Apply dual quaternion rotation only
norm = QuatRotateVector(weightedRotQuat, normalPass1);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment