WebGPU 是下一代浏览器图形接口,它让网页脚本能够以更接近原生的方式调用显卡资源。与 WebGL 不同,WebGPU 不再隐藏硬件细节,而是提供显式的设备管理、管线状态和命令编码机制,从而把渲染与计算任务真正交给 GPU 并行处理。

一、WebGPU 与 WebGL 的核心差异
WebGL 基于 OpenGL ES 的即时模式,每次绘制都要由 CPU 设置大量全局状态,驱动层再做校验和翻译,这种方式在简单场景里够用,但遇到频繁换材质、多批次小物体时,CPU 提交开销会迅速堆积。WebGPU 采用预编译管线对象,状态在创建时就固定下来,绘制时只需绑定资源和发起绘制调用,省掉了运行期的大量判断。
另一个关键区别是计算能力。WebGL 基本只能做图形光栅化,而 WebGPU 自带 compute pass,可以用 WGSL 写通用并行程序,直接读写缓冲做物理模拟、图像处理。这对复杂 3D 渲染很有价值,比如先用计算通道算粒子位置,再把结果缓冲交给渲染通道画出来,全程数据不回 CPU,延迟极低。
二、初始化设备与画布上下文
使用 WebGPU 的第一步是向浏览器申请一个 GPU 适配器,再基于适配器创建逻辑设备。适配器代表某块物理显卡或软件实现,设备则是你发出命令的会话句柄。下面代码展示了最基础的异步获取流程:
async function initWebGPU(canvas) {
if (!navigator.gpu) {
throw new Error('当前浏览器不支持 WebGPU');
}
const adapter = await navigator.gpu.requestAdapter({
powerPreference: 'high-performance'
});
if (!adapter) {
throw new Error('找不到可用的 GPU 适配器');
}
const device = await adapter.requestDevice();
const context = canvas.getContext('webgpu');
const format = navigator.gpu.getPreferredCanvasFormat();
context.configure({
device: device,
format: format,
alphaMode: 'opaque'
});
return { device, context, format };
}
上面的代码里,powerPreference 建议系统挑选独立显卡;getPreferredCanvasFormat 会返回如 bgra8unorm 这类当前平台最高效的存储格式。配置上下文后,canvas 就不再走 2D 或 WebGL 后端,而是直接接收 GPU 纹理作为交换链。
注意设备创建可能失败,例如设备丢失或驱动限制,所以生产环境要监听 device.lost 并做降级。拿到 device 后,所有缓冲、纹理、管线都要由它创建,不同设备的对象不能混用。
三、用 WGSL 编写渲染着色器
WGSL 是 WebGPU 的专用着色语言,语法类似 Rust,类型静态且明确。一个最小顶点着色器要把模型坐标变换到裁剪空间,片元着色器输出颜色。下面示例画一个随时间的旋转三角形:
struct Uniforms {
mvpMatrix : mat4x4<f32>,
};
@group(0) @binding(0) var<uniform> u : Uniforms;
struct VSOut {
@builtin(position) pos : vec4<f32>,
@location(0) color : vec3<f32>,
};
@vertex
fn vs_main(@location(0) position : vec3<f32>,
@location(1) color : vec3<f32>) -> VSOut {
var out : VSOut;
out.pos = u.mvpMatrix * vec4<f32>(position, 1.0);
out.color = color;
return out;
}
@fragment
fn fs_main(in : VSOut) -> @location(0) vec4<f32> {
return vec4<f32>(in.color, 1.0);
}
WGSL 要求资源通过 @group 和 @binding 声明绑定槽位,这与 WebGL 的全局 uniform 不同,更方便做资源分组切换。矩阵等数据要由 JS 端用 Float32Array 写入 uniform 缓冲,再在绘制前绑定到对应管线。
编写时常见错误是忘了把顶点布局的步长和格式与 JS 端创建顶点缓冲时对齐,比如位置用 vec3 占 12 字节、颜色占 12 字节,那么 arrayStride 就要设 24,不然取到的数据会错位导致画面扭曲。
四、构建渲染管线并提交命令
渲染管线把着色器、混合状态、深度测试等打包成一个不可变对象。创建好后,每帧只需写命令缓冲,交给队列执行。下面代码演示如何描述管线以及一帧的绘制:
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: {
module: shaderModule,
entryPoint: 'vs_main',
buffers: [{
arrayStride: 24,
attributes: [
{ shaderLocation: 0, offset: 0, format: 'float32x3' },
{ shaderLocation: 1, offset: 12, format: 'float32x3' }
]
}]
},
fragment: {
module: shaderModule,
entryPoint: 'fs_main',
targets: [{ format: format }]
},
primitive: { topology: 'triangle-list' }
});
function frame() {
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
clearValue: { r: 0, g: 0, b: 0, a: 1 },
loadOp: 'clear',
storeOp: 'store'
}]
});
pass.setPipeline(pipeline);
pass.setVertexBuffer(0, vertexBuffer);
pass.setBindGroup(0, uniformBindGroup);
pass.draw(3);
pass.end();
device.queue.submit([encoder.finish()]);
requestAnimationFrame(frame);
}
这里 layout 设成 auto 让 WebGPU 根据着色器推导绑定布局,减少手工出错。命令编码器在 CPU 端记录指令,finish 后生成 GPU 可执行的命令缓冲,再由队列异步提交,这种设计让 CPU 与 GPU 可以并行工作。
复杂场景通常会在一个 pass 里连续 draw 多个不同缓冲,或者拆成多个 pass 做延迟渲染。由于管线切换成本远低于 WebGL,你可以更自由地按材质分组,显著减少 overdraw 和状态同步。
五、用计算通道加速复杂效果
当场景需要大量动态元素,比如十万只昆虫或流体网格,用 CPU 每帧更新位置不现实。此时可建一个 compute shader,让 GPU 自己算。下面给出一个简化粒子更新逻辑:
struct Particle {
pos : vec2<f32>,
vel : vec2<f32>,
};
@group(0) @binding(0) var<storage, read_write> particles : array<Particle>;
@compute @workgroup_size(64)
fn cs_main(@builtin(global_invocation_id) id : vec3<u32>) {
let i = id.x;
if (i >= arrayLength(&particles)) { return; }
particles[i].pos = particles[i].pos + particles[i].vel * 0.016;
}
存储缓冲以 read_write 形式绑定,使着色器能直接改显存里的数组。workgroup_size 决定每组线程数,调度由硬件自动完成。计算完后,同一块缓冲可作为顶点缓冲被渲染通道读取,实现零拷贝流水线。
实践中要注意缓冲大小和对齐,storage 缓冲的元素布局要符合 WGSL 的步长规则,否则越界访问会让整个命令提交失败。另外计算量过大时可拆帧执行,避免一帧内挤占渲染时间导致掉帧。
六、性能与兼容建议
WebGPU 目前已在主流桌面浏览器落地,但移动端覆盖还在推进。上线前可用 navigator.gpu 探测,失败则回退到 WebGL2 并减配特效。性能方面,尽量合并小缓冲、复用命令编码器,并监控 device.queue 的等待情况。
对复杂 3D 应用,推荐把静态几何合批、动态部分走计算通道,同时用时间戳查询(timestamp-query 特性)定位哪段最耗时。这样你就能在浏览器里真正把显卡算力吃满,而不只是跑个演示级画面。
WebGPU3D_renderingGPU_compute修改时间:2026-08-01 05:54:37