本系列介绍 WebGPU 和一般计算机图形学。
首先让我们看看我们要构建什么,
生命游戏
3D 渲染
3D 渲染,但有灯光
渲染 3D 模型
除了JS基础知识外,不需要任何基础知识。
教程已经在我的 github 上完成,附有源代码。
WebGPU 是一个相对较新的 GPU API。尽管名为 WebGPU,但它实际上可以被视为 Vulkan、DirectX 12、Metal、OpenGL 和 WebGL 之上的一层。它被设计为低级 API,旨在用于高性能应用程序,例如游戏和模拟。
在本章中,我们将在屏幕上绘制一些东西。第一部分将参考 Google Codelabs 教程。我们将在屏幕上创建一个生活游戏。
起点
我们将在启用 typescript 的 vite 中创建一个空的普通 JS 项目。然后清除所有多余的代码,只留下main.ts。
const main = async () => { console.log('Hello, world!') } main()
在实际编码之前,请检查您的浏览器是否启用了 WebGPU。您可以在 WebGPU Samples 上查看它。
Chrome 现在默认处于启用状态。在 Safari 上,您应该转到开发者设置、标记设置并启用 WebGPU。
我们还需要为 WebGPU 启用这些类型,安装 @webgpu/types,并在 tsc 编译器选项中添加 "types": ["@webgpu/types"]。
此外,我们替换了
画一个三角形
WebGPU 有很多样板代码,如下所示。
请求设备
首先我们需要访问 GPU。在WebGPU中,是通过适配器的概念来完成的,适配器是GPU和浏览器之间的桥梁。
const adapter = await navigator.gpu.requestAdapter();
然后我们需要向适配器请求一个设备。
const device = await adapter.requestDevice(); console.log(device);
配置画布
我们在画布上绘制三角形。我们需要获取canvas元素并配置它。
const canvas = document.getElementById('app') as HTMLCanvasElement; const context = canvas.getContext("webgpu")!; const canvasFormat = navigator.gpu.getPreferredCanvasFormat(); context.configure({ device: device, format: canvasFormat, });
这里,我们使用 getContext 来获取画布的相关信息。通过指定 webgpu,我们将获得一个负责使用 WebGPU 进行渲染的上下文。
CanvasFormat其实就是颜色模式,例如srgb。我们通常只使用首选格式。
最后,我们使用设备和格式配置上下文。
了解 GPU 渲染管线
在深入研究工程细节之前,我们首先必须了解 GPU 如何处理渲染。
GPU 渲染管道是 GPU 渲染图像所采取的一系列步骤。
在 GPU 上运行的应用程序称为着色器。着色器是运行在GPU上的程序。着色器有一种特殊的编程语言,我们稍后会讨论。
渲染管道有以下步骤,
- CPU 将数据加载到 GPU 中。 CPU可能会移除一些不可见的物体以节省GPU资源。
- CPU 设置 GPU 渲染场景所需的所有颜色、纹理和其他数据。
- CPU 触发对 GPU 的绘制调用。
- GPU从CPU获取数据并开始渲染场景。
- GPU 运行到几何进程,该进程处理场景的顶点。
- 在几何过程中,第一步是顶点着色器,它处理场景的顶点。它可能会变换顶点,改变顶点的颜色,或者对顶点做其他事情。
- 下一步是曲面细分着色器,它处理场景的顶点。它对顶点进行细分,其目的是增加场景的细节。它的程序也很多,但是太复杂了,无法在这里解释。
- 下一步是几何着色器,它处理场景的顶点。与顶点着色器相比,开发人员只能定义如何变换一个顶点,而几何着色器可以定义如何变换多个顶点。它还可以创建新的顶点,新的顶点可用于创建新的几何体。
- 几何处理的最后一步包括裁剪,去除超出屏幕的多余部分,以及剔除,去除相机不可见的不可见部分。
- 下一步是光栅化过程,将顶点转换为片段。片段是将要在屏幕上渲染的像素。
- 下一步是三角形迭代,即迭代场景的三角形。
- 下一步是片段着色器,它处理场景的片段。它可能会改变片段的颜色,改变片段的纹理,或者对片段做其他事情。在这一部分中,还进行了深度测试和模板测试。深度测试是指为每个片段赋予深度值,深度值最小的片段将被渲染。 Stencil测试是指为每个fragment赋予stencil值,通过stencil测试的fragment将被渲染。模板值由开发者决定。
- 下一步是混合过程,混合场景的片段。例如,如果两个片段重叠,则混合过程会将两个片段混合在一起。
- 最后一步是输出过程,将碎片输出到交换链。交换链是用于渲染场景的图像链。更简单地说,它是一个缓冲区,用于保存将要在屏幕上显示的图像。
根据图元(GPU 可以渲染的最小单位)的不同,管道可能有不同的步骤。通常,我们使用三角形,它通知 GPU 将每 3 组顶点视为一个三角形。
创建渲染通道
Render Pass 是完整 GPU 渲染的一个步骤。创建渲染通道后,GPU 将开始渲染场景,完成后反之亦然。
要创建渲染通道,我们需要创建一个编码器,负责将渲染通道编译为 GPU 代码。
const main = async () => { console.log('Hello, world!') } main()
然后我们创建一个渲染通道。
const adapter = await navigator.gpu.requestAdapter();
在这里,我们创建一个带有颜色附件的渲染通道。附件是 GPU 中的一个概念,表示将要渲染的图像。一张图像可能有很多个方面需要 GPU 处理,每个方面都是一个附件。
这里我们只有一个附件,就是颜色附件。视图是 GPU 将在其上渲染的面板,这里我们将其设置为画布的纹理。
loadOp是GPU在渲染通道之前执行的操作,clear表示GPU将首先清除最后一帧之前的所有数据,storeOp是GPU在渲染通道之后执行的操作,store表示GPU将把数据存储到纹理中。
loadOp可以是load,它保留最后一帧的数据,也可以是clear,它清除最后一帧的数据。 storeOp可以是store,将数据存储到纹理,也可以是discard,丢弃数据。
现在,只需调用 pass.end() 即可结束渲染通道。现在,该命令已保存在 GPU 的命令缓冲区中。
要获取编译后的命令,请使用以下代码,
const device = await adapter.requestDevice(); console.log(device);
最后,将命令提交到 GPU 的渲染队列。
const canvas = document.getElementById('app') as HTMLCanvasElement; const context = canvas.getContext("webgpu")!; const canvasFormat = navigator.gpu.getPreferredCanvasFormat(); context.configure({ device: device, format: canvasFormat, });
现在,您应该看到一个丑陋的黑色画布。
根据我们对 3D 的刻板印象,我们期望空白空间是蓝色的。我们可以通过设置透明颜色来做到这一点。
const encoder = device.createCommandEncoder();
使用着色器绘制三角形
现在,我们将在画布上绘制一个三角形。我们将使用着色器来做到这一点。着色器语言将是 wgsl,WebGPU 着色语言。
现在,假设我们要绘制一个具有以下坐标的三角形,
const pass = encoder.beginRenderPass({ colorAttachments: [{ view: context.getCurrentTexture().createView(), loadOp: "clear", storeOp: "store", }] });
正如我们之前所说,要完成渲染管道,我们需要一个顶点着色器和一个片段着色器。
顶点着色器
使用以下代码创建着色器模块。
const commandBuffer = encoder.finish();
这里的label只是一个名称,用于调试。 code 是实际的着色器代码。
顶点着色器是一个接受任意参数并返回顶点位置的函数。然而,与我们的预期相反,顶点着色器返回一个四维向量,而不是一个三维向量。第四个维度是w维度,用于透视划分。我们稍后再讨论。
现在,您可以简单地将四维向量 (x, y, z, w) 视为三维向量 (x / w, y / w, z / w)。
但是,还有一个问题——如何将数据传递给着色器,以及如何从着色器中取出数据。
为了将数据传递给着色器,我们使用 vertexBuffer,一个包含顶点数据的缓冲区。我们可以使用以下代码创建一个缓冲区,
const main = async () => { console.log('Hello, world!') } main()
这里我们创建了一个缓冲区,大小为24字节,6个浮点数,这是顶点的大小。
usage是缓冲区的使用情况,对于顶点数据来说就是VERTEX。 GPUBufferUsage.COPY_DST 表示该缓冲区可作为复制目标。对于所有由CPU写入数据的缓冲区,我们需要设置这个标志。
这里的map是指将buffer映射到CPU,也就是说CPU可以对buffer进行读写操作。 unmap的意思是取消缓冲区的映射,这意味着CPU不能再读写缓冲区,因此内容可供GPU使用。
现在,我们可以将数据写入缓冲区。
const adapter = await navigator.gpu.requestAdapter();
这里,我们将缓冲区映射到CPU,并将数据写入缓冲区。然后我们取消映射缓冲区。
vertexBuffer.getMappedRange() 将返回映射到 CPU 的缓冲区范围。我们可以用它来将数据写入缓冲区。
但是,这些只是原始数据,GPU 不知道如何解释它们。我们需要定义缓冲区的布局。
const device = await adapter.requestDevice(); console.log(device);
这里,arrayStride是GPU在寻找下一个输入时需要在缓冲区中向前跳过的字节数。例如,如果 arrayStride 为 8,GPU 将跳过 8 个字节来获取下一个输入。
由于这里我们使用float32x2,步幅是8个字节,每个float 4个字节,每个顶点2个float。
现在我们可以编写顶点着色器了。
const canvas = document.getElementById('app') as HTMLCanvasElement; const context = canvas.getContext("webgpu")!; const canvasFormat = navigator.gpu.getPreferredCanvasFormat(); context.configure({ device: device, format: canvasFormat, });
这里,@vertex 表示这是一个顶点着色器。 @location(0) 表示属性的位置,如前面定义的那样,为 0。请注意,在着色器语言中,您正在处理缓冲区的布局,因此每当您传递一个值时,您需要传递一个结构体,其字段已定义@location,或者仅传递一个带有@location的值。
vec2f 是二维浮点向量,vec4f 是四维浮点向量。由于顶点着色器需要返回 vec4f 位置,因此我们需要使用 @builtin(position) 对其进行注释。
片段着色器
片段着色器,类似地,是获取插值顶点输出并输出附件(在本例中为颜色)的东西。插值意味着虽然只有顶点上的某些像素具有确定的值,但对于每隔一个像素,这些值都会被插值,可以是线性的、平均的或其他方式。 fragment的颜色是一个四维向量,即fragment的颜色,分别是红、绿、蓝、alpha。
请注意,颜色的范围是0到1,而不是0到255。此外,片段着色器定义的是每个顶点的颜色,而不是三角形的颜色。三角形的颜色由顶点的颜色通过插值确定。
由于我们目前不想控制片段的颜色,所以我们可以简单地返回一个常量颜色。
const main = async () => { console.log('Hello, world!') } main()
渲染管线
然后我们通过替换顶点和片段着色器来定义自定义渲染管道。
const adapter = await navigator.gpu.requestAdapter();
注意,在片段着色器中,我们需要指定目标的格式,也就是画布的格式。
抽奖
在渲染过程结束之前,我们添加绘制调用。
const device = await adapter.requestDevice(); console.log(device);
这里,在setVertexBuffer中,第一个参数是缓冲区的索引,在管道定义字段buffers中,第二个参数是缓冲区本身。
调用draw时,参数是要绘制的顶点数。由于我们有 3 个顶点,因此我们绘制 3 个。
现在,您应该在画布上看到一个黄色三角形。
绘制生命游戏细胞
现在我们稍微调整一下代码 - 因为我们想要构建一个生活游戏,所以我们需要绘制正方形而不是三角形。
正方形实际上是两个三角形,所以我们需要画6个顶点。这里的改动很简单,不需要详细解释。
const canvas = document.getElementById('app') as HTMLCanvasElement; const context = canvas.getContext("webgpu")!; const canvasFormat = navigator.gpu.getPreferredCanvasFormat(); context.configure({ device: device, format: canvasFormat, });
现在,您应该在画布上看到一个黄色方块。
坐标系
我们没有讨论GPU的坐标系。嗯,这相当简单。 GPU实际的坐标系是右手坐标系,即x轴指向右侧,y轴指向上方,z轴指向屏幕外。
坐标系的范围是-1到1。原点位于屏幕中心。 z轴从0到1,0是近平面,1是远平面。然而,z 轴代表深度。当你做3D渲染时,你不能仅仅使用z轴来确定物体的位置,你需要使用透视划分。这称为 NDC,标准化设备坐标。
例如,要在屏幕左上角画一个正方形,顶点为 (-1, 1), (-1, 0), (0, 1), (0, 0) ,尽管你需要使用两个三角形来绘制它。
以上是网络上的三角形 抓取一些东西的详细内容。更多信息请关注PHP中文网其他相关文章!

