wgpu-matrix
Fast 3d math library for webgpu
Why another 3d math library?
- Most other 3D math libraries are designed for WebGL, not WebGPU
- WebGPU uses clip space Z 0 to 1, vs WebGL -1 to 1. So
ortho
, perspective
, frustum
are different - WebGPU mat3s are 12 floats (padded), WebGL they're 9.
- Many other 3D math libraries are overly verbose
-
compare
const t = mat4.translation([x, y, z]);
const p = mat4.perspective(fov, aspect, near, far);
const r = mat4.rotationX(rad);
const t = mat4.create();
mat4.fromTranslation(t, [x, y, z]);
const p = mat4.create();
mat4.perspective(p, fov, aspect, near, far);
const r = mat4.create();
mat4.fromXRotation(r, rad);
note that if you want to pre-create matrices you can still do this in wgpu-matrix
const t = mat4.create();
mat4.translation([x, y, z], t);
const p = mat4.create();
mat4.perspective(fov, aspect, near, far, p);
const r = mat4.create();
mat4.rotationX(rad, r);
Usage
import {
vec3,
mat4,
} from 'https://wgpu-matrix.org/dist/3.x/wgpu-matrix.module.js';
const fov = 60 * Math.PI / 180
const aspect = width / height;
const near = 0.1;
const far = 1000;
const perspective = mat4.perspective(fov, aspect, near, far);
const eye = [3, 5, 10];
const target = [0, 4, 0];
const up = [0, 1, 0];
const view = mat4.lookAt(eye, target, up);
Note: for translation, rotation, and scaling there are 2 versions
of each function. One generates a translation, rotation, or scaling matrix.
The other translates, rotates, or scales a matrix.
const t = mat4.translation([1, 2, 3]);
const r = mat4.rotationX(Math.PI * 0.5);
const s = mat4.scaling([1, 2, 3]);
const m = mat4.identity();
const t = mat4.translate(m, [1, 2, 3]);
const r = mat4.rotateX(m, Math.PI * 0.5);
const s = mat4.scale(m, [1, 2, 3]);
Functions take an optional destination to hold the result.
const m = mat4.create();
mat4.identity(m);
mat4.translate(m, [1, 2, 3], m);
mat4.rotateX(m, Math.PI * 0.5, m);
mat4.scale(m, [1, 2, 3], m);
There is also the minified version
import {
vec3,
mat4,
} from 'https://wgpu-matrix.org/dist/3.x/wgpu-matrix.module.min.js';
and a UMD version
<script src="https://wgpu-matrix.org/dist/3.x/wgpu-matrix.js"></script>
<script>
const { mat4, vec3 } = wgpuMatrix;
const m = mat4.identity();
...
</script>
or UDM min version
<script src="https://wgpu-matrix.org/dist/3.x/wgpu-matrix.min.js"></script>
...
or via npm
npm install --save wgpu-matrix
then using a build process
import {vec3, mat3} from 'wgpu-matrix';
Example
Download
Types
wgpu-matrix functions take any compatible type as input.
Examples:
const view = mat4.lookAt(
[10, 20, 30],
[0, 5, 0],
[0, 1, 0],
);
const view2 = mat4.lookAt(
new Float32Array([10, 20, 30]),
new Float64Array([0, 5, 0],
[0, 1, 0],
);
wgpu-matrix functions return the type passed as the destination or their default
const a = vec2.add([1, 2], [3, 4]);
const b = vec2.add([1, 2], [3, 4], [0, 0]);
const j = vec2d.add([1, 2], [3, 4]);
const k = vec2d.add([1, 2], [3, 4], [0, 0]);
const f32 = new Float32Array(2);
const x = vec2d.add([1, 2], [3, 4]);
const y = vec2d.add([1, 2], [3, 4], f32);
etc...
Note: You're unlikely to need any thing except mat3
, mat4
, quat
,
vec2
, vec3
, and vec4
but, there are 3 sets of functions,
each one returning a different default
mat4.identity()
mat4d.identity()
mat4n.identity()
Similarly there's mat3d
, mat3n
, quatd
, quatn
,
vec2d
, vec2n
, vec3d
, vec3n
, vec4d
, vec4n
.
Just to be clear, identity
, like most functions, takes a destination so
const f32 = new Float32Array(16);
const f64 = new Float64Array(16);
const arr = new Array<number>(16).fill(0);
mat4.identity()
mat4.identity(f32)
mat4.identity(f64)
mat4.identity(arr)
mat4d.identity()
mat4d.identity(f32)
mat4d.identity(f64)
mat4d.identity(arr)
mat4n.identity()
mat4n.identity(f32)
mat4n.identity(f64)
mat4n.identity(arr)
The only difference between the sets of functions is what type they default to returning.
Notes
mat4.perspective
,
mat4.ortho
, and
mat4.frustum
all return matrices with Z clip space from 0 to 1 (unlike most WebGL matrix libraries which return -1 to 1)
mat4.create
makes an all zero matrix if passed no parameters.
If you want an identity matrix call mat4.identity
Important!
mat3
uses the space of 12 elements
[
xx, xy, xz, ?
yx, yy, yz, ?
zx, zy, zz, ?
]
This is because WebGPU requires mat3s to be in this format and since
this library is for WebGPU it makes sense to match so you can manipulate
mat3s in TypeArrays directly.
vec3
in this library uses 3 floats per but be aware that an array of
vec3
in a Uniform Block or other structure in WGSL, each vec3 is
padded to 4 floats! In other words, if you declare
struct Foo {
bar: vec3<f32>[3];
};
then bar[0] is at byte offset 0, bar[1] at byte offset 16, bar[2] at byte offset 32.
See the WGSL spec on alignment and size.
Columns vs Rows
WebGPU follows the same conventions as OpenGL, Vulkan, Metal for matrices. Some people call this "column major". The issue is the columns of
a traditional "math" matrix are stored as rows when declaring a matrix in code.
[
x1, x2, x3, x4,
y1, y2, y3, y4,
z1, z2, z3, z4,
w1, w2, w3, w4,
]
To put it another way, the translation vector is in elements 12, 13, 14
[
xx, xy, xz, 0,
yx, yy, yz, 0,
zx, zy, zz, 0,
tx, ty, tz, 1,
]
This issue has confused programmers since at least the early 90s 😌
Performance vs Convenience
Most functions take an optional destination as the last argument.
If you don't supply it, a new one (vector, matrix) will be created
for you.
const persp = mat4.perspective(fov, aspect, near, far);
const camera = mat4.lookAt(eye, target, up);
const view = mat4.inverse(camera);
const persp = mat4.create();
const camera = mat4.create();
const view = mat4.create();
mat4.perspective(fov, aspect, near, far, persp);
mat4.lookAt(eye, target, up, camera);
mat4.inverse(camera, view);
For me, most of the stuff I do in WebGPU, the supposed performance I might lose
from using the convenient style is so small as to be unmeasurable. I'd prefer to
stay convenient and then, if and only if I find a performance issue, then I
might bother to switch to the performant style.
As the saying goes premature optimization is the root of all evil. 😉
Migration
2.x -> 3.x
In JavaScript there should be no difference in the API except for the removable of setDefaultType
.
In TypeScript, 3.x should mostly be type compatible with 2.x.
3.x is an attempt to fix the casting that was necessary in 2.x.
device.queue.writeData(buffer, 0, mat4.identity() as Float32Array);
device.queue.writeData(buffer, 0, mat4.identity());
In TypeScript the differences are as follows
Functions have a default type but return what is passed to them as the dst.
In 3.x each function has a default type but if you pass it
a destination it returns the type of the destination
mat4.identity()
mat4.identity(new Float32Array(16));
mat4.identity(new Float64Array(16));
mat4.identity(new Array(16));
Types are specific
const a: Mat4 = ...;
const b: Mat4d = ...;
const c: Mat4n = ...;
This is means code like this
const position: Mat4 = [10, 20, 30];
No longer works because Mat4
is a Float32Array
.
BUT, functions take any of the normal types as an argument just like they used to
const position = [10, 20, 30];
const target = vec3.create(1, 2, 3);
const up = new Float64Array([0, 1, 0]);
const view = mat4.lookAt(position, target, up);
If you really want types for each concrete type there's
Float32Array
types: Mat3
, Mat4
, Quat
, Vec2
, Vec3
, Vec4
Float64Array
types: Mat3d
, Mat4d
, Quatd
, Vec2d
, Vec3d
, Vec4d
,number[]
types: Mat3n
, Mat4n
, Quatn
, Vec2n
, Vec3n
, Vec4n
There are 3 sets of functions, each one returning a different default
mat4.identity()
mat4d.identity()
mat4n.identity()
Similarly there's mat3d
, mat3n
, quatd
, quatn
,
vec2d
, vec2n
, vec3d
, vec3n
, vec4d
, vec4n
.
Note: that in general you're unlikely to need any of these. Just use the
same ones you were using in 2.x
1.x -> 2.x
mat4.lookAt
changed from a "camera matrix" to a "view matrix" (same as gluLookAt).
If you want a matrix that orients an something in world space see
mat4.aim
.
Sorry about this change but people are used to lookAt making a a view matrix
and it seemed prudent to make this change now and save more people from
frustration going forward.
Development
git clone https://github.com/greggman/wgpu-matrix.git
cd wgpu-matrix
npm i
npm run build
npm test
You can run tests in the browser by starting a local server
npx servez
Now go to wherever your server serves pages. In the case of servez
that's
probably http://localhost:8080/test/.
By default the tests test the minified version. To test the source use src=true
as in http://localhost:8080/test/?src=true.
To limit which tests are run use grep=<regex>
. For example
http://localhost:8080/test/?src=true&grep=mat3.*?translate
runs only tests with mat3
followed by translate
in the name of test.
License
MIT