美文网首页
WebGPU入门(一)--认识 WebGPU

WebGPU入门(一)--认识 WebGPU

作者: 晨钟暮鼓_7bc3 | 来源:发表于2023-05-17 10:46 被阅读0次

1. WebGpu的前辈WebGL

要说起webGPU就不得不提起一下他的前辈webgl.这样才能更好知道WebGPU 取代 WebGL 为什么是大势所趋。

 说到 WebGL,就不得不说说 OpenGL。在早期的个人电脑中,使用最广泛的 3D 图形渲染技术是 Direct3D 和 OpenGL。Direct3D 是微软 DirectX 技术的一部分,主要用于 Windows 平台。 OpenGL 作为一种开源的跨平台技术,赢得了众多开发者的青睐。

  后来一个特殊的版本——OpenGL ES,它专为嵌入式计算机、智能手机、家用游戏机和其他设备而设计。它从 OpenGL 中删除了许多旧的和无用的功能,同时添加了新功能。例如去掉了矩形等多余的多边形,只保留点、线、三角形等基本图形。这使它在保持轻巧的同时仍然足够强大以渲染精美的 3D 图形。

  而 WebGL 是从 OpenGL ES 派生出来的,它专注于 Web 的 3D 图形渲染。
下图展示了它们之间的关系:


image.png

WebGL 历史

image.png

 从上图可以看出,WebGL 已经很老了。不仅因为它存在已久,还因为它的标准是从 OpenGL 继承而来的。OpenGL 的设计理念可以追溯到 1992 年,而这些古老的理念其实与今天 GPU 的工作原理非常不符。

  对于浏览器开发者来说,需要适配 GPU 的不同特性,这给他们带来了很多不便。虽然这些对于上层开发人员来说是看不到的。

 从上图可以看出,2014 年苹果发布了 Metal。 Steve Jobs 是 OpenGL ES 的支持者,他认为这是行业的未来。所以当时苹果设备上的游戏都依赖 OpenGL ES(比如愤怒的小鸟,水果忍者),都是很经典的游戏。

 但乔布斯去世后,苹果放弃了 OpenGL ES,开发了新的图形框架 Metal。

 微软在 2015 年也发布了自己的 D3D12【Direct3D 12】图形框架。紧随其后的是 Khronos Group,图形界的国际组织,类似于前端圈子里的 W3C、TC39。而 WebGL 是它的标准。甚至也逐渐淡化了 WebGL,转而支持现在的 Vulkan。

 迄今为止,Metal、D3D12 [Direct3D 12] 和 Vulkan 并列为现代三大图形框架。这些框架充分释放了 GPU 的可编程能力,让开发者可以最大限度的自由控制 GPU。

 同样重要的是要注意,当今的主流操作系统不再支持 OpenGL 作为主要支持。这意味着今天编写的每一行 WebGL 代码都有 90% 的机会不被 OpenGL 绘制。它在 Windows 计算机上使用 DirectX 绘制,在 Mac 计算机上使用 Metal 绘制。

 可见 OpenGL 已经过期了。但这并不意味着它会消失。继续在嵌入式、科学研究等特殊领域发挥作用。

 WebGL 也是如此,大量的适配工作使得推进困难重重,于是推出了 WebGPU。

2. WebGPU介绍

WebGPU 是由 W3C GPU for the Web 社区组所发布的规范,目标是允许网页代码以高性能且安全可靠的方式访问 GPU 功能。

  • WebGPU API 使 web 开发人员能够使用底层系统的 GPU(图形处理器)进行高性能计算并绘制可在浏览器中渲染的复杂图形。
  • WebGPU 是 WebGL 的继任者,为现代 GPU 提供更好的兼容、支持更通用的 GPU 计算、更快的操作以及能够访问到更高级的 GPU 特性。
  • 封装了现代图形API(Dx12、Vulkan、Metal),提供给Web 3D程序员,为 Web释放了更多的GPU 硬件的功能。

3. WebGPU架构原理

image.png

