姆姆极客分享MU·GEEK·SHARE
// WebGPU Render Pipeline Vertex @vertex Primitive assemble Rasterizer scan Fragment @fragment Output texture BindGroup (uniform) Buffer (vertex) Texture + Sampler explicit · typed · pre-compiled PSO · low CPU overhead
游戏开发·

WebGPU 渲染管线初探:从 WebGL 到下一代浏览器图形 API

WebGPU 不只是"更快的 WebGL",而是一套重新设计的现代图形 API。本文从管线、绑定、着色器三个维度,演示 WebGPU 的核心心智模型。


WebGPU 在 2023 年于 Chrome 113 正式上线,到 2026 年初,所有主流浏览器(Chrome/Edge/Safari/Firefox)都已稳定支持。它不是 WebGL 的“小升级”,而是一次从 API 设计到执行模型的彻底重构。本文从实战角度梳理 WebGPU 的核心概念,帮 WebGL 老手快速迁移。

一、为什么需要 WebGPU

WebGL(基于 OpenGL ES 2.0/3.0)有几个根本性问题:

  1. 状态机模型:所有状态全局可变,调试困难
  2. GLSL 字符串拼接:着色器是字符串,没有类型检查
  3. CPU ↔ GPU 通信低效:每次 draw call 都要验证状态
  4. 无法用现代 GPU 特性:compute shader、indirect draw、storage buffer 都不支持

WebGPU 的设计目标是“显式、类型安全、低开销”,更接近 Vulkan/Metal/D3D12 的现代 API 风格。

二、初始化:相比 WebGL 的简化

// WebGL: 30+ 行才能画一个三角形
const gl = canvas.getContext('webgl2');
// ... shaders, buffers, attributes, uniforms ...

// WebGPU: 显式但清晰
async function init() {
  const adapter = await navigator.gpu.requestAdapter();
  const device = await adapter.requestDevice();
  const context = canvas.getContext('webgpu');
  const format = navigator.gpu.getPreferredCanvasFormat();

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

  return { device, context, format };
}

adapter 代表物理 GPU,device 是逻辑设备(带自己的命令队列)。这种分层让“多 device 共享一个 adapter”成为可能。

三、管线:显式描述,预先编译

WebGPU 要求预先描述整个渲染管线,而不是运行时设置状态:

const pipeline = device.createRenderPipeline({
  layout: 'auto',
  vertex: {
    module: shaderModule,
    entryPoint: 'vs_main',
    buffers: [{
      arrayStride: 12,            // 每个顶点 12 字节
      attributes: [{
        shaderLocation: 0,
        offset: 0,
        format: 'float32x3',      // position: vec3<f32>
      }],
    }],
  },
  fragment: {
    module: shaderModule,
    entryPoint: 'fs_main',
    targets: [{ format }],
  },
  primitive: { topology: 'triangle-list' },
});

管线一旦创建就是不可变的——这给驱动留下了大量优化空间(PSO 缓存、shader 编译、状态验证一次性完成)。WebGL 的“运行时切换状态”在驱动层反复触发验证,是性能瓶颈的来源。

四、WGSL:类型安全的着色器语言

WebGPU 引入了 WGSL(WebGPU Shading Language),不再用 GLSL:

struct VertexInput {
  @location(0) position: vec3<f32>,
};

struct VertexOutput {
  @builtin(position) clip_position: vec4<f32>,
  @location(0) color: vec3<f32>,
};

@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
  var out: VertexOutput;
  out.clip_position = vec4<f32>(in.position, 1.0);
  out.color = vec3<f32>(1.0, 0.5, 0.3);
  return out;
}

@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
  return vec4<f32>(in.color, 1.0);
}

WGSL 的几个关键改进:

  • 类型严格vec3<f32> 不能隐式转 vec3<f32>(编译期检查)
  • 入口显式@vertex / @fragment / @compute 直接标注
  • 绑定模型@group(0) @binding(0) 显式描述资源位置
  • 内置 WGSL spec:不再依赖第三方编译器

五、资源绑定:Bind Group 模型

WebGL 的 uniform 是全局变量,WebGPU 用 Bind Group 模型批量绑定:

// 1. 创建 Bind Group Layout
const bgLayout = device.createBindGroupLayout({
  entries: [{
    binding: 0,
    visibility: GPUShaderStage.VERTEX,
    buffer: { type: 'uniform' },
  }, {
    binding: 1,
    visibility: GPUShaderStage.FRAGMENT,
    texture: { sampleType: 'float' },
  }, {
    binding: 2,
    visibility: GPUShaderStage.FRAGMENT,
    sampler: { type: 'filtering' },
  }],
});

