欢迎来到 WebGPU 的世界 下

简介: 欢迎来到 WebGPU 的世界

无框架手写WebGPU代码



通过框架,我们可以迅速地跟上技术的前沿。但是,框架的封装也容易让我们迷失对于技术本质的把握。

现在我们来看看如何手写WebGPU代码。

1、从Canvas说起

不管是WebGL还是WebGPU,都是对于Canvas的扩展。做为HTML 5的重要新增功能,大家对于2D的Canvas应该都不陌生。

比如我们要画一个三角形,就可以调用lineTo API来实现:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Canvas</title>
</head>
<body>
    <canvas id="webcanvas" width="200" height="200" style="background-color: #eee"></canvas>
    <script>
        const canvas=document.getElementById('webcanvas');
        const ctx=canvas.getContext('2d');
        ctx.beginPath();
        ctx.moveTo(75,50);
        ctx.lineTo(100,75);
        ctx.lineTo(100,25);
        ctx.fill();
    </script>
</body>

画出来的结果如下:

image.gif图片.png

我们要修改画出来的图的颜色怎么办?

ctx有fillStyle属性,支持CSS的颜色字符串。

比如我们设成红色,可以这么写:

ctx.fillStyle = 'red';

也可以这么写:

ctx.fillStyle = '#F00';

还可以这么写:

ctx.fillStyle = 'rgb(255,0,0,1)';


2、从2D到3D

从2D Canvas到3D WebGL的最大跨越,就是从调用API,到完全不同于JavaScript的新语言GLSL的出场。

第一步的步子我们迈得小一点,不画三角形了,只画一个点。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Test OpenGL for a point</title>
</head>
<body>
    <canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
    <script>
        const canvas = document.getElementById('webgl');
        const gl = canvas.getContext('webgl');
        const program = gl.createProgram();
        const vertexShaderSource = `
           void main(){
              gl_PointSize=sqrt(20.0);
              gl_Position =vec4(0.0,0.0,0.0,1.0);
           }`;
        const vertexShader = gl.createShader(gl.VERTEX_SHADER);
        gl.shaderSource(vertexShader, vertexShaderSource);
        gl.compileShader(vertexShader);
        gl.attachShader(program, vertexShader);
        const fragShaderSource = `
          void main(){
            gl_FragColor = vec4(1.0,0.0,0.0,1.0);
          }
        `;
        const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
        gl.shaderSource(fragmentShader, fragShaderSource);
        gl.compileShader(fragmentShader);
        gl.attachShader(program, fragmentShader);
        gl.linkProgram(program);
        gl.useProgram(program);
        gl.drawArrays(gl.POINTS, 0, 1);
    </script>
</body>
</html>

getContext时将2d换成webgl。

我们可以加一行console.log(gl)来看下gl是什么东西:

1.png

我们可以看到,它是一个WebGLRenderingContext对象。

顺便说一句,之前我们拿到的2D的Context是CanvasRenderingContext2D。

下面就引入了两段程序中的程序,第一段叫做顶点着色器,用于顶点的坐标信息。第二段叫做片元着色器,用于配置如何进行一些属性的操作,在本例中我们做一个最基本的操作,改颜色。

我们先看顶点着色器的代码:

           void main(){
              gl_PointSize=sqrt(20.0);
              gl_Position =vec4(0.0,0.0,0.0,1.0);
           }

像其他语言一样,glsl中的代码也需要一个入口函数。

gl_PointSize是一个系统变量,用于存储点的大小。我特意给大小加个了sqrt函数,给大家展示glsl的库函数。

gl_Position用于存储起点的位置。vec4是由4个元素构成的向量。

GLSL的数据类型很丰富,包括标量、向量、数组、矩阵、结构体和采样器等。

标量有布尔型bool, 有符号整数int, 无符号整数uint和浮点数float 4种类型。

类型的使用方式跟C语言一样,比如我们用float来定义浮点变量。

                float pointSize = sqrt(20.0);
                gl_PointSize=pointSize;

GLSL没有double这样表示双精度的类型。在顶点着色器中是没有精度设置的。

但是在片元着色器中有精度的设置,需要指定低精度lowp, 中精度mediump和高精度highp. 一般采用中精度:

            void main(){    
                mediump vec4 pointColor;
                pointColor.r = 1.0;
                pointColor.a = 1.0;
                gl_FragColor = pointColor;
            }

GLSL因为是基于C语言设计的,不支持泛型,所以每种向量同时有4种子类型的。

以四元组vec4为例,有4种类型:

  • vec4: 浮点型向量
  • ivec4: 整数型向量
  • uvec4: 无符号整数向量
  • bvec4: 布尔型向量。

另外还有vec2, vec3各有4种子类型,以此类推。

在GLSL里面,四元向量最常用的用途有两种,在顶点着色器里充当坐标,和在片元着色器里充当颜色。

当vec4作为坐标使用时,我们可以用x,y,z,w属性来对应4个维度。

