Table of Contents
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Conventions
The examples on this page use the package from Getting Started.
Storage
Every value is a specialised array of single floats, or of doubles for the types of Doubles:
| Type | Array | Holds |
|---|---|---|
vec2, vec3, vec4 |
2, 3 or 4 floats | X, Y, Z and W |
quat |
4 floats | X, Y and Z, the vector part, then W, the real part |
mat3, mat4 |
9 or 16 floats | The elements column by column, as GLSL stores them |
mat3x2 |
6 floats | A 2D affine transform's elements column by column: the X axis, the Y axis and the origin |
A vector or quaternion is also a typed structure over its array, so its
components have accessors, such as vec3-x; see
Vectors.
A quat and a vec4 are the same type of array, so a function cannot tell
them apart: give each function the kind it names. Their accessors cannot
either: quat-w reads a vec4's W.
Matrices are stored column by column, so a translation is in the last four floats, as a GPU expects it:
(gm:mat4-translation (gm:make-vec3 :x 1 :y 2 :z 3))
;; => #(1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0 1.0 2.0 3.0 1.0)
(gm:mref (gm:mat4-translation (gm:make-vec3 :x 1 :y 2 :z 3)) 0 3) ; row 0, column 3
;; => 1.0
mref reads the element of a mat3, mat3x2 or mat4 at a row and a
column, whatever the storage order; setf writes it. gm:make-mat3 and
gm:make-mat4 take their elements column by column, as GLSL's constructors
do.
Multiplication order
Matrices multiply column vectors: (gm:mat4*vec4 m v) is M v. A product
applies its right-hand matrix first: (gm:mat4* a b) applies B, then
A.
(let ((move (gm:mat4-translation (gm:make-vec3 :x 10)))
(turn (gm:mat4-rotation-z (gm:radians 90))))
(list (gm:mat4-transform-point (gm:mat4* move turn) (gm:make-vec3 :x 1)) ; turn, then move
(gm:mat4-transform-point (gm:mat4* turn move) (gm:make-vec3 :x 1)))) ; move, then turn
;; => (#(10.0 1.0 0.0) #(-4.808253e-7 11.0 0.0))
So a model-view-projection matrix is (gm:mat4* projection (gm:mat4* view model)), and a child's world matrix is (gm:mat4* parent-world child-local). Quaternions follow the same rule: (gm:quat* a b) rotates
by B, then by A.
Space
Right-handed, with +Y up, and a camera looks down −Z, as in Godot, Bevy, glTF and Blender. So +X is right, and +Z points out of the screen, toward the viewer.
A positive angle turns counterclockwise when its axis points at you: about +Z, from +X toward +Y; about +X, from +Y toward +Z; about +Y, from +Z toward +X.
(gm:mat4-transform-point (gm:mat4-rotation-z (gm:radians 90)) (gm:make-vec3 :x 1))
;; => #(-4.371139e-8 1.0 0.0)
The tiny X, rather than 0, is rounding: single floats hold π/2 only approximately.
Clip space
Projections map into Vulkan's clip space, which cl-gfx uses on every backend: after the division by W, X and Y run from −1 to 1 with +Y down, so the top of the view is at Y −1, and depth runs from 0 to 1. World and view space keep +Y up; the projection turns it over.
Reversed depth
mat4-perspective, mat4-perspective-infinite and mat4-orthographic
put the near plane at depth 1 and the far plane at 0:
(let ((projection (gm:mat4-perspective (gm:radians 60) 1 0.1 100))
(near (gm:make-vec3 :z -0.1)) ; a point on the near plane
(far (gm:make-vec3 :z -100))) ; and one on the far plane
(list (gm:vec3-z (gm:mat4-project-point projection near))
(gm:vec3-z (gm:mat4-project-point projection far))))
;; => (1.0 0.0)
Floating-point numbers are densest near 0, which reversed depth gives to
far away, where perspective packs distances closest together. Draw with the
depth test :greater, and clear depth to 0.
Angles
Angles are in radians. radians and degrees convert, giving a double
for a double, such as Lisp's pi, and otherwise a single float:
(gm:radians 180) ; => 3.1415927
(gm:degrees (/ pi 2)) ; => 90.0d0