欢迎来到 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等支持率更成问题的新技术一起,成为未来几年前端的主要工具。

相关实践学习
在云上部署ChatGLM2-6B大模型(GPU版)
ChatGLM2-6B是由智谱AI及清华KEG实验室于2023年6月发布的中英双语对话开源大模型。通过本实验,可以学习如何配置AIGC开发环境,如何部署ChatGLM2-6B大模型。
相关文章
|
数据可视化 图形学 开发者
【Qt 底层机制之图形渲染引擎】深入理解 Qt 的 渲染机制:从基础渲染到高级图形
【Qt 底层机制之图形渲染引擎】深入理解 Qt 的 渲染机制:从基础渲染到高级图形
2359 4
|
5月前
|
运维 安全 物联网
AR眼镜赋能船舶巡检:打造智能化运维新方案
AR眼镜赋能船舶智能巡检,破解传统模式漏检多、环境恶劣、响应慢、培训长等痛点。融合二维码/RFID识别与虚实交互,实现巡检自动化、维修远程协同,单次巡检从4小时缩至1.5小时,故障修复效率提升50%以上,年省成本超80万元/船,漏检率降至10%以下,助力船舶运维高效安全。(236字)
|
设计模式 开发框架 安全
C# 一分钟浅谈:GraphQL API 与 C#
本文介绍了 GraphQL API 的基本概念及其优势,并通过 C# 实现了一个简单的 GraphQL 服务。GraphQL 是一种高效的 API 查询语言,允许客户端精确请求所需数据,减少不必要的数据传输。文章详细讲解了如何使用 `GraphQL.NET` 库在 C# 中创建和配置 GraphQL 服务,并提供了常见问题的解决方案和代码示例。
405 4
|
11月前
|
XML 安全 前端开发
一行代码搞定禁用 web 开发者工具
在如今的互联网时代,网页源码的保护显得尤为重要,特别是前端代码,几乎就是明文展示,很容易造成源码泄露,黑客和恶意用户往往会利用浏览器的开发者工具来窃取网站的敏感信息。为了有效防止用户打开浏览器的 Web 开发者工具面板,今天推荐一个不错的 npm 库,可以帮助开发者更好地保护自己的网站源码,本文将介绍该库的功能和使用方法。 功能介绍 npm 库名称:disable-devtool,github 路径:/theajack/disable-devtool。从 f12 按钮,右键单击和浏览器菜单都可以禁用 Web 开发工具。 🚀 一行代码搞定禁用 web 开发者工具 该库有以下特性: • 支持可配
1138 22
HarmonyOS NEXT仓颉开发语言实战案例:图片预览器
本文介绍了如何使用仓颉语言实现图片放大预览器。通过弹窗组件`CustomDialogController`与`Swiper`容器结合,实现全屏图片浏览效果,支持多图切换与点击关闭功能,适配动态广场场景下的图片预览需求。
|
设计模式 Java API
05.接口隔离原则介绍
接口隔离原则(ISP)是SOLID原则之一,强调客户端不应依赖于它们不需要的接口。通过将庞大而臃肿的接口拆分为更小、更具体的接口,确保每个接口只包含客户端真正需要的方法,从而提高代码的可维护性和灵活性。本文详细介绍了接口隔离原则的概念、核心思想、实现方式及案例分析,并对比了其与单一职责原则的区别。关键点包括:接口应精简、独立且可扩展,避免强迫实现不必要的方法,减少系统的耦合性。
509 19
|
负载均衡 5G UED
蜂窝网络中的切换(Handover)及其类型详解
蜂窝网络中的切换(Handover)及其类型详解
2190 12
|
安全 jenkins 持续交付
如何在 Jenkins 中配置邮件通知?
如何在 Jenkins 中配置邮件通知?
941 11
threeJs用精灵模型Sprite实现下雨效果
这篇文章介绍了使用Three.js中的Sprite(精灵)模型来实现下雨特效的方法和技术细节。
673 2
threeJs用精灵模型Sprite实现下雨效果

热门文章

最新文章

下一篇
开通oss服务