WebGPU 使用Adapter来实现从操作系统的本机图形 API 到 WebGPU 的转换层。对于使用者是通过LogicalDevice来实现对GPU的抽象,由于浏览器是可以运行多个 Web 应用程序的单一 OS 级应用程序,因此需要多路复用,以便每个 Web 应用程序感觉就像它拥有对 GPU 的唯一控制权。这就是逻辑设备抽象的作用。

4、着色器

使用过WebGL的都知道,我们要告诉GPU需要绘制什么是通过着色器和片段着色器,需要你将数据缓冲区上传到 GPU,并告诉它如何将该数据解释为一系列三角形。每个顶点占据该数据缓冲区的一块,描述该顶点在 3D 空间中的位置,但可能还包括颜色、纹理 ID、法线和其他内容等辅助数据。列表中的每个顶点都由 GPU 在顶点阶段处理,在每个顶点上运行顶点着色器,这将应用平移、旋转或透视变形。

着色器: “着色器”这个词曾经让我感到困惑,因为你可以做的不仅仅是着色。但在过去(即 1980 年代后期!),这个术语是恰当的:它是在 GPU 上运行的一小段代码,用于决定每个像素应该是什么颜色,这样你就可以正在渲染的对象进行着色,实现灯光和阴影的错觉。如今,着色器泛指在 GPU 上运行的任何程序。

GPU 现在对三角形进行光栅化,这意味着 GPU 会计算出每个三角形在屏幕上覆盖的像素。然后每个像素由片段着色器处理,它可以访问像素坐标,也可以访问辅助数据来决定该像素应该是哪种颜色。如果使用得当,可以使用此过程创建令人惊叹的 3D 图形。

这种将数据传递到顶点着色器,然后到片段着色器,然后将其直接输出到屏幕上的系统称为管道,在 WebGPU 中,你必须明确定义管道。

5. 管线

管线(pipeline)是一个逻辑结构,其包含在你完成程序工作的可编程阶段。WebGPU 目前能够处理两种类型的管线:

  • 渲染管线用于渲染图形,通常渲染到 <canvas> 元素中,但它也可以在画面之外的地方渲染图形。它有两个主要阶段:

    • 顶点着色阶段:在该阶段中,顶点着色器(vertex shader)接受 GPU 输入的位置数据并使用像旋转、平移或透视等特定的效果将顶点在 3D 空间中定位。然后,这些顶点会被组装成基本的渲染图元,例如三角形等,然后通过 GPU 进行光栅化,计算出每个顶点应该覆盖在 canvas 上的哪些像素。
    • 片元着色阶段:在该阶段中,片元着色器(fragment shader)计算由顶点着色器生成的基本图元所覆盖的每个像素的颜色。这些计算通常使用输入,如图像(以纹理的方式)提供表面细节以及虚拟化光源的位置和颜色。
  • 计算管线用于通用计算。计算管线包含单独的计算阶段,在该阶段中,计算着色器(compute shader)接受通用的数据,在指定数量的工作组之间并行处理数据,然后将结果返回到一个或者多个缓冲区。这些缓冲区可以包含任意类型的数据。

image.png

6.命令编码器

命令编码器(mmand encoder),的意思是说你可以把你想让 GPU 执行所有的命令都压到命令编码器里面,让命令编码器把所有的这些命令编在一起,然后存成命令缓存,然后再发送到 GPU 里面去执行。这样设计的好处就是把数据编码这种cpu擅长的事情交给cpu处理,这样可以减少和GPU的通信开销。提高效率。

7.WebGPU作流程和代码实践

有了上面这些知识点,就可以来看看WebGPU的一个工作流程。

image.png

