Skip to content

管线(Pipeline)

管线是某一种绘制的"配方"。beam.pipeline(template) 接受一份声明——你手写的 WGSL 加上几个 schema——并返回一个 Pipeline<V, U, T, S>,它同时掌握三件事:

  • 每次绘制所需数据的 TypeScript 类型
  • 顶点缓冲布局(stride、offset、format),以及
  • 绑定组布局(哪些 uniform、纹理和采样器分别位于何处)。

关键在于:在 WebGPU 中你手写一个 WGSL 模块,而 Beam 绝不会改写它。Beam 只会根据你 声明的 schema,在你的着色器 周围 推导布局。

最小的管线

ts
import { Beam } from 'beam-gpu'
import wgsl from './hello.wgsl?raw'

const beam = await Beam.gpu(canvas)

const tri = beam.pipeline({
  wgsl,
  vertex: { position: 'vec3', color: 'vec3' },
  uniforms: { tint: 'vec4' }
})

就这么简单。tri 现在是一个具备完整类型的 Pipeline。其中 vertexuniformstexturessamplers 这几个键就是 schema;模板中其余的一切都是固定功能 预设,并带有合理的默认值。

schema 驱动一切

schema 由四个可选的记录组成(只有 vertex 是必需的)。每个记录将字段名映射到 schema 词汇表中的某个类型字符串。键的 顺序 很重要: 它决定了 @location@binding 的索引。

ts
const pipe = beam.pipeline({
  wgsl,
  vertex:   { position: 'vec3', normal: 'vec3', uv: 'vec2' },
  uniforms: { mvp: 'mat4', tint: 'vec4' },
  textures: { albedo: 'tex2d' },
  samplers: { samp: 'sampler' }
})

vertex → 顶点布局 + @location

每个顶点字段对应一个非交错(non-interleaved)的顶点缓冲。字段在记录中的位置就成为 它的 @location

schema 键类型@locationWGSL 类型format
positionvec30vec3ffloat32x3
normalvec31vec3ffloat32x3
uvvec22vec2ffloat32x2

因此你的 @vertex 入口要严格按照这个顺序读取属性:

wgsl
@vertex
fn vs(
  @location(0) position : vec3f,  // vertex.position
  @location(1) normal   : vec3f,  // vertex.normal
  @location(2) uv       : vec2f,  // vertex.uv
) -> VsOut { /* ... */ }

顶点 schema 只接受标量和向量类型——不支持矩阵。每个属性对应一个缓冲,不交错,也没有 逐实例属性;instances 仅仅是一个绘制计数。

uniforms → 位于 @group(0) @binding(0) 的单个 UBO

所有 uniform 字段会打包进 一个 std140 uniform 缓冲,按 schema 键的顺序声明为 一个 WGSL struct

wgsl
struct Uniforms {
  mvp  : mat4x4f,  // uniforms.mvp
  tint : vec4f,    // uniforms.tint
};
@group(0) @binding(0) var<uniform> u : Uniforms;

矩阵和 vec3 带有 std140 对齐陷阱(一个 mat3 是三个被填充的 vec4,共 48 字节; 一个 vec3 后面想要塞进一个标量)。WGSL 约定页面列出了 完整的对齐表。

textures 然后 samplers@group(1)

纹理按 textures 键的顺序获得 @binding(0..T-1);采样器紧随其后,按 samplers 键的顺序从 @binding(T..) 开始——它们全都位于 @group(1)

wgsl
@group(1) @binding(0) var albedo : texture_2d<f32>;  // textures.albedo
@group(1) @binding(1) var samp   : sampler;          // samplers.samp

由于 Beam 是根据你的 schema 构建这些布局的(绝不会使用 layout: 'auto'),因此 绑定组可以在任何共享相同 schema 的管线之间复用。

schema 喂养你的资源

schema 不仅用于 WGSL——你可以把它直接传给资源工厂函数,让你的数据相对管线得到类型 校验:

ts
const verts = beam.verts(pipe.schema.vertex, {
  position: positions,
  normal: normals,
  uv: uvs
})
const uniforms = beam.uniforms(pipe.schema.uniforms, { tint: [1, 1, 1, 1] })

这样,你交给 drawbindings 对象就会相对管线得到完整的类型检查:

ts
beam.frame(() => {
  beam.clear().draw(pipe, { verts, index, uniforms, textures: { albedo }, samplers: { samp } })
})

数据侧的内容请参见 资源绑定与绘制

固定功能预设

schema 之外的一切都用来配置固定功能阶段。它们全都是可选的,并默认采用常见情形 (一个不透明、经过深度测试、绘制到画布格式的三角形列表):

ts
const pipe = beam.pipeline({
  wgsl,
  vertex: { position: 'vec3' },

  primitive: 'tri',         // 'tri' | 'tri-strip' | 'line' | 'point'
  cull: 'back',             // 'none' | 'back' | 'front'
  depth: true,              // boolean | { test, write, compare, format }
  blend: 'alpha',           // 'none' | 'alpha' | 'add'
  samples: 4,               // 1 | 4 — MSAA
  vsEntry: 'vs',            // 覆盖 @vertex 入口名称
  fsEntry: 'fs',            // 覆盖 @fragment 入口名称
  constants: { LIGHTS: 3 }, // WGSL override 常量
  label: 'lit'
})
预设默认值说明
primitive'tri'三角形列表、三角形条带、线段或点列表。
cull'none'背面/正面剔除。
depthfalsetrue 启用深度附件;或传入 DepthOpts
blend'none''alpha' 用于透明,'add' 用于加法混合。
targets一个画布格式的目标ColorTarget[] 实现 MRT 或逐目标混合。
samples14 启用 MSAA(由 Beam 管理解析过程)。
constantsWGSL override 常量。
vsEntry / fsEntry'vs' / 'fs'重命名 Beam 查找的入口点。

depth 详解

depth: true 是常见的"使用 less 进行测试并写入"的简写。对于阴影 pass 或只读深度, 可以传入对象形式:

ts
const pipe = beam.pipeline({
  wgsl,
  vertex: { position: 'vec3' },
  depth: { test: true, write: false, compare: 'less-equal' }
})

blendtargets

blend 是针对单个默认颜色目标的快捷方式。对于多个渲染目标,或者要为每个附件混搭 不同的格式和混合模式,则需要明确写出 targets

ts
const pipe = beam.pipeline({
  wgsl,
  vertex: { position: 'vec3' },
  targets: [
    { format: 'rgba8unorm', blend: 'alpha' },
    { format: 'rgba16float' }
  ]
})

WGSL 由你手写

Beam 绝不会生成或改写你的着色器。你编写一个带有 @vertex@fragment 入口的 WGSL 模块(默认名为 vs / fs),并遵循 一套固定约定,使你的 @location@group/@binding 索引与 Beam 推导出的 schema 对齐。用 ?raw 导入它:

ts
import wgsl from './lit.wgsl?raw'
const pipe = beam.pipeline({ wgsl, vertex: { position: 'vec3' } })

完整的约定——类型映射、std140 对齐、绑定顺序以及推荐的文件结构——都在 WGSL 约定 中。在编写你的第一个着色器之前请先阅读它; 正是这份契约让你手写的 WGSL 与 schema 保持一致。