13. WebGPU 正交投影

news2024/11/13 19:38:05

在上一篇文章中,讨论了矩阵的工作原理。讨论了如何通过 1 个矩阵和一些神奇的矩阵数学来完成平移、旋转、缩放,甚至从像素到裁剪空间的投影。实现 3D 操作 只需要再向前迈一小步。

在之前的 2D 示例中,将 2D 点 (x, y) 乘以 3x3 矩阵。要实现 3D操作,需要 3D 点 (x, y, z) 和一个 4x4 矩阵。

以最后一个示例为例,将其更改为 3D。我们将再次使用 F,但这次是 3D“F”。

需要做的第一件事是更改顶点着色器以处理 3D。这是旧的顶点着色器。

struct Uniforms {
  color: vec4f,
 // matrix: mat3x3f,
  matrix: mat4x4f,
};
 
struct Vertex {
 // @location(0) position: vec2f,
  @location(0) position: vec4f,
};
 
struct VSOutput {
  @builtin(position) position: vec4f,
};
 
@group(0) @binding(0) var<uniform> uni: Uniforms;
 
@vertex fn vs(vert: Vertex) -> VSOutput {
  var vsOut: VSOutput;
 
 // let clipSpace = (uni.matrix * vec3f(vert.position, 1)).xy;
 // vsOut.position = vec4f(clipSpace, 0.0, 1.0);
  vsOut.position = uni.matrix * vert.position;
  return vsOut;
}
 
@fragment fn fs(vsOut: VSOutput) -> @location(0) vec4f {
  return uni.color;
}

它变得更加简单!就像在 2D 中我们提供 x 和 y 然后将 z 设置为 1,在 3D 中我们将提供 x 、 y 和 z 并且需要 w 为 1 但可以利用属性 w 默认为 1 的事实。

然后需要提供3D数据。

function createFVertices() {
  const vertexData = new Float32Array([
    // left column
    0 , 0  , 0,
    30, 0  , 0,
    0 , 150, 0,
    30, 150, 0,
 
    // top rung
    30 , 0 , 0,
    100, 0 , 0,
    30 , 30, 0,
    100, 30, 0,
 
    // middle rung
    30, 60, 0,
    70, 60, 0,
    30, 90, 0,
    70, 90, 0,
  ]);
 
  const indexData = new Uint32Array([
    0,  1,  2,    2,  1,  3,  // left column
    4,  5,  6,    6,  5,  7,  // top run
    8,  9, 10,   10,  9, 11,  // middle run
  ]);
 
  return {
    vertexData,
    indexData,
    numVertices: indexData.length,
  };
}

上面只是在每行的末尾添加了一个 0,

const pipeline = device.createRenderPipeline({
    label: '2 attributes',
    layout: 'auto',
    vertex: {
      module,
      entryPoint: 'vs',
      buffers: [
        {
          //arrayStride: (2) * 4, // (2) floats, 4 bytes each
          arrayStride: (3) * 4, // (3) floats, 4 bytes each
          attributes: [
            //{shaderLocation: 0, offset: 0, format: 'float32x2'},  // position
            {shaderLocation: 0, offset: 0, format: 'float32x3'},  // position
          ],
        },
      ],
    },
    fragment: {
      module,
      entryPoint: 'fs',
      targets: [{ format: presentationFormat }],
    },
  });

Next we need to change all the matrix math from 2D to 3D
接下来我们需要将所有矩阵数学从 2D 更改为 3D

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

还可以制作 X 和 Y 旋转矩阵

在这里插入图片描述

现在有 3 个旋转矩阵。我们只需要一个二维的,因为我们实际上只绕 Z 轴旋转。不过现在,为了制作 3D,我们还希望能够绕 X 轴和 Y 轴旋转。可以看到他们都非常相似。如果我们要解决它们,您会看到它们像以前一样简化

Z轴旋转

newX = x *  c + y * s;
newY = x * -s + y * c;

Y旋转

newX = x *  c + z * s;
newZ = x * -s + z * c;

X旋转

newY = y *  c + z * s;
newZ = y * -s + z * c;

这些旋转如下所示。
在这里插入图片描述

这是 mat3.translation mat3.rotationmat3.scaling 的二维(之前)版本

const mat3 = {
  ...
  translation([tx, ty], dst) {
    dst = dst || new Float32Array(12);
    dst[0] = 1;   dst[1] = 0;   dst[2] = 0;
    dst[4] = 0;   dst[5] = 1;   dst[6] = 0;
    dst[8] = tx;  dst[9] = ty;  dst[10] = 1;
    return dst;
  },
 
  rotation(angleInRadians, dst) {
    const c = Math.cos(angleInRadians);
    const s = Math.sin(angleInRadians);
    dst = dst || new Float32Array(12);
    dst[0] = c;   dst[1] = s;  dst[2] = 0;
    dst[4] = -s;  dst[5] = c;  dst[6] = 0;
    dst[8] = 0;   dst[9] = 0;  dst[10] = 1;
    return dst;
 
  },
 
  scaling([sx, sy], dst) {
    dst = dst || new Float32Array(12);
    dst[0] = sx;  dst[1] = 0;   dst[2] = 0;
    dst[4] = 0;   dst[5] = sy;  dst[6] = 0;
    dst[8] = 0;   dst[9] = 0;   dst[10] = 1;
    return dst;
  },
  ...

这是更新的 3D 版本

const mat4 = {
  ...
  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 mat3.multiply(m, mat3.translation(translation), dst);
  },
 
  rotate(m, angleInRadians, dst) {
    return mat3.multiply(m, mat3.rotation(angleInRadians), dst);
  },
 
  scale(m, scale, dst) {
    return mat3.multiply(m, mat3.scaling(scale), dst);
  },

现在是 3D 的。除了将它们命名为 mat4 并添加另外 2 个旋转函数外,没有太大变化。

  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.scaling(m, mat4.scaling(scale), dst);
  },
  ...

