网络上的三角形 抓取一些东西
本系列介绍 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中文网其他相关文章!

热AI工具

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

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

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

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

热门文章

热工具

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

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

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

JavaScript是现代Web开发的基石,它的主要功能包括事件驱动编程、动态内容生成和异步编程。1)事件驱动编程允许网页根据用户操作动态变化。2)动态内容生成使得页面内容可以根据条件调整。3)异步编程确保用户界面不被阻塞。JavaScript广泛应用于网页交互、单页面应用和服务器端开发,极大地提升了用户体验和跨平台开发的灵活性。

JavaScript的最新趋势包括TypeScript的崛起、现代框架和库的流行以及WebAssembly的应用。未来前景涵盖更强大的类型系统、服务器端JavaScript的发展、人工智能和机器学习的扩展以及物联网和边缘计算的潜力。

不同JavaScript引擎在解析和执行JavaScript代码时,效果会有所不同,因为每个引擎的实现原理和优化策略各有差异。1.词法分析:将源码转换为词法单元。2.语法分析:生成抽象语法树。3.优化和编译:通过JIT编译器生成机器码。4.执行:运行机器码。V8引擎通过即时编译和隐藏类优化,SpiderMonkey使用类型推断系统,导致在相同代码上的性能表现不同。

JavaScript是现代Web开发的核心语言,因其多样性和灵活性而广泛应用。1)前端开发:通过DOM操作和现代框架(如React、Vue.js、Angular)构建动态网页和单页面应用。2)服务器端开发:Node.js利用非阻塞I/O模型处理高并发和实时应用。3)移动和桌面应用开发:通过ReactNative和Electron实现跨平台开发,提高开发效率。

Python更适合初学者,学习曲线平缓,语法简洁;JavaScript适合前端开发,学习曲线较陡,语法灵活。1.Python语法直观,适用于数据科学和后端开发。2.JavaScript灵活,广泛用于前端和服务器端编程。

本文展示了与许可证确保的后端的前端集成,并使用Next.js构建功能性Edtech SaaS应用程序。 前端获取用户权限以控制UI的可见性并确保API要求遵守角色库

从C/C 转向JavaScript需要适应动态类型、垃圾回收和异步编程等特点。1)C/C 是静态类型语言,需手动管理内存,而JavaScript是动态类型,垃圾回收自动处理。2)C/C 需编译成机器码,JavaScript则为解释型语言。3)JavaScript引入闭包、原型链和Promise等概念,增强了灵活性和异步编程能力。

JavaScript不需要安装,因为它已内置于现代浏览器中。你只需文本编辑器和浏览器即可开始使用。1)在浏览器环境中,通过标签嵌入HTML文件中运行。2)在Node.js环境中,下载并安装Node.js后,通过命令行运行JavaScript文件。
