WebGPU Uniforms (part 1)
WebGPU是WebGPU标准的一部分,由Khronos Group领导的WebGPU工作组制定,是一种为Web公开GPU硬件功能的API。它提供对网络上硬件的图形和计算能力的访问,旨在提供基于现代GPU的高性能、低延迟的图形渲染服务。
推荐使用NSDT 3DConvert进行3D模型格式转换,支持glb、obj、stp、fbx、ifc等多种3D模型格式之间进行互相转换,在转换过程中,能够很好的保留模型原有的颜色、材质等信息。
上一篇文章讲的是阶段间变量。这篇文章将是关于uniforms的基础知识。
uniforms有点像着色器的全局变量。您可以在执行着色器之前设置它们的值,它们可以在着色器的每次迭代中使用这些值。下次您要求 GPU 执行着色器时,您可以将它们设置为其他值。
我们将从三角形示例重新开始并修改它以使用一些uniforms。
const module = device.createShaderModule({
label: 'triangle shaders with uniforms',
code: `
struct OurStruct {
color: vec4f,
scale: vec2f,
offset: vec2f,
};
@group(0) @binding(0) var<uniform> ourStruct: OurStruct;
@vertex fn vs(
@builtin(vertex_index) vertexIndex : u32
) -> @builtin(position) vec4f {
var pos = array<vec2f, 3>(
vec2f( 0.0, 0.5), // top center
vec2f(-0.5, -0.5), // bottom left
vec2f( 0.5, -0.5) // bottom right
);
// return vec4f(pos[vertexIndex], 0.0, 1.0);
return vec4f(
pos[vertexIndex] * ourStruct.scale + ourStruct.offset, 0.0, 1.0);
}
@fragment fn fs() -> @location(0) vec4f {
//return vec4f(1, 0, 0, 1);
return ourStruct.color;
}
`,
});
});
首先我们声明了一个有 3 个成员的结构体。
struct OurStruct {
color: vec4f,
scale: vec2f,
offset: vec2f,
};
然后我们声明了一个具有该结构类型的uniform变量。变量名称是 ourStruct ,类型是刚刚定义的结构体类型OurStruct 。
@group(0) @binding(0) var<uniform> ourStruct: OurStruct;
接下来更改从 顶点着色器 返回的内容以使用uniforms。
@vertex fn vs(
...
) ... {
...
return vec4f(
pos[vertexIndex] * ourStruct.scale + ourStruct.offset, 0.0, 1.0);
}
您可以看到我们将顶点位置乘以scale,然后与offset 相加。这将让我们设置三角形的大小并定位它。
我们还更改片段着色器从 uniforms返回颜色。
@fragment fn fs() -> @location(0) vec4f {
return ourStruct.color;
}
现在我们已经将着色器设置为使用uniforms,需要在 GPU 上创建一个缓冲区来保存它们的值。
首先,我们创建一个缓冲区并为其分配使用标志,以便它可以与uniforms一起使用,并且我们可以通过将数据复制到它来进行更新。
const uniformBufferSize =
4 * 4 + // color is 4 32bit floats (4bytes each)
2 * 4 + // scale is 2 32bit floats (4bytes each)
2 * 4; // offset is 2 32bit floats (4bytes each)
const uniformBuffer = device.createBuffer({
size: uniformBufferSize,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
然后我们创建一个 TypedArray ,这样我们就可以在 JavaScript 中设置值。
// create a typedarray to hold the values for the uniforms in JavaScript
const uniformValues = new Float32Array(uniformBufferSize / 4);
我们将填写我们的结构的 2 个值,这些值以后不会改变。
// offsets to the various uniform values in float32 indices
const kColorOffset = 0;
const kScaleOffset = 4;
const kOffsetOffset = 6;
uniformValues.set([0, 1, 0, 1], kColorOffset); // set the color
uniformValues.set([-0.5, -0.25], kOffsetOffset); // set the offset
上面我们将颜色设置为绿色。偏移量会将三角形移动到画布左侧 1/4 处和下方 1/8 处。 (请记住,剪辑空间从 -1 到 1,即 2 个单位宽,因此 0.25 是 2 的 1/8)。
接下来,为了告诉着色器我们的缓冲区,我们需要创建一个绑定组并将缓冲区绑定到我们在着色器中设置的相同 @binding 。
const bindGroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: { buffer: uniformBuffer }},
],
});
现在,在我们提交命令缓冲区之前的某个时间,我们需要设置 uniformValues 的其他值,然后将这些值复制到 GPU 上的缓冲区。我们将在 render 函数的顶部执行此操作。
function render() {
// Set the uniform values in our JavaScript side Float32Array
const aspect = canvas.width / canvas.height;
uniformValues.set([0.5 / aspect, 0.5], kScaleOffset); // set the scale
// copy the values from JavaScript to the GPU
device.queue.writeBuffer(uniformBuffer, 0, uniformValues);
我们将scale 设置为一半大小并考虑到画布的纵横比,因此无论画布的大小如何,三角形都将保持相同的宽高比。
最后,我们需要在绘制前设置bind group。
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup); //<===here
pass.draw(3); // call our vertex shader 3 times
pass.end();
这样我们就得到了一个绿色三角形,和前面所述的一样。
对于这个三角形,我们在执行绘制命令时的状态是这样的。