三、WebGPU Uniforms

news2024/10/6 10:26:18

三、WebGPU Uniforms

Uniform有点像着色器的全局变量。你可以在执行着色器之前设置它们的值,着色器的每次迭代都会有这些值。你可以在下一次请求GPU执行着色器时将它们设置为其他值。我们将再次从第一篇文章中的三角形示例开始,并对其进行修改以使用一些统一格式。

  const module = device.createShaderModule({
    label: 'triangle shaders with uniforms',
    code: `
      struct OurStruct {
        color: vec4f,
        scale: vec2f,
        offset: vec2f,
      };
 
      @group(0) @binding(0) var<uniform> ourStruct: OurStruct;
 
      @vertex fn vs(
        @builtin(vertex_index) vertexIndex : u32
      ) -> @builtin(position) vec4f {
        let pos = array(
          vec2f( 0.0,  0.5),  // top center
          vec2f(-0.5, -0.5),  // bottom left
          vec2f( 0.5, -0.5)   // bottom right
        );
 
        return vec4f(
          pos[vertexIndex] * ourStruct.scale + ourStruct.offset, 0.0, 1.0);
      }
 
      @fragment fn fs() -> @location(0) vec4f {
        return ourStruct.color;
      }
    `,
  });
 
  });

首先声明一个有3个成员的结构体

      struct OurStruct {
        color: vec4f,
        scale: vec2f,
        offset: vec2f,
      };

然后我们声明了一个uniform变量,其类型为该结构体。变量是ourStruct,它的类型是ourStruct。

  @group(0) @binding(0) var<uniform> ourStruct: OurStruct;

接下来,我们修改顶点着色器的返回值,使其使用uniform

@vertex fn vs(
         ...
      ) ... {
        ...
        return vec4f(
          pos[vertexIndex] * ourStruct.scale + ourStruct.offset, 0.0, 1.0);
      }

可以看到,我们将顶点位置乘以比例,然后添加一个偏移量。这将允许我们设置三角形的大小和位置。我们还更改了fragment shader,使其返回 uniforms 的颜色

@fragment fn fs() -> @location(0) vec4f {
        return ourStruct.color;
      }

现在我们已经设置了使用uniform的着色器,我们需要在GPU上创建一个缓冲区来保存它们的值。

如果你从未处理过原生数据和大小,那么这是一个需要学习的领域。这是一个大话题,所以这里有一篇关于这个话题的单独文章。如果你不知道如何在内存中布局结构体,请阅读这篇文章WebGPU Data Memory Layout (webgpufundamentals.org)。那就回到这里来。本文假设您已经阅读了它。

阅读完本文后,我们可以继续使用与着色器中的结构体匹配的数据填充缓冲区。

首先,我们创建一个缓冲区,并为它分配使用标志,以便它可以与uniform一起使用,这样我们就可以通过复制数据来更新它。

 const uniformBufferSize =
    4 * 4 + // color is 4 32bit floats (4bytes each)
    2 * 4 + // scale is 2 32bit floats (4bytes each)
    2 * 4;  // offset is 2 32bit floats (4bytes each)
  const uniformBuffer = device.createBuffer({
    size: uniformBufferSize,
    usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
  });

然后我们创建一个TypedArray,这样我们就可以在JavaScript中设置值

  // create a typedarray to hold the values for the uniforms in JavaScript
  const uniformValues = new Float32Array(uniformBufferSize / 4);

我们将填充结构体中两个以后不会改变的值。偏移量是使用我们在关于内存布局的文章中介绍的内容计算的WebGPU Data Memory Layout (webgpufundamentals.org)。

// offsets to the various uniform values in float32 indices
  const kColorOffset = 0;
  const kScaleOffset = 4;
  const kOffsetOffset = 6;
 
  uniformValues.set([0, 1, 0, 1], kColorOffset);        // set the color
  uniformValues.set([-0.5, -0.25], kOffsetOffset);      // set the offset

上面我们将颜色设置为绿色。偏移量将使三角形向左移动画布的1/4,向下移动1/8。(记住,剪辑空间从-1到1,这是2个单位的宽度,所以0.25是2的1/8)。接下来,正如第一篇文章中的图表所示,为了告诉着色器我们的缓冲区,我们需要创建一个绑定组,并将缓冲区绑定到我们在着色器中设置的@binding(?)。

  const bindGroup = device.createBindGroup({
    layout: pipeline.getBindGroupLayout(0),
    entries: [
      { binding: 0, resource: { buffer: uniformBuffer }},
    ],
  });

现在,在我们提交我们的命令缓冲区之前,我们需要设置uniformValues的剩余值,然后将这些值复制到GPU上的缓冲区。我们将在渲染函数的顶部进行操作。

