Ⅰ、基于 WebGPU 从 0 到 1 渲染 GLTF:第一个三角形

Ⅰ、基于 WebGPU 从 0 到 1 渲染 GLTF:第一个三角形

WebGPU 是一种面相网页的现代图形 API,由主要浏览器供应商开发。与 WebGL 相比,WebGPU 对 GPU 提供了更直接的控制,使应用程序能更有效地利用硬件,类似于 Vulkan 和 DirectX 12。WebGPU 还提供了更多 WebGL 所不具备的 GPU 功能,如计算着色器(compute shaders)和存储缓冲区(storage buffers),使强大的 GPU 计算应用程序能够在网页上运行。从 OpenGL 到 Vulkan,WebGPU 向用户暴露了比 WebGL 更多的复杂性,不过该 API 在复杂性和可用性之间取得了很好的平衡,总体来说非常好用。在本系列中,我们将从头开始学习 WebGPU 的关键方面,目标是实现从零到基本的 glTF 模型渲染器。本篇文章标志着我们在这条道路上迈出了第一步,我们将设置一个 WebGPU 上下文,并在屏幕上显示一个三角形。

1、项目搭建:Vite + TypeScript + HTML

1、控制台执行以下两个命令

①全局安装 vite:npm install -g create-vite

在这里插入图片描述

②指定文件夹下创建工程:create-vite 1-first-triangle-ts --template vanilla-ts

这里的 1-first-triangle-ts 为 工程名/文件夹名

在这里插入图片描述

2、打开工程,安装所需依赖包

① 安装基础依赖

通过 vscode 打开 1-first-triangle-ts 文件夹

打开后在控制台,执行 npm i 和 npm run dev

在这里插入图片描述

npm i 安装依赖; npm run dev 是安装好基础依赖后,运行代码,查看网页是否正常启动、显示。

首次运行显示的界面:

在这里插入图片描述

② 安装 webgpu 所需依赖:npm install @webgpu/types

在这里插入图片描述

③删除、修改多余文件

生成的工程中,src 文件夹下会有多余的文件:counter.ts、style.css、typescript.svg、vite-env.d.ts,这里将它们删除,只保留 main.ts。

在这里插入图片描述

main.ts 中的代码修改,这里用 console.log(“正常启动了”); 来查看代码是否修改生效。

在这里插入图片描述

再次在控制台 npm run dev 运行项目,可以看到启动的网页没有了内容,ctrl + shift + i 打开调试控制台,可以看到控制台打印了我们在 main.ts 中写的代码,运行成功。

在这里插入图片描述

后续我们就在 main.ts 中编写 webgpu 的代码。

至此基础的前端工程就已经搭建完毕,可以进行 webgpu 的相关开发了。

这里 main.ts 的代码之所以生效,是因为 index.html 中引入了 main.ts,index.html 就是一个网页的入口。

在这里插入图片描述

3、基础配置

① 在 tsconfig.json 中的 compilerOptions 中添加两个配置:

启用实验性的装饰器特性:“experimentalDecorators”: true

指定需要包含在编译中的类型声明文件: “types”: [“vite/client”, “@webgpu/types”],

在这里插入图片描述

这样在 ts 文件编写 navigator.gpu 时就不会提示以下错误了。

在这里插入图片描述

4、基础概念

1、 GPUAdapter、GPUDevice、浏览器 之间的关系

在这里插入图片描述

他们的逻辑关系为:

假设浏览器支持 WebGPU,那么浏览器中的 JS 主线程 或 Web Worker 可以通过 GPUAdapter(显卡适配器) 来获取当前系统中的 GPUDevice(显卡设备)。

GPUAdapter(显卡适配器)的作用:

WebGPU 不光要考虑不同的操作系统(Windows、Linux、Mac OS、Android等),还要考虑系统上不同的显卡设备硬件(集成显卡、独立显卡、多个显卡等),因此 WebGPU 需要创建一个 GPUAdapter(显卡适配器) 来统一负责获取系统中的显卡设备(GPUDevice)。

GPUDevice(显卡设备)的作用:

而这里的 GPUDevice(显卡设备) 并不是真的电脑硬件上的显卡。

  1. 电脑硬件上的显卡是由不同厂商生产的。

  2. 即使同一显卡在不同的操作系统上又是由不同的底层图形 API 驱动的,WebGPU 所支持的 3 个底层图形架构分别是:DirectX 12(即 D3D12)、Metal、Vulkan。

    DirectX 12 属于 微软

    Metal 属于 苹果

    Vulkan 属于 Khronos Group

结论:为了适配不同的显卡设备以及系统底层图形架构,因此 WebGPU 需要创建 GPUDevice(显卡设备) 来统一负责 “系统中的显卡设备”。

