本部分应用将在画布上绘制一些简单的几何图形:一个彩色正方形。显示这么简单的输出似乎需要大量工作,这是因为 WebGPU 旨在高效地渲染大量的几何图形。这种效率的副作用是,执行相对简单的操作可能让人感觉异常困难。需要执行的操作稍微复杂一些。
定义顶点
类型化数组Float32Array表示顶点坐标。一般来说通过WebGPU绘制一个几何图形的时候,比如一个三角形、一个矩形、一个立方体...需要使用顶点先表示几何体的形状。
const vertices = new Float32Array([
// X, Y,
-0.8, -0.8, // Triangle 1
0.8, -0.8,
0.8, 0.8,
-0.8, -0.8, // Triangle 2
0.8, 0.8,
-0.8, 0.8,
]);
创建顶点缓冲区
GPU 无法使用 JavaScript 数组中的数据绘制顶点。GPU 通常有自己的内存,该内存经过高度优化,因此需要导入 GPU 绘制过程中使用的所有数据。
const vertexBuffer = device.createBuffer({
label: "Cell vertices",
size: vertices.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
// 顶点数据写入顶点缓冲区
device.queue.writeBuffer(vertexBuffer, /*bufferOffset=*/0, vertices);
定义顶点布局
现在,您有了一个包含顶点数据的缓冲区,但就 GPU 而言,它只是一个 blob 字节。如果您要使用此类内容进行绘制,则需要再多提供一些信息。您需要能够让 WebGPU 更详细地了解顶点数据的结构。
// 使用 GPUVertexBufferLayout 字典定义顶点数据结构
const vertexBufferLayout = {
arrayStride: 8,
attributes: [{
format: "float32x2",
offset: 0,
shaderLocation: 0, // Position, see vertex shader
}],
};
//arrayStride: 3*4,一个顶点数据占用的字节长度,该缓冲区一个顶点包含xyz三个分量,每个数字是4字节浮点数,3*4字节长度
// attributes: [] 顶点缓冲区属性
// shaderLocation: 0,GPU显存上顶点缓冲区标记存储位置
// format: "float32x3",格式:loat32x3表示一个顶点数据包含3个32位浮点数
// offset: 0 arrayStride表示每组顶点数据间隔字节数,offset表示读取改组的偏差字节数,没特殊需要一般设置0
开始使用着色器
现在,您已拥有要渲染的数据,但仍需要告知 GPU 如何处理具体数据。这在很大程度上取决于着色器。
// 以下代码 vertexBufferLayout 下面
//创建一个用于输入着色器代码的位置
const cellShaderModule = device.createShaderModule({
label: "Cell shader",
code: `
// Your shader code will go here
`
});
定义顶点着色器
@vertex
fn vertexMain(@location(0) pos: vec2f) ->
@builtin(position) vec4f {
return vec4f(pos, 0, 1);
}
定义 fragment 着色器
@fragment
fn fragmentMain() -> @location(0) vec4f {
return vec4f(1, 0, 0, 1); // (Red, Green, Blue, Alpha)
}
const cellShaderModule = device.createShaderModule({
label: 'Cell shader',
code: `
@vertex
fn vertexMain(@location(0) pos: vec2f) ->
@builtin(position) vec4f {
return vec4f(pos, 0, 1);
}
@fragment
fn fragmentMain() -> @location(0) vec4f {
return vec4f(1, 0, 0, 1);
}
`
});
创建渲染流水线
着色器模块不能用于自行渲染。相反,您必须将其用作通过调用 device.createRenderPipeline() 创建的 GPURenderPipeline 的一部分来使用。渲染管道可控制如何绘制几何图形,包括如何使用着色器、如何解读顶点缓冲区中的数据、应渲染的几何图形类型(线条、点、三角形等)!
const cellPipeline = device.createRenderPipeline({
label: "Cell pipeline",
layout: "auto",
vertex: {
module: cellShaderModule,
entryPoint: "vertexMain",
buffers: [vertexBufferLayout]
},
fragment: {
module: cellShaderModule,
entryPoint: "fragmentMain",
targets: [{
format: canvasFormat
}]
}
});
绘制方形
若要绘制方块,请回退到 encoder.beginRenderPass() 和 pass.end() 对的调用,然后在它们之间添加以下新命令:
// After encoder.beginRenderPass()
pass.setPipeline(cellPipeline);//指明应使用哪个流水线进行绘制
pass.setVertexBuffer(0, vertexBuffer);//此缓冲区对应于当前流水线的 vertex.buffers 定义中的第 0 个元素
pass.draw(vertices.length / 2); // 6 vertices
// before pass.end()