目录
1. 简介
什么是 WebGPU?
现代 API
渲染
计算
构建内容
学习内容
所需条件
2. 进行设置
获取代码
使用开发者控制台!
3. 初始化 WebGPU
以“”开头
index.html
请求适配器和设备
index.html
index.html
index.html
配置画布
index.html
清空画布
index.html
index.html
index.html
index.html
index.html
index.html
选择一种颜色!
index.html
4. 绘制几何图形
了解 GPU 的绘制方式
定义顶点
index.html
index.html
创建顶点缓冲区
index.html
index.html
定义顶点布局
index.html
开始使用着色器
index.html
定义顶点着色器
index.html(createShaderModule 代码)
index.html (createShaderModule 代码)
index.html (createShaderModule 代码)
index.html (createShaderModule 代码)
index.html (createShaderModule 代码)
index.html (createShaderModule 代码)
定义 fragment 着色器
index.html (createShaderModule 代码)
index.html (createShaderModule 代码)
index.html
创建渲染流水线
index.html
绘制方形
index.html
5. 绘制网格
定义网格
index.html
创建统一缓冲区 Create a uniform buffer
index.html
在着色器中使用 uniform
index.html(createShaderModule 调用)
创建绑定组 Create a Bind Group
index.html
绑定绑定组
index.html
在着色器中操纵几何图形
index.html(createShaderModule 调用)
index.html(createShaderModule 调用)
index.html(createShaderModule 调用)
index.html(createShaderModule 调用)
绘制实例
index.html
index.html
6. 额外的功劳:使其更绚丽!
在着色器中使用结构体
index.html(createShaderModule 调用)
index.html(createShaderModule 调用)
在顶点和 Fragment 函数之间传递数据
index.html(createShaderModule 调用)
index.html(createShaderModule 调用)
index.html(createShaderModule 调用)
index.html(createShaderModule 调用)
index.html(createShaderModule 调用)
index.html(createShaderModule 调用)
createShaderModule 调用
7. 管理单元格状态
创建存储缓冲区
在着色器中读取存储缓冲区
将存储缓冲区添加到绑定组
使用乒乓球缓冲区模式
设置渲染循环
8. 运行模拟
最后使用计算着色器!
使用绑定组和流水线布局
index.html
index.html
创建计算流水线
Compute passes
实现 Game of Life 算法
9. 恭喜!
后续操作
深入阅读
参考文档
源码位置
mysqldoc: mysql学习的一些文档和总结 - Gitee.com
关于学习webgpu的一些参考资料
Rotating Cube - WebGPU SamplesThis example shows how to upload uniform data every frame to render a rotating object.
讯享网https://webgpu.github.io/webgpu-samples/samples/rotatingCubewebgpu-samples/src/sample at main · webgpu/webgpu-samples · GitHubWebGPU Samples. Contribute to webgpu/webgpu-samples development by creating an account on GitHub.
https://github.com/webgpu/webgpu-samples/tree/main/src/sampleWebGPU Fundamentalswebgpu tutorials and solutions
https://webgpufundamentals.org/WebGPU Best Practices | Toji.devA walkthrough of building a basic, efficient glTF renderer with WebGPU.
https://toji.dev/webgpu-best-practices/
1. 简介

上次更新日期:2023 年 4 月 13 日
什么是 WebGPU?
WebGPU 是一种新型的现代 API,可用于在 Web 应用中访问 GPU 的功能。
现代 API
在 WebGPU 之前,WebGL 提供了 WebGPU 的部分功能。它促成了一类新的富媒体内容,开发者利用它打造了许多精彩内容。不过,此 API 基于 2007 年发布的 OpenGL ES 2.0 API,而OpenGL ES 2.0则 基于更早的 OpenGL API。在此期间,GPU 发生了显著变化,用于与GPU 进行交互的原生 API 也随着 Direct3D 12、Metal 和 Vulkan 不断完善。
WebGPU 为网络平台带来了这些现代 API 的进步。该 API 专注于跨平台启用 GPU 功能,同时呈现了在网络上操作的自然体验,并且比基于该 API 构建的一些原生 API 更详细。
渲染
GPU 通常与快速、详细的图形渲染有关,WebGPU 也不例外。它具备多种功能,能够支持桌面设备和移动设备 GPU 中当前最流行的许多呈现技术,并且为未来随着硬件功能不断发展而添加的新功能提供了途径。
计算
除了渲染,WebGPU 还可以发挥 GPU 执行通用、高度并行的工作负载的潜力。这些计算着色器可独立使用,没有任何渲染组件,也可用作渲染流水线的紧密集成部分。
在今天的 Codelab 中,您将学习如何利用 WebGPU 的渲染和计算功能来创建简单的入门项目!
构建内容
在此 Codelab 中,您将使用 WebGPU 构建 Conway 的 Game of Life。您的应用将:
- 使用 WebGPU 的渲染功能绘制简单的 2D 图形。
- 使用 WebGPU 的计算功能来执行模拟。

