0
\$\begingroup\$

I'm trying to learn quaternions and decided to implement my own quaternion class.

To test it I made a couple vertex shaders, one that gets a model matrix (calculated from the quaternion) and another that gets the rotation quaternion directly and rotates vertices in-shader.

I then feed these a quaternion that rotates around the Y axis t / 10000 radians (t = time):

const rotation = Quat.fromAxisAngle(new Vec3([0, 1, 0]), t / 10000)

The result surprises me because my model is rotating counter-clockwise but I expected it to rotate clockwise, since the angle is increasing (checked it, quat's xyz increase with time). Rotation around the X or Z axes is also backwards.

All translations as well as matrix rotations work as expected in my coordinate system. I suspect my formulas are wrong, and probably assume +Z is forward. If I transpose my model matrix (or post-multiply with it) it rotates as expected, which suggests it's a handedness problem.

Where are my formulas wrong and how can I understand the math behind this?

Details

  • My matrices are column-major in memory (to match GLSL's mat4).
  • My coordinate system is +X right, +Y up, -Z forward.

My code

Quaternion from axis + angle

function fromAxisAngle(axis: Vec3, angle: number, dest = new Quat()): Quat {
  angle *= 0.5
  const sin = Math.sin(angle)

  dest.x = axis.x * sin
  dest.y = axis.y * sin
  dest.z = axis.z * sin
  dest.w = Math.cos(angle)

  return dest
}

Note: I noticed if I negate this quaternion's x, y and z, everthing works as expected. Is this the root cause? I thought quaternions didn't have handedness!

Since this is actually just inverting the quaternion (and thus its rotation) I'm afraid I'm just patching over the root cause. I don't want to patch the issue, I want to fix my math!

Matrix from quaternion

toMat4(dest = new Mat4()): Mat4 {
  const { x, y, z, w } = this

  const x2 = x + x
  const y2 = y + y
  const z2 = z + z

  const xx = x * x2
  const xy = x * y2
  const xz = x * z2
  const yy = y * y2
  const yz = y * z2
  const zz = z * z2
  const wx = w * x2
  const wy = w * y2
  const wz = w * z2

  // Notice matrix is column-major
  // Source:
  // https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Quaternion-derived_rotation_matrix
  dest.init([
    1 - (yy + zz), xy - wz,       xz + wy,       0,
    xy + wz,       1 - (xx + zz), yz - wx,       0,
    xz - wy,       yz + wx,       1 - (xx + yy), 0,
    0,             0,             0,             1,
  ])

  return dest
}

Matrix vertex shader

precision mediump float;

attribute vec3 aVertexPosition;
attribute vec2 aVertexUV;

uniform mat4 uModelMatrix;
uniform mat4 uViewMatrix;
uniform mat4 uProjectionMatrix;

varying vec2 vUV;

void main(void) {
  vUV = aVertexUV;

  gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aVertexPosition.xyz, 1.0);
}

Quaternion vertex shader

precision mediump float;

attribute vec3 aVertexPosition;
attribute vec2 aVertexUV;

struct Transform {
  float scale;
  vec3 translation;
  vec4 rotation;
};

uniform Transform uModel;
uniform Transform uView;
uniform mat4 uProjection;

varying vec2 vUV;

// Source:
// https://twistedpairdevelopment.wordpress.com/2013/02/11/rotating-a-vector-by-a-quaternion-in-glsl/
vec3 rotateVector(vec4 quat, vec3 vec) {
  return vec + 2.0 * cross(cross(vec, quat.xyz) + quat.w * vec, quat.xyz);
}

void main(void) {
  vUV = aVertexUV;

  vec3 world = rotateVector(uModel.rotation, aVertexPosition * uModel.scale) + uModel.translation;
  vec3 view = rotateVector(uView.rotation, world * uView.scale) + uView.translation;

  gl_Position = uProjection * vec4(view, 1.0);
}
\$\endgroup\$
0
2
\$\begingroup\$

Turns out my quaternion rotations are completely fine. Positive rotations are CCW in right-handed coordinate systems[1].

My rotation matrix functions were the ones rotating backwards! And in fact they were transposed by mistake.

[1] https://www.evl.uic.edu/ralph/508S98/coordinates.html

\$\endgroup\$

You must log in to answer this question.

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