Miscellaneous Shader Input(杂项着色器输入)
原文作者:webgpufundamentals Contributors
原文链接:https://webgpufundamentals.org/webgpu/lessons/webgpu-miscellaneous-input.html
编译说明
本文是 WebGPU Fundamentals 系列中「向着色器传递数据」的其中一篇。前面的文章已经依次介绍了:
阶段间变量(Inter-stage Variables)→ Uniforms → Storage Buffers → Vertex Buffers → Textures → Loading Images → Using Video → Cube Maps → Storage Textures → Multisampling / MSAA → Immediates → Constants,本篇文章为最后一篇:Miscellaneous Shader Input。
本解读聚焦于一种不太常见、但非常实用的着色器输入方式:firstInstance。
正文解读:用 firstInstance 给着色器传递数据
在调用 draw 或 drawIndexed 时,我们可以传入一个 firstInstance 值:
pass.draw(numVertices,
numInstances = 1,
firstVertex = 0,
firstInstance = 0); // <== 这里
pass.drawIndexed(indexCount,
instanceCount = 1,
firstIndex = 0,
baseVertex = 0,
firstInstance = 0); // <== 这里
这个值在着色器中对应 @builtin(instance_index)。也就是说,即使我们不进行真正的实例化渲染,也可以用它来传递一个索引,从而选择不同的数据。例如,在“第一篇文章”的简单三角形例子上,我们可以做如下修改。
示例 1:用 instanceIndex 选择偏移量
+struct VertexOut {
+ @builtin(position) pos: vec4f,
+ @location(0) @interpolate(flat, either) colorNdx: u32,
+};
@vertex fn vs(
* @builtin(vertex_index) vertexIndex : u32,
+ @builtin(instance_index) instanceIndex: u32,
) -> VertexOut {
let pos = array(
vec2f( 0.0, 0.5), // top center
vec2f(-0.5, -0.5), // bottom left
vec2f( 0.5, -0.5) // bottom right
);
+ let offsets = array(
+ vec2f( 0.0, 0.5), // top middle
+ vec2f(-0.5, -0.5), // left bottom
+ vec2f( 0.5, -0.5), // right bottom
+ );
- return vec4f(pos[vertexIndex], 0.0, 1.0);
+ return VertexOut(
+ vec4f(pos[vertexIndex] + offsets[instanceIndex], 0.0, 1.0),
+ instanceIndex,
+ );
}
-@fragment fn fs() -> @location(0) vec4f {
- return vec4f(1, 0, 0, 1);
-}
+@fragment fn fs(in: VertexOut) -> @location(0) vec4f {
+ let colors = array(
+ vec4f(1, 1, 0, 1), // yellow
+ vec4f(0, 1, 1, 1), // cyan
+ vec4f(1, 0, 1, 1), // magenta
+ );
+ return colors[in.colorNdx];
+}
上面的代码使用 instanceIndex 来选择偏移量,并通过一个带有 @interpolate(flat, either) 的阶段间变量,把实例索引传递给片元着色器。
flat 表示“不要插值”;either 表示从三角形的第一个或最后一个顶点取值。由于我们给所有顶点传递的都是同一个值,因此取第一个还是最后一个并不重要。不过 either 在兼容模式(compatibility mode)下也能工作,所以是更好的选择。
最后,只需更新调用 draw 的次数,画出 3 个三角形:
function render() {
// 获取 canvas 当前纹理并设为渲染目标
renderPassDescriptor.colorAttachments[0].view =
context.getCurrentTexture().createView();
const encoder = device.createCommandEncoder({ label: 'our encoder' });
const pass = encoder.beginRenderPass(renderPassDescriptor);
pass.setPipeline(pipeline);
pass.draw(3); // 调用顶点着色器 3 次
+ pass.draw(3, 1, 0, 1); // 为 instance_index 传递 1
+ pass.draw(3, 1, 0, 2); // 为 instance_index 传递 2
pass.end();
const commandBuffer = encoder.finish();
device.queue.submit([commandBuffer]);
}
渲染结果就是三个不同颜色的三角形(在线示例参见原文链接)。
示例 2:结合 Storage Buffer 数组
我们不一定要在着色器中硬编码数组,也可以使用 instance_index 从 Storage Buffer 的数组中选取数据:
struct VertexOut {
@builtin(position) pos: vec4f,
@location(0) @interpolate(flat, either) instanceIndex: u32,
};
struct PerInstanceInfo {
matrix: mat4x4f,
color: vec4f,
};
@group(0) @binding(0) `var<storage, read>` perInst: `array<PerInstanceInfo>`;
@vertex fn vs(
@location(0) position: vec4f,
@builtin(instance_index) instanceIndex: u32,
) -> VertexOut {
let info = perInst[instanceIndex];
return VertexOut(
info.matrix * position,
instanceIndex,
);
}
@fragment fn fs(in: VertexOut) -> @location(0) vec4f {
let info = perInst[in.instanceIndex];
return info.color;
}
何时使用该方法
是否采用这种方式由你自己决定。对于 draw 之间的小幅变化,你也可以使用 immediates。
使用 firstInstance 的优势在于:
- 减少 WebGPU API 调用次数;
- 不用仅仅为了传递一个数字而创建一个
ArrayBufferView。
本系列文章在生成 mipmap、并使其兼容 compatibility mode 时也会使用该方法。它帮助我们轻松选择 2D 数组纹理或立方体贴图的某一层,而不必额外创建 uniform buffer。
另外注意:
vertex_index也可以通过draw调用参数来控制,但由于我们通常需要它来选择顶点,所以较少用它来传递自定义数据。
三个核心优化要点
-
使用
firstInstance传递轻量整数索引,避免额外的 Uniform Buffer / ArrayBufferView
对于单数字或少量变化,直接传入draw/drawIndexed的firstInstance,在着色器里通过@builtin(instance_index)读取。这样可以减少 WebGPU 调用次数,避免为一个小值创建 JS 类型数组视图或绑定 uniform 资源。 -
结合 Storage Buffer 数组,用
instanceIndex直接索引 per-instance 数据
将多个实例的矩阵、颜色等数据一次性放入var<storage, read> array<PerInstanceInfo>,然后通过instanceIndex在着色器里索引,而不是为每个实例单独设置 uniform 或多次 draw。这样可以批量传参,减少绑定和调用的复杂度。 -
使用
@interpolate(flat, either)传递常量阶段间变量,提升兼容性并避免插值开销
当从顶点着色器向片元着色器传递不应当插值的值(例如实例索引)时,用flat禁用插值;选择either可在 compatibility mode 下正常工作。这比默认的插值方式更高效且更可移植。
GLSL/WebGL 专业术语检查
经核查,本文大量使用的是 WebGPU / WGSL 术语,而非 GLSL/WebGL 代码。以下是核心术语及与 GLSL/WebGL 的对应关系:
| WebGPU/WGSL 术语 | 说明 | GLSL/WebGL 类似术语或概念 |
|---|---|---|
@builtin(instance_index) | 当前实例索引 | gl_InstanceID / gl_InstanceID(WebGL2) |
@builtin(vertex_index) | 当前顶点索引 | gl_VertexID / gl_VertexIndex |
@builtin(position) | 顶点输出裁剪坐标 | gl_Position |
@interpolate(flat, either) | 阶段间变量插值修饰符 | GLSL flat 插值限定符 |
Storage Buffer | 可读可写的缓冲区 | Shader Storage Buffer Object(SSBO) |
Uniform Buffer | 只读统一缓冲区 | Uniform Buffer Object(UBO) |
Vertex Buffer | 顶点数据缓冲区 | Vertex Buffer Object(VBO) |
Inter-stage Variables | 顶点到片元之间的传递变量 | varying / in / out |
firstInstance | draw 系列调用参数,指定实例索引起始值 | baseInstance(部分 GLES/GL 版本) |
compatibility mode | WebGPU 兼容模式 | WebGL 模式相关,非 GLSL 原生术语 |
no interpolation | 禁用插值 | flat |
结论:本文没有混用 GLSL/WebGL 专业术语;术语使用符合 WebGPU/WGSL 规范。either 是 WGSL 特有的插值采样选项,在 GLSL 中并不存在直接对应。