注意:为简化此 Codelab,所有代码段均为原始 JavaScript。WebGPU 与 TypeScript 非常适合,但您需要包含 @webgpu/types 定义。在更广泛地实现 API 后,这些类型应成为标准 TypeScript DOM 和工作器库的一部分。
“Game of Life”就是所谓的单元格自动程序,即单元格网格网格根据一组规则随时间变化。在《Game of Life》中,细胞会变为活跃状态或非活跃状态,具体取决于其相邻的单元格数量,从而形成值得注意的波动模式。
学习内容
- 如何设置 WebGPU 并配置画布。
- 如何绘制简单的 2D 几何图形。
- 如何使用顶点和片元着色器来修改所绘制的内容。
- 如何使用计算着色器来执行简单的模拟。
此 Codelab 将重点介绍 WebGPU 背后的基本概念。其中并未涵盖 API 的全面审核内容,也不涵盖(或要求)经常相关的主题(例如 3D 矩阵)。
所需条件
- 适用于 ChromeOS、macOS 或 Windows 的最新版 Chrome(113 或更高版本)。WebGPU 是一种跨浏览器、跨平台的 API,但尚未在所有地方推出。
- 了解 HTML、JavaScript 和 Chrome 开发者工具。
不要求熟悉其他图形 API(例如 WebGL、金属、Vulkan 或 Direct3D),但如果您有这些方面的经验,可能会发现它与 WebGPU 有许多相似之处,可帮助您快速上手!
2. 进行设置
获取代码
此 Codelab 没有任何依赖项,它会引导您完成创建 WebGPU 应用所需的每个步骤,因此您无需编写任何代码即可开始使用。不过,Glitch :・゚✧ 上提供了一些可以用作检查点的有效示例。如果您遇到问题,可以立即查看并参考它们。
使用开发者控制台!
WebGPU 是一个相当复杂的 API,很多规则都规定了正确使用。更糟糕的是,由于此 API 的工作原理,它无法针对许多错误引发典型的 JavaScript 异常,因此更难查明问题的来源。
使用 WebGPU 进行开发(尤其是新手)时会遇到问题,这没关系!该 API 背后的开发者深知在 GPU 开发方面所面临的挑战,并且一直努力确保每当您的 WebGPU 代码引发错误时,您会在控制台中收到非常详细且有用的消息,这有助于您发现和解决问题。
无论在使用哪 Web 应用时,让控制台保持打开状态都非常有用,但在这里它尤其适用!
3. 初始化 WebGPU
以“<canvas>”开头
如果您只希望在 WebGPU 上进行计算,可以使用 WebGPU,而无需在屏幕上显示任何内容。但是,如果您想渲染任何内容,就像我们在此 Codelab 中要做的那样,您需要一个画布。最好从此处开始!
创建一个仅包含一个 <canvas> 元素的新 HTML 文档,以及一个用于查询画布元素的 <script> 标记。(或者使用 00-starter-page.html 这个文件)。
- 创建一个
index.html文件,其中包含以下代码:
index.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>WebGPU Life</title>
</head>
<body>
<canvas width="512" height="512"></canvas>
<script type="module">
const canvas = document.querySelector("canvas");
// Your WebGPU code will begin here!
</script>
</body>
</html>
讯享网
注意:画布元素的默认尺寸为 300x150 像素,这个尺寸足以满足此应用的需求。尽管不要求使用 WebGPU,但您可能希望为画布设置更高的宽度和高度。上述示例代码将画布尺寸设为 512x512 CSS 像素,但您可以随意选择尺寸。
注意:为脚本标记提供 "module" 类型后,您可以使用顶层 awaits,这对 WebGPU 初始化非常有用!
请求适配器和设备
现在,您可以深入了解 WebGPU 了!首先,您应考虑 WebGPU 等 API 可能需要一段时间才能传播到整个网络生态系统。因此,一个良好的预防措施第一步是检查用户的浏览器是否可以使用 WebGPU。
- 如需检查用作 WebGPU 入口点的 navigator.gpu 对象,请添加以下代码:
index.html
讯享网if (!navigator.gpu) { throw new Error("WebGPU not supported on this browser."); }
理想情况下,如果页面 GPU 不可用,您需要让用户回退到不使用 WebGPU 的模式,从而通知用户。(也许它可以改用 WebGL?)不过,在此 Codelab 中,您只是抛出了一个错误来阻止代码进一步执行。
确定浏览器支持 WebGPU 后,为应用初始化 WebGPU 的第一步是请求 GPUAdapter。您可以将适配器视为 WebGPU 代表设备中的特定 GPU 硬件。
- 如需获取适配器,请使用 navigator.gpu.requestAdapter() 方法。它会返回 promise,因此用
await调用它最为方便。
index.html
const adapter = await navigator.gpu.requestAdapter(); if (!adapter) { throw new Error("No appropriate GPUAdapter found."); }
如果未找到合适的适配器,返回的 adapter 值可能是 null,因此您需要处理这种可能性。如果用户的浏览器支持 WebGPU,但其 GPU 硬件不具备使用 WebGPU 所需的所有功能,则可能会发生这种情况。
大多数时候,您可以只是让浏览器像选择此处一样选择默认适配器,但为了满足更高级的需求,可以将参数传递给 requestAdapter(),用于指定您是否要在具有多个 GPU 的设备(例如某些笔记本电脑)上使用低功耗或高性能硬件。
有了适配器后,最后一步就是开始请求 GPUDevice,然后才能开始使用 GPU。设备是与 GPU 进行交互的主要界面。
- 通过调用 adapter.requestDevice() 获取设备,该方法也会返回一个 promise。
index.html
讯享网const device = await adapter.requestDevice();
与 requestAdapter() 一样,这里有一些选项可以传递,用于更高级的用法,例如启用特定的硬件功能或请求更高的限制,但对于您的用途,默认设置没有问题。
配置画布
现在,您已拥有设备,接下来还需要完成一个操作:如果您想使用该设备来显示网页上的任何内容,请将画布配置为与您刚刚创建的设备搭配使用。
- 为此,请先通过调用
canvas.getContext("webgpu")从画布请求 GPUCanvasContext。(这与用于分别初始化2d和webgl上下文类型且用于初始化 Canvas 2D 或 WebGL 上下文的调用相同。)然后,它必须使用 configure() 方法将返回的context与设备相关联,如下所示:
index.html
const context = canvas.getContext("webgpu"); const canvasFormat = navigator.gpu.getPreferredCanvasFormat(); context.configure({ device: device, format: canvasFormat, });
此处有几个选项可以传递,但最重要的选项是您要用于使用上下文的 device 以及 format,这是上下文应使用的纹理格式。
纹理是 WebGPU 用来存储图片数据的对象,每个纹理都有一种格式,可让 GPU 知道这些数据在内存中的布局方式。纹理内存的工作原理详情不在此 Codelab 的范围内。重要的是,画布上下文会提供代码的纹理供您绘制,而您使用的格式可能会影响画布显示这些图像的效率。不同类型的设备在使用不同纹理格式时效果**,如果您不使用设备的首选格式,就可能会导致后台产生额外的内存副本,然后图片才能作为页面的一部分显示。
幸运的是,您不必费心担心任何这些原因,因为 WebGPU 会告知您画布使用哪种格式!几乎在所有情况下,您都需要传递通过调用 navigator.gpu.getPreferredCanvasFormat() 返回的值,如上所示。
注意:与 WebGL 相比,WebGPU 的工作原理有一个很大的不同,因为画布配置与设备创建是相互独立的,因此您可以根据需要让任意数量的画布都通过同一台设备进行渲染!这会让某些用例(例如多窗格 3D 编辑器)更易于开发。
清空画布
现在,您已经有了一台设备,并且配置了画布,接下来就可以开始使用该设备更改画布内容了。首先,请用纯色将其初始化。
为此,您需要向 GPU 提供一些命令,指示它执行什么操作(或者 WebGPU 中的大多数其他操作)。
- 为此,让设备创建 GPUCommandEncoder,以提供用于记录 GPU 命令的接口。
index.html
讯享网const encoder = device.createCommandEncoder();
您要向 GPU 发送的命令与渲染相关(在本例中是清除画布),因此下一步是使用 encoder 开始一个渲染通道(Render Pass)。
渲染通道(Render Pass)是指 WebGPU 中的所有绘制操作都发生的时间。
Render passes are when all drawing operations in WebGPU happen。

