WebGPU你让我等的好辛苦啊

news2024/11/28 19:37:43

什么是WebGPU

WebGPU是一种新兴的Web标准,旨在为Web应用程序提供高性能的图形和计算功能。它是一种低级别的图形API,为开发人员提供了对现代GPU的直接访问,以实现更高效的图形渲染和通用计算。

WebGPU的设计目标是提供与现代图形API(如Vulkan和DirectX 12)类似的功能和性能,并且跨平台、可移植。它旨在解决现有Web图形API(如WebGL)的一些限制和性能瓶颈,并提供更好的控制权和更高的性能。

我是跟着 [Orillusion官方](https://www.bilibili.com/video/BV1uu411B7uq/?spm_id_from=333.337.search-card.all.click&vd_source=32b963024a0f192400a68b86871ed132)学习的,大家可以直接去看他们的视频。

示例代码地址

WebGPU的优势

以下是WebGPU的一些关键特性和优势:

  1. 高性能:WebGPU提供了更接近底层GPU的访问,允许开发人员充分利用现代图形硬件的性能。它采用了显式的并发性和更低的开销,可以更好地利用多核CPU和多个GPU。
  2. 现代特性:WebGPU支持现代图形特性,如计算着色器、几何着色器、裁剪、光栅化和深度测试等。它还提供了更灵活的着色器编程模型和更多的着色器阶段,以实现更高级的图形效果。
  3. 可移植性:WebGPU是为不同平台和设备设计的,可以在各种浏览器和操作系统上运行。这意味着开发人员可以编写一次代码,然后在各种设备上实现相似的性能和效果。
  4. 安全性:WebGPU在设计上考虑了安全性,并提供了一些保护措施,以防止恶意代码滥用图形硬件资源。它通过沙盒机制确保了Web应用程序的安全性。

WebGPU工作流程

webgpu的工作流程大概是这样的:

  • webgpu是一套基于浏览器的图形API,浏览器封装了现代图形API(Dx12、Vulkan、Metal),提供给Web 3D程序员,为 Web释放了更多的GPU 硬件的功能。
  • webgpu支持两种类型的管线:渲染管线和计算管线。
  • 渲染管线用于渲染图形,通常渲染到 <canvas> 元素中,它有两个主要阶段:顶点着色阶段和片元着色阶段。
  • 计算管线用于通用计算,它包含单独的计算阶段,在该阶段中,计算着色器接受通用的数据,在指定数量的工作组之间并行处理数据,然后将结果返回到一个或者多个缓冲区¹。
  • webgpu应用程序需要通过 Navigator.gpu 属性获取 GPU 对象,然后通过 GPU.requestAdapter () (en-US) 方法访问适配器,再通过 GPUAdapter.requestDevice () (en-US) 方法请求设备¹。
  • webgpu应用程序需要在 WGSL 写你的着色器代码并将其打包到一个或者多个着色器模块,然后创建相应的管线对象,并将其绑定到设备上。

image-20230524145410244

从一个三角形开始

废话不多说,我们来完成图形编程界的”Hello world“,来画一个三角形把。

基础配置

获取显卡适配器

通过 Navigator.gpu 属性获取 GPU 对象,然后通过 GPU.requestAdapter () 方法请求一个 GPUAdapter 对象,这个对象表示一个物理 GPU 和可用的驱动程序1。这个方法返回一个 Promise,如果成功,它会解析为一个 GPUAdapter 对象,你可以用它来获取 GPUDevice 对象,这个对象表示一个逻辑设备,你可以用它来访问 WebGPU 的所有功能1。你可以通过传递一个可选的设置对象来指定你想要的适配器类型,比如高性能或低功耗1。如果没有传递设置对象,设备会提供对默认适配器的访问,这通常对于大多数用途来说足够了。

const adapter = await navigator.gpu.requestAdapter();
if (!adapter) {
  throw new Error();
}

显卡适配器(GPUAdapter)是一个对象,它表示一个物理 GPU 和可用的驱动程序。你需要它,因为不同的系统可能有不同的 GPU 类型和本地 GPU API,而 WebGPU 需要提供一个统一的接口来访问 GPU 的功能。这段代码是通过 Navigator.gpu 属性获取 GPU 对象,然后通过 GPU.requestAdapter () 方法请求一个显卡适配器对象,并等待它返回一个 Promise,如果成功,它会解析为一个 GPUAdapter 对象。

获取显卡设备

这段代码的意思是,通过 adapter.requestDevice () 方法请求一个 GPUDevice 对象,这个对象表示一个逻辑设备,你可以用它来访问 WebGPU 的所有功能¹。这个方法返回一个 Promise,如果成功,它会解析为一个 GPUDevice 对象²。你可以通过传递一个可选的描述符对象来指定你想要的设备的特性和限制²。如果没有传递描述符对象,设备会使用默认的特性和限制。

  const device = await adapter.requestDevice();

GPUDevice 是一个对象,它表示一个逻辑设备,你可以用它来访问 WebGPU 的所有功能¹。它是通过 GPUAdapter.requestDevice () 方法从显卡适配器获取的²。你可以用它来创建渲染管线、计算管线、纹理、缓冲区、着色器模块等对象,以及发送渲染或计算命令到 GPU 队列³。你也可以用它来监听设备丢失或错误事件,以及销毁设备⁴。

获取WebGPU上下文

  const canvas = document.querySelector('#triangle') as HTMLCanvasElement;
  const context = canvas?.getContext('webgpu') as GPUCanvasContext;

webgpu上下文的作用是,让 HTML 上的 canvas 元素,作为 WebGPU 中的一个纹理,与 WebGPU 进行渲染互动¹。你可以通过 canvas 元素的 getContext (‘webgpu’) 方法获取 webgpu 上下文对象,它是一个 GPUCanvasContext 类型的对象²。你可以用它来配置 canvas 的显示属性,以及将渲染结果输出到 canvas 上³。

配置 上下文纹理对象

通过 navigator.gpu.getPreferredCanvasFormat() 方法返回一个最佳的 canvas 纹理格式,用于在当前系统上显示 8 位深度,标准动态范围的内容。这个方法通常用于提供给 GPUCanvasContext.configure() 调用的最佳格式值。

  const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
  context.configure({
    device,
    format: presentationFormat,
  });

这是推荐的做法,因为如果你不使用最佳格式来配置 canvas 上下文,你可能会产生额外的开销,比如额外的纹理拷贝,这取决于平台。这个方法不需要参数,返回值是一个字符串,表示一个 canvas 纹理格式,可以是 rgba8unorm 或 bgra8unorm。

顶点配置

初始化顶点buffer对象

  • 首先,创建一个Float32Array对象,用于存储三个顶点的坐标,每个顶点有三个分量(x, y, z)。
  • 然后,使用device.createBuffer方法,传入sizeusage参数,创建一个WebGPUBuffer对象。size参数表示缓冲区的字节长度,usage参数表示缓冲区的用途,这里是用作顶点缓冲区(GPUBufferUsage.VERTEX),并且可以被复制到其他缓冲区(GPUBufferUsage.COPY_DST)。
  • [最后,将Float32Array对象的数据复制到WebGPUBuffer对象中,以便在渲染时使用。
  const vertexArray = new Float32Array([
    0.0, 0.0, 0.0,
    1.0, 0.0, 0.0,
    0.0, 1.0, 0.0,
  ]);
  const vertexBuffer = device.createBuffer({
    size: vertexArray.byteLength,
    usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
  });

将顶点数据写入顶点buffer中

  • device.queue是一个GPUQueue对象,表示一个命令队列,用于执行一些操作,如拷贝或执行渲染命令。
  • writeBuffer方法是一个异步方法,用于将一个ArrayBuffer或其子类的数据写入到一个WebGPUBuffer对象中。
  • 这个方法有四个参数:目标缓冲区(destination),目标偏移量(destinationOffset),源数据(data),源偏移量(dataOffset)。
  • 在这段代码中,目标缓冲区是vertexBuffer,目标偏移量是0,源数据是vertexArray,源偏移量也是0。这意味着将vertexArray的所有数据写入到vertexBuffer的开头位置。
 device.queue.writeBuffer(vertexBuffer, 0, vertexArray);

初始化pipline

let pipeline = device.createRenderPipeline({
  layout: 'auto',
  primitive: {
    topology: 'triangle-list'
  },
  fragment: {
    module: device.createShaderModule({code: fragmentShader}),
    entryPoint: 'main',
    targets: [
      {
        format: presentationFormat
      }
    ]
  },
  vertex: {
    module: device.createShaderModule({
      code: vertexShader
    }),
    entryPoint: 'main',
    buffers: [
      {
        arrayStride: 12,

        attributes: [
          {
            shaderLocation: 0,
            offset: 0,
            format: 'float32x3'
          }
        ]
      }
    ]
  }
});

参数解释

  • layout

    layout是一个GPUPipelineLayout对象,用于描述一个渲染管线的布局1。渲染管线的布局指定了渲染管线需要的资源,如缓冲区、纹理、采样器等,以及它们在着色器中的绑定方式。在这段代码中,layout被设置为’auto’,表示由WebGPU自动推断渲染管线的布局,而不需要显式地创建一个GPUPipelineLayout对象3。

  • fragment

    fragment是一个GPUFragmentState对象,用于描述片元着色器的状态。片元着色器是用于计算每个像素的颜色的程序。在这段代码中,fragment对象有三个属性:

    • module,一个GPUShaderModule对象,表示片元着色器的代码模块,由device.createShaderModule方法创建,传入一个包含着色器代码的对象 。
    • entryPoint,一个字符串,表示片元着色器的入口函数的名称,这里是 main
    • targets,一个数组,表示片元着色器的输出目标,每个目标对应一个GPUColorTargetState对象,用于指定输出目标的格式、混合模式和写掩码 。在这段代码中,只有一个输出目标,格式为presentationFormat,表示与画布的格式相同。
  • vertex

    vertex是一个GPUVertexState对象,用于描述顶点着色器的状态。顶点着色器是用于计算每个顶点的位置和属性的程序。在这段代码中,vertex对象有三个属性:

    • module,一个GPUShaderModule对象,表示顶点着色器的代码模块,由device.createShaderModule方法创建,传入一个包含着色器代码的对象 。
    • entryPoint,一个字符串,表示顶点着色器的入口函数的名称,这里是’main’ 。
    • buffers,一个数组,表示顶点着色器的输入缓冲区,每个缓冲区对应一个GPUVertexBufferLayout对象,用于指定缓冲区的步长、属性和插值模式 。在这段代码中,只有一个输入缓冲区,步长为12(表示每个顶点占用12个字节),属性为一个数组,包含一个GPUVertexAttribute对象,用于指定属性在着色器中的位置、偏移量和格式 。这里的属性位置是0,偏移量是0,格式是’float32x3’(表示每个属性由三个32位浮点数组成)。
  • primitive

    primitive是一个GPUPrimitiveState对象,用于描述图元的状态。图元是由顶点组成的基本图形,如点、线、三角形等。在这段代码中,primitive对象有一个属性:

    • point-list:表示每个顶点都是一个独立的点。
    • line-list:表示每两个顶点都是一条独立的线段。
    • line-strip:表示每个顶点都和前一个顶点连成一条线段,形成一条折线。
    • triangle-list:表示每三个顶点都是一个独立的三角形。
    • triangle-strip:表示每个顶点都和前两个顶点连成一个三角形,形成一条三角形带。

顶点着色器代码

@vertex
fn main(@location(0) pos:vec3<f32>)-> @builtin(position) vec4<f32>{
    var pos2 = vec4<f32>(pos, 1.0);
    pos2.x -= 0.2;
    return pos2;
}

片元着色器

@fragment
fn main()->@location(0) vec4<f32>{
    return vec4<f32>(1.0,0.0,0.0,1.0);

渲染流程

image-20230529144518386

应用程序阶段

用阶段是在CPU中进行的,主要任务是准备好场景数据,设置好渲染状态,然后输出渲染图元,即为下一阶段提供所需的几何信息。什么是图元?图元是指渲染的基本图形,通俗来讲图元可以是顶点,线段,三角面等,复杂的图形可以通过渲染多个三角形来实现。

应用阶段可细分为3个子阶段

  1. 把数据加载到显存中。所有渲染所需的数据都需要从硬盘加载到系统内存中(RAM),然后网格和纹理等数据又被加载到显存(VRAM)。这是因为显卡对于显存的访问速度更快,而且大多数显卡对于RAM没有直接的访问权利。
  2. 设置渲染状态。比如设置使用的着色器,材质,纹理,光源属性等。
  3. 调用Draw Call。Draw Call就是一个命令,它的发起方是CPU,接收方是GPU。这个命令仅仅会指向一个需要被渲染的图元列表,而不会再包含任何材质信息,这是因为我们已经在上一个阶段设置过了。当给定了一个Draw Call时,GPU就会根据渲染状态和所有输入的顶点数据来进行计算,最终输出成屏幕上显示的那些漂亮的像素。

几何阶段

几何阶段是在GPU上进行的,主要任务是输出屏幕空间的顶点信息。几何阶段用于处理从上一阶段接收到的待绘制物体的几何数据(可以理解为Draw Call指向的图元列表),与每个渲染图元打交道,进行逐顶点,逐多边形的操作。几何阶段的一个重要任务就是把顶点坐标变换到屏幕空间中,再交给光栅化器进行处理。通过对输入的图元进行多步处理后,这一阶段将会输出屏幕空间的二维顶点坐标,每个顶点对应的深度值,着色等相关信息。

顶点着色器

顶点着色器的处理单位是顶点,输入进来的每个顶点都会调用一次顶点着色器。顶点着色器本身不可以创建或者销毁任何顶点,而且无法得到顶点和顶点之间的关系,例如我们无法得知两个顶点是否属于同一个三角网格。但正因为这样的相互独立性,GPU可以利用本身的特性并行化处理每一个顶点,这意味着这一阶段的处理速度会很快。
顶点着色器完成的工作主要有:坐标变换和逐顶点光照。

image-20230530111951637

顶点着色器必须进行顶点的坐标变换,需要时还可以计算和输出顶点的颜色。例如我们可能需要进行逐顶点的光照。坐标变换,就是对顶点的坐标进行某种变换。顶点着色器可以在这一步中改变顶点的位置,这在顶点动画中是非常有用的。无论我们在顶点着色器中怎样改变顶点的位置,一个基本的顶点着色器必须要完成的一个工作是,把顶点坐标从模型空间转换到齐次裁剪空间。
img

把顶点坐标转换到齐次裁剪空间后,接着通常再由硬件做透视除法,最终得到归一化的设备坐标(NDC)。

屏幕映射

这一步输入的坐标仍然是三维坐标系下的坐标(范围在单位立方体内)。屏幕映射的任务是把每个图元的x和y坐标转换到屏幕坐标系下,这实际上是一个缩放的过程。屏幕坐标系是一个二维坐标系,它和我们用于显示画面的分辨率有很大关系

image-20230529151613050

裁剪

裁剪阶段的目的是将那些不在摄像机视野内的顶点裁减掉,并剔除某些三角图元的面片(面片通常是由一个一个更小的图元来构成的)。

image-20230529151427095

光栅化阶段

这一阶段也是在GPU上执行的,将会使用上个阶段传递的数据来产生屏幕上的像素,并输出最终的图像。光栅化的任务主要是决定每个渲染图元中的哪些像素应该被绘制在屏幕上。它需要对上一个阶段得到的逐顶点数据(例如纹理坐标,顶点颜色等)进行插值,然后再进行逐像素处理。可以这样理解,几何阶段只是得到了图元顶点的相关信息,例如对于三角形图元,得到的就是三个顶点的坐标和颜色信息等。而光栅化阶段要做的就是根据这三个顶点,计算出这个三角形覆盖了哪些像素,并为这些像素通过插值计算出它们的颜色。

三角形设置

这个阶段会计算光栅化一个三角网格所需的信息。具体来说,上一个阶段输出的都是三角网格的顶点,但如果要得到整个三角网格对像素的覆盖情况,我们就必须计算每条边上的像素坐标。为了能够计算边界像素的坐标信息,我们就需要得到三角形边界的表示方式。这样一个计算三角网格表示数据的过程就叫做三角形设置。它的输出是为了给下一个阶段做准备。

三角形遍历

三角形遍历阶段将会检查每个像素是否被一个三角网格所覆盖。如果被覆盖的话,就会生成一个片元。而这样一个找到哪些像素被三角网格覆盖的过程就是三角形遍历,这个阶段也被称为扫描变换。
三角形遍历阶段会根据上一个阶段的计算结果来判断一个三角网格覆盖了哪些像素,并使用三角网格3个顶点的顶点信息对整个覆盖区域的像素进行插值。像素和片元是一一对应的,每个像素都会生成一个片元,片元中的状态记录了对应像素的信息,是对三个顶点的信息进行插值得到的。

image-20230529151754391

这一步的输出就是得到一个片元序列。需要注意的是一个片元并不是真正意义上的像素,而是包含了很多状态的集合,这些状态用于计算每个像素的最终颜色。这些状态包括了但不限于它的屏幕坐标,深度信息,以及其他从几何阶段输出的顶点信息,例如法线,纹理坐标等。

片元着色器

片元着色器用于实现逐片元的着色操作,输出是一个或者多个颜色值(即计算该片元对应像素的颜色,但不是最终颜色)。这一阶段可以完成很多重要的渲染技术,其中最重要的技术之一就是纹理采样。为了在片元着色器中进行纹理采样,我们通常会在顶点着色器阶段输出每个顶点对应的纹理坐标,然后经过光栅化阶段对三角网格的3个顶点对应的纹理坐标进行插值后,就可以得到其覆盖的片元的纹理坐标了。
img

根据上一步插值后的片元信息,片元着色器计算该片元的输出颜色
虽然片元着色器可以完成很多重要效果,但它的局限在于,它仅可以影响单个片元。也就是说,当执行片元着色器时,它不可以将自己的任何结果直接发送给它的邻居们。当然导数信息例外。

创建CPUCommandEncoder

GPUCommandEncoderWebGPU API 的一个接口,用于编码要发送给 GPU 的命令。

创建CPUCommandEncoder

创建 GPUCommandEncoder 对象,用于编码要发送给GPU的命令。GPUCommandEncoder 对象是通过 device.createCommandEncoder() 方法创建的,它可以调用不同的方法来开始渲染或计算通道,清除缓冲区,复制数据,写入时间戳等。GPUCommandEncoder 对象与 GPUBuffer 对象的方法不同,它们是“缓冲”的,意味着它们会在某个时刻批量地发送给GPU。而 GPUBuffer 对象的方法是“非缓冲”的,意味着它们在被调用时就立即执行。

 const commandEncoder = device.createCommandEncoder();

创建渲染通道

开始一个渲染通道,返回一个 GPURenderPassEncoder 对象,用于控制渲染过程。这段代码指定了一个颜色附件,它是一个 GPUTextureView 对象,用于表示要渲染到的纹理。这段代码还指定了清除值(r: 0.5, g: 0.5, b: 0.5, a: 0.0),加载操作(‘clear’)和存储操作(‘store’),分别表示在渲染开始时要清除纹理的颜色,以及在渲染结束时要保留纹理的颜色。

  const renderPass = commandEncoder.beginRenderPass({
    colorAttachments: [
      {
        view: context.getCurrentTexture().createView(),
        clearValue: {r: 0.5, g: 0.5, b: 0.5, a: 0.0},
        loadOp: 'clear',
        storeOp: 'store'
      }
    ]
  });

配置渲染通道

在渲染通道中设置渲染管线,顶点缓冲区,绘制三角形,结束渲染通道,完成命令编码,生成命令缓冲区,并将命令缓冲区提交给设备队列,以便在GPU上执行。

  renderPass.setPipeline(pipeline);
  renderPass.setVertexBuffer(0, vertexBuffer);
  renderPass.draw(3);
  renderPass.end();
  let commandBuffer = commandEncoder.finish();
  device.queue.submit([commandBuffer]);

渲染效果

image-20230530112324316

进阶挑战

画一个四边形

经过前面的学习,我们知道画三角形只需要指定三个顶点即可。并且我们的图元拓扑结构为 triangle-list。因此我们直接在绘制一个三角形,让两个三角形长边重合即可。因此直接改变顶点数组即可,顺带也要修改 renderPass.draw(6);由原本的3变为6,因为一共有六个点了现在:

  const vertexArray = new Float32Array([
    0.0, 0.0, 0.0,
    1.0, 0.0, 0.0,
    0.0, 1.0, 0.0,
    1.0, 0.0, 0.0,
    0.0, 1.0, 0.0,
    1.0, 1.0, 1.0
  ]);

效果如下:

image-20230530162827445

画一个立方体

首先我们要知道,屏幕上显示的所有物体本质都是像素,我们可以认为这些像素组成的图形就是平面的,所谓的立体感不过是欺骗眼睛的来。就想画画的时候,只要满足近大远小的透视规则,就可以让画看起来很立体。

image-20230530163235059

因此我们只要实现类似的效果即可,那么怎么实现呢,那就要介绍一下mvp矩阵了。

mvp矩阵

MVP分别是模型(Model)、观察(View)、投影(Projection)三个矩阵,它们用来将三维模型的顶点坐标从模型空间变换到屏幕空间。具体来说:

  • 模型矩阵用于将模型的局部坐标转换为世界坐标,以便将模型摆放到世界空间中。
  • 观察矩阵用于将世界坐标转换为以摄像机为中心的视觉坐标,以便从摄像机的视角观察模型。
  • 投影矩阵用于将视觉坐标转换为裁剪坐标,并判断顶点是否在可见范围内,以便进行透视或正交投影。

具体的公式可以参考以下:

  • 模型矩阵:
    M = T × R × S M = T \times R \times S M=T×R×S

  • 观察矩阵:
    V = [ R − R T × C O 1 ] V = \begin{bmatrix}R & -RT \times C \\ O & 1\end{bmatrix} V=[RORT×C1]

  • 投影矩阵:
    P = M 正交 × M 挤压 P = M_{正交} \times M_{挤压} P=M正交×M挤压

其中, T 是平移矩阵, R 是旋转矩阵, S 是缩放矩阵, C 是摄像机位置, M 正交 是正交投影或透视投影矩阵, M 挤压 是将视锥体挤压成立方体的矩阵。 其中,T 是平移矩阵,R是旋转矩阵,S是缩放矩阵,C是摄像机位置,M_{正交}是正交投影或透视投影矩阵,M_{挤压}是将视锥体挤压成立方体的矩阵。 其中,T是平移矩阵,R是旋转矩阵,S是缩放矩阵,C是摄像机位置,M正交是正交投影或透视投影矩阵,M挤压是将视锥体挤压成立方体的矩阵。

说人话就是,三维物体每个顶点肯定对应 x,y,z。M矩阵的作用就是判断这些顶点通过平移,旋转,缩放等变换之后的位置。V矩阵的作用就是根据你看的角度和距离的不通,这些顶点可能就在屏幕的不同的位置。比如正视一个人的时候那个人就在你眼睛画面的正中间,但是你用余光看的时候他就在你眼睛画面的最边上,相对于你的眼睛,那个人的位置就变了。而投影矩阵类似皮影戏的效果,物体的影子投射在幕布上,此时三维的物体就被平面化了。并且超过幕布的部分我们就不显示了。

代码实现

这里使用 gl-matrix库进行矩阵运算。

模型,观察矩阵如下:

    const mvMatrix = mat4.create()
    mat4.translate(mvMatrix, mvMatrix, vec3.fromValues(position.x, position.y, position.z))
    mat4.rotateX(mvMatrix, mvMatrix, rotation.x);
    mat4.rotateY(mvMatrix, mvMatrix, rotation.y);
    mat4.rotateZ(mvMatrix, mvMatrix, rotation.z);
    mat4.scale(mvMatrix, mvMatrix, vec3.fromValues(scale.x, scale.y, scale.z));

投影矩阵如下:

    const projectMatrix = mat4.create();
    mat4.perspective(projectMatrix, Math.PI / 4, size.width / size.height, 1, 100);

mvp矩阵如下:

    const mvpMatrix = mat4.create();
    mat4.multiply(mvpMatrix, projectMatrix, mvMatrix);

写如GPUBuffer中:

    device.queue.writeBuffer(mvpMatrixBuffer, 0, mvpMatrix as Float32Array)

更改顶点着色器:

//通过绑定获取mvp矩阵
@group(0) @binding(1) var<uniform> mvp:mat4x4<f32>;

struct  VertexOutput{
    @builtin(position) position: vec4<f32>,
    @location(0) fragPostion:vec4<f32>
}

@vertex
fn main(@location(0) pos:vec3<f32>)-> VertexOutput{
    var out:VertexOutput;
    // mvp矩阵乘以顶点位置,就可以得到对应变换后的坐标
    out.position= mvp*vec4<f32>(pos,1.0);
    // 偏远颜色根据坐标位置变化
    out.fragPostion = vec4<f32>(pos,1.0);
    return out;
}

效果如下:

image-20230530170331111

总结

在肉眼可见的未来WebGPU肯定会替代WebGL,目前各大主流的封装库比如three和babylon也都在积极兼容WebGL,甚至也出现了纯WebGPU实现的引擎比如Orillusion。我们学习WebGPU并不意味着一定用它来完成项目,更多的还是使用其封装库去实现。学习只是为了加深理解,知其所以然,亦或者做到对程序的更高程度的掌控,做到更好的特质化。

限于篇幅和能力,只能挑部分说,文中所有的代码我都在Github开源,大家可以自行下载运行查看效果。

关于WebGPU的计算着色器我打算单独写文章来谈谈了。

示例代码地址

参考

一篇文章搞懂到底什么是渲染流水线

WebGPU小白入门

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

文心一言 VS 讯飞星火 VS chatgpt (26)-- 算法导论5.1 1题

一、证明:假设在过程 HIRE-ASSISTANT 的第 4 行中&#xff0c;我们总能决定哪一个应聘者最佳。则意味着我们知道应聘者排名的全部次序。 文心一言&#xff1a; 证明: 假设在过程 HIRE-ASSISTANT 的第 4 行中&#xff0c;我们总能决定哪一个应聘者最佳。则意味着我们知道应聘…

png图片压缩后黑底问题解决

问题背景 使用thumbnail对图片进行压缩&#xff0c;偶然会发现对png图片出现黑底的情况如下&#xff1a; 压缩前 压缩后 问题解决 对网上搜到的解决方法主要有两种&#xff1a; 1.指定png输出 JAVA - Get black background when uploading PNG image - Stack Overflow 一…

大数据好找工作么?前景如何

大数据好不好找工作不是一概而论的&#xff0c;要根据你个人的学历情况&#xff0c;掌握技能程度&#xff0c;所在城市招聘需求&#xff0c;甚至是你的面试能力和简历是否突出优势有关。 但是毋庸置疑的是&#xff0c;大数据目前的发展前景还是相当优秀的。 我们知道&#xf…

什么是测试开发,聊聊我对测试开发的看法

目录 前 还没来阿里之前&#xff0c;我对测开的看法 多数人眼中的测试开发 来了阿里之后&#xff0c;对测开看法有了转变 阿里的测开是干嘛的 测试平台的好处和不足 我对测试平台的看法 测试平台是测开必需品吗&#xff1f; 实际项目中用不到测试平台&#xff0c;有必要…

GIT | 日常命令查阅表

最近公司代码管控比较乱&#xff0c;有天就利用分支进行了代码梳理&#xff0c;当时就遇到一些困惑&#xff0c;抽空就把git 再学了一下。 以前我是用git命令的&#xff0c;但是敲命令对我来说还是有点麻烦&#xff08;主要是git 功力不够&#xff09;&#xff0c;看到有同事用…

jmeter性能测试技巧(欢迎提问,不定时更新)

问题1 如何在大并发测试下&#xff0c;让登录或者后续接口只执行一次&#xff1f; 分析 2023Jmeter性能测试项目实战教程&#xff0c;十年测试大佬手把手教你做性能&#xff01;_哔哩哔哩_bilibili2023Jmeter性能测试项目实战教程&#xff0c;十年测试大佬手把手教你做性能&…

SQL综合案例之电商漏斗转化分析,pv,uv及

漏斗模型示例&#xff1a; 不同的业务场景有不同的业务路径 : 有先后顺序, 事件可以出现多次 注册转化漏斗 : 启动APP --> APP注册页面--->注册结果 -->提交订单-->支付成功 搜购转化漏斗 : 搜索商品--> 点击商品--->加入购物车-->提交订单-->支付成功…

Scrum中可以有测试人员吗?

作者 | Federico Toledo Scrum 是将质量融入产品开发和创建敏捷团队的宝贵框架。测试人员如何适应这一切&#xff1f;让我们深入研究这篇文章。 毫无疑问&#xff0c;Scrum是在任何环境中为团队寻求业务敏捷性&#xff0c;以及应对不同复杂挑战的最重要工具之一。 正如《福布斯…

在pycharm里安装pytorch环境-GPU版

1、安装Anaconda 在官网下载安装&#xff1a;https://www.anaconda.com/download 2、安装pycharm https://www.jetbrains.com/pycharm/download/#sectionwindows 使用社区版即可。 3、检查conda环境 按winr&#xff0c;输入cmd回车打开命令窗 在命令窗内输入conda 环境无问…

小学课后兴趣班选课平台的设计与实现(ASP.NET,SQLServer)

系统功能模块设计 中小学课后兴趣班选课平台包括前台功能模块和后台功能模块&#xff1a;前台功能模块是给会员使用的功能模块&#xff0c;在前台功能模块中会员可以实现在线注册&#xff0c;登录&#xff0c;查看发布的新闻资讯信息&#xff0c;查看教师&#xff0c;在线留言&…

软件测试的 20 个误区

软件测试中常遇到的 20 个误区&#xff0c;争取能给想从事软件测试的小伙伴一点启发。 1、测试人员不需要了解软件开发知识 抛开自动化测试&#xff0c;测试开发等&#xff0c;这些是必须要学习软件开发知识。功能测试和接口测试等还是需要软件开发知识的&#xff0c;例如新建…

电动汽车、车载充电器及其过流保护电路介绍

摘要&#xff1a;本文通过比亚迪公司的专利了解电动汽车、车载充电器及其过流保护电路&#xff0c;其中&#xff0c;车载充电器包括AC/DC变换器和DC/DC变换器&#xff0c;AC/DC变换器和DC/DC变换器均采用光耦驱动的SiC开关管&#xff0c;过流保护电路包括&#xff1a;电流检测单…

ChatGPT与网络安全

文章目录 一、“AI用于攻击”二、“AI用于安全&#xff08;防御&#xff09;”三、“AI的防御”四、“AI被攻击” ChatGPT作为基于生成式预训练模型&#xff08;GPT&#xff09;的聊天机器人&#xff0c;其核心技术是自然语言处理&#xff08;NLP&#xff09;。随着NLP技术的不…

OS实战笔记(9)-- 构建二级引导器

Grub内核映像格式 Grub工作的时候&#xff0c;需要一个内核映像文件&#xff0c;其中包括了二级引导器模块、内核模块、图片和字库等。这些不同的文件都被放到了一个映像文件中&#xff0c;为了Grub能够正常加载&#xff0c;需要一个预先定义好的格式&#xff0c;以便Grub能解析…

代码实现 ResNet 详解

零、ResNet的介绍 ResNet代码&#xff08;含详细的使用说明&#xff09;&#xff1a; https://github.com/GarsonWw/resnet-garson.git 当谈到深度学习中的卷积神经网络时&#xff0c;ResNet&#xff08;Residual Network&#xff09;是一个备受赞誉且引人注目的架构。ResNet…

最全的mysql编码集问题排查

用navicate导入一个json文件数据的时候&#xff0c;发现中文有一些是乱码的&#xff0c;查了很多资料&#xff0c;发现mysql、navicate编码集都没问题&#xff0c;包括导入流程&#xff0c;那是什么原因呢&#xff1f;想着难道是电脑的编码集影响了&#xff1f;于是调整以后&am…

【C语言】结构体——我就是秩序的创建者!(结构体数组、结构体指针、嵌套、匿名、字面量、伸缩型数组、链式结构)

一、结构体基础1.1 声明和定义1.1 初始化和赋值1.3 访问结构体成员 二、结构体数组2.1 定义和初始化2.2 访问 三、结构体的嵌套五、指向结构体的指针六、向函数传递结构体6.1 只传递结构体成员6.2 传递结构体指针6.3 传递结构体 七、结构体的其他特性——不容小觑7.1 结构体的大…

硬盘数据丢失怎么办?一招轻松恢复硬盘数据!

硬盘应该是最为常用的数据存储设备了&#xff0c;它为电脑等设备提供巨大的存储空间。我们在平时的工作和学习中也经常会使用硬盘来存储数据&#xff0c;很多用户会将多年搜集到的资料存到电脑硬盘里。 硬盘上的文件&#xff0c;不论是工作文档还是照片、视频&#xff0c;对用…

(四)ArcGIS空间数据的转换与处理——数据结构转换

ArcGIS空间数据的转换与处理——数据转换 空间数据的来源很多&#xff0c;如地图、工程图、规划图、航空与遥感影像等&#xff0c;因此空间数据也有多种格式。根据应用需要&#xff0c;需对数据进行格式转换&#xff0c;不同数据结构间的转换主要包括矢量数据到栅格数据的转换…

Guava的骚操作,大大提升了我摸鱼的时间

以面向对象思想处理字符串对基本类型进行支持总结 概述 1、Guava是google公司开发的一款Java类库扩展工具包&#xff0c;包括新的集合类型&#xff08;如 multimap 和 multiset&#xff09;、不可变集合、图形库&#xff0c;以及用于缓存、并发、I/O等实用程序。使用这些API一…