JavaScript可用于前端和后端开发。前端通过DOM操作增强用户体验,后端通过Node.js处理服务器任务。1.前端示例:改变网页文本内容。2.后端示例:创建Node.js服务器。

选择Python还是JavaScript应基于职业发展、学习曲线和生态系统:1)职业发展:Python适合数据科学和后端开发,JavaScript适合前端和全栈开发。2)学习曲线:Python语法简洁,适合初学者;JavaScript语法灵活。3)生态系统:Python有丰富的科学计算库,JavaScript有强大的前端框架。

JavaScript框架的强大之处在于简化开发、提升用户体验和应用性能。选择框架时应考虑:1.项目规模和复杂度,2.团队经验,3.生态系统和社区支持。

引言我知道你可能会觉得奇怪,JavaScript、C 和浏览器之间到底有什么关系?它们之间看似毫无关联,但实际上,它们在现代网络开发中扮演着非常重要的角色。今天我们就来深入探讨一下这三者之间的紧密联系。通过这篇文章,你将了解到JavaScript如何在浏览器中运行,C 在浏览器引擎中的作用,以及它们如何共同推动网页的渲染和交互。JavaScript与浏览器的关系我们都知道,JavaScript是前端开发的核心语言,它直接在浏览器中运行,让网页变得生动有趣。你是否曾经想过,为什么JavaScr