每个都以 beginRenderPass() 调用开头,该调用定义了接收所执行的任何绘制命令的输出的纹理。更高级的使用场景可以提供多种纹理(称为“附件 attachments”),其具体用途包括存储渲染几何图形的深度或提供抗锯齿功能。不过,对于此应用,您只需要 1 个。
- 通过调用 context.getCurrentTexture() 从您之前创建的画布上下文获取纹理,这将返回一个纹理,该像素的宽度和高度匹配画布的
width和height属性,以及您调用context.configure()时指定的format。
index.html
const pass = encoder.beginRenderPass({ colorAttachments: [{ view: context.getCurrentTexture().createView(), loadOp: "clear", storeOp: "store", }] });
纹理作为 colorAttachment的 view 属性提供。渲染通道要求您提供 GPUTextureView 而不是 GPUTexture,后者会告知渲染纹理的哪些部分。这对于更高级的用例来说真正重要,因此在这里,您调用 createView() 时不带纹理参数,这表示您希望渲染通道使用整个纹理。
此外,您还必须指定渲染通道在纹理启动时和结束时间对纹理所做的处理:
loadOp值为"clear",表示您希望在渲染通道启动时清除纹理。storeOp的"store"值表示渲染渲染完成后,您需要将渲染过程中完成的任何绘制的结果保存到纹理中。
渲染通道开始后,您什么都没做!至少目前是这样。使用 loadOp: "clear" 启动渲染通道的操作足以清除纹理视图和画布。
- 在
beginRenderPass()之后立即添加以下调用来结束渲染通道:
index.html
讯享网pass.end();
需要注意的是,仅进行这些调用并不会使 GPU 实际上执行任何操作。它们只是记录 GPU 后续需要执行的命令。
- 为了创建
GPUCommandBuffer,请在命令编码器上调用finish()。命令缓冲区是记录的命令的不透明句柄。
index.html
const commandBuffer = encoder.finish();
- 使用
GPUDevice的queue将命令缓冲区提交到 GPU。该队列执行所有 GPU 命令,确保其执行顺序合理且同步正确。队列的submit()方法可接收命令缓冲区数组,但在本例中,您只有一个。
index.html
讯享网device.queue.submit([commandBuffer]);
提交命令缓冲区后,便无法再次使用它,因此无需一直使用。如果要提交更多命令,您需要再构建一个命令缓冲区。因此,将这两个步骤合并为一个是比较常见的情况,如本 Codelab 的示例网页所示:
index.html
// Finish the command buffer and immediately submit it. device.queue.submit([encoder.finish()]);
将命令提交到 GPU 后,让 JavaScript 将控制权交还给浏览器。此时,浏览器会看到您已更改上下文的当前纹理,并更新画布以将该纹理显示为图片。在此之后,如果您想再次更新画布内容,则需要记录并提交一个新的命令缓冲区,并再次调用 context.getCurrentTexture() 以获取渲染通道的新纹理。
- 重新加载页面。请注意,画布上填充了黑色。恭喜!这意味着您已成功创建了您的第一个 WebGPU 应用。

