此示例需要 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把 buildFlower(96, 12) 的分段数降到 8,看看低多边形的样子。
- 2在 Uniforms 里再加一个 f32 字段(比如亮度),注意要重新计算缓冲区大小和对齐。
- 3把 multisample 的 count 改成 1,直接渲染到画布纹理(不再用 msaa 纹理和 resolveTarget),对比边缘锯齿。
还没有输出。console.log 的内容会显示在这里。
运行中