我们来看个例子:

                vec4 pos;
                pos.x = 0.0;
                pos.y = 0.0;
                pos.z = 0.0;
                pos.w = 1.0;
                gl_Position = pos;

同样,我们在片元着色器里面表示红色的时候只用指令r和a两个属性,g,b让它们默认是0:

            void main(){    
                mediump vec4 pointColor;
                pointColor.r = 1.0;
                pointColor.a = 1.0;
                gl_FragColor = pointColor;
            }

有了顶点着色器和片元着色器的GLSL代码之后,我们将其进行编程,并attach到program上面。

最后再link和use这个program,就可以调用drawArrays来进行绘制了。


3、更现代的GPU编程方法

跨越了从 Canvas API到GLSL的鸿沟了之后,最后到WebGPU这一步相对就容易一些了。

我们要熟悉的是以Vulkan为代表的更现代的GPU的编程方法。

渲染管线不再是唯一,我们可以使用更通用的计算管线了。也不再有顶点着色器和片元着色器那么严格的限制。

另外最重要的一点是,为了提升GPU执行效率,WebGPU不再是像WebGL一样基本每一步都要由CPU来控制,我们使用commandEncoder将所有GPU指令打包在一起,一次性执行。

我们先看一下完整代码有个印象:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Test WebGPU</title>
</head>
<body>
    <canvas id="webgpu" width="500" height="500" style="background-color: blue"></canvas>
    <script>
        async function testGPU() {
            const canvas = document.getElementById('webgpu');
            const gpuContext = canvas.getContext('webgpu');
            const adapter = await navigator.gpu.requestAdapter();
            const device = await adapter.requestDevice();
            presentationFormat = gpuContext.getPreferredFormat(adapter);
            gpuContext.configure({
                device,
                format: presentationFormat
            });
            const triangleVertWGSL = `
            @stage(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 redFragWGSL = `
            @stage(fragment)
                fn main() -> @location(0) vec4<f32> {
                return vec4<f32>(1.0, 0.0, 0.0, 1.0);
            }
            `
            const commandEncoder = device.createCommandEncoder();
            const textureView = gpuContext.getCurrentTexture().createView();
            const pipeline = device.createRenderPipeline({
                vertex: {
                    module: device.createShaderModule({
                        code: triangleVertWGSL,
                    }),
                    entryPoint: 'main',
                },
                fragment: {
                    module: device.createShaderModule({
                        code: redFragWGSL,
                    }),
                    entryPoint: 'main',
                    targets: [
                        {
                            format: presentationFormat,
                        },
                    ],
                },
                primitive: {
                    topology: 'triangle-list',
                },
            });
            const renderPassDescriptor = {
                colorAttachments: [
                    {
                        view: textureView,
                        loadValue: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }, 
                        storeOp: 'store',
                    },
                ],
            };
            const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
            console.log(passEncoder);
            passEncoder.setPipeline(pipeline);
            passEncoder.draw(3, 1, 0, 0);
            passEncoder.end();
            device.queue.submit([commandEncoder.finish()]);
        }
        testGPU();
    </script>
</body>
</html>

因为浏览器还没有支持,所以我们需要像Chrome Canary这样的支持最新技术的浏览器。而且还要打开支持的开关,比如在Chrome Canary里是enable-unsafe-webgpu.

图片.png

三角形画出来的结果如下:

图片.png

现在的Context从WebGL的WebGLRenderingContext变成了GPUCanvasContext。

WGSL语言的语法更像Rust,vec4这样的容器可以用泛型的写法绑定类型:

            @stage(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);
            }

对比下Rust的代码看看像不像:

fn fib2(n: i32) -> i64 {
    if n <= 2 {
        return 1i64
    } else {
        return fib2(n - 1) + fib2(n - 2)
    }
}

WGSL是为了规避知识产权问题发明的新语言,本质上它和GLSL,HLSL等语言一样,都可以编译成Vulkan的SPIR-V二进制格式:

图片.png.

Vulkan不限制使用什么样的语言,既可以使用GLSL, HLSL,也可以使用Open CL或者是Open CL的高级封装SYCL。

转换成SPIR-V格式之后,可以转成iOS上的Metal Shading Language,也可以转成Windows Direct 12上用的DXIL。

WebGPU没有这么自由,发明了一门新语言WGSL,不过其思想都是基于SPIR-V的。

在WebGPU和WGSL还未定版,资料还比较缺乏的情况下,我们可以先学习Vulkan相关的知识,然后迁移到WebGPU上来。本质上是同样的东西,只是封装略有不同。

我们之前学习的GLSL的知识同样用得上,而且在这种类Rust风格中可以写得更爽一些。

比如同样是给片元用的颜色值,在保留了vec4可以继续使用r,g,b,a分量的好处之外,因为指定了f32的精度,就不需要mediump了。而且,类型可以自动推断,我们直接给个var就好了:

            @stage(fragment)
                fn main() -> @location(0) vec4<f32> {
                var triColor = vec4<f32>(0.0,0.0,0.0,0.0);
                triColor.r = 1.0;
                triColor.a = 1.0;    
                return triColor;
            }

有了作为功能核心的WGSL,剩下的工作主要就是组装了。

我们把指令打包在 CommandEncoder中,然后通过beginRenderPass来创建一个渲染Pass,再给这个Pass设置一个渲染的流水线,添加相应的draw操作,最后提交到GPU设备的队列中,就大功告成了。


小结


相对于基于OpenGL ES 2.0的WebGL 1.0,WebGPU更接近于Vulkan这样更能发挥GPU能力的新API,可以更有效地发挥出新的GPU的能力。就像渲染上Three.js和Babylon.js给我们展示的那样和计算上Tensorflow.js的飞跃一样。

虽然浏览器还不支持,但是不成熟的主要是封装,底层的Vulkan和Metal技术已经非常成熟,并且广泛被客户端所使用了。

WebGPU这个能力暴露给H5和小程序之后,将给元宇宙等热门应用插上性能倍增的翅膀。结合WebXR等支持率更成问题的新技术一起,成为未来几年前端的主要工具。

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
3月前
|
人工智能 自然语言处理 前端开发
💻2024 年值得一试的 8 个开发者工具💡
在本文中,我们精选了8款开发人员必备的高效工具,包括Webcrumbs、Pieces.app、Warp、Raycast等。这些工具涵盖了从前端插件生成、代码片段管理到多语言界面构建等多种功能,帮助开发人员简化工作流程、提高生产力。无论您是经验丰富的开发者,还是刚入行的新手,这些工具都将为您的开发过程带来效率提升和便利。探索这些工具,让您的开发工作更加轻松高效!
379 66
|
2月前
|
前端开发 JavaScript 数据处理
前端界的宝藏技术:掌握这些,让你的网页秒变交互神器!
【10月更文挑战第31天】前端开发藏有众多宝藏技术,如JavaScript异步编程和Web Components。异步编程通过Promise、async/await实现复杂的网络请求,提高代码可读性;Web Components则允许创建可重用、封装良好的自定义组件,提升代码复用性和独立性。此外,CSS动画、SVG绘图等技术也极大丰富了网页的视觉和交互体验。不断学习和实践,让网页秒变交互神器。
41 2
|
5月前
|
Rust 前端开发 JavaScript
震惊!JavaScript 与 WebAssembly 强强联合,开启前端性能传奇之旅,你准备好了吗?
【8月更文挑战第27天】在互联网飞速发展的今天,前端技术,特别是核心语言JavaScript,正经历着持续的革新。为了突破JavaScript在处理复杂计算时的性能局限,WebAssembly应运而生。作为一种高效的二进制格式,WebAssembly能以接近原生的速度在浏览器中运行,支持C、C++和Rust等语言编写的高性能代码。它与JavaScript相辅相成,前者专注于高性能计算任务(如游戏开发、图像处理),后者则负责页面的交互与逻辑控制。通过结合使用,二者为前端开发者提供了更为强大和灵活的工具集,共同推动前端技术进入一个全新的性能时代。
121 2
|
7月前
|
开发框架 JavaScript 前端开发
技术经验解读:从零开始学习jQuery(十)jQueryUI常用功能实战
技术经验解读:从零开始学习jQuery(十)jQueryUI常用功能实战
60 0
扫雷小游戏 万字全网最详细(可展开一片空白)下
扫雷小游戏 万字全网最详细(可展开一片空白)
77 0
|
8月前
|
算法
连连看游戏系列教程开篇
连连看游戏系列教程开篇
121 0
|
存储 前端开发 JavaScript
【五子棋实战】第5章 开发五子棋前端页面
页面设计原则   1、可配置性。比如棋盘的大小可配置,棋盘边长可配置,黑白空期的值可配置;   2、响应式。各种屏幕大小下棋盘的布局要合理;   3、面向对象。棋子、棋盘的定义都用类来封装,代码要写的好看。
|
小程序 算法 数据可视化
扫雷小游戏 万字全网最详细(可展开一片空白)上
扫雷小游戏 万字全网最详细(可展开一片空白)
105 0
游戏开发零基础入门教程(5):不要挡住我,我要去上面
这一节我们学习了层级管理,以及通过使用拖拽的方式来调整层级的顺序。 在真实的游戏中可能会包含很多的层级,在调整层级时,不要忘记口诀:谁挡住了我,我就去谁的上面。 试着在游戏中加入更多的层级,拖拽调整层级的顺序,然后观察编辑区中的图片显示效果。
168 0
从零开始手把手教你使用javascript+canvas开发一个塔防游戏07塔的升级和出售
从零开始手把手教你使用javascript+canvas开发一个塔防游戏07塔的升级和出售
121 0