WebGPU你让我等的好辛苦啊

什么是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小白入门

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值