Created
March 31, 2019 19:57
-
-
Save gszauer/3ff5001f4df5b86d2a74c74239eadea3 to your computer and use it in GitHub Desktop.
Dual Quaternion With Scaling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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