小总结:

  1. GPUAdapter 显卡适配器:用于抹平和获取不同类型的 GPUDevice。
  2. GPUDevice 显卡设备:用于抹平不同底层图形框架下的显卡设备,提供发送执行 GPU 渲染或计算命令的能力。

参考:puxiao/webgpu-tutorial: WebGPU 系列教程,学习和探索 WebGPU 世界。 (github.com)

2、获取 WebGPU 上下文

使用 WebGPU 的前提是设浏览器要支持WebGPU。

我们将在本篇文章中实现的三角形渲染器。

WebGPU 渲染上下文的初始设置与 WebGL 类似。我们的网页将有一个画布(canvas)来显示我们渲染的图像,并从 main.ts 加载我们的渲染代码。

因此,我们将把渲染代码放在一个异步函数中,该函数将在加载脚本时执行。我们的第一步是从 WebGPU API 获取一个 GPUAdapter。每个适配器代表一台机器上的 GPU 以及浏览器在该 GPU 上的 WebGPU 实现。然后,我们可以向适配器请求一个 GPUDevice,它为我们提供了与硬件协同工作的上下文。GPUDevice 提供了创建 GPU 对象(如缓冲区和纹理)和在设备上执行命令的 API。GPUAdapter 和 GPUDevice 之间的区别类似于 Vulkan 中的 VkPhysicalDevice 和 VkDevice。与 WebGL 一样,我们需要一个用于显示渲染图像的画布(canvas)的上下文。要在画布上使用 WebGPU,我们需要一个 webgpu 上下文。设置完成后,我们就可以加载着色器和顶点数据、配置渲染目标并构建渲染管道,从而绘制三角形!