// 2. 创建 Pipeline Layout
const pipelineLayout = device.createPipelineLayout({
  bindGroupLayouts: [bgLayout],
});

// 3. 创建 Bind Group(绑定实际资源)
const bindGroup = device.createBindGroup({
  layout: bgLayout,
  entries: [
    { binding: 0, resource: { buffer: uniformBuffer } },
    { binding: 1, resource: textureView },
    { binding: 2, resource: sampler },
  ],
});

// 4. 在 render pass 里使用
passEncoder.setBindGroup(0, bindGroup);
passEncoder.setPipeline(pipeline);
passEncoder.draw(3, 1, 0, 0);

这套模型让“切换材质”变成“换 Bind Group”,开销远低于 WebGL 的“切换 uniform”。

六、Compute Shader:GPU 通用计算的官方入口

WebGL 想做 GPGPU 要用 transform feedback 这种 hack,WebGPU 直接支持 compute shader:

@group(0) @binding(0) var<storage, read_write> data: array<f32>;

@compute @workgroup_size(64)
fn cs_main(@builtin(global_invocation_id) id: vec3<u32>) {
  let i = id.x;
  if (i >= arrayLength(&data)) { return; }
  data[i] = data[i] * 2.0;
}
// 调度
const computePass = encoder.beginComputePass();
computePass.setPipeline(computePipeline);
computePass.setBindGroup(0, computeBindGroup);
computePass.dispatchWorkgroups(Math.ceil(N / 64));
computePass.end();

适用场景:粒子模拟、图像处理、FFT、AI 推理(小模型)。这是浏览器端“严肃计算”的真正起点。

七、一个完整的渲染循环

function frame() {
  // 1. 获取当前帧的 texture
  const surfaceTexture = context.getCurrentTexture();
  const textureView = surfaceTexture.createView();

  // 2. 创建 command encoder
  const encoder = device.createCommandEncoder();

  // 3. 开始 render pass
  const pass = encoder.beginRenderPass({
    colorAttachments: [{
      view: textureView,
      clearValue: { r: 0, g: 0, b: 0, a: 1 },
      loadOp: 'clear',
      storeOp: 'store',
    }],
  });

  pass.setPipeline(pipeline);
  pass.setBindGroup(0, bindGroup);
  pass.setVertexBuffer(0, vertexBuffer);
  pass.draw(3);  // 画一个三角形
  pass.end();

  // 4. 提交
  device.queue.submit([encoder.finish()]);

  requestAnimationFrame(frame);
}

注意 encoder.finish() 之后命令才被提交到 GPU 队列,这种“录制-提交”分离是现代 API 的核心模式,让你可以多线程录制命令、复用 command buffer。

八、性能对比:何时能看到收益

场景 WebGL WebGPU 提升幅度
1 万个 draw call 8ms 2.5ms 3.2x
实时阴影(cascaded) 14ms 5ms 2.8x
GPU 粒子(10万) 不支持 3ms
Compute blur(1080p) transform feedback 32ms 4ms 8x

在简单场景下提升不明显,但场景越复杂、draw call 越多,WebGPU 的优势越大。

九、生态与生产可用性

  • Three.js:通过 WebGPURenderer 适配,已有 production 案例
  • Babylon.js:原生 WebGPU 支持,迁移成本最低
  • PlayCanvas:双后端,自动 fallback
  • WGPU(Rust):可在 WASM 中直接调用
  • Dawn(C++):Chrome 内部实现,可嵌入 Electron

降级策略:if (navigator.gpu) 检测,没有就回退到 WebGL2,保证兼容性。

十、迁移建议

  1. 新项目直接上 WebGPU:用 Three.js/Babylon.js 等引擎,几乎零成本
  2. 老项目逐步迁移:先把核心场景切到 WebGPU,UI 等保持 WebGL
  3. 避免手写 WGSL:用 TypeScript + WGSL preprocessor(如 naga-oil)做模块化
  4. 监控浏览器占比:WebGL2 仍是 fallback,确保 fallback 路径性能可接受

结语

WebGPU 不是“WebGL 的快版本”,而是浏览器图形 API 的一次范式转移——从“灵活但低效的状态机”到“严格但高效的管线模型”。它把过去十年 PC/移动端图形 API 的演进(Vulkan/Metal/D3D12)搬到了浏览器,让 Web 端真正具备了做“严肃图形应用”的能力。

对游戏开发者,WebGPU 意味着浏览器可以作为游戏发布的真正平台;对图形研究者,它意味着 GPU 计算不再需要装应用;对前端工程师,它是了解现代 GPU 编程的最好入口。无论如何,2026 年是开始学 WebGPU 的最佳时机。