function perspectiveZO(out, fovy, aspect, near, far) {
    var f = 1.0 / Math.tan(fovy / 2);
    out[0] = f / aspect;
    out[1] = 0;
    out[2] = 0;
    out[3] = 0;
    out[4] = 0;
    out[5] = f;
    out[6] = 0;
    out[7] = 0;
    out[8] = 0;
    out[9] = 0;
    out[11] = -1;
    out[12] = 0;
    out[13] = 0;
    out[15] = 0;

    if (far != null && far !== Infinity) {
      var nf = 1 / (near - far);
      out[10] = far * nf;
      out[14] = far * near * nf;
    } else {
      out[10] = -1;
      out[14] = -near;
    }

    return out;
  }
Generates a perspective projection matrix suitable for WebGPU with the given bounds.
The near/far clip planes correspond to a normalized device coordinate Z range of [0, 1],
which matches WebGPU/Vulkan/DirectX/Metal's clip volume.
Passing null/undefined/no value for far will generate infinite projection matrix.

@param {mat4} out mat4 frustum matrix will be written into
@param {number} fovy Vertical field of view in radians
@param {number} aspect Aspect ratio. typically viewport width/height
@param {number} near Near bound of the frustum
@param {number} far Far bound of the frustum, can be null or Infinity
@returns {mat4} out