跳到主要内容
3D Playground
GPU 与 WebGPU 计算第 3 / 11 节

缓冲区与绑定组

进阶WebGPU
此示例需要 WebGPU(Chrome / Edge 113+、Safari 26+)。

缓冲区:GPU 上的一块内存

device.createBuffer({ size, usage }) 在 GPU 上分配内存。usage 标志声明它的用途:VERTEX、INDEX、UNIFORM、STORAGE,以及能否作为拷贝目标 COPY_DST。用错用途会直接报校验错误,这让驱动能提前做好优化。数据用 queue.writeBuffer 上传,它是排队执行的,开销很小。

顶点缓冲与交错布局

这朵“花”的每个顶点占 24 字节:位置(2 个 float)、颜色(3 个)、环号(1 个)交错存放。管线里的 buffers 描述告诉 GPU 怎么切分:

js
arrayStride: 24,
attributes: [
  { shaderLocation: 0, offset: 0,  format: "float32x2" },
  { shaderLocation: 1, offset: 8,  format: "float32x3" },
  { shaderLocation: 2, offset: 20, format: "float32" },
]

shaderLocation 对应 WGSL 里的 @location(n)。索引缓冲让共享的顶点只存一次,三角形用索引引用它们,最后用 drawIndexed 绘制。

Uniform 缓冲与对齐

uniform 是一次绘制里所有顶点和片元都相同的数据,比如时间、宽高比。WGSL 结构体有对齐规则:vec2f 按 8 字节对齐,vec3f 和 vec4f 按 16 字节对齐,结构体总大小向上取整到 16 的倍数。所以这里补了一个 _pad 字段:

wgsl
struct Uniforms { time : f32, aspect : f32, spin : f32, pulse : f32,
                  hue : f32, petals : f32, _pad : vec2f };
@group(0) @binding(0) var<uniform> u : Uniforms;

每帧在 CPU 上更新一个 Float32Array,再 writeBuffer 一次即可。

绑定组布局与绑定组

  • 绑定组布局描述资源的“形状”:哪个 binding、什么类型、哪些着色器阶段可见。
  • 绑定组把具体的缓冲区插进这个形状。
  • 绘制前 pass.setBindGroup(0, bindGroup),对应 WGSL 里的 @group(0)。

把很少变化的资源和每帧变化的资源放在不同的 group 里,切换起来更便宜。

MSAA

管线设置 multisample: { count: 4 } 后,需要一张 4 倍采样的颜色纹理作为渲染目标,再通过 resolveTarget 解析到画布上。画布尺寸变化时这张纹理也要重建。

和所有 WebGPU 示例一样,渲染循环用 onSubmittedWorkDone 把在途帧数限制在 2 帧以内。

核心 API

device.createBufferGPUBufferUsagedevice.queue.writeBufferdevice.createBindGroupLayoutdevice.createBindGroupdevice.createPipelineLayoutpass.setVertexBufferpass.setIndexBufferpass.drawIndexedmultisample

动手试试

  1. 1把 buildFlower(96, 12) 的分段数降到 8,看看低多边形的样子。
  2. 2在 Uniforms 里再加一个 f32 字段(比如亮度),注意要重新计算缓冲区大小和对齐。
  3. 3把 multisample 的 count 改成 1,直接渲染到画布纹理(不再用 msaa 纹理和 resolveTarget),对比边缘锯齿。
运行中