现在需要一个 4x4 矩阵乘法函数

 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;
  },

还需要更新投影功能。这是旧的

 projection(width, height, dst) {
    // Note: This matrix flips the Y axis so that 0 is at the top.
    dst = dst || new Float32Array(12);
    dst[0] = 2 / width;  dst[1] = 0;             dst[2] = 0;
    dst[4] = 0;          dst[5] = -2 / height;   dst[6] = 0;
    dst[8] = -1;         dst[9] = 1;             dst[10] = 1;
    return dst;
  },

从像素转换为裁剪空间。我们第一次尝试将它扩展到 3D 让我们试试

 projection(width, height, depth, dst) {
    // Note: This matrix flips the Y axis so that 0 is at the top.
    dst = dst || new Float32Array(16);
    dst[ 0] = 2 / width;  dst[ 1] = 0;            dst[ 2] = 0;          dst[ 3] = 0;
    dst[ 4] = 0;          dst[ 5] = -2 / height;  dst[ 6] = 0;          dst[ 7] = 0;
    dst[ 8] = 0;          dst[ 9] = 0;            dst[10] = 2 / depth;  dst[11] = 0;
    dst[12] = -1;         dst[13] = 1;            dst[14] = 1;          dst[15] = 1;
    return dst;
  },

Just like we needed to convert from pixels to clip space for X and Y, for Z we need to do the same thing. In this case we making the Z axis “pixel units” as well?. We’ll pass in some value similar to width for the depth so our space will be 0 to width pixels wide, 0 to height pixels tall, but for depth it will be -depth / 2 to +depth / 2.

就像我们需要将 X 和 Y 的像素空间转换为裁剪空间一样,对于 Z,我们也需要做同样的事情。在这种情况下,我们也将 Z 轴设为“单位像素”吗?我们将为 depth 传递一些类似于 width 的值,因此我们的空间将为 0 到 width 像素宽,0 到 height 像素高,但对于 depth 它将是 -depth / 2 到 +depth / 2 。

我们需要在uniforms中提供一个 4x4 矩阵

 // color, matrix
 // const uniformBufferSize = (4 + 12) * 4;
  const uniformBufferSize = (4 + 16) * 4;
  const uniformBuffer = device.createBuffer({
    label: 'uniforms',
    size: uniformBufferSize,
    usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
  });
 
  const uniformValues = new Float32Array(uniformBufferSize / 4);
 
  // offsets to the various uniform values in float32 indices
  const kColorOffset = 0;
  const kMatrixOffset = 4;
 
  const colorValue = uniformValues.subarray(kColorOffset, kColorOffset + 4);
  //const matrixValue = uniformValues.subarray(kMatrixOffset, kMatrixOffset + 12);
  const matrixValue = uniformValues.subarray(kMatrixOffset, kMatrixOffset + 16);

需要更新计算矩阵的代码。

