0

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?

tlckpl
  • 59
  • 1
  • 8
  • Your camera is not looking straight down. To look straight down, position and target would have the same x and z. I'd expect up for looking straight down to be [0, 0, -1] (north), not [1, 0, 0] (east) – gman Aug 01 '23 at 15:19
  • @gman Yeah, "straight" was not the right word, I will edit my post. I just tilted it a bit because I think it looks better this way. But regardless of the position being [-3, 0, -10] or [0, 0, -10] or the up vector being [1, 0, 0] or [0, 0, -1] all objects are still in this weird coordinate system where they go up with -Z. Even in the cameras up vector, it should be [0, 1, 0], not [0, 0, -1]. I don't know why this is happening. – tlckpl Aug 01 '23 at 15:30

1 Answers1

1

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>
gman
  • 100,619
  • 31
  • 269
  • 393
  • The result with up as +X or -Z is the same. I recorded [a video](https://www.youtube.com/watch?v=2HobYC9a2D4) to show it better. – tlckpl Aug 01 '23 at 16:18
  • 1
    Maybe you have a bug somewhere else but the example above seems to work and seems to be using the same math that you posted. – gman Aug 01 '23 at 22:00
  • Yeah, I though about it and decided to look elsewhere for the problem, maybe my assumption of the model being correct was wrong somehow. So I made 2 models in blender: a red plane on the ground plane and 3 boxes representing the 3 axes (colored accordingly). The result was cursed: https://i.imgur.com/cIfYnXd.png There is probably something wrong with my gltf importer. I will look into it – tlckpl Aug 02 '23 at 13:24
  • 1
    I finally found it! I was importing gltf node quaternions as WXYZ but in the spec they are specified as XYZW. – tlckpl Aug 02 '23 at 15:37