WebGPU 入门(三)

WebGPU 入门知识,包括其概念、优势及与 WebGL 的关系。重点讲解了如何用 WebGPU 绘制三角形,涵盖创建相关对象、配置 canvas、设置背景色、创建缓冲区、设置读取方式、编写着色器、创建渲染流水线等步骤。

WebGPU 入门(三)
推荐使用NSDT 3DConvert进行3D模型格式转换,支持glb、obj、stp、fbx、ifc等多种3D模型格式之间进行互相转换,在转换过程中,能够很好的保留模型原有的颜色、材质等信息。

着色器

声明 WebGPU 的着色器,创建着色器模块(GPUShaderModule)。

WebGPU 使用特有的 WGSL 着色器语言,顶点着色器和片元着色器可以写在一起的。

// 创建着色器模块
const vertexShaderModule = device.createShaderModule({
  label: 'Vertex Shader',
  code: `
    @vertex
    fn vertexMain(@location(0) pos: vec2f) -> @builtin(position) vec4f {
      return vec4f(pos, 0, 1);
    }

    @fragment
    fn fragmentMain() -> @location(0) vec4f {
      return vec4f(1, 0, 0, 1);
    }
  `,
});

顶点着色器函数:

@vertex 
fn vertexMain(@location(0) pos: vec2f) -> @builtin(position) vec4f {
  return vec4f(pos, 0, 1);
}
  • @vertex:装饰器,表示顶点着色器主函数。
  • @location(0):缓冲区读取方式设置的 shaderLocation,这里拿到了两个浮点数。
  • vec2f:两个浮点数的向量,同理,vec4f 为 4 浮点数的向量。
  • -> @builtin(position):表示函数的返回值会被设置为内置的顶点位置变量。WebGPU 是利用函数的返回值配合修饰符的方式进行内部变量赋值的。

片元着色器:

@fragment
fn fragmentMain() -> @location(0) vec4f {
  return vec4f(1, 0, 0, 1); // 红色
}
  • @fragment 表示片元着色器主函数。
  • -> @location(0) 表示将返回的颜色输出到位置为 0 的颜色附件上,简单来说,就是给对应点设置为对应颜色。

渲染流水线

创建渲染流水线,也就是把之前的设置组合起来,用哪个着色器的哪个函数作为入口、如何读取缓冲区等。

const pipeline = device.createRenderPipeline({
  label: 'pipeline', // 标识,定位错误用
  layout: 'auto', // 自动流水线布局
  vertex: {
    module: vertexShaderModule, // 着色器模块
    entryPoint: 'vertexMain', // 入口函数为 vertexMain
    buffers: [vertexBufferLayout], // 读取缓冲区的方式
  },
  fragment: {
    module: vertexShaderModule,
    entryPoint: 'fragmentMain',
    targets: [
      {
        format: canvasFormat, // 输出到 canvas 画布上
      },
    ],
  },
});

将渲染流水线设置到 pass 上。

pass.setPipeline(pipeline);

将缓冲区绑定到管线的第一个顶点缓冲槽(slot)。

pass.setVertexBuffer(0, vertexBuffer);

绘制图元,这里要设置绘制几组,一组是两个点,所以要处以 2。

pass.draw(vertices.length / 2);

然后就是前面讲过的收尾代码。

pass.end(); // 完成指令队列的记录
const commandBuffer = encoder.finish(); // 结束编码
device.queue.submit([commandBuffer]); // 提交给 GPU 命令队列

至此,一个三角形就画好了。

绘制结果

完整代码

完整代码:

const render = async () => {
  const adapter = await navigator.gpu.requestAdapter();
  const device = await adapter.requestDevice();
  const canvas = document.querySelector('canvas');
  const ctx = canvas.getContext('webgpu');
  const canvasFormat = navigator.gpu.getPreferredCanvasFormat();
  ctx.configure({
    device,
    format: canvasFormat,
  });

  const encoder = device.createCommandEncoder();
  const pass = encoder.beginRenderPass({
    colorAttachments: [
      {
        view: ctx.getCurrentTexture().createView(),
        loadOp: 'clear',
        clearValue: { r: 0.6, g: 0.8, b: 0.9, a: 1 },
        storeOp: 'store',
      },
    ],
  });

  // 创建顶点数据
  // prettier-ignore
  const vertices = new Float32Array([
    -0.5, -0.5,
    0.5, -0.5,
    0.5, 0.5,
  ]);

  // 缓冲区
  const vertexBuffer = device.createBuffer({
    // 标识,字符串随意写,报错时会通过它定位,
    label: 'Triangle Vertices',
    // 缓冲区大小,这里是 24 字节。6 个 4 字节(即 32 位)的浮点数
    size: vertices.byteLength,
    // 标识缓冲区用途(1)用于顶点着色器(2)可以从 CPU 复制数据到缓冲区
    // eslint-disable-next-line no-undef
    usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
  });
  // 将顶点数据复制到缓冲区
  device.queue.writeBuffer(vertexBuffer, /* bufferOffset */ 0, vertices);

  // GPU 应该如何读取缓冲区中的数据
  const vertexBufferLayout = {
    arrayStride: 2 * 4, // 每一组的字节数,每组有两个数字(2 * 4字节)
    attributes: [
      {
        format: 'float32x2', // 每个数字是32位浮点数
        offset: 0, // 从每组的第一个数字开始
        shaderLocation: 0, // 顶点着色器中的位置
      },
    ],
  };

  // 着色器用的是 WGSL 着色器语言
  const vertexShaderModule = device.createShaderModule({
    label: 'Vertex Shader',
    code: `
      @vertex
      fn vertexMain(@location(0) pos: vec2f) -> @builtin(position) vec4f {
        return vec4f(pos, 0, 1);
      }

      @fragment
      fn fragmentMain() -> @location(0) vec4f {
        return vec4f(1, 0, 0, 1);
      }
    `,
  });

  // 渲染流水线
  const pipeline = device.createRenderPipeline({
    label: 'pipeline',
    layout: 'auto',
    vertex: {
      module: vertexShaderModule,
      entryPoint: 'vertexMain',
      buffers: [vertexBufferLayout],
    },
    fragment: {
      module: vertexShaderModule,
      entryPoint: 'fragmentMain',
      targets: [
        {
          format: canvasFormat,
        },
      ],
    },
  });

  pass.setPipeline(pipeline);
  pass.setVertexBuffer(0, vertexBuffer);
  pass.draw(vertices.length / 2);

  pass.end();
  const commandBuffer = encoder.finish();
  device.queue.submit([commandBuffer]);
};

render();

WebGPU 入门 - 索引

WebGPU 入门(一)

WebGPU 入门(二)

WebGPU 入门(三)

NSDT场景编辑器 | NSDT 数字孪生 | GLTF在线编辑器 | 3D模型在线转换 | UnrealSynth虚幻合成数据生成器 | 3D模型自动纹理化工具
2023 power by nsdt©鄂ICP备2023000829号