index.html:

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><link rel="icon" type="image/svg+xml" href="/vite.svg" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>WebGPU-GLTF</title><style>html,body {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;margin: 0;width: 100%;height: 100%;background: #000;color: #fff;display: flex;}#app {width: 100%;height: 100%;display: flex;}</style>
</head><body><div id="app"></div><script type="module" src="/src/main.ts"></script>
</body></html>

main.ts:

// 1、创建画布 用于 GPU 渲染 在此画布上绘制
const createCanvas = (containerID = "app") => {const canvas = document.createElement('canvas');const contianer = document.getElementById(containerID) as HTMLDivElement;canvas.style.width = '100%';canvas.style.height = '100%';if (!contianer) {document.body.appendChild(canvas);} else {contianer.appendChild(canvas);}const devicePixelRatio = window.devicePixelRatio || 1;const w = Math.floor(canvas.clientWidth * devicePixelRatio);const h = Math.floor(canvas.clientHeight * devicePixelRatio);canvas.width = w;canvas.height = h;return canvas;
}// 2、初始化 WebGPU、获取上下文
const setupGPU = async () => { // 异步函数 异步方法if (!navigator.gpu) { // 判断是否支持 WebGPUthrow new Error("不支持 WebGPU");}// 获取 GPU deviceconst adapter = await navigator.gpu.requestAdapter({powerPreference: 'high-performance'}); // 显卡适配器const device = await adapter?.requestDevice(); // 显卡设备const canvas = createCanvas(); // 创建画布const context = canvas.getContext("webgpu"); // 获取 WebGPU 上下文return {adapter,device,canvas,context}
}const render = () => {setupGPU(); // 调用 initGPU
}render(); // 执行 render 

编写代码逻辑如下:

1、判断是否支持 WebGPU

2、获取显卡适配器(GPUAdapter)、显卡设备(GPUDevice)

3、创建画布(canvas),并将画布添加至页面中(appendChild(canvas))

4、获取画布(canvas)的 WebGPU 上下文(context)

此时控制台执行 npm run dev,并 ctrl + shift + i 打开网页浏览器的控制台,在 id=“app” 的 div 里面,就有一个创建成功的 canvas,由于 canvas(画布)中尚未绘制任何东西,所以网页页面是空白的。

在这里插入图片描述

3、WebGPU 渲染管线

WebGPU 渲染管道包括两个可编程阶段:顶点着色器和片段着色器,与 WebGL 类似。WebGPU 还增加了对计算着色器的支持,计算着色器存在于渲染管道之外。

在这里插入图片描述

WebGPU 的渲染流水线由两个可编程着色器阶段组成:顶点着色器负责将输入顶点转换到裁剪空间,片段着色器负责为每个三角形覆盖的像素着色。

要渲染三角形,我们需要配置这样一个管道,指定着色器、顶点属性配置等。在 WebGPU 中,该流水线采用具体对象 GPURenderPipeline 的形式,它指定了流水线的不同部分。该流水线组件(如着色器、顶点状态、渲染输出状态等)的配置是固定的,这样 GPU 就能更好地优化流水线的渲染。绑定到相应输入或输出的缓冲区或纹理可以更改,但输入和输出的数量及其类型等不能更改。这与 WebGL 形成了鲜明对比,WebGL 通过修改全局状态机隐式地指定了绘制的流水线状态,而且着色器、顶点状态等可以在绘制调用之间随时交换,这给优化流水线带来了挑战。

4、着色器模块

创建流水线的第一步是创建顶点和片段着色器模块,这些模块将在流水线中执行。WebGPU 着色器是用 WGSL 编写的。本应用的着色器相对简单。我们为顶点数据的输入和输出格式定义了一个结构,并将位置和颜色从输入传递到输出。

在 src 文件夹下新建triangle.wgsl.ts,并编码如下:

triangle.wgsl.ts:

const triangleShader = `// This type definition is just to make typing a bit easier
alias float4 = vec4<f32>;struct VertexInput {@location(0) position: float4,@location(1) color: float4,
};struct VertexOutput {// This is the equivalent of gl_Position in GLSL@builtin(position) position: float4,@location(0) color: float4,
};@vertex
fn vertex_main(vert: VertexInput) -> VertexOutput {var out: VertexOutput;out.color = vert.color;out.position = vert.position;return out;
};@fragment
fn fragment_main(in: VertexOutput) -> @location(0) float4 {return float4(in.color);
}
`export default triangleShader;

我们将包含 WGSL 代码的字符串传递给 createShaderModule 方法来编译它们。我们可以检查生成的着色器模块的编译信息,查看是否有任何错误导致着色器编译失败。

代码逻辑如下:

1、定义 wgsl 着色器

2、maint.ts 中引入

3、创建画布 canvas、初始化 GPU

4、载入 shader 着色器

main.ts:

import shaderCode from "./triangle.wgsl.ts";// 3、加载 初始化  shader 着色器
const setupShaderModules = async (device: GPUDevice) => {// Setup shader modulesconst shaderModule = device.createShaderModule({ code: shaderCode }); // 传入 shaderCodeconst compilationInfo = await shaderModule.getCompilationInfo(); // 编译信息if (compilationInfo.messages.length > 0) {let hadError = false;console.log("Shader compilation log:");for (let i = 0; i < compilationInfo.messages.length; ++i) {const msg = compilationInfo.messages[i];console.log(`${msg.lineNum}:${msg.linePos} - ${msg.message}`);hadError = hadError || msg.type == "error";}if (hadError) {console.log("Shader failed to compile"); // 编译失败return null;}}return shaderModule;
}

5、指定顶点数据

接下来,我们将指定三角形的顶点数据。我们将在单个缓冲区中指定顶点位置和颜色,位置和颜色相互交错。每个位置和颜色都将存储为 float4。首先,我们使用 createBuffer 在设备上分配和映射一个有足够空间存储顶点数据的缓冲区。该方法获取我们要创建的缓冲区的大小(以字节为单位),以及一组指定缓冲区所需的使用模式的标志。通过设置 mappedAtCreation 参数,我们可以指定在创建缓冲区时对其进行映射(mapped)。

createBuffer 返回 GPUBuffer 和 ArrayBuffer,我们可以使用 ArrayBuffer 将数据上传到缓冲区。要写入顶点数据,我们要为数组缓冲区创建一个 Float32Array 视图,并通过该视图设置数据。最后,我们必须先取消缓冲区的映射(unmap),然后再在渲染中使用。

为了告诉 GPU 如何将顶点数据传递给我们的着色器,我们将指定一个 GPUVertexState 作为创建时渲染管道的一部分。该对象既指定了我们要运行的着色器,也指定了该着色器的属性输入应如何填充顶点缓冲区的数据。我们通过传递 GPUVertexBufferLayout 对象数组来指定顶点缓冲区的解释方式。每个条目描述了渲染时从绑定到相应槽的顶点缓冲区中读取的顶点属性。

在本示例中,我们有一个单一的缓冲区,其中包含每个顶点的交错属性。因此,元素之间的间隔为 32 字节(2 个 float4),缓冲区指定了两个 float4 属性。第一个属性是位置,发送到着色器输入位置 0;第二个属性是颜色,发送到着色器输入位置 1。

WebGPU 指定顶点缓冲区和属性的模型与 D3D12 和 Vulkan 相同,其中顶点缓冲区绑定到输入插槽,并提供一组顶点属性,如下图所示。从 D3D12 的角度来看,顶点缓冲区(vertexBuffers)成员映射到创建图形流水线时通过 D3D12_INPUT_LAYOUT_DESC 传递的 D3D12_INPUT_ELEMENT_DESC 结构数组。在 Vulkan 视图中,vertexBuffers 成员直接映射到创建图形流水线(graphics pipeline)时传递的 VkPipelineVertexInputStateCreateInfo 结构。

在这里插入图片描述

通过缓冲区向输入装配器提供三角形的顶点属性。我们从绑定到输入插槽 0 的缓冲区中读取属性,并使用为输入插槽和属性指定的跨距和偏移量传递到指定的着色器输入位置。

///4、指定顶点数据
const specifyVertexData = (device: GPUDevice, shaderModule: GPUShaderModule) => {// Specify vertex data// Allocate room for the vertex data: 3 vertices, each with 2 float4'sconst dataBuf = device.createBuffer({size: 3 * 2 * 4 * 4,usage: GPUBufferUsage.VERTEX,mappedAtCreation: true});// Interleaved positions and colorsnew Float32Array(dataBuf.getMappedRange()).set([1, -1, 0, 1,  // position1, 0, 0, 1,   // color-1, -1, 0, 1, // position0, 1, 0, 1,   // color0, 1, 0, 1,   // position0, 0, 1, 1,   // color]);dataBuf.unmap();// Vertex attribute state and shader stageconst vertexState: GPUVertexState = {// Shader stage infomodule: shaderModule,entryPoint: "vertex_main",// Vertex buffer infobuffers: [{arrayStride: 2 * 4 * 4,attributes: [{ format: "float32x4", offset: 0, shaderLocation: 0 },{ format: "float32x4", offset: 4 * 4, shaderLocation: 1 }]}]};return { dataBuf, vertexState };
}

6、编写渲染输出(Writing Rendering Outputs)

接下来,我们将创建一个交换链,并指定片段着色器输出结果的写入位置。要在画布上显示图像,我们需要一个与其上下文相关联的交换链。交换链可让我们旋转画布上显示的图像,在显示另一个缓冲区时渲染到不可见的缓冲区(即双重缓冲)。我们通过指定所需的图像格式和纹理用途来创建交换链。交换链将为我们创建一个或多个纹理,大小与要显示的画布相匹配。由于我们将直接对交换链纹理进行渲染,因此我们指定将它们用作输出附件。交换链在 WebGPU 中不再是一个显式对象,而是由上下文进行内部管理。要设置交换链,我们需要调用 configure 并传递交换链参数。

虽然在本例中我们只绘制了一个三角形,但我们仍将创建并使用深度纹理,因为我们稍后会用到它。深度纹理的创建与普通纹理一样,需要指定大小、格式和用途。和之前一样,我们将直接对该纹理进行渲染,因此指定它将用作输出附件。

与片段着色器阶段的 GPUVertexState 类似的是 GPUFragmentState。该对象指定了要运行的片段着色器,以及它将以何种格式写入输出纹理。指定渲染目标是为了与着色器中的输出位置相匹配,这与之前指定顶点输入和缓冲槽的方式类似。我们将直接渲染到交换链,因此我们只有一个渲染目标,其格式就是交换链格式。

// 5、渲染输出
const setupRenderOutputs = (canvas: HTMLCanvasElement, context: GPUCanvasContext, device: GPUDevice, shaderModule: GPUShaderModule) => {// Setup render outputsconst swapChainFormat = "bgra8unorm";context.configure({device: device,format: swapChainFormat,usage: GPUTextureUsage.RENDER_ATTACHMENT});const depthFormat: GPUTextureFormat = "depth24plus-stencil8";const depthTexture = device.createTexture({size: {width: canvas.width,height: canvas.height,depthOrArrayLayers: 1.0},format: depthFormat,usage: GPUTextureUsage.RENDER_ATTACHMENT});const fragmentState: GPUFragmentState = {// Shader infomodule: shaderModule,entryPoint: "fragment_main",// Output render target infotargets: [{ format: swapChainFormat }]};return {depthTexture,fragmentState,depthFormat}
}

7、创建渲染管线

最后,我们可以创建渲染管道,将着色器、顶点属性和输出配置结合起来(shaders、vertex attributes、output configuration),用于渲染三角形。渲染管道描述通过 GPURenderPipelineDescriptor 对象传递给 createRenderPipeline。我们的渲染管道需要的最后部分是管道布局(pipeline layout,指定管道使用的绑定组布局)和深度/模版状态(depth/stencil)。图元拓扑结构(primitive topology)也可以在此结构中指定;不过,默认值是三角形列表,这已经是我们所需要的了。

// 6、创建渲染管线
const createRenderPipeline = (device: GPUDevice, vertexState: GPUVertexState, fragmentState: GPUFragmentState, depthFormat: GPUTextureFormat) => {// Create render pipelineconst layout = device.createPipelineLayout({ bindGroupLayouts: [] });const renderPipeline = device.createRenderPipeline({layout: layout,vertex: vertexState,fragment: fragmentState,depthStencil: { format: depthFormat, depthWriteEnabled: true, depthCompare: "less" }});return renderPipeline;
}

8、渲染

WebGPU 中的渲染是在渲染通道(Render Pass)中进行的,渲染通道通过 GPURenderPassDescriptor 进行描述。渲染通道描述符指定了要绑定到片段着色器中要写入的目标的图像,以及可选的深度缓冲区和遮挡查询集。(occlusion query,遮挡查询在现代图形API上面很常见,基于遮挡查询可以做一些遮挡剔除,减少drawCall,优化性能。)指定的颜色和深度附件必须与渲染通道中使用的渲染管道所指定的颜色和深度状态相匹配。我们的片段着色器会写入一个输出槽,即对象颜色,并将其写入当前交换链图像。由于每帧图像都会更改为当前的交换链图像,因此我们暂时不对其进行设置。

剩下要做的就是编写我们的渲染循环,并将其传递给 requestAnimationFrame,以便每帧都调用它来更新图像。为了记录和提交 GPU 命令,我们使用了 GPUCommandEncoder。命令编码器可用于预先录制可多次提交给 GPU 的命令缓冲区,或重新录制并提交每一帧。由于我们将在每一帧更改渲染通道颜色附件,因此我们将在每一帧重新录制并提交命令缓冲区。

对于每一帧,我们都要获取最新的交换链图像,将渲染输出写入其中,并将其设置为输出色彩附件图像。然后,我们创建一个命令编码器来记录我们的渲染命令。我们通过调用 beginRenderPass 开始渲染过程,并传递渲染过程描述符来获取 GPURenderPassEncoder,这样我们就可以记录渲染命令了。然后,我们可以设置要使用的渲染流水线,将顶点缓冲区绑定到相应的输入插槽,绘制三角形,并结束渲染过程。要获得可提交给 GPU 执行的命令缓冲区,我们需要在命令编码器上调用 finish。然后,返回的命令缓冲区将传递给设备执行。命令缓冲区运行后,我们的三角形将被写入交换链图像并显示在画布上,如下图所示!

// 7、渲染
const setupRender = (context: GPUCanvasContext, device: GPUDevice, depthTexture: GPUTexture, renderPipeline: GPURenderPipeline, dataBuf: GPUBuffer) => {const renderPassDesc: GPURenderPassDescriptor = {colorAttachments: [{// view will be set to the current render target each frameview: context.getCurrentTexture().createView(),loadOp: "clear",clearValue: [0.3, 0.3, 0.3, 1],storeOp: "store"}],depthStencilAttachment: {view: depthTexture.createView(),depthLoadOp: "clear",depthClearValue: 1.0,depthStoreOp: "store",stencilLoadOp: "clear",stencilClearValue: 0,stencilStoreOp: "store"}};// Render!const frame = function () {const colorAttachments = renderPassDesc.colorAttachments as Iterable<GPURenderPassColorAttachment>;Array.from(colorAttachments)[0].view = context.getCurrentTexture().createView();const commandEncoder = device.createCommandEncoder();const renderPass = commandEncoder.beginRenderPass(renderPassDesc);renderPass.setPipeline(renderPipeline);renderPass.setVertexBuffer(0, dataBuf);renderPass.draw(3, 1, 0, 0);renderPass.end();device.queue.submit([commandEncoder.finish()]);requestAnimationFrame(frame);};requestAnimationFrame(frame);
}

9、总结

有了屏幕上的第一个三角形,我们就可以制作一个基本的 glTF 模型查看器了。在下一篇文章中,我们将介绍如何使用绑定组(bind groups)向着色器传递额外数据(例如均匀缓冲区)。

main.ts 完整代码:

import shaderCode from "./triangle.wgsl.ts";// 1、创建画布 用于 GPU 渲染 在此画布上绘制
const createCanvas = (containerID = "app") => {const canvas = document.createElement('canvas');const contianer = document.getElementById(containerID) as HTMLDivElement;canvas.style.width = '100%';canvas.style.height = '100%';if (!contianer) {document.body.appendChild(canvas);} else {contianer.appendChild(canvas);}const devicePixelRatio = window.devicePixelRatio || 1;const w = Math.floor(canvas.clientWidth * devicePixelRatio);const h = Math.floor(canvas.clientHeight * devicePixelRatio);canvas.width = w;canvas.height = h;return canvas;
}// 2、初始化 WebGPU、获取上下文
const setupGPU = async () => { // 异步函数 异步方法if (!navigator.gpu) { // 判断是否支持 WebGPUthrow new Error("不支持 WebGPU");}// 获取 GPU deviceconst adapter = await navigator.gpu.requestAdapter({powerPreference: 'high-performance'}); // 显卡适配器const device = await adapter?.requestDevice(); // 显卡设备const canvas = createCanvas(); // 创建画布const context = canvas.getContext("webgpu"); // 获取 WebGPU 上下文return {adapter,device,canvas,context}
}// 3、加载 初始化  shader 着色器
const setupShaderModules = async (device: GPUDevice) => {// Setup shader modulesconst shaderModule = device.createShaderModule({ code: shaderCode }); // 传入 shaderCodeconst compilationInfo = await shaderModule.getCompilationInfo(); // 编译信息if (compilationInfo.messages.length > 0) {let hadError = false;console.log("Shader compilation log:");for (let i = 0; i < compilationInfo.messages.length; ++i) {const msg = compilationInfo.messages[i];console.log(`${msg.lineNum}:${msg.linePos} - ${msg.message}`);hadError = hadError || msg.type == "error";}if (hadError) {console.log("Shader failed to compile"); // 编译失败return null;}}return shaderModule;
}
///4、指定顶点数据
const specifyVertexData = (device: GPUDevice, shaderModule: GPUShaderModule) => {// Specify vertex data// Allocate room for the vertex data: 3 vertices, each with 2 float4'sconst dataBuf = device.createBuffer({size: 3 * 2 * 4 * 4,usage: GPUBufferUsage.VERTEX,mappedAtCreation: true});// Interleaved positions and colorsnew Float32Array(dataBuf.getMappedRange()).set([1, -1, 0, 1,  // position1, 0, 0, 1,   // color-1, -1, 0, 1, // position0, 1, 0, 1,   // color0, 1, 0, 1,   // position0, 0, 1, 1,   // color]);dataBuf.unmap();// Vertex attribute state and shader stageconst vertexState: GPUVertexState = {// Shader stage infomodule: shaderModule,entryPoint: "vertex_main",// Vertex buffer infobuffers: [{arrayStride: 2 * 4 * 4,attributes: [{ format: "float32x4", offset: 0, shaderLocation: 0 },{ format: "float32x4", offset: 4 * 4, shaderLocation: 1 }]}]};return { dataBuf, vertexState };
}
// 5、渲染输出
const setupRenderOutputs = (canvas: HTMLCanvasElement, context: GPUCanvasContext, device: GPUDevice, shaderModule: GPUShaderModule) => {// Setup render outputsconst swapChainFormat = "bgra8unorm";context.configure({device: device,format: swapChainFormat,usage: GPUTextureUsage.RENDER_ATTACHMENT});const depthFormat: GPUTextureFormat = "depth24plus-stencil8";const depthTexture = device.createTexture({size: {width: canvas.width,height: canvas.height,depthOrArrayLayers: 1.0},format: depthFormat,usage: GPUTextureUsage.RENDER_ATTACHMENT});const fragmentState: GPUFragmentState = {// Shader infomodule: shaderModule,entryPoint: "fragment_main",// Output render target infotargets: [{ format: swapChainFormat }]};return {depthTexture,fragmentState,depthFormat}
}// 6、创建渲染管线
const createRenderPipeline = (device: GPUDevice, vertexState: GPUVertexState, fragmentState: GPUFragmentState, depthFormat: GPUTextureFormat) => {// Create render pipelineconst layout = device.createPipelineLayout({ bindGroupLayouts: [] });const renderPipeline = device.createRenderPipeline({layout: layout,vertex: vertexState,fragment: fragmentState,depthStencil: { format: depthFormat, depthWriteEnabled: true, depthCompare: "less" }});return renderPipeline;
}
// 7、渲染
const setupRender = (context: GPUCanvasContext, device: GPUDevice, depthTexture: GPUTexture, renderPipeline: GPURenderPipeline, dataBuf: GPUBuffer) => {const renderPassDesc: GPURenderPassDescriptor = {colorAttachments: [{// view will be set to the current render target each frameview: context.getCurrentTexture().createView(),loadOp: "clear",clearValue: [0.3, 0.3, 0.3, 1],storeOp: "store"}],depthStencilAttachment: {view: depthTexture.createView(),depthLoadOp: "clear",depthClearValue: 1.0,depthStoreOp: "store",stencilLoadOp: "clear",stencilClearValue: 0,stencilStoreOp: "store"}};// Render!const frame = function () {const colorAttachments = renderPassDesc.colorAttachments as Iterable<GPURenderPassColorAttachment>;Array.from(colorAttachments)[0].view = context.getCurrentTexture().createView();const commandEncoder = device.createCommandEncoder();const renderPass = commandEncoder.beginRenderPass(renderPassDesc);renderPass.setPipeline(renderPipeline);renderPass.setVertexBuffer(0, dataBuf);renderPass.draw(3, 1, 0, 0);renderPass.end();device.queue.submit([commandEncoder.finish()]);requestAnimationFrame(frame);};requestAnimationFrame(frame);
}
// 8、调用执行
const render = async () => {const { adapter, device, canvas, context } = await setupGPU(); // 调用 initGPUif (!adapter || !device || !canvas || !context) return; // 初始化异常 如果以上值不对 直接返回const shaderModule = await setupShaderModules(device); // 初始化 shaderif (!shaderModule) return; // shader 异常const { dataBuf, vertexState } = specifyVertexData(device, shaderModule);const {depthTexture,fragmentState,depthFormat} = setupRenderOutputs(canvas, context, device, shaderModule);const renderPipeline = createRenderPipeline(device, vertexState, fragmentState, depthFormat);setupRender(context, device, depthTexture, renderPipeline, dataBuf);}render(); // 执行 render 

渲染结果:

在这里插入图片描述

参考资料:

From 0 to glTF with WebGPU 1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/403872.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何在C++ QT 程序中集成cef3浏览器组件去显示网页?

目录 1、问题描述 2、为什么选择cef3浏览器组件 3、cef3组件的介绍与下载 4、将cef3组件封装成sdk 5、如何使用cef3组件加载web页面 5.1、了解CefApp与CefClient 5.2、初始化与消息循环 5.3、如何创建浏览器 5.4、重载CefClient类 6、在qt客户端集成cef组件 7、最后…

「12月·长沙」第三届传感、测量、通信和物联网技术国际会议(SMC-IoT 2024)

第三届传感、测量、通信和物联网技术国际会议&#xff08;SMC-IoT 2024&#xff09;将于2024年11月29日-2024年12月1日召开&#xff0c;由湖南涉外经济学院主办。会议中发表的文章将会被收录, 并于见刊后提交EI核心索引。 会议旨在围绕传感、测量、通信和物联网技术等相关研究…

基于node.js的宠物寄存管理系统,基于express的宠物寄存系统

摘 要 伴随着社会以及科学技术的发展&#xff0c;互联网已经渗透在人们的身边&#xff0c;网络慢慢的变成了人们的生活必不可少的一部分&#xff0c;紧接着网络飞速的发展&#xff0c;系统管理这一名词已不陌生&#xff0c;越来越多的宠物店等机构都会定制一款属于自己个性化…

DWA局部路径规划算法

DWA——Dynamic Window Approach动态窗口法 发展 动态窗口法是一种局部路径规划算法&#xff0c;起源于对移动机器人在复杂环境中实时避障的需求。该算法由F. D. Proentzen和O. Khatib提出&#xff0c;后经过不断优化&#xff0c;已成为移动机器人领域中的标准算法之一。 运…

xss.function靶场(hard)

文章目录 WW3源码分析源码 DOMPpurify框架绕过覆盖变量notifyjs作用域和作用链域构建payload WW3 源码 <!-- Challenge --> <div><h4>Meme Code</h4><textarea class"form-control" id"meme-code" rows"4"><…

Spring Boot实战:使用模板方法模式优化数据处理流程

概述 在软件开发过程中&#xff0c;我们经常需要处理各种各样的数据&#xff0c;这些数据可能来自不同的源&#xff0c;比如数据库、文件系统或者外部API等。尽管数据来源不同&#xff0c;但很多情况下处理这些数据的步骤是相似的&#xff1a;读取数据、清洗数据、转换数据格式…

华为的流程管理

华为建设流程体系始于2000年&#xff0c;那时华为公司面临着快速扩张和全球化发展的挑战&#xff0c;意识到传统的管理模式已经无法满足业务发展的需求。为了提高公司的管理效率和竞争优势&#xff0c;华为决定启动流程体系的建设。在建设过程中&#xff0c;华为借鉴了业界最佳…

云计算的三大服务模式:IaaS、PaaS、SaaS的深入解析

在数字化转型的浪潮中&#xff0c;云计算以其独特的灵活性、可扩展性和成本效益&#xff0c;正逐渐成为企业IT架构的核心。云计算提供了三种主要的服务模式&#xff0c;分别是基础设施即服务&#xff08;IaaS&#xff09;、平台即服务&#xff08;PaaS&#xff09;和软件即服务…

Spring发送邮件性能优化?如何集成发邮件?

Spring发送邮件安全性探讨&#xff01;Spring发送邮件功能有哪些&#xff1f; 邮件发送的性能逐渐成为影响用户体验的重要因素之一。AokSend将探讨如何在Spring框架中进行Spring发送邮件的性能优化&#xff0c;确保系统能够高效、稳定地处理大量邮件请求。 Spring发送邮件&am…

和鲸携手山东大学数字人文实验室,推动新文科与人工智能融合发展

为深入推进产教融合与校企合作&#xff0c;推动人工智能在人文学科中的广泛应用与深入发展&#xff0c;8 月 15 日&#xff0c;山东大学数字人文实验室与和鲸科技 101 计划推进会暨新文科人工智能实验室标杆案例打造讨论会于威海顺利召开。山东大学数字人文实验室副主任陈建红、…

12.2 使用prometheus-sdk向pushgateway打点

本节重点介绍 : 使用golang sdk打prometheus4种指标&#xff0c;推送到pushgateway gauge、counter、histogram、summary的初始化4种类似的设置值的方法推送到pushgateway的方法 prometheus配置采集pushgateway&#xff0c;grafana上配大盘 golang-sdk 项目地址 https://git…

系统架构设计师 - 软件工程(2)

软件工程 软件工程&#xff08;13-22分&#xff09;非常重要软件系统建模系统设计界面设计 ★★软件设计结构化设计 ★★面向对象设计 ★★★★★基本过程设计原则设计模式创建型模式&#xff1a;创建对象结构型模式&#xff1a;更大的结构行为型模式&#xff1a;交互及职责分配…

科三预约考试,为什么我场次排名在前,后面排名又变了

什么时候知道是否预约成功 系统确认考试预约结果的时间一般为考试前5-7个工作日&#xff0c;同时根据预约人数系统会自行判断提前1-2日或延长1-2日公示预约结果&#xff0c;学员至少考试前三天会收到预约成功短信通知。 如果预约失败了怎么办&#xff1f;会计入考试次数吗&am…

Java之线程篇一

目录 如何理解进程&#xff1f; 进程和线程的区别 线程的优点 线程的缺点 线程异常 线程用途 创建线程 方法一&#xff1a;继承Thread类&#xff0c;重写run() 观察线程 小结 方法二&#xff1a; 实现Runnable接口&#xff0c;重写run() 方法三&#xff1a;继承Threa…

【西安电子科技大学】2024年士兵计划考研信息总结!

西安电子科技大学 学校简介上方图片奖助学金下方图片研招网址https://gr.xidian.edu.cn/普通复试https://gr.xidian.edu.cn/info/1073/13301.htm士兵复试总分为各学科门类、各专业学位类别&#xff08;领域&#xff09;国家A类线&#xff0c;单科不限。士兵名额20报考说明无录取…

MES系统从哪几方面提升企业制造水平?

在当今这个快速变化的制造环境中&#xff0c;企业对于提升制造水平的追求从未停止。制造执行系统&#xff08;MES&#xff09;作为连接企业战略规划与车间实际操作的核心工具&#xff0c;其重要性日益凸显。盘古信息MES系统&#xff0c;凭借其独特的功能模块和创新的设计理念&a…

两种图像透明背景转特定颜色方法的比较

之前写过一篇博客&#xff0c;关于透明背景转换为特定颜色&#xff0c;当时使用了NumPy数组采用布尔索引转换的方式&#xff0c;这次我们把这种转换和常规的逐像素转换的方式进行比较&#xff0c;看那种方法效率更高。记得以前使用Matlab的时候&#xff0c;显然是矩阵布尔索引的…

基于SSM的体育馆预约管理系统---附源码84196

摘 要 体育馆作为一个重要的运动场所&#xff0c;需要进行预约管理以保证资源的合理利用和场馆秩序的维护。传统的人工预约管理方式存在效率低、容易出错等问题&#xff0c;因此&#xff0c;在互联网高速发展的当下&#xff0c;需要设计和实现一个基于SSM的体育馆预约管理系统&…

优思学院|六西格玛实施关键:如何整合定性与定量数据

在精益六西格玛的世界中&#xff0c;数据不仅是工具&#xff0c;更是推动变革和改进的关键力量。了解定性数据和定量数据的区别&#xff0c;可以为流程改进提供强大的见解和策略。 定性数据与定量数据的本质 首先&#xff0c;定性数据和定量数据是两种截然不同的概念。定性数据…

springboot+vue 初始

1.控制器 2.文件上传拦截器 #过滤规则 # 默认访问static下面的文件http://localhost:8009/4.jpeg&#xff0c; # 带上static-path-pattern/static/**后&#xff0c;http://localhost:8009/static/4.jpeg spring.mvc.static-path-pattern/static/**#静态资源位置 spring.web.res…