Search code examples
webgpu

How do I flip and switch my axes in WebGPU?


Currently my axes --for some reason-- are X and Y defining the ground plane and -Z as up. I want to have X and Z as the ground plane and +Y as up.

To illustrate what I'm saying, here's the normal scene: Normal scene

Brazil with a translation of [1, 0, 0]: Brazil with +x

Brazil with a translation of [0, 1, 0]: Brazil with +y

Brazil with a translation of [0, 0, 1] and a bit of rotation on the camera to see it better: Brazil with +z

What I want is to have up as +Y instead of -Z.

I'm sure my model is oriented correctly with the coordinates I want. I made it in Blender and exported as a gltf file with the "+Y Up" option. Just to be sure, here's my model inside the first gltf online viewer that I found (https://gltf-viewer.donmccurdy.com/): model inside online gltf viewer showing +Y as up

My vertex shader doesn't do anything else with the coordinates besides multiply them by the projection, view and model matrices:

...
var worldPos = vsUniqueUniforms.model * vec4f(v.position, 1.0);
output.position = vsCommonUniforms.projection * vsCommonUniforms.camera * worldPos;
...

My view matrix is the inverse of the following:

static cameraAim(pos: Vec3, target: Vec3, up: Vec3) {
    const zAxis = Vec3.subtract(pos, target).normalize();
    const xAxis = Vec3.cross(up, zAxis).normalize();
    const yAxis = Vec3.cross(zAxis, xAxis).normalize();

    return new Mat4([
        xAxis.x, xAxis.y, xAxis.z, 0,
        yAxis.x, yAxis.y, yAxis.z, 0,
        zAxis.x, zAxis.y, zAxis.z, 0,
        pos.x  , pos.y  , pos.z  , 1
    ]);
}

And my projection matrix is:

static perspective(fovRadians: number, aspect: number, near: number, far: number): Mat4 {
    const f = Math.tan(Math.PI * 0.5 - 0.5 * fovRadians);
    const rangeInv = 1 / (near - far);

    return new Mat4([
        f / aspect , 0 , 0                    , 0,
        0          , f , 0                    , 0,
        0          , 0 , far * rangeInv       , -1,
        0          , 0 , near * far * rangeInv, 0,
    ]);
}

Just for the sake of it I'll post here the Vec3.coss/subtract/normalize functions but they should be right:

static cross(a: Vec3, b: Vec3): Vec3 {
    return new Vec3(
        a.y * b.z - a.z * b.y,
        a.z * b.x - a.x * b.z,
        a.x * b.y - a.y * b.x
    );
}

static subtract(a: Vec3, b: Vec3): Vec3 {
    return new Vec3(
        a.x - b.x,
        a.y - b.y,
        a.z - b.z
    );
}

squaredNorm() {
    return this.x**2 + this.y**2 + this.z**2;
}

normalize() {
    let length = Math.sqrt(this.squaredNorm());
    if (length >  0.00001) {
        this.x /= length;
        this.y /= length;
        this.z /= length;
    } else {
        this.x = 0;
        this.y = 0;
        this.z = 0;
    }
    return this;
}

To take those screenshots earlier I was using the following parameters on my camera:

  • Position: [-3, 0, -10] (Yes, the camera also goes up with -Z)
  • Target: [0, 0, 0]
  • Up: [1, 0, 0]

I'm using the camera up as +X because the camera is looking down, but the problem is still the same if I set the up vector as -Z (Which is the problem, as it should be +Y).

If I set the cameras up vector as +Y it just rotates counter-clowise the image, which is understandable as for some reason X and Y are forming the ground plane.

To summarize: How can I make my objects (and camera) go up when they are translated by +Y instead of -Z?


Solution

  • I think you just want to set up to [0, 0, -1]

    <canvas></canvas>
      
    <script type="module">
    // modified from WebGPU Simple Textured Quad - Import Image
    // from https://webgpufundamentals.org/webgpu/webgpu-simple-textured-quad-import.html
    
    
    import GUI from 'https://webgpufundamentals.org/3rdparty/muigui-0.x.module.js';
    
    const vec3 = {
      cross(a, b, dst) {
        dst = dst || new Float32Array(3);
    
        const t0 = a[1] * b[2] - a[2] * b[1];
        const t1 = a[2] * b[0] - a[0] * b[2];
        const t2 = a[0] * b[1] - a[1] * b[0];
    
        dst[0] = t0;
        dst[1] = t1;
        dst[2] = t2;
    
        return dst;
      },
    
      subtract(a, b, dst) {
        dst = dst || new Float32Array(3);
    
        dst[0] = a[0] - b[0];
        dst[1] = a[1] - b[1];
        dst[2] = a[2] - b[2];
    
        return dst;
      },
    
      normalize(v, dst) {
        dst = dst || new Float32Array(3);
    
        const length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
        // make sure we don't divide by 0.
        if (length > 0.00001) {
          dst[0] = v[0] / length;
          dst[1] = v[1] / length;
          dst[2] = v[2] / length;
        } else {
          dst[0] = 0;
          dst[1] = 0;
          dst[2] = 0;
        }
    
        return dst;
      },
    };
    
    const mat4 = {
      projection(width, height, depth, dst) {
        // Note: This matrix flips the Y axis so that 0 is at the top.
        return mat4.ortho(0, width, height, 0, depth, -depth, dst);
      },
    
      perspective(fieldOfViewYInRadians, aspect, zNear, zFar, dst) {
        dst = dst || new Float32Array(16);
    
        const f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewYInRadians);
        const rangeInv = 1 / (zNear - zFar);
    
        dst[0] = f / aspect;
        dst[1] = 0;
        dst[2] = 0;
        dst[3] = 0;
    
        dst[4] = 0;
        dst[5] = f;
        dst[6] = 0;
        dst[7] = 0;
    
        dst[8] = 0;
        dst[9] = 0;
        dst[10] = zFar * rangeInv;
        dst[11] = -1;
    
        dst[12] = 0;
        dst[13] = 0;
        dst[14] = zNear * zFar * rangeInv;
        dst[15] = 0;
    
        return dst;
      },
    
      ortho(left, right, bottom, top, near, far, dst) {
        dst = dst || new Float32Array(16);
    
        dst[0] = 2 / (right - left);
        dst[1] = 0;
        dst[2] = 0;
        dst[3] = 0;
    
        dst[4] = 0;
        dst[5] = 2 / (top - bottom);
        dst[6] = 0;
        dst[7] = 0;
    
        dst[8] = 0;
        dst[9] = 0;
        dst[10] = 1 / (near - far);
        dst[11] = 0;
    
        dst[12] = (right + left) / (left - right);
        dst[13] = (top + bottom) / (bottom - top);
        dst[14] = near / (near - far);
        dst[15] = 1;
    
        return dst;
      },
    
      identity(dst) {
        dst = dst || new Float32Array(16);
        dst[ 0] = 1;  dst[ 1] = 0;  dst[ 2] = 0;   dst[ 3] = 0;
        dst[ 4] = 0;  dst[ 5] = 1;  dst[ 6] = 0;   dst[ 7] = 0;
        dst[ 8] = 0;  dst[ 9] = 0;  dst[10] = 1;   dst[11] = 0;
        dst[12] = 0;  dst[13] = 0;  dst[14] = 0;   dst[15] = 1;
        return dst;
      },
    
      multiply(a, b, dst) {
        dst = dst || new Float32Array(16);
        const b00 = b[0 * 4 + 0];
        const b01 = b[0 * 4 + 1];
        const b02 = b[0 * 4 + 2];
        const b03 = b[0 * 4 + 3];
        const b10 = b[1 * 4 + 0];
        const b11 = b[1 * 4 + 1];
        const b12 = b[1 * 4 + 2];
        const b13 = b[1 * 4 + 3];
        const b20 = b[2 * 4 + 0];
        const b21 = b[2 * 4 + 1];
        const b22 = b[2 * 4 + 2];
        const b23 = b[2 * 4 + 3];
        const b30 = b[3 * 4 + 0];
        const b31 = b[3 * 4 + 1];
        const b32 = b[3 * 4 + 2];
        const b33 = b[3 * 4 + 3];
        const a00 = a[0 * 4 + 0];
        const a01 = a[0 * 4 + 1];
        const a02 = a[0 * 4 + 2];
        const a03 = a[0 * 4 + 3];
        const a10 = a[1 * 4 + 0];
        const a11 = a[1 * 4 + 1];
        const a12 = a[1 * 4 + 2];
        const a13 = a[1 * 4 + 3];
        const a20 = a[2 * 4 + 0];
        const a21 = a[2 * 4 + 1];
        const a22 = a[2 * 4 + 2];
        const a23 = a[2 * 4 + 3];
        const a30 = a[3 * 4 + 0];
        const a31 = a[3 * 4 + 1];
        const a32 = a[3 * 4 + 2];
        const a33 = a[3 * 4 + 3];
    
        dst[0] = b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30;
        dst[1] = b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31;
        dst[2] = b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32;
        dst[3] = b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33;
    
        dst[4] = b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30;
        dst[5] = b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31;
        dst[6] = b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32;
        dst[7] = b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33;
    
        dst[8] = b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30;
        dst[9] = b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31;
        dst[10] = b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32;
        dst[11] = b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33;
    
        dst[12] = b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30;
        dst[13] = b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31;
        dst[14] = b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32;
        dst[15] = b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33;
    
        return dst;
      },
    
      inverse(m, dst) {
        dst = dst || new Float32Array(16);
    
        const m00 = m[0 * 4 + 0];
        const m01 = m[0 * 4 + 1];
        const m02 = m[0 * 4 + 2];
        const m03 = m[0 * 4 + 3];
        const m10 = m[1 * 4 + 0];
        const m11 = m[1 * 4 + 1];
        const m12 = m[1 * 4 + 2];
        const m13 = m[1 * 4 + 3];
        const m20 = m[2 * 4 + 0];
        const m21 = m[2 * 4 + 1];
        const m22 = m[2 * 4 + 2];
        const m23 = m[2 * 4 + 3];
        const m30 = m[3 * 4 + 0];
        const m31 = m[3 * 4 + 1];
        const m32 = m[3 * 4 + 2];
        const m33 = m[3 * 4 + 3];
    
        const tmp0 = m22 * m33;
        const tmp1 = m32 * m23;
        const tmp2 = m12 * m33;
        const tmp3 = m32 * m13;
        const tmp4 = m12 * m23;
        const tmp5 = m22 * m13;
        const tmp6 = m02 * m33;
        const tmp7 = m32 * m03;
        const tmp8 = m02 * m23;
        const tmp9 = m22 * m03;
        const tmp10 = m02 * m13;
        const tmp11 = m12 * m03;
        const tmp12 = m20 * m31;
        const tmp13 = m30 * m21;
        const tmp14 = m10 * m31;
        const tmp15 = m30 * m11;
        const tmp16 = m10 * m21;
        const tmp17 = m20 * m11;
        const tmp18 = m00 * m31;
        const tmp19 = m30 * m01;
        const tmp20 = m00 * m21;
        const tmp21 = m20 * m01;
        const tmp22 = m00 * m11;
        const tmp23 = m10 * m01;
    
        const t0 = (tmp0 * m11 + tmp3 * m21 + tmp4 * m31) -
                   (tmp1 * m11 + tmp2 * m21 + tmp5 * m31);
        const t1 = (tmp1 * m01 + tmp6 * m21 + tmp9 * m31) -
                   (tmp0 * m01 + tmp7 * m21 + tmp8 * m31);
        const t2 = (tmp2 * m01 + tmp7 * m11 + tmp10 * m31) -
                   (tmp3 * m01 + tmp6 * m11 + tmp11 * m31);
        const t3 = (tmp5 * m01 + tmp8 * m11 + tmp11 * m21) -
                   (tmp4 * m01 + tmp9 * m11 + tmp10 * m21);
    
        const d = 1 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3);
    
        dst[0] = d * t0;
        dst[1] = d * t1;
        dst[2] = d * t2;
        dst[3] = d * t3;
    
        dst[4] = d * ((tmp1 * m10 + tmp2 * m20 + tmp5 * m30) -
                      (tmp0 * m10 + tmp3 * m20 + tmp4 * m30));
        dst[5] = d * ((tmp0 * m00 + tmp7 * m20 + tmp8 * m30) -
                      (tmp1 * m00 + tmp6 * m20 + tmp9 * m30));
        dst[6] = d * ((tmp3 * m00 + tmp6 * m10 + tmp11 * m30) -
                      (tmp2 * m00 + tmp7 * m10 + tmp10 * m30));
        dst[7] = d * ((tmp4 * m00 + tmp9 * m10 + tmp10 * m20) -
                      (tmp5 * m00 + tmp8 * m10 + tmp11 * m20));
    
        dst[8] = d * ((tmp12 * m13 + tmp15 * m23 + tmp16 * m33) -
                      (tmp13 * m13 + tmp14 * m23 + tmp17 * m33));
        dst[9] = d * ((tmp13 * m03 + tmp18 * m23 + tmp21 * m33) -
                      (tmp12 * m03 + tmp19 * m23 + tmp20 * m33));
        dst[10] = d * ((tmp14 * m03 + tmp19 * m13 + tmp22 * m33) -
                       (tmp15 * m03 + tmp18 * m13 + tmp23 * m33));
        dst[11] = d * ((tmp17 * m03 + tmp20 * m13 + tmp23 * m23) -
                       (tmp16 * m03 + tmp21 * m13 + tmp22 * m23));
    
        dst[12] = d * ((tmp14 * m22 + tmp17 * m32 + tmp13 * m12) -
                       (tmp16 * m32 + tmp12 * m12 + tmp15 * m22));
        dst[13] = d * ((tmp20 * m32 + tmp12 * m02 + tmp19 * m22) -
                       (tmp18 * m22 + tmp21 * m32 + tmp13 * m02));
        dst[14] = d * ((tmp18 * m12 + tmp23 * m32 + tmp15 * m02) -
                       (tmp22 * m32 + tmp14 * m02 + tmp19 * m12));
        dst[15] = d * ((tmp22 * m22 + tmp16 * m02 + tmp21 * m12) -
                       (tmp20 * m12 + tmp23 * m22 + tmp17 * m02));
        return dst;
      },
    
      cameraAim(eye, target, up, dst) {
        dst = dst || new Float32Array(16);
    
        const zAxis = vec3.normalize(vec3.subtract(eye, target));
        const xAxis = vec3.normalize(vec3.cross(up, zAxis));
        const yAxis = vec3.normalize(vec3.cross(zAxis, xAxis));
    
        dst[ 0] = xAxis[0];  dst[ 1] = xAxis[1];  dst[ 2] = xAxis[2];  dst[ 3] = 0;
        dst[ 4] = yAxis[0];  dst[ 5] = yAxis[1];  dst[ 6] = yAxis[2];  dst[ 7] = 0;
        dst[ 8] = zAxis[0];  dst[ 9] = zAxis[1];  dst[10] = zAxis[2];  dst[11] = 0;
        dst[12] = eye[0];    dst[13] = eye[1];    dst[14] = eye[2];    dst[15] = 1;
    
        return dst;
      },
    
      lookAt(eye, target, up, dst) {
        return mat4.inverse(mat4.cameraAim(eye, target, up, dst), dst);
      },
    
      translation([tx, ty, tz], dst) {
        dst = dst || new Float32Array(16);
        dst[ 0] = 1;   dst[ 1] = 0;   dst[ 2] = 0;   dst[ 3] = 0;
        dst[ 4] = 0;   dst[ 5] = 1;   dst[ 6] = 0;   dst[ 7] = 0;
        dst[ 8] = 0;   dst[ 9] = 0;   dst[10] = 1;   dst[11] = 0;
        dst[12] = tx;  dst[13] = ty;  dst[14] = tz;  dst[15] = 1;
        return dst;
      },
    
      rotationX(angleInRadians, dst) {
        const c = Math.cos(angleInRadians);
        const s = Math.sin(angleInRadians);
        dst = dst || new Float32Array(16);
        dst[ 0] = 1;  dst[ 1] = 0;   dst[ 2] = 0;  dst[ 3] = 0;
        dst[ 4] = 0;  dst[ 5] = c;   dst[ 6] = s;  dst[ 7] = 0;
        dst[ 8] = 0;  dst[ 9] = -s;  dst[10] = c;  dst[11] = 0;
        dst[12] = 0;  dst[13] = 0;   dst[14] = 0;  dst[15] = 1;
        return dst;
      },
    
      rotationY(angleInRadians, dst) {
        const c = Math.cos(angleInRadians);
        const s = Math.sin(angleInRadians);
        dst = dst || new Float32Array(16);
        dst[ 0] = c;  dst[ 1] = 0;  dst[ 2] = -s;  dst[ 3] = 0;
        dst[ 4] = 0;  dst[ 5] = 1;  dst[ 6] = 0;   dst[ 7] = 0;
        dst[ 8] = s;  dst[ 9] = 0;  dst[10] = c;   dst[11] = 0;
        dst[12] = 0;  dst[13] = 0;  dst[14] = 0;   dst[15] = 1;
        return dst;
      },
    
      rotationZ(angleInRadians, dst) {
        const c = Math.cos(angleInRadians);
        const s = Math.sin(angleInRadians);
        dst = dst || new Float32Array(16);
        dst[ 0] = c;   dst[ 1] = s;  dst[ 2] = 0;  dst[ 3] = 0;
        dst[ 4] = -s;  dst[ 5] = c;  dst[ 6] = 0;  dst[ 7] = 0;
        dst[ 8] = 0;   dst[ 9] = 0;  dst[10] = 1;  dst[11] = 0;
        dst[12] = 0;   dst[13] = 0;  dst[14] = 0;  dst[15] = 1;
        return dst;
      },
    
      scaling([sx, sy, sz], dst) {
        dst = dst || new Float32Array(16);
        dst[ 0] = sx;  dst[ 1] = 0;   dst[ 2] = 0;    dst[ 3] = 0;
        dst[ 4] = 0;   dst[ 5] = sy;  dst[ 6] = 0;    dst[ 7] = 0;
        dst[ 8] = 0;   dst[ 9] = 0;   dst[10] = sz;   dst[11] = 0;
        dst[12] = 0;   dst[13] = 0;   dst[14] = 0;    dst[15] = 1;
        return dst;
      },
    
      translate(m, translation, dst) {
        return mat4.multiply(m, mat4.translation(translation), dst);
      },
    
      rotateX(m, angleInRadians, dst) {
        return mat4.multiply(m, mat4.rotationX(angleInRadians), dst);
      },
    
      rotateY(m, angleInRadians, dst) {
        return mat4.multiply(m, mat4.rotationY(angleInRadians), dst);
      },
    
      rotateZ(m, angleInRadians, dst) {
        return mat4.multiply(m, mat4.rotationZ(angleInRadians), dst);
      },
    
      scale(m, scale, dst) {
        return mat4.multiply(m, mat4.scaling(scale), dst);
      },
    };
    
    
    async function main() {
      const adapter = await navigator.gpu?.requestAdapter();
      const device = await adapter?.requestDevice();
      if (!device) {
        fail('need a browser that supports WebGPU');
        return;
      }
    
      // Get a WebGPU context from the canvas and configure it
      const canvas = document.querySelector('canvas');
      const context = canvas.getContext('webgpu');
      const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
      context.configure({
        device,
        format: presentationFormat,
      });
    
      const module = device.createShaderModule({
        label: 'our hardcoded textured quad shaders',
        code: `
          struct OurVertexShaderOutput {
            @builtin(position) position: vec4f,
          };
    
          struct Uniforms {
            matrix: mat4x4f,
          };
    
          @group(0) @binding(2) var<uniform> uni: Uniforms;
    
          @vertex fn vs(
            @builtin(vertex_index) vertexIndex : u32
          ) -> OurVertexShaderOutput {
            let pos = array(
    
              vec2f( 0.0,  0.0),  // center
              vec2f( 1.0,  0.0),  // right, center
              vec2f( 0.0,  1.0),  // center, top
    
              // 2st triangle
              vec2f( 0.0,  1.0),  // center, top
              vec2f( 1.0,  0.0),  // right, center
              vec2f( 1.0,  1.0),  // right, top
            );
    
            var vsOutput: OurVertexShaderOutput;
            let xy = pos[vertexIndex];
            vsOutput.position = uni.matrix * vec4f(xy.x, 0.0, xy.y, 1.0);
            return vsOutput;
          }
    
          @fragment fn fs(fsInput: OurVertexShaderOutput) -> @location(0) vec4f {
            return vec4f(1, 0, 0, 1);
          }
        `,
      });
    
      const pipeline = device.createRenderPipeline({
        label: 'hardcoded textured quad pipeline',
        layout: 'auto',
        vertex: {
          module,
          entryPoint: 'vs',
        },
        fragment: {
          module,
          entryPoint: 'fs',
          targets: [{ format: presentationFormat }],
        },
      });
    
      // offsets to the various uniform values in float32 indices
      const kMatrixOffset = 0;
    
      const sampler = device.createSampler();
    
      // make a grid of squares in the shape
      // of an F in the X, Z plane
      const objectsInfos = [
        { translation: [0, 0, 0] },
        { translation: [1, 0, 0] },
        { translation: [2, 0, 0] },
        { translation: [0, 0, 1] },
        { translation: [0, 0, 2] },
        { translation: [1, 0, 2] },
        { translation: [0, 0, 3] },
        { translation: [0, 0, 4] },
      ];
      for (const info of objectsInfos) {
        // create a buffer for the uniform values
        const uniformBufferSize =
          16 * 4; // matrix is 16 32bit floats (4bytes each)
        const uniformBuffer = device.createBuffer({
          label: 'uniforms for quad',
          size: uniformBufferSize,
          usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
        });
    
        // create a typedarray to hold the values for the uniforms in JavaScript
        const uniformValues = new Float32Array(uniformBufferSize / 4);
        const matrix = uniformValues.subarray(kMatrixOffset, 16);
    
        const bindGroup = device.createBindGroup({
          layout: pipeline.getBindGroupLayout(0),
          entries: [
            { binding: 2, resource: { buffer: uniformBuffer }},
          ],
        });
    
        Object.assign(info, {
          uniformBuffer,
          uniformValues,
          matrix,
          bindGroup,
        })
      }
    
      const renderPassDescriptor = {
        label: 'our basic canvas renderPass',
        colorAttachments: [
          {
            // view: <- to be filled out when we render
            clearValue: [0.3, 0.3, 0.3, 1],
            loadOp: 'clear',
            storeOp: 'store',
          },
        ],
      };
    
      const settings = {
        position: [0, 10, 0],
        target: [0, 0, 0],
        up: [0, 0, -1],
      };
    
      const gui = new GUI();
      gui.onChange(render);
      gui.add(settings.position, 0, -10, 10, 1).name('position x');
      gui.add(settings.position, 1, -10, 10, 1).name('position y');
      gui.add(settings.position, 2, -10, 10, 1).name('position z');
      gui.add(settings.target, 0, -10, 10, 1).name('target x');
      gui.add(settings.target, 1, -10, 10, 1).name('target y');
      gui.add(settings.target, 2, -10, 10, 1).name('target z');
      gui.add(settings.up, 0, -1, 1, 1).name('up x');
      gui.add(settings.up, 1, -1, 1, 1).name('up y');
      gui.add(settings.up, 2, -1, 1, 1).name('up z');
    
      function render() {
        const fov = 60 * Math.PI / 180;  // 60 degrees in radians
        const aspect = canvas.clientWidth / canvas.clientHeight;
        const zNear  = 0.1;
        const zFar   = 20;
        const projectionMatrix = mat4.perspective(fov, aspect, zNear, zFar);
    
        const cameraMatrix = mat4.cameraAim(settings.position, settings.target, settings.up);
        const viewMatrix = mat4.inverse(cameraMatrix);
        const viewProjectionMatrix = mat4.multiply(projectionMatrix, viewMatrix);
    
        // Get the current texture from the canvas context and
        // set it as the texture to render to.
        renderPassDescriptor.colorAttachments[0].view =
            context.getCurrentTexture().createView();
    
        const encoder = device.createCommandEncoder({
          label: 'render quad encoder',
        });
        const pass = encoder.beginRenderPass(renderPassDescriptor);
        pass.setPipeline(pipeline);
    
        for (const {translation, matrix, bindGroup, uniformValues, uniformBuffer} of objectsInfos) {
          mat4.translate(viewProjectionMatrix, translation, matrix);
    
          // copy the values from JavaScript to the GPU
          device.queue.writeBuffer(uniformBuffer, 0, uniformValues);
    
          pass.setBindGroup(0, bindGroup);
          pass.draw(6);  // call our vertex shader 6 times
        }
    
        pass.end();
    
        const commandBuffer = encoder.finish();
        device.queue.submit([commandBuffer]);
      }
      render();
    }
    
    function fail(msg) {
      // eslint-disable-next-line no-alert
      alert(msg);
    }
    
    main();
      
    </script>