Skip to content

Guide: gl-matrix in Practice (vec3/mat4/quat)

A quick story: you built a third-person camera using yaw/pitch/roll. At some angles it “locks up” or jitters—classic gimbal lock. After switching to quaternions with gl-matrix, you accumulate rotations with quat, compute directions with vec3.transformQuat, and build the view with mat4.lookAt. The jitters vanish and the code gets simpler.


1. Install

bash
npm i gl-matrix

2. Vectors and transforms

ts
import { vec3, mat4 } from "gl-matrix";

const pos = vec3.fromValues(0, 1, 0);
const trans = mat4.fromTranslation(mat4.create(), [1, 0, 0]);
const out = vec3.transformMat4(vec3.create(), pos, trans); // [1,1,0]

3. Camera lookAt

ts
import { mat4, vec3 } from "gl-matrix";

const eye = vec3.fromValues(0, 2, 5);
const target = vec3.fromValues(0, 1, 0);
const up = vec3.fromValues(0, 1, 0);
const view = mat4.lookAt(mat4.create(), eye, target, up);

4. Quaternions for smooth rotation

ts
import { quat, vec3 } from "gl-matrix";

const rot = quat.create();

function addYawPitch(yawDelta: number, pitchDelta: number) {
  const qYaw = quat.setAxisAngle(quat.create(), [0, 1, 0], yawDelta);
  const right = vec3.transformQuat(vec3.create(), [1, 0, 0], rot);
  const qPitch = quat.setAxisAngle(quat.create(), right, pitchDelta);
  quat.multiply(rot, qYaw, rot);
  quat.multiply(rot, qPitch, rot);
  quat.normalize(rot, rot);
}

5. Advanced math snippets

  • Align vector a → b
ts
import { vec3, quat } from "gl-matrix";
const a = vec3.normalize(vec3.create(), [1, 0, 0]);
const b = vec3.normalize(vec3.create(), [0, 0, 1]);
const q = quat.create();
quat.rotationTo(q, a, b);
  • Frustum planes from view-projection
ts
import { mat4 } from "gl-matrix";
function extractPlanes(m: mat4) {
  const p = {
    left:   [m[3] + m[0],  m[7] + m[4],  m[11] + m[8],  m[15] + m[12]] as number[],
    right:  [m[3] - m[0],  m[7] - m[4],  m[11] - m[8],  m[15] - m[12]] as number[],
    bottom: [m[3] + m[1],  m[7] + m[5],  m[11] + m[9],  m[15] + m[13]] as number[],
    top:    [m[3] - m[1],  m[7] - m[5],  m[11] - m[9],  m[15] - m[13]] as number[],
    near:   [m[3] + m[2],  m[7] + m[6],  m[11] + m[10], m[15] + m[14]] as number[],
    far:    [m[3] - m[2],  m[7] - m[6],  m[11] - m[10], m[15] - m[14]] as number[],
  } as const;
  for (const k in p) {
    const v = (p as any)[k] as number[];
    const len = Math.hypot(v[0], v[1], v[2]);
    v[0] /= len; v[1] /= len; v[2] /= len; v[3] /= len;
  }
  return p;
}
  • Ray–triangle (Möller–Trumbore)
ts
import { vec3 } from "gl-matrix";
function rayTriangle(o: vec3, dir: vec3, a: vec3, b: vec3, c: vec3) {
  const EPS = 1e-6;
  const ab = vec3.sub(vec3.create(), b, a);
  const ac = vec3.sub(vec3.create(), c, a);
  const pvec = vec3.cross(vec3.create(), dir, ac);
  const det = vec3.dot(ab, pvec);
  if (Math.abs(det) < EPS) return null;
  const inv = 1 / det;
  const tvec = vec3.sub(vec3.create(), o, a);
  const u = vec3.dot(tvec, pvec) * inv;
  if (u < 0 || u > 1) return null;
  const qvec = vec3.cross(vec3.create(), tvec, ab);
  const v = vec3.dot(dir, qvec) * inv;
  if (v < 0 || u + v > 1) return null;
  const t = vec3.dot(ac, qvec) * inv;
  if (t < 0) return null;
  const hit = vec3.scaleAndAdd(vec3.create(), o, dir, t);
  return { t, u, v, hit };
}

6. Best practices

  • Normalize after interpolation/multiplication (vec3.normalize, quat.normalize).
  • Watch multiply order: mat4.multiply(out, A, B) applies B then A.
  • Reuse output objects in hotspots to avoid allocations.

神岛实验室