wgpu-matrix
    Preparing search index...

    wgpu-matrix

    wgpu-matrix

    NPM Package

    Fast 3d math library for webgpu

    • 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

        // wgpu-matrix
        const t = mat4.translation([x, y, z]);
        const p = mat4.perspective(fov, aspect, near, far);
        const r = mat4.rotationX(rad);
        // gl-matrix
        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);
    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]);    // a translation matrix
    const r = mat4.rotationX(Math.PI * 0.5); // a rotation matrix
    const s = mat4.scaling([1, 2, 3]); // a scaling matrix
    const m = mat4.identity();
    const t = mat4.translate(m, [1, 2, 3]); // m * translation([1, 2, 3])
    const r = mat4.rotateX(m, Math.PI * 0.5); // m * rotationX(Math.PI * 0.5)
    const s = mat4.scale(m, [1, 2, 3]); // m * scaling([1, 2, 3])

    Functions take an optional destination to hold the result.

    const m = mat4.create();            // m = new mat4
    mat4.identity(m); // m = identity
    mat4.translate(m, [1, 2, 3], m); // m *= translation([1, 2, 3])
    mat4.rotateX(m, Math.PI * 0.5, m); // m *= rotationX(Math.PI * 0.5)
    mat4.scale(m, [1, 2, 3], m); // m *= scaling([1, 2, 3])

    There is also the minified version

    import {
    vec3,
    mat4,
    } from 'https://wgpu-matrix.org/dist/3.x/wgpu-matrix.module.min.js';

    // ... etc ...

    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';

    // ... etc ...

    Example

    Examples:

    const view = mat4.lookAt(        //  view is Float32Array
    [10, 20, 30], // position
    [0, 5, 0], // target
    [0, 1, 0], // up
    );

    const view2 = mat4.lookAt( // view2 is Float32Array
    new Float32Array([10, 20, 30]), // position
    new Float64Array([0, 5, 0], // target
    [0, 1, 0], // up
    );
    const a = vec2.add([1, 2], [3, 4]);           // a is Float32Array
    const b = vec2.add([1, 2], [3, 4], [0, 0]); // b is number[]

    const j = vec2d.add([1, 2], [3, 4]); // j is Float64Array
    const k = vec2d.add([1, 2], [3, 4], [0, 0]); // b is number[]

    const f32 = new Float32Array(2);
    const x = vec2d.add([1, 2], [3, 4]); // x is number[]
    const y = vec2d.add([1, 2], [3, 4], f32); // y is Float32Array

    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()   // returns Float32Array
    mat4d.identity() // returns Float64Array
    mat4n.identity() // returns number[]

    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() // returns Float32Array
    mat4.identity(f32) // returns Float32Array (f32)
    mat4.identity(f64) // returns Float64Array (f64)
    mat4.identity(arr) // returns number[] (arr)

    mat4d.identity() // returns Float64Array
    mat4d.identity(f32) // returns Float32Array (f32)
    mat4d.identity(f64) // returns Float64Array (f64)
    mat4d.identity(arr) // returns number[] (arr)

    mat4n.identity() // returns number[]
    mat4n.identity(f32) // returns Float32Array (f32)
    mat4n.identity(f64) // returns Float64Array (f64)
    mat4n.identity(arr) // returns number[] (arr)

    The only difference between the sets of functions is what type they default to returning.

    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

    mat3 uses the space of 12 elements

    // a mat3
    [
    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.

    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, // <- column 0
    y1, y2, y3, y4, // <- column 1
    z1, z2, z3, z4, // <- column 2
    w1, w2, w3, w4, // <- column 3
    ]

    To put it another way, the translation vector is in elements 12, 13, 14

    [
    xx, xy, xz, 0, // <- x-axis
    yx, yy, yz, 0, // <- y-axis
    zx, zy, zz, 0, // <- z-axis
    tx, ty, tz, 1, // <- translation
    ]

    This issue has confused programmers since at least the early 90s 😌

    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.

    // convenient usage

    const persp = mat4.perspective(fov, aspect, near, far);
    const camera = mat4.lookAt(eye, target, up);
    const view = mat4.inverse(camera);
    // performant usage

    // at init time
    const persp = mat4.create();
    const camera = mat4.create();
    const view = mat4.create();

    // at usage time
    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. 😉

    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.

    // 2.x
    device.queue.writeData(buffer, 0, mat4.identity() as Float32Array); // sadness! 😭

    // 3.x
    device.queue.writeData(buffer, 0, mat4.identity()); // Yay! 🎉

    In TypeScript the differences are as follows

    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()                       // returns Float32Array
    mat4.identity(new Float32Array(16)); // returns Float32Array
    mat4.identity(new Float64Array(16)); // returns Float64Array
    mat4.identity(new Array(16)); // returns number[]
    const a: Mat4 = ...;    // a = Float32Array
    const b: Mat4d = ...; // b = Float64Array
    const c: Mat4n = ...; // c = number[]

    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];          // number[]
    const target = vec3.create(1, 2, 3); // Float32Array
    const up = new Float64Array([0, 1, 0]); // Float64Array

    // Works fine, even those types are different, just like 2.x did
    const view = mat4.lookAt(position, target, up); // Float32Array

    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
    mat4.identity()   // returns Float32Array
    mat4d.identity() // returns Float64Array
    mat4n.identity() // returns number[]

    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

    • 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.
    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.

    MIT