接下来让我们使用 UniformBuffer。UniformBuffer 是一个只读内存区域,可以在着色器上访问。
这次,我们将传递给着色器的矩阵存储在 UniformBuffer 中。演示示例
1.在顶点着色器中的 UniformBuffer
这次我们在顶点着色器里定义一个名为Uniforms
的新结构体,并定义一些成员变量来将我们想要传递的矩阵存储在其中。我们准备了三个矩阵:投影矩阵projectionMatrix 、视图矩阵viewMatrix 和世界矩阵worldMatrix(或者说模型矩阵)。
struct Uniforms {
projectionMatrix : mat4x4<f32>,
viewMatrix : mat4x4<f32>,
worldMatrix : mat4x4<f32>,
}
@binding(0) @group(0) var<uniform> uniforms : Uniforms;
struct VertexOutput {
@builtin(position) Position : vec4<f32>,
@location(0) fragColor : vec4<f32>,
}
@vertex
fn main(
@location(0) position: vec4<f32>,
@location(1) color: vec4<f32>
) -> VertexOutput {
var output : VertexOutput;
output.Position = uniforms.projectionMatrix * uniforms.viewMatrix * uniforms.worldMatrix * position;
output.fragColor = color;
return output;
}
2.生成UniformBuffer
下面是生成 UniformBuffer 的代码。
const uniformBufferSize = 4 /* bytes */ * 16 * 3; // 4x4 matrix * 3
const uniformBuffer = g_device.createBuffer({
size: uniformBufferSize,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
usage
除了GPUBufferUsage.UNIFORM
的选项,还有一个GPUBufferUsage.COPY_DST
选项,这个选项是用来表明将数据复制到 GPU 内存区域。
3.复制矩阵到UniformBuffer
然后将具体的矩阵数据复制到 UniformBuffer 中,这次我们使用 glMatrix 库来处理矩阵。
function getTransformationMatrix(uniformBuffer: GPUBuffer) {
const projectionMatrix = glMatrix.mat4.create();
glMatrix.mat4.perspective(projectionMatrix, (2 * Math.PI) / 5, 1, 1, 100.0);
g_device.queue.writeBuffer(
uniformBuffer,
4 * 16 * 0,
projectionMatrix.buffer,
projectionMatrix.byteOffset,
projectionMatrix.byteLength
);
const viewMatrix = glMatrix.mat4.create();
glMatrix.mat4.translate(viewMatrix, viewMatrix, glMatrix.vec3.fromValues(0, 0, -4));
g_device.queue.writeBuffer(
uniformBuffer,
4 * 16 * 1,
viewMatrix.buffer,
viewMatrix.byteOffset,
viewMatrix.byteLength
);
const worldMatrix = glMatrix.mat4.create();
const now = Date.now() / 1000;
glMatrix.mat4.rotate(
worldMatrix,
worldMatrix,
1,
glMatrix.vec3.fromValues(Math.sin(now), Math.cos(now), 0)
);
g_device.queue.writeBuffer(
uniformBuffer,
4 * 16 * 2,
worldMatrix.buffer,
worldMatrix.byteOffset,
worldMatrix.byteLength
);
}
设置每个矩阵后,使用g_device.queue.writeBuffer
将数据复制到 GPU 上的 UniformBuffer。 注意第二个参数指定了复制目标的偏移量,它指定应放置每个矩阵数据的起始地址。
4.生成GPUBindGroup
接下来,创建一个名为 GPUBindGroup 的对象。WebGPU 在为 GPU 设置 GPU 资源(例如 UniformBuffers、纹理和采样器)时使用此对象。
前面创建的 uniformBuffer 设置为entities[0].resource.buffer
。
const bindGroup = g_device.createBindGroup({
layout: g_pipeline.getBindGroupLayout(0),
entries: [
{
binding: 0, // @binding(0) in shader
resource: {
buffer: uniformBuffer,
},
},
],
});
这里binding: 0
对应的是顶点着色器中写的属性@binding(0)
。layout:g_pipeline.getBindGroupLayout(0)
中的0
对应顶点着色器中写的属性@group(0)
。
5.在renderPassEncoder中设置GPUBindGroup
最后,使用renderPassEncoder
上的setBindGroup
方法设置 GPUBindGroup。
const renderPassEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
renderPassEncoder.setPipeline(pipeline);
renderPassEncoder.setBindGroup(0, uniformBindGroup); // <--- 设置bindGroup
renderPassEncoder.setVertexBuffer(0, verticesBuffer);
renderPassEncoder.draw(cubeVertexCount);
renderPassEncoder.end();
g_device.queue.submit([commandEncoder.finish()]);
setBindGroup
的第一个参数0
对应于顶点着色器中写入的属性@group(0)
。
总结
以上,使用UniformBuffer的绘制就完成了。
虽然这个立方体看起来很奇怪。。。这是因为我们还没有在绘图时启用深度测试。下次我们将启用深度测试以确保正确绘制立方体。