首页 > web前端 > js教程 > 网络上的三角形 抓取一些东西

网络上的三角形 抓取一些东西

Patricia Arquette
发布: 2024-11-30 02:42:10
原创
149 人浏览过

本系列介绍 WebGPU 和一般计算机图形学。

首先让我们看看我们要构建什么,

生命游戏

Triangles On Web Chraw Something

3D 渲染

Triangles On Web Chraw Something

3D 渲染,但有灯光

Triangles On Web Chraw Something

渲染 3D 模型

Triangles On Web Chraw Something

除了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上的程序。着色器有一种特殊的编程语言,我们稍后会讨论。

渲染管道有以下步骤,

  1. CPU 将数据加载到 GPU 中。 CPU可能会移除一些不可见的物体以节省GPU资源。
  2. CPU 设置 GPU 渲染场景所需的所有颜色、纹理和其他数据。
  3. CPU 触发对 GPU 的绘制调用。
  4. GPU从CPU获取数据并开始渲染场景。
  5. GPU 运行到几何进程,该进程处理场景的顶点。
  6. 在几何过程中,第一步是顶点着色器,它处理场景的顶点。它可能会变换顶点,改变顶点的颜色,或者对顶点做其他事情。
  7. 下一步是曲面细分着色器,它处理场景的顶点。它对顶点进行细分,其目的是增加场景的细节。它的程序也很多,但是太复杂了,无法在这里解释。
  8. 下一步是几何着色器,它处理场景的顶点。与顶点着色器相比,开发人员只能定义如何变换一个顶点,而几何着色器可以定义如何变换多个顶点。它还可以创建新的顶点,新的顶点可用于创建新的几何体。
  9. 几何处理的最后一步包括裁剪,去除超出屏幕的多余部分,以及剔除,去除相机不可见的不可见部分。
  10. 下一步是光栅化过程,将顶点转换为片段。片段是将要在屏幕上渲染的像素。
  11. 下一步是三角形迭代,即迭代场景的三角形。
  12. 下一步是片段着色器,它处理场景的片段。它可能会改变片段的颜色,改变片段的纹理,或者对片段做其他事情。在这一部分中,还进行了深度测试和模板测试。深度测试是指为每个片段赋予深度值,深度值最小的片段将被渲染。 Stencil测试是指为每个fragment赋予stencil值,通过stencil测试的fragment将被渲染。模板值由开发者决定。
  13. 下一步是混合过程,混合场景的片段。例如,如果两个片段重叠,则混合过程会将两个片段混合在一起。
  14. 最后一步是输出过程,将碎片输出到交换链。交换链是用于渲染场景的图像链。更简单地说,它是一个缓冲区,用于保存将要在屏幕上显示的图像。

根据图元(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中文网其他相关文章!

来源:dev.to
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板