const settings = {
   // translation: [150, 100],
   // rotation: degToRad(30),
   // scale: [1, 1],
    translation: [45, 100, 0],
    rotation: [degToRad(40), degToRad(25), degToRad(325)],
    scale: [1, 1, 1],
  };
 
  ...
 
  function render() {
    ...
 
   // mat3.projection(canvas.clientWidth, canvas.clientHeight, matrixValue);
   // mat3.translate(matrixValue, settings.translation, matrixValue);
   // mat3.rotate(matrixValue, settings.rotation, matrixValue);
   // mat3.scale(matrixValue, settings.scale, matrixValue);
    mat4.projection(canvas.clientWidth, canvas.clientHeight, 400, matrixValue);
    mat4.translate(matrixValue, settings.translation, matrixValue);
    mat4.rotateX(matrixValue, settings.rotation[0], matrixValue);
    mat4.rotateY(matrixValue, settings.rotation[1], matrixValue);
    mat4.rotateZ(matrixValue, settings.rotation[2], matrixValue);
    mat4.scale(matrixValue, settings.scale, matrixValue);

在这里插入图片描述

遇到的第一个问题是现在的数据是平面 F,这使得很难看到任何 3D。为了解决这个问题,将数据扩展到 3D。当前的 F 由 3 个矩形组成,每个矩形有 2 个三角形。要使其成为 3D,总共需要 16 个矩形。正面有 3 个矩形,背面有 3 个,左侧有 1 个,右侧有 4 个,顶部有 2 个,底部有 3 个。
在这里插入图片描述

只需要获取所有当前顶点位置并复制它们,但将它们移动到 Z 中。然后将它们与索引连接起来

function createFVertices() {
  const vertexData = new Float32Array([
    // left column
    0, 0, 0,
    30, 0, 0,
    0, 150, 0,
    30, 150, 0,
 
    // top rung
    30, 0, 0,
    100, 0, 0,
    30, 30, 0,
    100, 30, 0,
 
    // middle rung
    30, 60, 0,
    70, 60, 0,
    30, 90, 0,
    70, 90, 0,
 
    // left column back
    0, 0, 30,
    30, 0, 30,
    0, 150, 30,
    30, 150, 30,
 
    // top rung back
    30, 0, 30,
    100, 0, 30,
    30, 30, 30,
    100, 30, 30,
 
    // middle rung back
    30, 60, 30,
    70, 60, 30,
    30, 90, 30,
    70, 90, 30,
  ]);
 
  const indexData = new Uint32Array([
    // front
    0,  1,  2,    2,  1,  3,  // left column
    4,  5,  6,    6,  5,  7,  // top run
    8,  9, 10,   10,  9, 11,  // middle run
 
    // back
    12,  13,  14,   14, 13, 15,  // left column back
    16,  17,  18,   18, 17, 19,  // top run back
    20,  21,  22,   22, 21, 23,  // middle run back
 
    0, 5, 12,   12, 5, 17,   // top
    0, 12, 2,   12, 2, 14,   // left
    2, 3, 14,   14, 3, 15,   // bottom
    5, 7, 17,   17, 7, 19,   // top rung right
    6, 7, 18,   18, 7, 19,   // top rung bottom
    6, 8, 18,   18, 8, 20,   // between top and middle rung
    8, 9, 20,   20, 9, 21,   // middle rung top
    9, 11, 21,  21, 11, 23,  // middle rung right
    10, 11, 22, 22, 11, 23,  // middle rung bottom
    10, 3, 22,  22, 3, 15,   // stem right
  ]);
 
  return {
    vertexData,
    indexData,
    numVertices: indexData.length,
  };
}

效果如下所示:

在这里插入图片描述

移动滑块很难判断它是 3D 的。尝试为每个矩形着色不同的颜色。为此,将向顶点着色器添加另一个属性,并通过阶段间变量将其从顶点着色器传递到片段着色器。

首先更新着色器

struct Uniforms {
  //color: vec4f,
  matrix: mat4x4f,
};
 
struct Vertex {
  @location(0) position: vec4f,
  @location(1) color: vec4f,//here
};
 
struct VSOutput {
  @builtin(position) position: vec4f,
  @location(0) color: vec4f, //here
};
 
@group(0) @binding(0) var<uniform> uni: Uniforms;
 
@vertex fn vs(vert: Vertex) -> VSOutput {
  var vsOut: VSOutput;
  vsOut.position = uni.matrix * vert.position;
  vsOut.color = vert.color; //here
  return vsOut;
}
 
@fragment fn fs(vsOut: VSOutput) -> @location(0) vec4f {
  //return uni.color;
  return vsOut.color;
}

需要为顶点数据添加颜色,但是有一个问题。目前使用索引来共享顶点。但是,如果想为每个面绘制不同的颜色,这些顶点就不能共享,因为它们每个只能获得一种颜色。
在这里插入图片描述

上面的角顶点需要为它共享的 3 个面中的每一个使用一次,但每次都需要不同的颜色,因此使用索引是有问题的。 [注释1]

因此,将数据从索引扩展到非索引,同时将添加顶点颜色,以便 F 的每个部分都获得不同的颜色。

function createFVertices() {
  //const vertexData = new Float32Array([
  const positions = [
    // left column
    0, 0, 0,
    30, 0, 0,
    0, 150, 0,
    30, 150, 0,
 
    // top rung
    30, 0, 0,
    100, 0, 0,
    30, 30, 0,
    100, 30, 0,
 
    // middle rung
    30, 60, 0,
    70, 60, 0,
    30, 90, 0,
    70, 90, 0,
 
    // left column back
    0, 0, 30,
    30, 0, 30,
    0, 150, 30,
    30, 150, 30,
 
    // top rung back
    30, 0, 30,
    100, 0, 30,
    30, 30, 30,
    100, 30, 30,
 
    // middle rung back
    30, 60, 30,
    70, 60, 30,
    30, 90, 30,
    70, 90, 30,
 // ]);
  ];
 
 // const indexData = new Uint32Array([
  const indices = [
    // front
    0,  1,  2,    2,  1,  3,  // left column
    4,  5,  6,    6,  5,  7,  // top run
    8,  9, 10,   10,  9, 11,  // middle run
 
    // back
    12,  13,  14,   14, 13, 15,  // left column back
    16,  17,  18,   18, 17, 19,  // top run back
    20,  21,  22,   22, 21, 23,  // middle run back
 
    0, 5, 12,   12, 5, 17,   // top
    0, 12, 2,   12, 2, 14,   // left
    2, 3, 14,   14, 3, 15,   // bottom
    5, 7, 17,   17, 7, 19,   // top rung right
    6, 7, 18,   18, 7, 19,   // top rung bottom
    6, 8, 18,   18, 8, 20,   // between top and middle rung
    8, 9, 20,   20, 9, 21,   // middle rung top
    9, 11, 21,  21, 11, 23,  // middle rung right
    10, 11, 22, 22, 11, 23,  // middle rung bottom
    10, 3, 22,  22, 3, 15,   // stem right
 // ]);
  ];
 
  const quadColors = [
      200,  70, 120,  // left column front
      200,  70, 120,  // top rung front
      200,  70, 120,  // middle rung front
       80,  70, 200,  // left column back
       80,  70, 200,  // top rung back
       80,  70, 200,  // middle rung back
       70, 200, 210,  // top
      160, 160, 220,  // left side
       90, 130, 110,  // bottom
      200, 200,  70,  // top rung right
      210, 100,  70,  // under top rung
      210, 160,  70,  // between top rung and middle
       70, 180, 210,  // top of middle rung
      100,  70, 210,  // right of middle rung
       76, 210, 100,  // bottom of middle rung.
      140, 210,  80,  // right of bottom
  ];
 
  const numVertices = indices.length;
  const vertexData = new Float32Array(numVertices * 4); // xyz + color
  const colorData = new Uint8Array(vertexData.buffer);
 
  for (let i = 0; i < indices.length; ++i) {
    const positionNdx = indices[i] * 3;
    const position = positions.slice(positionNdx, positionNdx + 3);
    vertexData.set(position, i * 4);
 
    const quadNdx = (i / 6 | 0) * 3;
    const color = quadColors.slice(quadNdx, quadNdx + 3);
    colorData.set(color, i * 16 + 12);  // set RGB
    colorData[i * 16 + 15] = 255;       // set A
  }
 
  return {
    vertexData,
   // indexData,
   // numVertices: indexData.length,
    numVertices,
  };
}

遍历每个索引,获取该索引的位置并将位置值放入 vertexData 。对与 colorData 相同的数据有一个单独的视图,因此通过四边形索引(每 6 个顶点一个四边形)提取颜色,并为该四边形的每个顶点插入相同的颜色。数据最终会变成这样。
在这里插入图片描述

我们添加的颜色是无符号字节,值从 0 到 255,类似于 css rgb() 颜色。通过将管线中的属性类型设置为 unorm8x4 (无符号规范化 8 位值 x 4),GPU 会将值从缓冲区中拉出并在将它们提供给着色器时对其进行规范化。这意味着它将使它们从 0 变为 1,在本例中是将它们除以 255。

现在我们有了数据,需要更改管线以使用它。

const pipeline = device.createRenderPipeline({
    label: '2 attributes',
    layout: 'auto',
    vertex: {
      module,
      entryPoint: 'vs',
      buffers: [
        {
          //arrayStride: (3) * 4, // (3) floats, 4 bytes each
          arrayStride: (4) * 4, // (3) floats 4 bytes each + one 4 byte color
          attributes: [
            {shaderLocation: 0, offset: 0, format: 'float32x3'},  // position
            {shaderLocation: 1, offset: 12, format: 'unorm8x4'},  // color
          ],
        },
      ],
    },
    fragment: {
      module,
      entryPoint: 'fs',
      targets: [{ format: presentationFormat }],
    },
  });

不再需要制作索引缓冲区。

  // const { vertexData, indexData, numVertices } = createFVertices();
  const { vertexData, numVertices } = createFVertices();
  const vertexBuffer = device.createBuffer({
    label: 'vertex buffer vertices',
    size: vertexData.byteLength,
    usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
  });
  device.queue.writeBuffer(vertexBuffer, 0, vertexData);
 // const indexBuffer = device.createBuffer({
 //   label: 'index buffer',
 //   size: indexData.byteLength,
 //   usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
 // });
 // device.queue.writeBuffer(indexBuffer, 0, indexData);

hide deleted
and we need to draw without indices
我们需要在没有索引的情况下绘制

 function render() {
    ...
    pass.setPipeline(pipeline);
    pass.setVertexBuffer(0, vertexBuffer);
   // pass.setIndexBuffer(indexBuffer, 'uint32');
 
    ...
 
    pass.setBindGroup(0, bindGroup);
  //  pass.drawIndexed(numVertices);
    pass.draw(numVertices);
 
    ...
  }

现在效果如下。

在这里插入图片描述

Uh oh, what’s that mess? Well, it turns out all the various parts of that 3D ‘F’, front, back, sides, etc get drawn in the order they appear in our geometry data. That doesn’t give us quite the desired results as sometimes the ones in the back get drawn after the ones in the front.

哦,那是什么烂摊子?好吧,事实证明 3D “F”的所有不同部分,正面、背面、侧面等都是按照它们在我们的几何数据中出现的顺序绘制的。这并没有带来非常理想的结果,因为有时后面的那些会在前面的那些之后被绘制。
在这里插入图片描述

在这里插入图片描述

红色部分是“F”的前面,但因为它是数据的第一部分,所以它先被绘制,然后它后面的其他三角形被绘制,覆盖它。例如,紫色部分实际上是“F”的背面。它排在第二位,因为它在我们的数据中排在第二位。

Triangles in WebGPU have the concept of front facing and back facing. By default a front facing triangle has its vertices go in a counter clockwise direction in clip space. A back facing triangle has its vertices go in a clockwise direction in clip space.

WebGPU 中的三角形有前向和后向的概念。默认情况下,正面三角形的顶点在裁剪空间中沿逆时针方向移动。背面三角形的顶点在裁剪空间中沿顺时针方向移动。

在这里插入图片描述

The gpu has the ability to draw only forward facing or only back facing triangles. We can turn that feature on by modifying the pipeline

gpu 能够只绘制向前或后向三角形。我们可以通过修改管道来打开该功能

 const pipeline = device.createRenderPipeline({
    label: '2 attributes',
    layout: 'auto',
    vertex: {
      module,
      entryPoint: 'vs',
      buffers: [
        {
          arrayStride: (4) * 4, // (3) floats 4 bytes each + one 4 byte color
          attributes: [
            {shaderLocation: 0, offset: 0, format: 'float32x3'},  // position
            {shaderLocation: 1, offset: 12, format: 'unorm8x4'},  // color
          ],
        },
      ],
    },
    fragment: {
      module,
      entryPoint: 'fs',
      targets: [{ format: presentationFormat }],
    },
    primitive: {
      cullMode: 'back', //here
    },
  });

With cullMode set to back, “back facing” triangles will be culled. “Culling” in this case is a fancy word for “not drawing”. So, with cullMode set to ‘back’, this is what we get

当 cullMode 设置为 back 时,“背面(back facing)”三角形将被剔除。在这种情况下,“剔除”是“不绘制”的奇特词。所以,将 cullMode 设置为 ‘back’ ,这就是我们得到的

在这里插入图片描述

Hey! Where did all the triangles go? It turns out, many of them are facing the wrong way. Rotate it and you’ll see them appear when you look at the other side. Fortunately it’s easy to fix. We just look at which ones are backward and exchange 2 of their vertices. For example if one backward triangle has indices

嘿!所有的三角形都去哪儿了?事实证明,他们中的许多人正面临着错误的道路。旋转它,当您看向另一侧时,您会看到它们出现。幸运的是,它很容易修复。我们只是看看哪些是向后的,并交换他们的 2 个顶点。例如,如果一个向后三角形有索引

6, 7, 8,

我们可以交换其中的两个让他们走另一条路

6, 8, 7,

Importantly, as far as WebGPU is concerned, whether or not a triangle is considered to be going clockwise or counter clockwise depends on the vertices of that triangle in clip space. In other words, WebGPU figures out whether a triangle is front or back AFTER you’ve applied math to the vertices in the vertex shader. That means for example, a clockwise triangle that is scaled in X by -1 becomes a counter clockwise triangle or, a clockwise triangle rotated 180 degrees becomes a counter clockwise triangle. Because we didn’t set cullMode before, we could see both clockwise(front) and counter clockwise(back) facing triangles. Now that we’ve set cullMode to back, any time a front facing triangle flips around, either because of scaling or rotation or for whatever reason, WebGPU won’t draw it. That’s a good thing since, as you turn something around in 3D, you generally want whichever triangles are facing you to be considered front facing.

重要的是,就 WebGPU 而言,三角形是顺时针还是逆时针取决于裁剪空间中该三角形的顶点。换句话说,WebGPU 会在您对顶点着色器中的顶点应用数学运算后计算出三角形是正面还是背面。这意味着,例如,在 X 中按 -1 缩放的顺时针三角形变为逆时针三角形,或者旋转 180 度的顺时针三角形变为逆时针三角形。因为我们之前没有设置 cullMode ,所以我们可以看到顺时针(正面)和逆时针(背面)的三角形。现在我们已经将 cullMode 设置为 back ,任何时候因为缩放或旋转或出于任何原因,前面的三角形翻转,WebGPU 都不会绘制它。这是一件好事,因为当您在 3D 中旋转某物时,您通常希望面向您的任何三角形都被视为正面。

BUT! Remember that in clip space +Y is at the top, but in our pixel space +Y is at the bottom. In other words, our matrix is flipping all the triangles vertically. This means that in order to draw things with +Y at the bottom we either need to set cullMode to ‘front’, OR flip all our triangles vertices. Let’s set cullMode to ‘front’ and then also fix the vertex data so all the triangles have the same direction.

但!请记住,在裁剪空间中 +Y 位于顶部,但在我们的像素空间中 +Y 位于底部。换句话说,我们的矩阵垂直翻转所有三角形。这意味着为了在底部绘制带有 +Y 的东西,我们需要将 cullMode 设置为 ‘front’ ,或者翻转所有三角形顶点。让我们将 cullMode 设置为 ‘front’ ,然后固定顶点数据,使所有三角形都具有相同的方向。

const indices = [
    // front
    0,  1,  2,    2,  1,  3,  // left column
    4,  5,  6,    6,  5,  7,  // top run
    8,  9, 10,   10,  9, 11,  // middle run
 
    // back
    //12,  13,  14,   14, 13, 15,  // left column back
    12,  14,  13,   14, 15, 13,  // left column back
    //16,  17,  18,   18, 17, 19,  // top run back
    16,  18,  17,   18, 19, 17,  // top run back
    //20,  21,  22,   22, 21, 23,  // middle run back
    20,  22,  21,   22, 23, 21,  // middle run back
 
    //0, 5, 12,   12, 5, 17,   // top
    0, 12, 5,   12, 17, 5,   // top
    //5, 7, 17,   17, 7, 19,   // top rung right
    5, 17, 7,   17, 19, 7,   // top rung right
    6, 7, 18,   18, 7, 19,   // top rung bottom
    //6, 8, 18,   18, 8, 20,   // between top and middle rung
    6, 18, 8,   18, 20, 8,   // between top and middle rung
    //8, 9, 20,   20, 9, 21,   // middle rung top
    8, 20, 9,   20, 21, 9,   // middle rung top
    //9, 11, 21,  21, 11, 23,  // middle rung right
    9, 21, 11,  21, 23, 11,  // middle rung right
    10, 11, 22, 22, 11, 23,  // middle rung bottom
    //10, 3, 22,  22, 3, 15,   // stem right
    10, 22, 3,  22, 15, 3,   // stem right
    2, 3, 14,   14, 3, 15,   // bottom
    0, 2, 12,   12, 2, 14,   // left
  ];
const pipeline = device.createRenderPipeline({
    ...
    primitive: {
      // cullMode: 'back',
      cullMode: 'front',
    },
  });

有了这些变化,让所有的三角形都面向一个方向,我们就可以做到这一点

在这里插入图片描述

That’s closer but there’s still one more problem. Even with all the triangles facing in the correct direction, and with the ones facing away from us being culled, we still have places where triangles that should be in the back are being drawn over triangles that should be in front.

这更接近了,但还有一个问题。即使所有的三角形都朝向正确的方向,并且背对我们的三角形被剔除,仍然有一些地方应该在后面的三角形被绘制在应该在前面的三角形上。

应用“深度纹理” (Enter “Depth Textures” )

A depth texture, sometimes called a depth-buffer or Z-Buffer, is a rectangle of depth texels, one depth texel for each color texel in the texture we’re drawing to. If we create and bind a depth texture, then, as WebGPU draws each pixel it can also draw a depth pixel. It does this based on the values we return from the vertex shader for Z. Just like we had to convert to clip space for X and Y, Z is also in clip space. For Z, clip space is 0 to +1.

深度纹理,有时称为深度缓冲区或 Z 缓冲区,是一个矩形的深度纹素,一个深度纹素对应我们正在绘制的纹理中的每个颜色纹素。如果我们创建并绑定一个深度纹理,那么,当 WebGPU 绘制每个像素时,它也可以绘制一个深度像素。它根据我们从 Z 的顶点着色器返回的值来执行此操作。就像我们必须为 X 和 Y 转换到裁剪空间一样,Z 也在裁剪空间中。对于 Z,剪辑空间为 0 到 +1。

Before WebGPU draws a color pixel it will check the corresponding depth pixel. If the depth (Z) value for the pixel it’s about to draw does not match some condition relative to the value of the corresponding depth pixel. then WebGPU will not draw the new color pixel. Otherwise it draws both the new color pixel with the color from your fragment shader AND it draws the depth pixel with the new depth value. This means, pixels that are behind other pixels won’t get drawn.

在 WebGPU 绘制颜色像素之前,它会检查相应的深度像素。如果要绘制的像素的深度 (Z) 值与相应深度像素的值相关的某些条件不匹配。那么 WebGPU 将不会绘制新的颜色像素。否则,它会使用片段着色器中的颜色绘制新的颜色像素,并使用新的深度值绘制深度像素。这意味着,不会绘制位于其他像素后面的像素。

要设置和使用深度纹理,需要更新管线

 const pipeline = device.createRenderPipeline({
    label: '2 attributes',
    layout: 'auto',
    vertex: {
      module,
      entryPoint: 'vs',
      buffers: [
        {
          arrayStride: (4) * 4, // (3) floats 4 bytes each + one 4 byte color
          attributes: [
            {shaderLocation: 0, offset: 0, format: 'float32x3'},  // position
            {shaderLocation: 1, offset: 12, format: 'unorm8x4'},  // color
          ],
        },
      ],
    },
    fragment: {
      module,
      entryPoint: 'fs',
      targets: [{ format: presentationFormat }],
    },
    primitive: {
      cullMode: 'front',
    },
    depthStencil: { //here
      depthWriteEnabled: true, //here
      depthCompare: 'less', //here
      format: 'depth24plus', //here
    },
  });

Above we’re setting depthCompare: 'less'. This means, only draw the new pixel, if the Z value for the new pixel is “less” than the corresponding pixel in the depth texture. other options include never, equal, less-equal, greater, not-equal, greater-equal, always.

上面我们设置了 depthCompare: 'less' 。这意味着,如果新像素的 Z 值“小于”深度纹理中的相应像素,则只绘制新像素。其他选项包括 never 、 equal 、 less-equal 、 greater 、 not-equal 、 greater-equal 、 always 。

depthWriteEnabled: true means, if we pass the depthCompare test, then write the Z value of our new pixel to the depth texture. In our case, this means each time a pixel we’re drawing has a Z value less than what’s already in the depth texture, we’ll draw that pixel and update the depth texture. In this way, if we later try to draw a pixel that’s further back (has a higher Z value) it will not be draw.

depthWriteEnabled: true 表示,如果我们通过了 depthCompare 测试,则将新像素的 Z 值写入深度纹理。在我们的例子中,这意味着每当我们绘制的像素的 Z 值小于深度纹理中已有的值时,我们将绘制该像素并更新深度纹理。这样,如果我们稍后尝试绘制更靠后的像素(具有更高的 Z 值),它将不会被绘制。

format is similar to fragment.targets[?].format. It’s the format of the depth texture we will use. The available depth texture formats were listed in the article on textures. depth24plus is a good default format to choose.

format 类似于 fragment.targets[?].format 。这是我们将使用的深度纹理的格式。有关纹理的文章中列出了可用的深度纹理格式。 depth24plus 是一个很好的默认格式可供选择。

我们还需要更新渲染过程描述符,使其具有深度模板附件。

const renderPassDescriptor = {
    label: 'our basic canvas renderPass',
    colorAttachments: [
      {
        // view: <- to be filled out when we render
        loadOp: 'clear',
        storeOp: 'store',
      },
    ],
    depthStencilAttachment: {
      // view: <- to be filled out when we render
      depthClearValue: 1.0,
      depthLoadOp: 'clear',
      depthStoreOp: 'store',
    },
  };

Depth values generally go from 0.0 to 1.0. We set depthClearValue to 1. This makes sense since we set depthCompare to less.

深度值通常从 0.0 到 1.0。我们将 depthClearValue 设置为 1。这是有道理的,因为我们将 depthCompare 设置为 less 。

Finally, we need to create a depth texture. The catch is, it has to match the size the color attachments, which in this case is the texture we get from the canvas. The canvas texture changes size when we change the size of the canvas in our ResizeObserver callback. Or, to be more clear. The texture we get when we call context.getCurrentTexture will be whatever size we set the canvas to. With that in mind, let’s create the correct size texture at render time.

最后,我们需要创建一个深度纹理。问题是,它必须匹配颜色附件的大小,在本例中是我们从画布上获得的纹理。当我们在 ResizeObserver 回调中更改画布的大小时,画布纹理会更改大小。或者,更清楚一点。我们调用 context.getCurrentTexture 时获得的纹理将是我们将画布设置为的任何大小。考虑到这一点,让我们在渲染时创建正确大小的纹理。

 let depthTexture;
 
  function render() {
    // 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 canvasTexture = context.getCurrentTexture();
    renderPassDescriptor.colorAttachments[0].view = canvasTexture.createView();
 
    // If we don't have a depth texture OR if its size is different
    // from the canvasTexture when make a new depth texture
    if (!depthTexture ||
        depthTexture.width !== canvasTexture.width ||
        depthTexture.height !== canvasTexture.height) {
      if (depthTexture) {
        depthTexture.destroy();
      }
      depthTexture = device.createTexture({
        size: [canvasTexture.width, canvasTexture.height],
        format: 'depth24plus',
        usage: GPUTextureUsage.RENDER_ATTACHMENT,
      });
    }
    renderPassDescriptor.depthStencilAttachment.view = depthTexture.createView();
 
  ...

添加深度纹理后,我们现在得到

在这里插入图片描述

这才是3D!

正交 (Ortho / Orthographic )

One minor thing. In most 3D math libraries there is no projection function to do our conversions from clip space to pixel space. Rather, there’s usually a function called ortho or orthographic that looks like this

一件小事。在大多数 3D 数学库中,没有 projection 函数来完成从裁剪空间到像素空间的转换。相反,通常有一个名为 ortho 或 orthographic 的函数,看起来像这样

const mat4 = {
  ...
  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;
  },
  ...

Unlike our simplified projection function above, which only had width, height, and depth parameters, with this more common orthographic projection function we can pass in left, right, bottom, top, near, and far which gives as more flexibility. To use it the same as our original projection function we’d call it with

与我们上面简化的 projection 函数不同,它只有宽度、高度和深度参数,有了这个更常见的正交投影函数,我们可以传入左、右、下、上、近和远,这提供了更大的灵活性。要像我们原来的投影函数一样使用它,我们会调用它

// mat4.projection(canvas.clientWidth, canvas.clientHeight, 400, matrixValue);
mat4.ortho(
    0,                   // left
    canvas.clientWidth,  // right
    canvas.clientHeight, // bottom
    0,                   // top
    400,                 // near
    -400,                // far
    matrixValue,         // dst
);   

在这里插入图片描述

接下来我们将讨论如何让它具有透视图。

为什么叫正交投影

在这种情况下,Orthographic 来自正交这个词

orthogonal 正交的
adjective: 形容词:

of or involving right angles
属于或涉及直角


注释1

it’s possible with creative arrangement of the indices we could use @interpolate(flat) as mentioned in the article on inter-stage variables and still use indices.

通过创造性地安排索引,我们可以使用 @interpolate(flat) ,如关于阶段间变量的文章中所述,并且仍然使用索引。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/673793.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【机器学习】——续上:卷积神经网络(CNN)与参数训练

目录 引入 一、CNN基本结构 1、卷积层 2、下采样层 3、全连接层 二、CNN参数训练 总结 引入 卷积神经网络&#xff08;CNN&#xff09;是一种有监督深度模型框架&#xff0c;尤其适合处理二维数据问题&#xff0c;如行人检测、人脸识别、信号处理等领域&#xff0c;是带…

19c rac添加节点

在正常的节点 [rootdb1 ~]# xhost access control disabled, clients can connect from any host [rootdb1 ~]# su - grid ASM1:/home/griddb1>export DISPLAY:1.0 ASM1:/home/griddb1>$ORACLE_HOME/gridSetup.sh [rootdb2 ~]# /tmp/GridSetupActions2021-09-16_…

基于Nginx1.22+PHP8+MySQL8安装Discuz! X3.5

基于Nginx1.22PHP8MySQL8安装Discuz! X3.5 1. 安装PHP82. 安装MySQL83. 配置Nginx1.224. 安装Discuz! X3.5 1. 安装PHP8 更新系统&#xff1a; yum update安装EPEL存储库&#xff1a; yum install epel-release安装Remi存储库&#xff08;提供了最新的 PHP 版本&#xff09;&…

【十三】druid 原理解析

druid 原理解析 先前写了一篇博客关于druid集成相关的&#xff0c;这里来分析一下druid原理&#xff0c;结合这两篇文章希望读者能够把druid理解透彻。 一、druid介绍 Druid连接池是阿里巴巴开源的数据库连接池项目。Druid连接池为监控而生&#xff0c;内置强大的监控功能&…

【前端JS交互篇】ECMA核心语法 ——常量、变量、数据类型、各种弹框

一、javascript简介 1.1 Javascript简史 在WEB日益发展的同时&#xff0c;网页的大小和复杂性不断增加&#xff0c;受制于网速的限制&#xff0c;为完成简单的表单验证而频繁地与服务器交换数据只会加重用户的负担&#xff0c;当时走在技术革新最前沿的Netscape&#xff08;网…

阿里云服务器的弹性计算能力如何?是否支持按需扩展和缩减?

阿里云服务器的弹性计算能力如何&#xff1f;是否支持按需扩展和缩减&#xff1f;   【本文由阿里云代理商[聚搜云www.4526.cn]撰写】   阿里云服务器&#xff0c;作为业界领先的云计算服务提供商&#xff0c;其弹性计算能力是如何体现的&#xff1f;是否真的支持按需扩展和…

定制你的Blocks UI布局:Gradio的Block Layouts模块介绍

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

【python】如何在 Python 中创建相关矩阵

目录 一、说明 二、相关理论 2.1 何为相关 2.2 相关的前提 2.3 Correlation Matrix是个啥&#xff1f; 2.4 皮尔逊相关系数 三、Python演示如何创建相关矩阵 四、数据可视化观察 五、后记 一、说明 本教程介绍如何在 Python 中创建和解释相关矩阵。然而&#xff0c;创…

English Learning - L3 作业打卡 Lesson7 Day47 2023.6.20 周二

English Learning - L3 作业打卡 Lesson7 Day47 2023.6.20 周二 引言&#x1f349;句1: Growing up in a hot Las Vegas desert, all I wanted was to be free.成分划分弱读连读语调 &#x1f349;句2: I would daydream about traveling the world, living in a place where i…

有三个线程,分别只能打印A,B和C要求按顺序打印ABC,打印10次(多种方法,小白也懂)

目录 第一种方法:使用LockSupport的park和unpark功能(推荐) 第二种方式:synchronizedwaitnotify 第三种:暴力循环方法(不推荐) 第一种方法:使用LockSupport的park和unpark功能(推荐) 简单来说我们有一个名为LockSupport的方法 park就是阻塞当前进程 unpark就是取消阻塞让其…

DRIFTINGBLUES: 4实战演练

文章目录 DRIFTINGBLUES: 4实战演练一、前期准备1、相关信息 二、信息收集1、端口扫描2、访问网站3、查看源码4、解密5、访问网页6、解密7、访问网页8、微信扫一扫9、爆破FTP10、登录FTP11、下载文件并查看12、写入SSH密钥并上传13、SSH连接 三、后渗透1、查看第一个flag2、查找…

Golang | Web开发之Gin静态资源映射及HTML模板渲染

欢迎关注「全栈工程师修炼指南」公众号 点击 &#x1f447; 下方卡片 即可关注我哟! 设为「星标⭐」每天带你 基础入门 到 进阶实践 再到 放弃学习&#xff01; 专注 企业运维实践、网络安全、系统运维、应用开发、物联网实战、全栈文章 等知识分享 “ 花开堪折直须折&#xf…

[Eigen中文文档] 稀疏矩阵操作

文档总目录 本文目录 稀疏矩阵格式SparseMatrix 类 第一个示例SparseMatrix 类矩阵和向量属性迭代非零系数 填充稀疏矩阵支持的运算符和函数基本操作矩阵乘积块操作三角形视图和自共轭视图 英文原文(Sparse matrix manipulations) 处理和解决稀疏问题涉及各种模块&#xff0c…

【马蹄集】第十六周作业

第十六周作业 目录 MT2149 最长子段和MT2150 旅费MT2156 矩阵取数MT2157 迷宫MT2155 四柱河内塔 MT2149 最长子段和 难度&#xff1a;钻石    时间限制&#xff1a;1秒    占用内存&#xff1a;128M 题目描述 给出一个长度为 n n n 的序列 A A A&#xff0c;选出其中连续…

Android studio的安装的详细过程

Android开发环境 Eclipse Eclipse最初是由IBM公司开发的替代商业软件Visual Age for Java的下一代IDE开发环境&#xff0c;2001年11月贡献给开源社区&#xff0c;现在它由非营利软件供应商联盟Eclipse基金会&#xff08;Eclipse Foundation&#xff09;管理。 Eclipse是一种面…

动手实现条件随机场(上)

引言 本文基于PyTorch实现条件随机场&#xff0c;实现CRF层参考论文Neural Architectures for Named Entity Recognition中关于CRF层的描述。包含大量的图解和例子说明&#xff0c;看完一定能理解&#xff01; 论文地址&#xff1a; https://arxiv.org/pdf/1603.01360.pdf 也可…

【Linux】随机数的生成

生成随机数目录 生成随机数&#xff1a;默认为(0-32767)生成指定区间随机数&#xff1a;随机生成1-50之间的数随机生成时间戳秒和纳秒加密运算生成一个随机字符指定10个在使用md5sum校验/dev/random是什么&#xff1f; 生成随机的UUID加密算法相关文章 生成随机数&#xff1a;默…

CODESYS斜坡函数功能块(ST源代码)

SMART PLC梯形图斜坡函数FC请参看下面文章链接: SMART PLC斜坡函数功能块(梯形图代码)_RXXW_Dor的博客-CSDN博客斜坡函数Ramp的具体应用可以参看下面的文章链接:PID优化系列之给定值斜坡函数(PLC代码+Simulink仿真测试)_RXXW_Dor的博客-CSDN博客很多变频器里的工艺PID,…

Gradio Blocks:queue、integrate和load方法介绍

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

(2019,图像指纹 模型指纹)溯源虚假图像的 GAN:学习和分析 GAN 指纹

Attributing fake images to gans: Learning and analyzing gan fingerprints 公众号&#xff1a;EDPJ 目录 0. 摘要 1. 简介 2. 相关工作 3. 用于图像溯源的指纹学习 3.1 溯源网络 3.2 成分分析网络 3.3 指纹可视化 4. 实验 4.1 设置 4.2 存在性和唯一性&#xff…