Node.js擅长于高效I/O,这在很大程度上要归功于流。 流媒体汇总处理数据,避免内存过载 - 大型文件,网络任务和实时应用程序的理想。将流与打字稿的类型安全结合起来创建POWE

Python和JavaScript在性能和效率方面的差异主要体现在:1)Python作为解释型语言,运行速度较慢,但开发效率高,适合快速原型开发;2)JavaScript在浏览器中受限于单线程,但在Node.js中可利用多线程和异步I/O提升性能,两者在实际项目中各有优势。

JavaScript起源于1995年,由布兰登·艾克创造,实现语言为C语言。1.C语言为JavaScript提供了高性能和系统级编程能力。2.JavaScript的内存管理和性能优化依赖于C语言。3.C语言的跨平台特性帮助JavaScript在不同操作系统上高效运行。

JavaScript在浏览器和Node.js环境中运行,依赖JavaScript引擎解析和执行代码。1)解析阶段生成抽象语法树(AST);2)编译阶段将AST转换为字节码或机器码;3)执行阶段执行编译后的代码。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

SublimeText3汉化版
中文版,非常好用

WebStorm Mac版
好用的JavaScript开发工具

SublimeText3 英文版
推荐:为Win版本,支持代码提示!

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3 Linux新版
SublimeText3 Linux最新版