function render() {
    // Set the uniform values in our JavaScript side Float32Array
    const aspect = canvas.width / canvas.height;
    uniformValues.set([0.5 / aspect, 0.5], kScaleOffset); // set the scale
 
    // copy the values from JavaScript to the GPU
    device.queue.writeBuffer(uniformBuffer, 0, uniformValues);

注意:writeBuffer是将数据复制到缓冲区的一种方法。本文还介绍了其他几种方法。WebGPU Copying Data (webgpufundamentals.org)

我们将缩放比例设置为一半,并考虑到画布的宽高比,因此无论画布大小如何,三角形都将保持相同的宽高比。最后,我们需要在绘图前设置绑定组

 pass.setPipeline(pipeline);
    pass.setBindGroup(0, bindGroup);
    pass.draw(3);  // call our vertex shader 3 times
    pass.end();

以下为代码及运行结果:

HTML:

<!--
 * @Description: 
 * @Author: tianyw
 * @Date: 2022-11-11 12:50:23
 * @LastEditTime: 2023-09-17 22:53:56
 * @LastEditors: tianyw
-->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>001hello-triangle</title>
    <style>
        html,
        body {
            margin: 0;
            width: 100%;
            height: 100%;
            background: #000;
            color: #fff;
            display: flex;
            text-align: center;
            flex-direction: column;
            justify-content: center;
        }

        div,
        canvas {
            height: 100%;
            width: 100%;
        }
    </style>
</head>

<body>
    <div id="005uniform-scale-triangle">
        <canvas id="gpucanvas"></canvas>
    </div>
    <script type="module" src="./005uniform-scale-triangle.ts"></script>

</body>

</html>

TS:

/*
 * @Description:
 * @Author: tianyw
 * @Date: 2023-04-08 20:03:35
 * @LastEditTime: 2023-09-17 23:02:46
 * @LastEditors: tianyw
 */
export type SampleInit = (params: {
  canvas: HTMLCanvasElement;
}) => void | Promise<void>;

import shaderWGSL from "./shaders/shader.wgsl?raw";
const init: SampleInit = async ({ canvas }) => {
  const adapter = await navigator.gpu?.requestAdapter();
  if (!adapter) return;
  const device = await adapter?.requestDevice();
  if (!device) {
    console.error("need a browser that supports WebGPU");
    return;
  }
  const context = canvas.getContext("webgpu");
  if (!context) return;
  const devicePixelRatio = window.devicePixelRatio || 1;
  canvas.width = canvas.clientWidth * devicePixelRatio;
  canvas.height = canvas.clientHeight * devicePixelRatio;
  const presentationFormat = navigator.gpu.getPreferredCanvasFormat();

  context.configure({
    device,
    format: presentationFormat,
    alphaMode: "premultiplied"
  });

  const shaderModule = device.createShaderModule({
    label: "our hardcoded rgb triangle shaders",
    code: shaderWGSL
  });
  const renderPipeline = device.createRenderPipeline({
    label: "hardcoded rgb triangle pipeline",
    layout: "auto",
    vertex: {
      module: shaderModule,
      entryPoint: "vs"
    },
    fragment: {
      module: shaderModule,
      entryPoint: "fs",
      targets: [
        {
          format: presentationFormat
        }
      ]
    },
    primitive: {
      // topology: "line-list"
      // topology: "line-strip"
      //  topology: "point-list"
      topology: "triangle-list"
      // topology: "triangle-strip"
    }
  });

  const uniformBufferSize =
    4 * 4 + // color is 4 32bit floats (4bytes each)
    2 * 4 + // scale is 2 32bit floats (4bytes each)
    2 * 4; // offset is 2 32bit floats (4bytes each)
  const uniformBuffer = device.createBuffer({
    size: uniformBufferSize,
    usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
  });
  const uniformValues = new Float32Array(uniformBufferSize / 4);

  const kColorOffset = 0;
  const kScaleOffset = 4;
  const kOffsetOffset = 6;

  uniformValues.set([0, 1, 0, 1], kColorOffset); // set the color
  uniformValues.set([-0.5, -0.25], kOffsetOffset); // set the offset
  const bindGroup = device.createBindGroup({
    layout: renderPipeline.getBindGroupLayout(0),
    entries: [{ binding: 0, resource: { buffer: uniformBuffer } }]
  });

  function frame() {
    const aspect = canvas.width / canvas.height;
    uniformValues.set([0.5 / aspect, 0.5], kScaleOffset); // set the scale
    const renderCommandEncoder = device.createCommandEncoder({
      label: "render vert frag"
    });
    if (!context) return;

    device.queue.writeBuffer(uniformBuffer, 0, uniformValues);

    const textureView = context.getCurrentTexture().createView();
    const renderPassDescriptor: GPURenderPassDescriptor = {
      colorAttachments: [
        {
          view: textureView,
          clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
          loadOp: "clear",
          storeOp: "store"
        }
      ]
    };
    const renderPass =
      renderCommandEncoder.beginRenderPass(renderPassDescriptor);
    renderPass.setPipeline(renderPipeline);
    renderPass.setBindGroup(0, bindGroup);
    renderPass.draw(3, 1, 0, 0);
    renderPass.end();
    const renderBuffer = renderCommandEncoder.finish();
    device.queue.submit([renderBuffer]);

    requestAnimationFrame(frame);
  }

  requestAnimationFrame(frame);
};

const canvas = document.getElementById("gpucanvas") as HTMLCanvasElement;
init({ canvas: canvas });

Shaders:

shader:

struct OurStruct {
    color: vec4f,
    scale: vec2f,
    offset: vec2f
};

@group(0) @binding(0) var<uniform> ourStruct: OurStruct;

@vertex 
fn vs(@builtin(vertex_index) vertexIndex: u32) -> @builtin(position) vec4f {
    let pos = array<vec2f, 3>(
        vec2f(0.0, 0.5), // top center
        vec2f(-0.5, -0.5), // bottom left
        vec2f(0.5, -0.5)  // bottom right
    );
   return vec4f(pos[vertexIndex] * ourStruct.scale + ourStruct.offset,0.0,1.0);
}

@fragment
fn fs() -> @location(0) vec4f {
    return ourStruct.color;
}

在这里插入图片描述

在这里插入图片描述

对于这个单三角形,当绘制命令被执行时,我们的状态是这样的:

在这里插入图片描述

到目前为止,我们在着色器中使用的所有数据要么是硬编码的(顶点着色器中的三角形顶点位置,以及片段着色器中的颜色)。现在我们可以将值传递给我们的着色器,我们可以使用不同的数据多次调用draw。

通过更新单个缓冲区,我们可以在不同的地方使用不同的偏移量、比例和颜色进行绘制。重要的是要记住,我们的命令被放在命令缓冲区中,直到我们提交它们,它们才真正执行。所以,我们不能这样做

 // BAD!
    for (let x = -1; x < 1; x += 0.1) {
      uniformValues.set([x, x], kOffsetOffset);
      device.queue.writeBuffer(uniformBuffer, 0, uniformValues);
      pass.draw(3);
    }
    pass.end();
 
    // Finish encoding and submit the commands
    const commandBuffer = encoder.finish();
    device.queue.submit([commandBuffer]);

因为,正如你在上面看到的,device.queue.xxx函数发生在一个“队列/queue”上,而不是pass。XXX函数只是在命令缓冲区中编码一个命令。

当我们使用命令缓冲区调用submit时,缓冲区中唯一的东西就是我们最后写入的值。我们可以把它改成这个

    // BAD! Slow!
    for (let x = -1; x < 1; x += 0.1) {
      uniformValues.set([x, 0], kOffsetOffset);
      device.queue.writeBuffer(uniformBuffer, 0, uniformValues);
 
      const encoder = device.createCommandEncoder();
      const pass = encoder.beginRenderPass(renderPassDescriptor);
      pass.setPipeline(pipeline);
      pass.setBindGroup(0, bindGroup);
      pass.draw(3);
      pass.end();
 
      // Finish encoding and submit the commands
      const commandBuffer = encoder.finish();
      device.queue.submit([commandBuffer]);
    }

上面的代码更新一个缓冲区,创建一个命令缓冲区,添加命令来绘制一个东西,然后完成命令缓冲区并提交它。这是有效的,但由于多种原因很慢。最大的好处是在单个命令缓冲区中完成更多工作是最佳实践。

因此,相反,我们可以为每个我们想要绘制的东西创建一个统一的缓冲区。而且,由于缓冲区是通过绑定组间接使用的,所以我们还需要为每个要绘制的东西创建一个绑定组。然后我们可以将所有我们想要绘制的东西放入单个命令缓冲区。

让我们来建立一个随机函数:

// A random number between [min and max)
// With 1 argument it will be [0 to min)
// With no arguments it will be [0 to 1)
const rand = (min, max) => {
  if (min === undefined) {
    min = 0;
    max = 1;
  } else if (max === undefined) {
    max = min;
    min = 0;
  }
  return min + Math.random() * (max - min);
};
 

现在我们用一些颜色和偏移量来设置缓冲区我们可以画一些单独的东西。

  // offsets to the various uniform values in float32 indices
  const kColorOffset = 0;
  const kScaleOffset = 4;
  const kOffsetOffset = 6;
 
  const kNumObjects = 100;
  const objectInfos = [];
 
  for (let i = 0; i < kNumObjects; ++i) {
    const uniformBuffer = device.createBuffer({
      label: `uniforms for obj: ${i}`,
      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);
    uniformValues.set([rand(), rand(), rand(), 1], kColorOffset);        // set the color
    uniformValues.set([rand(-0.9, 0.9), rand(-0.9, 0.9)], kOffsetOffset);      // set the offset
 
    const bindGroup = device.createBindGroup({
      label: `bind group for obj: ${i}`,
      layout: pipeline.getBindGroupLayout(0),
      entries: [
        { binding: 0, resource: { buffer: uniformBuffer }},
      ],
    });
 
    objectInfos.push({
      scale: rand(0.2, 0.5),
      uniformBuffer,
      uniformValues,
      bindGroup,
    });
  }

我们还没有在buffer中设置值,因为我们希望它考虑到画布的长宽比/比例,并且在渲染时间之前我们不会知道画布的长宽比/比例。

在渲染时,我们将用正确的长宽比/比例更新所有缓冲区的调整比例。

  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 encoder = device.createCommandEncoder();
    const pass = encoder.beginRenderPass(renderPassDescriptor);
    pass.setPipeline(pipeline);
 
    // Set the uniform values in our JavaScript side Float32Array
    const aspect = canvas.width / canvas.height;
 
    for (const {scale, bindGroup, uniformBuffer, uniformValues} of objectInfos) {
      uniformValues.set([scale / aspect, scale], kScaleOffset); // set the scale
      device.queue.writeBuffer(uniformBuffer, 0, uniformValues);
       pass.setBindGroup(0, bindGroup);
       pass.draw(3);  // call our vertex shader 3 times
    }
    pass.end();
 
    const commandBuffer = encoder.finish();
    device.queue.submit([commandBuffer]);
  }

再次记住,编码器 encoder 和传递对象 pass 只是将命令编码到命令缓冲区。因此,当render函数退出时,我们实际上已经按此顺序发出了这些命令。

device.queue.writeBuffer(...) // update uniform buffer 0 with data for object 0
device.queue.writeBuffer(...) // update uniform buffer 1 with data for object 1
device.queue.writeBuffer(...) // update uniform buffer 2 with data for object 2
device.queue.writeBuffer(...) // update uniform buffer 3 with data for object 3
...
// execute commands that draw 100 things, each with their own uniform buffer.
device.queue.submit([commandBuffer]);

以下为完整代码及运行效果:

HTML:

<!--
 * @Description: 
 * @Author: tianyw
 * @Date: 2022-11-11 12:50:23
 * @LastEditTime: 2023-09-18 12:28:51
 * @LastEditors: tianyw
-->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>001hello-triangle</title>
    <style>
        html,
        body {
            margin: 0;
            width: 100%;
            height: 100%;
            background: #000;
            color: #fff;
            display: flex;
            text-align: center;
            flex-direction: column;
            justify-content: center;
        }

        div,
        canvas {
            height: 100%;
            width: 100%;
        }
    </style>
</head>

<body>
    <div id="005uniform-srandom-triangle">
        <canvas id="gpucanvas"></canvas>
    </div>
    <script type="module" src="./005uniform-srandom-triangle.ts"></script>

</body>

</html>

TS:

/*
 * @Description:
 * @Author: tianyw
 * @Date: 2023-04-08 20:03:35
 * @LastEditTime: 2023-09-18 20:51:33
 * @LastEditors: tianyw
 */
export type SampleInit = (params: {
  canvas: HTMLCanvasElement;
}) => void | Promise<void>;

import shaderWGSL from "./shaders/shader.wgsl?raw";

const rand = (
  min: undefined | number = undefined,
  max: undefined | number = undefined
) => {
  if (min === undefined) {
    min = 0;
    max = 1;
  } else if (max === undefined) {
    max = min;
    min = 0;
  }
  return min + Math.random() * (max - min);
};

const init: SampleInit = async ({ canvas }) => {
  const adapter = await navigator.gpu?.requestAdapter();
  if (!adapter) return;
  const device = await adapter?.requestDevice();
  if (!device) {
    console.error("need a browser that supports WebGPU");
    return;
  }
  const context = canvas.getContext("webgpu");
  if (!context) return;
  const devicePixelRatio = window.devicePixelRatio || 1;
  canvas.width = canvas.clientWidth * devicePixelRatio;
  canvas.height = canvas.clientHeight * devicePixelRatio;
  const presentationFormat = navigator.gpu.getPreferredCanvasFormat();

  context.configure({
    device,
    format: presentationFormat,
    alphaMode: "premultiplied"
  });

  const shaderModule = device.createShaderModule({
    label: "our hardcoded rgb triangle shaders",
    code: shaderWGSL
  });
  const renderPipeline = device.createRenderPipeline({
    label: "hardcoded rgb triangle pipeline",
    layout: "auto",
    vertex: {
      module: shaderModule,
      entryPoint: "vs"
    },
    fragment: {
      module: shaderModule,
      entryPoint: "fs",
      targets: [
        {
          format: presentationFormat
        }
      ]
    },
    primitive: {
      // topology: "line-list"
      // topology: "line-strip"
      //  topology: "point-list"
      topology: "triangle-list"
      // topology: "triangle-strip"
    }
  });

  const uniformBufferSize =
    4 * 4 + // color is 4 32bit floats (4bytes each)
    2 * 4 + // scale is 2 32bit floats (4bytes each)
    2 * 4; // offset is 2 32bit floats (4bytes each)

  const kColorOffset = 0;
  const kScaleOffset = 4;
  const kOffsetOffset = 6;

  const kNumObjects = 100;
  const objectInfos: {
    scale: number;
    uniformBuffer: GPUBuffer;
    uniformValues: Float32Array;
    bindGroup: GPUBindGroup;
  }[] = [];
  for (let i = 0; i < kNumObjects; ++i) {
    const uniformBuffer = device.createBuffer({
      label: `uniforms for obj: ${i}`,
      size: uniformBufferSize,
      usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
    });

    const uniformValues = new Float32Array(uniformBufferSize / 4);

    uniformValues.set([rand(), rand(), rand(), 1], kColorOffset); // set the color
    uniformValues.set([rand(-0.9, 0.9), rand(-0.9, 0.9)], kOffsetOffset); // set the offset
    const bindGroup = device.createBindGroup({
      label: `bind group for obj: ${i}`,
      layout: renderPipeline.getBindGroupLayout(0),
      entries: [{ binding: 0, resource: { buffer: uniformBuffer } }]
    });
    objectInfos.push({
      scale: rand(0.2, 0.5),
      uniformBuffer,
      uniformValues,
      bindGroup
    });
  }
  function frame() {
    const aspect = canvas.width / canvas.height;

    const renderCommandEncoder = device.createCommandEncoder({
      label: "render vert frag"
    });
    if (!context) return;

    const textureView = context.getCurrentTexture().createView();
    const renderPassDescriptor: GPURenderPassDescriptor = {
      label: "our basic canvas renderPass",
      colorAttachments: [
        {
          view: textureView,
          clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
          loadOp: "clear",
          storeOp: "store"
        }
      ]
    };
    const renderPass =
      renderCommandEncoder.beginRenderPass(renderPassDescriptor);
    renderPass.setPipeline(renderPipeline);
    for (const {
      scale,
      bindGroup,
      uniformBuffer,
      uniformValues
    } of objectInfos) {
      uniformValues.set([scale / aspect, scale], kScaleOffset); // set the scale
      device.queue.writeBuffer(uniformBuffer, 0, uniformValues);
      renderPass.setBindGroup(0, bindGroup);
      renderPass.draw(3, 1, 0, 0);
    }

    renderPass.end();
    const renderBuffer = renderCommandEncoder.finish();
    device.queue.submit([renderBuffer]);

    requestAnimationFrame(frame);
  }

  requestAnimationFrame(frame);
};

const canvas = document.getElementById("gpucanvas") as HTMLCanvasElement;
init({ canvas: canvas });

Shaders:

shader:

struct OurStruct {
    color: vec4f,
    scale: vec2f,
    offset: vec2f
};

@group(0) @binding(0) var<uniform> ourStruct: OurStruct;

@vertex 
fn vs(@builtin(vertex_index) vertexIndex: u32) -> @builtin(position) vec4f {
    let pos = array<vec2f, 3>(
        vec2f(0.0, 0.5), // top center
        vec2f(-0.5, -0.5), // bottom left
        vec2f(0.5, -0.5)  // bottom right
    );
   return vec4f(pos[vertexIndex] * ourStruct.scale + ourStruct.offset,0.0,1.0);
}

@fragment
fn fs() -> @location(0) vec4f {
    return ourStruct.color;
}

在这里插入图片描述

在这里插入图片描述

既然我们在这里,还有一件事要讲。你可以自由地在你的着色器中引用多个统一缓冲区。在上面的例子中,每次绘制时我们都会更新比例,然后writeBuffer将该对象的uniformValues上传到相应的统一缓冲区。但是,只有比例在更新,颜色和偏移量没有更新,所以我们在浪费时间上传颜色和偏移量。

我们可以将制服分成需要设置一次的制服和每次绘制时更新的制服。

 const module = device.createShaderModule({
    code: `
      struct OurStruct {
        color: vec4f,
        offset: vec2f,
      };
 
      struct OtherStruct {
        scale: vec2f,
      };
 
      @group(0) @binding(0) var<uniform> ourStruct: OurStruct;
      @group(0) @binding(1) var<uniform> otherStruct: OtherStruct;
 
      @vertex fn vs(
        @builtin(vertex_index) vertexIndex : u32
      ) -> @builtin(position) vec4f {
        let pos = array(
          vec2f( 0.0,  0.5),  // top center
          vec2f(-0.5, -0.5),  // bottom left
          vec2f( 0.5, -0.5)   // bottom right
        );
 
        return vec4f(
          pos[vertexIndex] * otherStruct.scale + ourStruct.offset, 0.0, 1.0);
      }
 
      @fragment fn fs() -> @location(0) vec4f {
        return ourStruct.color;
      }
    `,
  });

当我们想要绘制的每样东西需要2个统一的缓冲区时:

  // create 2 buffers for the uniform values
  const staticUniformBufferSize =
    4 * 4 + // color is 4 32bit floats (4bytes each)
    2 * 4 + // offset is 2 32bit floats (4bytes each)
    2 * 4;  // padding
  const uniformBufferSize =
    2 * 4;  // scale is 2 32bit floats (4bytes each)
 
  // offsets to the various uniform values in float32 indices
  const kColorOffset = 0;
  const kOffsetOffset = 4;
 
  const kScaleOffset = 0;
 
  const kNumObjects = 100;
  const objectInfos = [];
 
  for (let i = 0; i < kNumObjects; ++i) {
    const staticUniformBuffer = device.createBuffer({
      label: `static uniforms for obj: ${i}`,
      size: staticUniformBufferSize,
      usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
    });
 
    // These are only set once so set them now
    {
      const uniformValues = new Float32Array(staticUniformBufferSize / 4);
      uniformValues.set([rand(), rand(), rand(), 1], kColorOffset);        // set the color
      uniformValues.set([rand(-0.9, 0.9), rand(-0.9, 0.9)], kOffsetOffset);      // set the offset
 
      // copy these values to the GPU
      device.queue.writeBuffer(staticUniformBuffer, 0, uniformValues);
    }
 
    // create a typedarray to hold the values for the uniforms in JavaScript
    const uniformValues = new Float32Array(uniformBufferSize / 4);
    const uniformBuffer = device.createBuffer({
      label: `changing uniforms for obj: ${i}`,
      size: uniformBufferSize,
      usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
    });
 
    const bindGroup = device.createBindGroup({
      label: `bind group for obj: ${i}`,
      layout: pipeline.getBindGroupLayout(0),
      entries: [
        { binding: 0, resource: { buffer: staticUniformBuffer }},
        { binding: 1, resource: { buffer: uniformBuffer }},
      ],
    });
 
    objectInfos.push({
      scale: rand(0.2, 0.5),
      uniformBuffer,
      uniformValues,
      bindGroup,
    });
  }

我们的渲染代码没有任何变化。每个对象的绑定组包含对每个对象的统一缓冲区的引用。和以前一样,我们正在更新比例尺。但是现在我们只有在调用device.queue.writeBuffer来更新保存比例值的统一缓冲区时才上传比例,而之前我们是上传每个对象的颜色+偏移量+比例。

以下为完整代码及运行结果:

HTML:

<!--
 * @Description: 
 * @Author: tianyw
 * @Date: 2022-11-11 12:50:23
 * @LastEditTime: 2023-09-18 12:28:51
 * @LastEditors: tianyw
-->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>001hello-triangle</title>
    <style>
        html,
        body {
            margin: 0;
            width: 100%;
            height: 100%;
            background: #000;
            color: #fff;
            display: flex;
            text-align: center;
            flex-direction: column;
            justify-content: center;
        }

        div,
        canvas {
            height: 100%;
            width: 100%;
        }
    </style>
</head>

<body>
    <div id="005uniform-srandom-triangle2">
        <canvas id="gpucanvas"></canvas>
    </div>
    <script type="module" src="./005uniform-srandom-triangle2.ts"></script>

</body>

</html>

TS:

/*
 * @Description:
 * @Author: tianyw
 * @Date: 2023-04-08 20:03:35
 * @LastEditTime: 2023-09-18 21:16:58
 * @LastEditors: tianyw
 */
export type SampleInit = (params: {
  canvas: HTMLCanvasElement;
}) => void | Promise<void>;

import shaderWGSL from "./shaders/shader.wgsl?raw";

const rand = (
  min: undefined | number = undefined,
  max: undefined | number = undefined
) => {
  if (min === undefined) {
    min = 0;
    max = 1;
  } else if (max === undefined) {
    max = min;
    min = 0;
  }
  return min + Math.random() * (max - min);
};

const init: SampleInit = async ({ canvas }) => {
  const adapter = await navigator.gpu?.requestAdapter();
  if (!adapter) return;
  const device = await adapter?.requestDevice();
  if (!device) {
    console.error("need a browser that supports WebGPU");
    return;
  }
  const context = canvas.getContext("webgpu");
  if (!context) return;
  const devicePixelRatio = window.devicePixelRatio || 1;
  canvas.width = canvas.clientWidth * devicePixelRatio;
  canvas.height = canvas.clientHeight * devicePixelRatio;
  const presentationFormat = navigator.gpu.getPreferredCanvasFormat();

  context.configure({
    device,
    format: presentationFormat,
    alphaMode: "premultiplied"
  });

  const shaderModule = device.createShaderModule({
    label: "our hardcoded rgb triangle shaders",
    code: shaderWGSL
  });
  const renderPipeline = device.createRenderPipeline({
    label: "hardcoded rgb triangle pipeline",
    layout: "auto",
    vertex: {
      module: shaderModule,
      entryPoint: "vs"
    },
    fragment: {
      module: shaderModule,
      entryPoint: "fs",
      targets: [
        {
          format: presentationFormat
        }
      ]
    },
    primitive: {
      // topology: "line-list"
      // topology: "line-strip"
      //  topology: "point-list"
      topology: "triangle-list"
      // topology: "triangle-strip"
    }
  });

  const staticUniformBufferSize =
    4 * 4 + // color is 4 32bit floats (4bytes each)
    2 * 4 + // scale is 2 32bit floats (4bytes each)
    2 * 4; // padding
  const uniformBUfferSzie = 2 * 4; // scale is 2 32 bit floats

  const kColorOffset = 0;
  const kOffsetOffset = 4;

  const kScaleOffset = 0;

  const kNumObjects = 100;
  const objectInfos: {
    scale: number;
    uniformBuffer: GPUBuffer;
    uniformValues: Float32Array;
    bindGroup: GPUBindGroup;
  }[] = [];
  for (let i = 0; i < kNumObjects; ++i) {
    const staticUniformBuffer = device.createBuffer({
      label: `staitc uniforms for obj: ${i}`,
      size: staticUniformBufferSize,
      usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
    });
    {
      const uniformValues = new Float32Array(staticUniformBufferSize / 4);

      uniformValues.set([rand(), rand(), rand(), 1], kColorOffset); // set the color
      uniformValues.set([rand(-0.9, 0.9), rand(-0.9, 0.9)], kOffsetOffset); // set the offset
      device.queue.writeBuffer(staticUniformBuffer, 0, uniformValues);
    }

    const uniformValues = new Float32Array(uniformBUfferSzie / 4);
    const uniformBuffer = device.createBuffer({
      label: `changing uniforms for obj: ${i}`,
      size: uniformBUfferSzie,
      usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
    });

    const bindGroup = device.createBindGroup({
      label: `bind group for obj: ${i}`,
      layout: renderPipeline.getBindGroupLayout(0),
      entries: [
        { binding: 0, resource: { buffer: staticUniformBuffer } },
        { binding: 1, resource: { buffer: uniformBuffer } }
      ]
    });
    objectInfos.push({
      scale: rand(0.2, 0.5),
      uniformBuffer,
      uniformValues,
      bindGroup
    });
  }
  function frame() {
    const aspect = canvas.width / canvas.height;

    const renderCommandEncoder = device.createCommandEncoder({
      label: "render vert frag"
    });
    if (!context) return;

    const textureView = context.getCurrentTexture().createView();
    const renderPassDescriptor: GPURenderPassDescriptor = {
      label: "our basic canvas renderPass",
      colorAttachments: [
        {
          view: textureView,
          clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
          loadOp: "clear",
          storeOp: "store"
        }
      ]
    };
    const renderPass =
      renderCommandEncoder.beginRenderPass(renderPassDescriptor);
    renderPass.setPipeline(renderPipeline);
    for (const {
      scale,
      bindGroup,
      uniformBuffer,
      uniformValues
    } of objectInfos) {
      uniformValues.set([scale / aspect, scale], kScaleOffset); // set the scale
      device.queue.writeBuffer(uniformBuffer, 0, uniformValues);
      renderPass.setBindGroup(0, bindGroup);
      renderPass.draw(3);
    }

    renderPass.end();
    const renderBuffer = renderCommandEncoder.finish();
    device.queue.submit([renderBuffer]);

    requestAnimationFrame(frame);
  }

  requestAnimationFrame(frame);
};

const canvas = document.getElementById("gpucanvas") as HTMLCanvasElement;
init({ canvas: canvas });

Shaders:

shader:

struct OurStruct {
    color: vec4f,
    offset: vec2f
};

struct OtherStruct {
    scale: vec2f
};

@group(0) @binding(0) var<uniform> ourStruct: OurStruct;
@group(0) @binding(1) var<uniform> otherStruct: OtherStruct;

@vertex 
fn vs(@builtin(vertex_index) vertexIndex: u32) -> @builtin(position) vec4f {
    let pos = array<vec2f, 3>(
        vec2f(0.0, 0.5), // top center
        vec2f(-0.5, -0.5), // bottom left
        vec2f(0.5, -0.5)  // bottom right
    );
   return vec4f(pos[vertexIndex] * otherStruct.scale + ourStruct.offset,0.0,1.0);
}

@fragment
fn fs() -> @location(0) vec4f {
    return ourStruct.color;
}

在这里插入图片描述

在这里插入图片描述

虽然在这个简单的示例中,划分为多个统一的缓冲区可能有些过分,但通常根据更改的内容和时间进行划分。示例可能包括用于共享矩阵的统一缓冲区。例如一个项目矩阵,一个视图矩阵,一个相机矩阵。因为通常我们想要绘制的所有东西都是相同的,所以我们可以只创建一个缓冲区,并让所有对象使用相同的统一缓冲区。

另外,我们的着色器可能会引用另一个统一的缓冲区,它只包含特定于这个对象的东西,比如它的世界/模型矩阵和它的正常矩阵。

另一个统一缓冲区可能包含材料设置。这些设置可能由多个对象共享。当我们涉及到绘制3D时,我们会做很多这方面的工作。

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

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

相关文章

Go 字符串操作实战

1. 引言 在现代编程中&#xff0c;字符串处理是不可或缺的一部分。无论是简单的用户界面&#xff0c;还是复杂的数据处理&#xff0c;字符串都扮演着关键的角色。Go语言&#xff0c;作为一个现代的、性能优越的编程语言&#xff0c;为字符串处理提供了一系列强大的工具和功能。…

通过后台系统添加一段div,在div中写一个<style></style>标签来修改div外面的元素的深层元素的样式

先看图 btn元素就是通过后台系统加上的元素,现在需要通过在btn里面写一个style标签来修改grid-nine里面的head元素的高度.开始想通过style来修改,但是不知道怎么去获取这个div外面的元素,想通过js方法去修改,写了script标签加了js代码,但不生效,后面问了才知道,这个项目是vue打…

深度学习笔记之优化算法(六)RMSprop算法的简单认识

深度学习笔记之优化算法——RMSProp算法的简单认识 引言回顾&#xff1a;AdaGrad算法AdaGrad算法与动量法的优化方式区别AdaGrad算法的缺陷 RMProp算法关于AdaGrad问题的优化方式RMSProp的算法过程描述 RMSProp示例代码 引言 上一节对 AdaGrad \text{AdaGrad} AdaGrad算法进行…

重载和重写的区别

方法重载&#xff1a; &#xff08;1&#xff09;在同一个类中 &#xff08;2&#xff09;方法名必须相同 &#xff08;3&#xff09;形参列表必须不同&#xff08;形参类型或个数或顺序&#xff0c;至少有一样不同&#xff0c;参数名无要求&#xff09; &#xff08;4&…

基于SpringBoot的校园社团信息管理系统

目录 前言 一、技术栈 二、系统功能介绍 学生管理 社长管理 社团信息管理 社团新闻管理 社团添加 社团活动 加入社团 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术…

Linux---进程(1)

操作系统 传统的计算机系统资源分为硬件资源和软件资源。硬件资源包括中央处理器&#xff0c;存储器&#xff0c;输入设备&#xff0c;输出设备等物理设备&#xff1b;软件资源是以文件形式保存在存储器上的成熟和数据等信息。 操作系统就是计算机系统资源的管理者。 如果你的计…

GEO生信数据挖掘(七)差异基因分析

上节&#xff0c;我们使用结核病基因数据&#xff0c;做了一个数据预处理的实操案例。例子中结核类型&#xff0c;包括结核&#xff0c;潜隐进展&#xff0c;对照和潜隐&#xff0c;四个类别。本节延续上个数据&#xff0c;进行了差异分析。 差异分析 计算差异指标step12 加载…

销售小白如何写客户拜访记录?

销售小白如何写客户拜访记录&#xff1f;10年客户管理经验&#xff0c;接下来我说的&#xff0c;都是实实在在的经验&#xff0c;小白能用到其中的40%&#xff0c;你的客户成单率会大大提升&#xff01; 首先&#xff0c;客户拜访记录的哪些信息是重要的&#xff1f; 答案是&…

【ccf-csp题解】第7次csp认证-第二题-俄罗斯方块-简单碰撞检测算法

题目描述 思路讲解 本题的主要思路是实现一个draw函数&#xff0c;这个函数可以绘制每一个状态的画布。然后从第一个状态往后遍历&#xff0c;当绘制到某一个状态发生碰撞时&#xff0c;答案就是上一个状态的画布。 此处的状态x实际就是在原来的15*10画布上的第x行开始画我们…

深度优先搜索详解

目录 前言 一、工作原理 二、模板 函数模板&#xff1a; 准备工作 三、主要应用 &#xff08;一&#xff09;寻找全部路径 题目描述 输入格式 输出格式 样例输入 样例输出 参考代码 思路 原题链接&#xff1a;1213: 走迷宫 &#xff08;二&#xff09;统计连通块…

哪款洗地机更好用?2023年最好用的洗地机

随着科技的发展和生活质量的提高&#xff0c;人们对洗地机的关注也越来越频繁&#xff0c;但是市场上洗地机品牌众多&#xff0c;消费者在选择时常常会感到困惑。那么&#xff0c;究竟哪个品牌的洗地机更好用呢? 我们在购买洗地机的时候&#xff0c;都要关注洗地机的哪些方面…

如何下载IEEE Journal/Conference/Magazine的LaTeX/Word模板

当你准备撰写一篇学术论文或会议论文时&#xff0c;使用IEEE&#xff08;电气和电子工程师协会&#xff09;的LaTeX或Word模板是一种非常有效的方式&#xff0c;它可以帮助你确保你的文稿符合IEEE出版的要求。无论你是一名研究生生或一名资深学者&#xff0c;本教程将向你介绍如…

深度学习问答题(更新中)

1. 各个激活函数的优缺点&#xff1f; 2. 为什么ReLU常用于神经网络的激活函数&#xff1f; 在前向传播和反向传播过程中&#xff0c;ReLU相比于Sigmoid等激活函数计算量小&#xff1b;避免梯度消失问题。对于深层网络&#xff0c;Sigmoid函数反向传播时&#xff0c;很容易就…

读懂MCU产品选型表

读懂MCU产品选型表 产品状态 MP&#xff1a;Mass Production&#xff08;大规模生产&#xff09; - 这表示产品已经进入了大规模生产阶段&#xff0c;可以大量生产并提供给市场。UD&#xff1a;Under Development&#xff08;开发中&#xff09; - 这表示产品目前正在开发阶段…

嵌入式音频软件开发之协议时序图分析方法

是否需要申请加入数字音频系统研究开发交流答疑群(课题组)&#xff1f;加我微信hezkz17, 本群提供音频技术答疑服务 1 TCP/IP 三次握手协议-时序图 序列号是随机数&#xff0c;但是对方回应则是序列号1 &#xff0c;同步1使能&#xff0c;ACK1使能该功能 2 iAP2 授权交互时序…

如何优化敏捷需求管理流程,敏捷需求如何管理。

优化敏捷需求管理流程的方法可以参照如下&#xff1a; 明确需求 。在项目开始时&#xff0c;要确保清楚地理解客户需求&#xff0c;明确项目的目标和范围&#xff0c;以便能够在敏捷迭代中快速响应需求变更。 使用用户故事 。采用用户故事的方式&#xff0c;让客户和开发团队…

云开发中关于Container与虚拟机之间的比较

虚拟机的优势&#xff1a; 1、较小的硬盘开销&#xff1a;一个物理机上可以运行多个虚拟机。 2、容易移植到其他机器上。 3、整合闲置工作负载。 提高设备利用率。 4、通过释放不用的资源来减少能源消耗。 5、多种操作系统让其具有灵活性和可扩展性。 虚拟机的缺点&#…

数据库中了mkp勒索病毒怎么恢复数据,勒索病毒解密,数据恢复

mkp勒索病毒是一种危害性极大&#xff0c;且比较常见的电脑病毒类型。根据数据统计&#xff0c;这种类型的勒索病毒每月攻击的用户数量高达数百起。其中绝大多数用户需要恢复的数据类型为数据库。包括但不限于SQL server、MySQL和oracle等数据库。所以云天数据恢复中心将针对数…

行情分析——加密货币市场大盘走势(9.10)

大饼在昨日下跌回踩了EMA21均线&#xff0c;但是遇到强阻力回调&#xff0c;形成了金针探底的形态。MACD日线级别来看&#xff0c;延续了绿色空心柱&#xff0c;并且还要继续延续的趋势。但是目前依然没有跌破上涨趋势区域。现在建议保持观望状态&#xff0c;现在多空盈亏比不够…

python 在window对exe、注册表、bat、系统服务操作等实例讲解

目录 前言&#xff1a; 1、python准备工作 具体操作实例 实例1&#xff1a;调用exe文件 实例2&#xff1a;调用bat批处理文件 实例3&#xff1a;调用mis安装文件 实例4&#xff1a; 操作注册表 实例5&#xff1a; window系统服务的操作 完整代码 前言&#xf…