选择一种颜色!
但说实话,黑色正方形太无聊了。所以,请花一点时间转到下一部分,对它进行一些个性化设置。
- 在
device.beginRenderPass()调用中,向colorAttachment添加包含clearValue的新代码行,如下所示:
index.html
讯享网const pass = encoder.beginRenderPass({ colorAttachments: [{ view: context.getCurrentTexture().createView(), loadOp: "clear", clearValue: { r: 0, g: 0, b: 0.4, a: 1 }, // New line storeOp: "store", }], });
clearValue 指示渲染通道在 pass 开始执行 clear 操作时应使用该颜色。传入它的字典包含四个值:r 表示红色、g表示绿色、b 表示蓝色,a 表示 alpha(透明度)。每个值的范围都介于 0 到 1 之间,共同描述该颜色通道的值。例如:
{ r: 1, g: 0, b: 0, a: 1 }为亮红色。{ r: 1, g: 0, b: 1, a: 1 }是亮紫色。{ r: 0, g: 0.3, b: 0, a: 1 }为深绿色。{ r: 0.5, g: 0.5, b: 0.5, a: 1 }为中灰色。{ r: 0, g: 0, b: 0, a: 0 }是默认的透明黑色。
注意:在 WebGPU 中定义颜色与使用 RGB() 函数表示法设置 CSS 颜色非常相似。
专家提示:为了减少输入量,您还可以使用数组简写形式,按 RGBA 顺序提供值。传递 [0, 0.5, 0.7, 1] 等同于传递 { r: 0, g: 0.5, b: 0.7, a: 1 }。
此 Codelab 中的示例代码和屏幕截图使用的是深蓝色,但您可以随意选择想要的颜色!
- 选择颜色后,重新加载页面。您应该会在画布中看到所选颜色。

4. 绘制几何图形
完成本部分后,您的应用将在画布上绘制一些简单的几何图形:一个彩色正方形。需要注意的是,现在显示这么简单的输出似乎需要大量工作,这是因为 WebGPU 旨在高效地渲染大量的几何图形。这种效率的副作用是,执行相对简单的操作可能让人感觉异常困难,但如果您转向 WebGPU 之类的 API,这是预料之中的。您需要执行的操作稍微复杂一些。
了解 GPU 的绘制方式
在对代码做出进一步更改之前,最好快速概括介绍一下 GPU 如何创建在屏幕上显示的形状。(如果您已熟悉 GPU 渲染的工作原理,可以直接跳到“定义顶点”部分。)
与包含大量形状和选项(例如 Canvas 2D)的 API 不同,GPU 实际上仅处理几种不同类型的形状(或 WebGPU 所指的基础图元):点、线和三角形。在本 Codelab 中,您将仅使用三角形。
GPU 几乎只使用三角形,因为三角形具有非常多的数学属性,使其能够以可预测且高效的方式进行处理。几乎所有通过 GPU 绘制的三角形都需要拆分成三角形,GPU 才能绘制到这些三角形,而且这些三角形必须由它们的顶点定义。
这些点(或顶点)以 X、Y 和 3D 内容(对于 3D 内容)中的 Z 值表示,这些值定义由 WebGPU 或类似 API 定义的笛卡尔坐标系。关于坐标系与页面上画布的关系,我们很容易想到它。无论您的画布宽度或高度是多少,左侧边缘的 X 轴始终为 -1,而右边缘的 X 轴始终为 +1。同样,Y 轴上的底边始终是 -1,上边的 Y 轴始终是 +1。这意味着 (0, 0) 始终是画布中心,(-1, -1) 始终位于左下角,而 (1, 1) 始终位于右上角。这称为“裁剪空间Clip Space”。
最初,在这种坐标系中很少定义顶点,因此 GPU 依靠名为“顶点着色器”的小段程序来执行将数学转换顶点空间所需的任何数学运算,以及绘制顶点所需的任何其他计算。例如,着色器可能会应用一些动画或计算从顶点到光源的方向。这些着色器由您作为 WebGPU 开发者编写,它们可让您显著控制 GPU 的工作方式。
The vertices are rarely defined in this coordinate system initially, so GPUs rely on small programs called vertex shaders to perform whatever math is necessary to transform the vertices into clip space, as well as any other calculations needed to draw the vertices. For example, the shader may apply some animation or calculate the direction from the vertex to a light source. These shaders are written by you, the WebGPU developer, and they provide an amazing amount of control over how the GPU works.
然后,GPU 会提取这些转换顶点构成的所有三角形,并确定需要绘制屏幕上的像素。然后,它会运行您编写的另一个小段程序,称为 fragment 着色器,用于计算每个像素应该是什么颜色。这种计算既可以很简单,也可以像返回绿色一样简单;也可以计算表面相对于其他附近表面的日光反射的角度,通过雾化进行过滤,并根据表面的金属度进行修改。这完全由您掌控,既有强大的力量,又有挑战。
然后,这些像素颜色的结果会叠加到纹理中,然后可以在屏幕上显示。
From there, the GPU takes all the triangles made up by these transformed vertices and determines which pixels on the screen are needed to draw them. Then it runs another small program you write called a fragment shader that calculates what color each pixel should be. That calculation can be as simple as return green or as complex as calculating the angle of the surface relative to sunlight bouncing off of other nearby surfaces, filtered through fog, and modified by how metallic the surface is. It's entirely under your control, which can be both empowering and overwhelming.
The results of those pixel colors are then accumulated into a texture, which is then able to be shown on screen.
注意:此 Codelab 将仅处理 2D 形状,但此过程也适用于 3D 内容!根据 GPU 而言,2D 和 3D 有何区别?略加算算!在绘制三角形之前,3D 内容通常使用一系列矩阵来转换顶点着色器中的位置(稍后会介绍),以便感知深度和体积,但最后,GPU 绘制的几乎所有内容都是裁剪空间中的三角形。
定义顶点
如前所述,The Game of Life 模拟以单元格网格形式显示。您的应用需要一种直观呈现网格来区分活动单元格和闲置单元格的方法。此 Codelab 中使用的方法是绘制活跃单元格中的彩色方块,并将闲置单元格留空。
这意味着需要为 GPU 提供四个不同的点,每个正方形的四个角各对应一个点。例如,在画布中心绘制的正方形从边缘插入的方式有如下角坐标:
This means that you'll need to provide the GPU with four different points, one for each of the four corners of the square. For example, a square drawn in the center of the canvas, pulled in from the edges a ways, has corner coordinates like this:

为了将这些坐标传送到 GPU,您需要将这些值放在 TypedArray 中。如果您还不熟悉此方法,TypedArray 就是一组 JavaScript 对象,让您能够分配连续的内存块,并将系列中的每个元素解读为特定的数据类型。例如,在 Uint8Array 中,数组中的每个元素都是一个无符号字节。TypedArray 非常适合使用对内存布局敏感的 API(例如 WebAssembly、WebAudio 和(当然)WebGPU)来回发送数据。
对于方形示例,由于值是小数,因此适合使用 Float32Array。
- 在您的代码中添加以下数组声明,创建一个用于存储图中所有顶点位置的数组。建议将其放置在顶部附近,就在
context.configure()调用下方。
index.html
const vertices = new Float32Array([ // X, Y, -0.8, -0.8, 0.8, -0.8, 0.8, 0.8, -0.8, 0.8, ]);
请注意,空格和注释不会影响这些值;它们只是为了方便您使用,使其更易于阅读。这有助于您了解每对值组成一个顶点的 X 坐标和 Y 坐标。
但有一个问题!GPU 的三角形的运作方式是这样吗?这意味着,您需要以 3 个群组为单位提供该顶点。你有四组。解决方案是重复两个顶点,以形成两个三角形共用,这些边缘穿过正方形的中心位置。
为了用图表形成方形,您必须列出两个 (-0.8, -0.8) 和 (0.8, 0.8) 顶点,分别代表蓝色三角形和红色三角形。(您也可以选择将边角与其他两个角分开;这不会产生任何影响。)
- 更新之前的
vertices数组,如下所示:此处是右手坐标系
index.html
讯享网const vertices = new Float32Array([ // X, Y, -0.8, -0.8, // Triangle 1 (Blue) 0.8, -0.8, 0.8, 0.8, -0.8, -0.8, // Triangle 2 (Red) 0.8, 0.8, -0.8, 0.8, ]);
为清楚起见,此图显示两个三角形之间的间隔,顶点位置则完全相同,而 GPU 渲染这些三角形时没有间隙。它将以单个纯方形呈现。
注意:您无需重复顶点数据即可制作三角形。使用所谓的 Index Buffers,您可以向 GPU 提供单独的值列表,告知它哪些顶点要连接三角形,这样就无需重复。就像是连点吧!由于您的顶点数据非常简单,因此使用索引缓冲区不在此 Codelab 的讨论范围内。但很显然,您可能希望使用它来构建更复杂的几何图形。
创建顶点缓冲区
GPU 无法使用 JavaScript 数组中的数据绘制顶点。GPU 通常有自己的内存,该内存经过高度优化,因此需要您在 GPU 绘制过程中使用的所有数据。
对于很多值(包括顶点数据),可通过 GPUBuffer 对象管理 GPU 端内存。缓冲区是 GPU 可以轻松访问的一种内存块,并会针对特定用途进行标记。它有点像 GPU 可见的 TypedArray。
- 若要创建用于保存顶点的缓冲区,请在
vertices数组定义后添加以下对 device.createBuffer() 的调用。
index.html
const vertexBuffer = device.createBuffer({ label: "Cell vertices", size: vertices.byteLength, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, });
首先要注意的是,您为缓冲区提供标签。您创建的每个 WebGPU 对象都可以有一个可选标签,您当然是想要这样做的!标签可以是您期望的任何字符串,只要有助于您识别对象是什么。如果您遇到任何问题,这些标签就会用在 WebGPU 生成的错误消息中,以帮助您了解哪里出了问题。
接下来,为缓冲区指定一个大小(以字节为单位)。您需要一个 48 字节的缓冲区,该缓冲区由 32 位浮点数(4 字节)乘以 vertices 数组(12)的浮点数来确定。值得高兴的是,TypedArrays 已经为您计算了其 byteLength,因此您可以在创建缓冲区时使用它。
注意:请熟悉这种字节大小的数学公式。使用 GPU时 需要相当多的时间与它打交道!
最后,您需要指定缓冲区的用法。这是一个或多个 GPUBufferUsage 标志,其中多个标志与 |(按位 OR)运算符结合使用。在本例中,您指定希望将缓冲区用于顶点数据 (GPUBufferUsage.VERTEX),并且希望能够将数据复制到其中 (GPUBufferUsage.COPY_DST)。
返回的缓冲区对象是不透明的,您无法(轻松)检查其包含的数据。此外,它的大部分属性是不可变的,您无法在创建 GPUBuffer 后调整其大小,也无法更改用法标志。您可以更改其内存内容。
最初创建缓冲区时,它包含的内存将初始化为零。有多种方式可以更改其内容,但最简单的方法是使用要复制的 TypedArray 调用 device.queue.writeBuffer()。
- 如需将顶点数据复制到缓冲区的内存中,请添加以下代码:
index.html
讯享网device.queue.writeBuffer(vertexBuffer, /*bufferOffset=*/0, vertices);
定义顶点布局
现在,您有了一个包含顶点数据的缓冲区,但就 GPU 而言,它只是一个 blob 字节。如果您要使用此类内容进行绘制,则需要再多提供一些信息。您需要能够让 WebGPU 更详细地了解顶点数据的结构。
- 使用 GPUVertexBufferLayout 字典定义顶点数据结构:
index.html
const vertexBufferLayout = { arrayStride: 8, attributes: [{ format: "float32x2", offset: 0, shaderLocation: 0, // Position, see vertex shader }], };
乍一看,这有点令人迷惑,但可以拆开仔细分析。
您要提供的第一项内容是 arrayStride。这是 GPU 在查找下一个顶点时需要在缓冲区中向前跳过的字节数。方形的每个顶点由两个 32 位浮点数组成。如前所述,32 位浮点数为 4 个字节,因此两个浮点数为 8 个字节。
接下来是 attributes 属性,它是一个数组。属性是编码到每个顶点中的各个信息。您的顶点仅包含一个属性(顶点位置),但更高级的用例经常会包含包含多个属性的顶点,例如顶点的颜色或几何图形表面的朝向。不过,这不属于此 Codelab 的讨论范围。
在您的单个属性中,您首先要定义数据的 format。此值来自 GPUVertexFormat 类型的列表,用于描述 GPU 可以理解的每种顶点数据。您的顶点有两个 32 位浮点数,因此请使用 float32x2 格式。例如,如果您的顶点数据由四个 16 位无符号整数组成,则应该改用 uint16x4。看到图案了吗?
接下来,offset 描述此特定属性开始顶点的字节数。仅当您的缓冲区中包含多个属性时,您才需要担心这一点,在本 Codelab 中,您不会遇到这种情况。
最后,您将拥有 shaderLocation。这是一个介于 0 到 15 之间的任意数字,对您定义的每个属性而言必须是唯一的。它将此属性与顶点着色器中的特定输入相关联,我们将在下一部分中对此进行介绍。
请注意,虽然您现在定义了这些值,但它们实际上还没有传入到 WebGPU API 的任何位置。这个值即将出现,但最容易在定义顶点时考虑这些值,因此您现在需要对它们进行设置,以供日后使用。
开始使用着色器
现在,您已拥有要渲染的数据,但仍需要告知 GPU 如何处理具体数据。这在很大程度上取决于着色器。
着色器是指您编写并在 GPU 上执行的一小段程序。每个着色器在数据的不同阶段运行:Vertex 处理、Fragment 处理或常规计算。由于这些容器位于 GPU 上,因此其结构比普通 JavaScript 更加严格。但是,这种结构使它们能够非常快速地并行执行!
WebGPU 中的着色器采用名为 WGSL(WebGPU 着色语言)的着色语言编写。WGSL 在语法上有些与 Rust 类似,其中包含旨在更轻松快捷地实现常见 GPU 工作类型(例如矢量和矩阵数学)的功能。教授整个着色语言的过程远远不在本 Codelab 的讲解范围内,但我们希望您在学习一些简单示例时能够掌握一些基础知识。
着色器本身会作为字符串传入 WebGPU。
- 通过将以下代码复制到
vertexBufferLayout下的代码中,创建一个用于输入着色器代码的位置:
index.html
讯享网const cellShaderModule = device.createShaderModule({ label: "Cell shader", code: ` // Your shader code will go here ` });
如要创建着色器,您需要调用 device.createShaderModule(),并在其中以字符串形式提供 label 和 WGSL code(可选)。(请注意,此处使用反引号允许多行字符串!)添加一些有效的 WGSL 代码后,该函数会返回包含编译结果的 GPUShaderModule 对象。
注意:WGSL 着色器具有一个相对严格的编译器,可强制执行强类型值之类的操作并消除未定义的行为,这对跨平台兼容性非常重要。因此,在构建自己的 WebGPU 应用时,您肯定会遇到着色器编译器错误。但不必担心。与其他 WebGPU 一样,WGSL 编译器会在出现问题时向 Play 管理中心输出非常详细的消息。
定义顶点着色器
从 顶点着色器开始,因为 GPU 也将从这里开始!
顶点着色器被定义为函数,GPU 会针对 vertexBuffer 中的每个顶点调用一次该函数。由于 vertexBuffer 包含六个位置(顶点),因此您定义的函数会被调用六次。每次调用该方法时,都会将 vertexBuffer 中的不同的顶点作为参数传递给该函数,顶点着色器函数的作用是在裁剪空间中返回对应的位置。
A vertex shader is defined as a function, and the GPU calls that function once for every vertex in your vertexBuffer. Since your vertexBuffer has six positions (vertices) in it, the function you define gets called six times. Each time it is called, a different position from the vertexBuffer is passed to the function as an argument, and it's the job of the vertex shader function to return a corresponding position in clip space.
也务必要理解,系统不一定会按顺序调用它们。相反,GPU 擅长并行运行上述着色器,这可能会同时处理数百(甚至数千!)顶点!GPU 拥有不可思议的速度,但这存在很大限制,存在一定限制。为了确保极端并行化,顶点着色器不能相互通信。每次着色器调用时只能查看单个顶点的数据,并且只能输出单个顶点的值。
在 WGSL 中,您可以为顶点着色器函数指定任意名称,但必须在前面指定 @vertex 属性,以指明它代表的着色器阶段。WGSL 表示带有 fn 关键字的函数,使用圆括号声明任何参数,并使用大括号定义范围。
- 创建一个如下所示的空
@vertex函数:
index.html(createShaderModule 代码)
@vertex fn vertexMain() { }
但这无效,因为顶点着色器必须返回至少在裁剪空间中处理的顶点的最终位置。始终将此值指定为 4 维矢量。矢量是着色器中常用的标记,在语言中,它们被视为一级基元(first-class primitives),具有自己的类型,例如用于四维矢量的 vec4f。2D 矢量 (vec2f) 和 3D 矢量 (vec3f) 也有类似类型!
- 为了指明返回的值为所需位置,请使用
@builtin(position)属性进行标记。->符号用于指明这是函数返回的内容。
index.html (createShaderModule 代码)
讯享网@vertex fn vertexMain() -> @builtin(position) vec4f { }
当然,如果函数具有返回值类型,则实际上需要在函数正文中返回值。您可以使用语法 vec4f(x, y, z, w) 构造要返回的新 vec4f。x、y 和 z 值都是浮点数,在返回值中表示顶点在裁剪空间中的位置。
注意:什么是 w?w 值是三维顶点齐次坐标的第四个分量。有点混乱了?没关系!您不必担心。
实际上,如果使用 w 值,就可以在数学上应用 4x4 矩阵,这是您在呈现 3D 图形时要执行的很多操作,但很少需要直接操作。在此 Codelab 中,您只需要将其保留为 1。
- 返回
(0, 0, 0, 1)的静态值,从技术上来说,您具有有效的顶点着色器,尽管该 GPU 永远不会显示任何内容,因为 GPU 发现它生成的三角形只是一个点,然后会舍弃它。
index.html (createShaderModule 代码)
@vertex fn vertexMain() -> @builtin(position) vec4f { return vec4f(0, 0, 0, 1); // (X, Y, Z, W) }
您只是想使用自己创建的缓冲区中的数据,为此,需要使用 @location() 指定(与您在 vertexBufferLayout 中描述的相匹配)函数的参数名称及参数类型。您指定的 shaderLocation 为 0,因此在 WGSL 代码中,请使用 @location(0) 标记该参数。您还将格式定义为 float32x2(2D 矢量),因此在 WGSL 中,您的参数为 vec2f。您可以随意为其命名,但由于这些位置代表您的顶点位置,因此 pos 这类名称似乎很自然。
- 将着色器函数更改为以下代码:
index.html (createShaderModule 代码)
讯享网@vertex fn vertexMain(@location(0) pos: vec2f) -> @builtin(position) vec4f { return vec4f(0, 0, 0, 1); }
现在,您需要返回该位置。由于位置是 2D 矢量,而返回类型是 4D 矢量,因此您必须稍微更改它。您要从位置参数中获取两个分量,并将其置于返回矢量的前两个分量中,使最后两个分量分别保持为 0 和 1。
- 通过明确说明要使用的位置组成部分,返回正确的位置:
index.html (createShaderModule 代码)
@vertex fn vertexMain(@location(0) pos: vec2f) -> @builtin(position) vec4f { return vec4f(pos.x, pos.y, 0, 1); }
不过,由于此类映射在着色器中很常见,因此您可以轻松地将位置矢量作为第一个参数传入到方便的位置,表示含义相同。
- 使用以下代码重写
return语句:
index.html (createShaderModule 代码)
讯享网@vertex fn vertexMain(@location(0) pos: vec2f) -> @builtin(position) vec4f { return vec4f(pos, 0, 1); }
这是您的初始顶点着色器!这非常简单,只是在一开始有效传递位置信息,但这样做是很好的切入点。
定义 fragment 着色器
接下来是 fragment 着色器。片元着色器的运作方式与顶点着色器的运作方式非常相似,但系统会为绘制的每个像素调用这些着色器,而不是为每个顶点调用。
总是在顶点着色器之后调用 Fragment 着色器。GPU 接受顶点着色器的输出并对其进行三角化,基于三点集创建三角形。然后,它会计算出该三角形中包含哪些输出颜色附件的像素,并为每个像素调用一次 fragment 着色器,从而对每个三角形进行光栅化。片元着色器会返回一种颜色(通常从顶点着色器发送给它的值以及纹理等资源计算得出),GPU 会写入这些颜色。
Fragment shaders are always called after vertex shaders. The GPU takes the output of the vertex shaders and triangulates it, creating triangles out of sets of three points. It then rasterizes each of those triangles by figuring out which pixels of the output color attachments are included in that triangle, and then calls the fragment shader once for each of those pixels. The fragment shader returns a color, typically calculated from values sent to it from the vertex shader and assets like textures, which the GPU writes to the color attachment.
与顶点着色器一样,片元着色器以极其并行的方式执行。它们在输入和输出方面比顶点着色器更灵活,但您可以认为他们只需为每个三角形的每个像素返回一种颜色。
WGSL 片元着色器函数以 @fragment 属性表示,会返回 vec4f。但在这种情况下,矢量代表颜色,而不是位置。您需要为返回值赋予 @location 属性,以指明返回的颜色要写入 beginRenderPass 中的哪个 colorAttachment。由于您只有 1 个attachment,因此位置为 0。
- 创建一个如下所示的空
@fragment函数:
index.html (createShaderModule 代码)
@fragment fn fragmentMain() -> @location(0) vec4f { }
返回的矢量的四个组成部分是红色、绿色、蓝色和 Alpha 值,其解释方式与您之前在 beginRenderPass 中设置的 clearValue 完全相同。因此,vec4f(1, 0, 0, 1) 为亮红色,看似正合适您的方形颜色。不过,您可以随意设置自己喜欢的颜色!
- 设置如下所示的返回矢量:
index.html (createShaderModule 代码)
讯享网@fragment fn fragmentMain() -> @location(0) vec4f { return vec4f(1, 0, 0, 1); // (Red, Green, Blue, Alpha) }
这就是完整的 fragment 着色器!这并不是很有趣;它只是将每个三角形的每一个像素都设为红色,但目前还足以满足需求。
重复一下,添加上述着色器代码后,您的 createShaderModule 调用现在如下所示:
index.html
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); } ` });
注意:您可以根据需要为顶点和片段着色器创建一个单独的着色器模块。例如,如果您想将多个不同的fragment 着色器与同一顶点着色器搭配使用,则会发现这样做非常有用。
创建渲染流水线
着色器模块不能用于自行渲染。相反,您必须将其用作通过调用 device.createRenderPipeline() 创建的 GPURenderPipeline 的一部分来使用。渲染管道可控制如何绘制几何图形,包括如何使用着色器、如何解读顶点缓冲区中的数据、应渲染的几何图形类型(线条、点、三角形等)!
渲染管道是整个 API 中最复杂的对象,但是不用担心!您可以向其传递的大多数值都是可选的,您只需提供几个值即可开始。
- 创建一个渲染流水线,如下所示:
index.html
讯享网const cellPipeline = device.createRenderPipeline({ label: "Cell pipeline", layout: "auto", vertex: { module: cellShaderModule, entryPoint: "vertexMain", buffers: [vertexBufferLayout] }, fragment: { module: cellShaderModule, entryPoint: "fragmentMain", targets: [{ format: canvasFormat }] } });
每个流水线都需要一个 layout 来描述流水线需要的输入类型(顶点缓冲区除外),但您没有这样的输入。幸运的是,您现在可以传递 "auto",流水线会从着色器构建自己的布局。
接下来,您必须提供有关 vertex 阶段的详细信息。module 是包含您的顶点着色器的 GPUShader 模块,entryPoint 是在着色器代码中为每次顶点调用调用的函数的名称。(单个着色器模块中可包含多个 @vertex 和 @fragment 函数!)buffers 是一个 GPUVertexBufferLayout 对象数组,用于描述如何将数据打包到您与此流水线使用的顶点缓冲区中。幸运的是,您之前在 vertexBufferLayout 中定义了此内容!您将该代码传递到此函数。
最后是 fragment 阶段的详细信息。它还包括着色器模块和 entryPoint,就像顶点阶段一样。最后一步是定义此流水线一起使用的 targets。这是一个字典数组,可提供流水线输出的颜色附件的详细信息(例如纹理 format)。这些详细信息需要匹配此流水线一起使用的任何渲染通道的 colorAttachments 中提供的纹理。渲染通道使用画布上下文中的纹理,并使用您在 canvasFormat 中保存的值作为其格式,因此此处传递的是相同的格式。
这还没有接近您在创建渲染管道时可以指定的所有选项,但足以满足此 Codelab 的需求!
注意:为什么这个对象中打包了太多内容?这种情况有多种原因。首先,并非所有选项组合都有效,因此,通过在一个位置提供所有选项,您可以在创建时轻松指明流水线是否可用。这会加快之后使用流水线绘制的速度,因为您无需检查很多内容。(这与 WebGL 大相径庭,后者每次绘制调用时都必须验证大量设置。)
第二个原因是,它可以让您通过单次 JavaScript 调用指示渲染在大量时间传递大量信息。这减少了您需要执行的总调用次数,还有助于提升渲染效率。(同样,WebGL 也在这个艰难的领域。)
绘制方形
至此,您已经有了绘制方形所需的一切!
- 若要绘制方块,请回退到 encoder.beginRenderPass() 和 pass.end() 对的调用,然后在它们之间添加以下新命令:
index.html
// After encoder.beginRenderPass() pass.setPipeline(cellPipeline); pass.setVertexBuffer(0, vertexBuffer); pass.draw(vertices.length / 2); // 6 vertices // before pass.end()
这会为 WebGPU 提供绘制方形所需的全部信息。首先,您需要使用 setPipeline() 来指明应使用哪个流水线进行绘制。其中包括使用的着色器、顶点数据的布局和其他相关状态数据。
接下来,使用包含方形的顶点的缓冲区调用 setVertexBuffer()。您使用 0 调用它,因为此缓冲区对应于当前流水线的 vertex.buffers 定义中的第 0 个元素。
最后,您调用了 draw(),在进行之前的所有设置后,这似乎很奇怪。您只需要传入应该渲染的顶点数,该顶点会从当前设置的顶点缓冲区中提取,并使用当前设置的流水线进行解释。您只需将其硬编码为 6,但从顶点数组(12 个浮点数 / 每个顶点 2 个坐标 == 6 个顶点)中计算得出,这意味着,如果您决定将正方形替换成圆形,手动更新的次数会变少。
- 刷新屏幕,并最终看到所有辛勤工作的结果:一个彩色大方形。

5. 绘制网格
首先,请花点时间祝贺您!对大多数 GPU API 来说,在屏幕上获取几何图形的前几个部分通常是最难的步骤之一。您在这一步所做的所有步骤都能用更小的步骤完成,让您更轻松地验证进度。
在本部分中,您将了解:
- 如何通过 JavaScript 将变量(称为“uniform”)传递到着色器。
- 如何使用 uniform 更改渲染行为。
- 如何使用示例来绘制同一几何图形的许多不同变体。
定义网格 Define the grid
若要渲染网格,您需要掌握一些关于该网格的非常基础的信息。其中包含多少个单元格,宽度和高度是多少?这由开发者决定,但为简单起见,请将网格视为方形(宽度和高度相同),并使用2的幂。(这使得部分计算更加轻松。)您最终会想要将图表放大一些,但在本部分的其余部分,将网格大小设置为 4x4,因为这样可以更轻松地演示此部分使用的一些数学概念。随后再扩大规模!
- 通过在 JavaScript 代码顶部添加一个常量,定义网格大小。
index.html
讯享网const GRID_SIZE = 4;
接下来,您需要更新方形的渲染方式,以便在画布上放置 GRID_SIZE 乘以 GRID_SIZE。这意味着方形需要小得多,需要包含大量方形。
现在,您可以方法之一是让顶点缓冲区明显变大,并定义大小合适的 GRID_SIZE 乘以GRID_SIZE方块。事实上,这方面的代码并不是很糟糕!只需要添加几个循环和一些数学概念即可。但这也无法充分利用 GPU,占用的内存也过多,无法实现此效果。本部分介绍了一种更适合 GPU 的方法。
创建统一缓冲区 Create a uniform buffer
首先,您需要将所选网格大小传递给着色器,因为它会更改着色器的显示方式。只需将尺寸硬编码到着色器,但这意味着每当想要更改网格大小时,您都必须重新创建着色器并渲染管道,但开销非常高。更好的方法是以uniforms的形式向着色器提供网格大小。
您在前面已经了解到,在每次调用顶点着色器时 会传递顶点缓冲区中一个不同的位置值。uniform 是每次调用时都使用缓冲区中的同一个值。它们对于传达几何形状(例如其位置)、完整动画帧(例如当前时间)甚至是应用的整个生命周期(例如用户偏好设置)很有用。
You learned earlier that a different value from the vertex buffer is passed to every invocation of a vertex shader. A uniform is a value from a buffer that is the same for every invocation. They're useful for communicating values that are common for a piece of geometry (like its position), a full frame of animation (like the current time), or even the entire lifespan of the app (like a user preference).
- 通过添加以下代码创建一个统一缓冲区:
index.html
// Create a uniform buffer that describes the grid. const uniformArray = new Float32Array([GRID_SIZE, GRID_SIZE]); const uniformBuffer = device.createBuffer({ label: "Grid Uniforms", size: uniformArray.byteLength, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, }); device.queue.writeBuffer(uniformBuffer, 0, uniformArray);
这个代码应该非常熟悉,因为它与您之前创建顶点缓冲区时所用的代码完全相同!这是因为 uniform 通过顶点相同的 GPUBuffer 对象传达给 WebGPU API,主要区别在于这次的 usage 包含 GPUBufferUsage.UNIFORM,而不是 GPUBufferUsage.VERTEX。
注意:您可以在这里改用 Uint32Array,因为网格大小值不是小数,但在本例中,它会导致即将进行的着色器代码更改变得更为混乱,因为您最终还是会在多个位置将值转换为浮点数。请记住,浮点数据不是可以放在 GPUBuffer 中的唯一一种类型!
在 着色器 中使用 uniform
- 通过添加以下代码来定义 uniform:
index.html(createShaderModule 调用)
讯享网// At the top of the `code` string in the createShaderModule() call @group(0) @binding(0) var<uniform> grid: vec2f; @vertex fn vertexMain(@location(0) pos: vec2f) -> @builtin(position) vec4f { return vec4f(pos / grid, 0, 1); } // ...fragmentMain is unchanged
这样会在着色器中定义一个名为 grid 的 uniform,它是一个 2D 浮点矢量,与刚刚复制到 uniform 缓冲区的数组匹配。还规定,uniform 在 @group(0) 和 @binding(0) 处绑定。稍后您将了解这些值的含义。
然后,在 着色器 代码的其他位置,您可以根据需要使用grid矢量。在此代码中,您将顶点位置除以grid矢量。由于 pos 是一个 2D 矢量,而 grid 是一个 2D 矢量,WGSL 会执行对应分量除法。换句话说,结果与显示 vec2f(pos.x / grid.x, pos.y / grid.y) 相同。
这些类型的矢量操作在 GPU 着色器中很常见,因为许多渲染和计算技术都依赖于它们。
这表示所使用的方形大小为原始大小的四分之一(如果您使用网格大小为 4 的方形)。如果您希望将 4 个方形 放入一行或一列,则非常适合这样做!
创建绑定组 Create a Bind Group
不过,在 着色器 中声明 uniform 并不会将其与您创建的缓冲区 连接起来。为此,您需要创建并设置绑定群组(bind group)。
绑定组是您希望同时可供着色器访问的资源的集合。它可以包含多种类型的缓冲区(例如统一缓冲区),以及其他资源(如纹理和采样器),但本文未涵盖但它们是 WebGPU 渲染技术的常见部分。
- 在创建统一缓冲区和渲染管道后,添加以下代码,使用统一缓冲区创建绑定组:
index.html
const bindGroup = device.createBindGroup({ label: "Cell renderer bind group", layout: cellPipeline.getBindGroupLayout(0), entries: [{ binding: 0, resource: { buffer: uniformBuffer } }], });
除了现在标准的 label 之外,您还需要一个 layout 来描述此绑定组包含的资源类型。我们会在后续步骤中深入探讨这一问题,但现在您可以愉快地询问流水线是否提供了绑定组布局,因为您是使用 layout: "auto" 创建的流水线。这会使流水线根据您在着色器代码本身中声明的绑定自动创建绑定组布局。在本例中,您需要向 getBindGroupLayout(0) 发出请求,其中 0 对应于您在着色器中输入的 @group(0)。
指定布局后,您需要提供 entries 数组。每个条目都是一个包含至少以下值的字典:
binding,对应于您在着色器中输入的@binding()值。在此示例中为0。resource,即要在指定绑定索引处向变量公开的实际资源。在此例中是统一缓冲区。
该函数会返回一个 GPUBindGroup,这是一个不透明的不可变句柄。绑定群组一经创建,您便无法更改所指向的资源,但您可以更改这些资源的内容。例如,如果您将 uniform 缓冲区更改为包含新的网格大小,那么今后使用此绑定组进行绘制时都会体现这一点。
绑定 绑定组 Bind the bind group
现在,绑定组已创建,您仍需要指示 WebGPU 在绘图时使用它。幸运的是,这非常简单。
- 返回到呈现通道,并在
draw()方法之前添加下面这行新代码:
index.html
讯享网pass.setPipeline(cellPipeline); pass.setVertexBuffer(0, vertexBuffer); pass.setBindGroup(0, bindGroup); // New line! pass.draw(vertices.length / 2);
作为第一个参数传递的 0 对应于着色器代码中的 @group(0)。您是说,@group(0) 中的每个 @binding 都会使用此绑定组中的资源。
现在,统一缓冲区已 暴露给 着色器!
- 刷新页面,然后您应该会看到如下内容:

太棒了!您的面积现在是原来的 1/4!这实现的功能并不多,但可以看出您的 uniform 实际上已应用,且 着色器现在可以访问网格的大小。
在 着色器中操纵几何图形
现在,您可以在 着色器 中引用网格大小,接下来可以开始做一些工作来操纵您渲染的几何形状,使其适合网格样式。为此,请认真考虑你想要达成的目标。
您需要从概念上将画布拆分为多个单元格。为遵循惯例,X 轴向右移动,而 Y 轴向上移动,那么假设第一个单元格位于画布左下角。这会得到如下所示的布局,其中您的当前方形几何图形位于中间:

您的挑战是在 着色器 中寻找一种方法,让您在给定单元格坐标的任何单元格中放置方形几何图形。
首先,您可以看到方形没有与任何单元格对齐良好,因为它被定义为围绕画布中心。您需要使方形的单元格偏移 0.5 个单元格,以使其在单元格内居中对齐。
解决此问题的一种方法是更新方形的顶点缓冲区。例如,通过移动顶点,使右下角位于 (0.1, 0.1) 而非 (-0.8, -0.8),这样就能更好地将这个方块与单元格边界对齐。但是,由于您可以在着色器中完全控制顶点的处理方式,因此使用着色器代码将这些顶点微移到位就非常容易!
- 使用以下代码修改顶点着色器模块:
index.html(createShaderModule 调用)
@group(0) @binding(0) var<uniform> grid: vec2f; @vertex fn vertexMain(@location(0) pos: vec2f) -> @builtin(position) vec4f { // Add 1 to the position before dividing by the grid size. let gridPos = (pos + 1) / grid; return vec4f(gridPos, 0, 1); }



版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/55169.html