根据这个流程,我们就可以上手来写一个简单的demo,通过编码来体会它的工作机制。直接上代码。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <canvas></canvas>
    <script type="module">
      const triangleVert = `
        @vertex
        fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
            var pos = array<vec2<f32>, 3>(
                vec2<f32>(0.0, 0.5),
                vec2<f32>(-0.5, -0.5),
                vec2<f32>(0.5, -0.5)
            );
            return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
        }
      `;
      const redFrag = `
        @fragment
        fn main() -> @location(0) vec4<f32> {
            return vec4<f32>(1.0, 0.0, 0.0, 1.0);
        }
       `;
      // initialize webgpu device & config canvas context
      async function initWebGPU(canvas) {
        if (!navigator.gpu) throw new Error("Not Support WebGPU");
        const adapter = await navigator.gpu.requestAdapter({
          powerPreference: "high-performance",
          // powerPreference: 'low-power'
        });
        if (!adapter) throw new Error("No Adapter Found");

        const device = await adapter.requestDevice({
          requiredFeatures: ["texture-compression-bc"],
          requiredLimits: {
            maxStorageBufferBindingSize:
              adapter.limits.maxStorageBufferBindingSize,
          },
        });

        // 获取canvas
        const context = canvas.getContext("webgpu");

        // 获取浏览器默认的颜色格式
        const format = navigator.gpu.getPreferredCanvasFormat();

        const devicePixelRatio = window.devicePixelRatio || 1;
        canvas.width = canvas.clientWidth * devicePixelRatio;
        canvas.height = canvas.clientHeight * devicePixelRatio;
        const size = { width: canvas.width, height: canvas.height };
        context.configure({
          // json specific format when key and value are the same
          device,
          format,
          // prevent chrome warning
          alphaMode: "opaque",
        });
        return { device, context, format, size };
      }

      // create a simple pipiline
      async function initPipeline(device, format) {
        const descriptor = {
          layout: "auto",
          vertex: {
            module: device.createShaderModule({
              code: triangleVert,
            }),
            entryPoint: "main",
          },
          primitive: {
            topology: "triangle-list", // try point-list, line-list, line-strip, triangle-strip?
          },
          fragment: {
            module: device.createShaderModule({
              code: redFrag,
            }),
            entryPoint: "main",
            targets: [
              {
                format: format,
              },
            ],
          },
        };
        return await device.createRenderPipelineAsync(descriptor);
      }
      // create & submit device commands
      function draw(device, context, pipeline) {
        const commandEncoder = device.createCommandEncoder();
        const view = context.getCurrentTexture().createView();
        const renderPassDescriptor = {
          colorAttachments: [
            {
              view: view,
              clearValue: { r: 0, g: 0, b: 0, a: 1.0 },
              loadOp: "clear", // clear/load
              storeOp: "store", // store/discard
            },
          ],
        };
        const passEncoder =
          commandEncoder.beginRenderPass(renderPassDescriptor);
        passEncoder.setPipeline(pipeline);
        // 3 vertex form a triangle
        passEncoder.draw(3);
        passEncoder.end();
        // webgpu run in a separate process, all the commands will be executed after submit
        device.queue.submit([commandEncoder.finish()]);
      }

      async function run() {
        const canvas = document.querySelector("canvas");
        if (!canvas) throw new Error("No Canvas");
        const { device, context, format } = await initWebGPU(canvas);
        const pipeline = await initPipeline(device, format);
        // start draw
        draw(device, context, pipeline);

        // re-configure context on resize
        window.addEventListener("resize", () => {
          canvas.width = canvas.clientWidth * devicePixelRatio;
          canvas.height = canvas.clientHeight * devicePixelRatio;
          // don't need to recall context.configure() after v104
          draw(device, context, pipeline);
        });
      }
      run();
    </script>
    <style>
      html,
      body {
        margin: 0;
        width: 100%;
        height: 100%;
        background: #000;
        color: #fff;
        display: flex;
        text-align: center;
        flex-direction: column;
        justify-content: center;
      }
      canvas {
        width: 100%;
        height: 100%;
      }
    </style>
  </body>
</html>

参考

相关文章

网友评论

      本文标题:WebGPU入门(一)--认识 WebGPU

      本文链接:https://www.haomeiwen.com/subject/tnlisdtx.html