欢迎来到WebGPU的世界

欢迎来到WebGPU的世界欢迎来到 WebGPU 的世界 WebGPU 是一门神奇的技术 在浏览器支持率 0 标准还没有定稿的情况下 就已经被 Three js 和 Babylon js 等主流 3D 和游戏框架支持了 而且被 Tensorflow js 用来加速手机端的深度学习 比起 WebGL 能带来 20 30 倍的显著提升 在主流框架中 WebGPU 的例子在 Three js 中使用 WebGPU 使用 Three js 的封装 我们可以直接生成 WebGPU 的调用 我们照猫画虎引入 WebGPU 相关的库 import asTHREEfrom

欢迎来到WebGPU的世界

WebGPU是一门神奇的技术,在浏览器支持率0%,标准还没有定稿的情况下,就已经被Three.js和Babylon.js等主流3D和游戏框架支持了。而且被Tensorflow.js用来加速手机端的深度学习,比起WebGL能带来20~30倍的显著提升。

欢迎来到WebGPU的世界

在主流框架中WebGPU的例子

在Three.js中使用WebGPU

使用Three.js的封装,我们可以直接生成WebGPU的调用。

欢迎来到WebGPU的世界

我们照猫画虎引入WebGPU相关的库:

 import * as THREE from 'three'; import * as Nodes from 'three-nodes/Nodes.js'; import { 
    add, mul } from 'three-nodes/ShaderNode.js'; import WebGPU from './jsm/capabilities/WebGPU.js'; import WebGPURenderer from './jsm/renderers/webgpu/WebGPURenderer.js'; ... 

剩下就跟普通的WebGL代码写起来差不多:

 async function init() { 
    if ( WebGPU.isAvailable() === false ) { 
    document.body.appendChild( WebGPU.getErrorMessage() ); throw new Error( 'No WebGPU support' ); } const container = document.createElement( 'div' ); document.body.appendChild( container ); camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 4000 ); camera.position.set( 0, 200, 1200 ); scene = new THREE.Scene(); ... 

只不过渲染器使用WebGPURenderer:

 renderer = new WebGPURenderer(); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); container.appendChild( renderer.domElement ); ... 

如果封装的不能满足需求了,我们可以使用WGSL语言进行扩展:

 material = new Nodes.MeshBasicNodeMaterial(); material.colorNode = desaturateWGSLNode.call( { 
    color: new Nodes.TextureNode( texture ) } ); materials.push( material ); const getWGSLTextureSample = new Nodes.FunctionNode( ` fn getWGSLTextureSample( tex: texture_2d 
    
      , tex_sampler: sampler, uv:vec2 
     
       ) -> vec4 
      
        { return textureSample( tex, tex_sampler, uv ) * vec4 
       
         ( 0.0, 1.0, 0.0, 1.0 ); } 
        
       
      
    ` ); const textureNode = new Nodes.TextureNode( texture ); material = new Nodes.MeshBasicNodeMaterial(); material.colorNode = getWGSLTextureSample.call( { 
    tex: textureNode, tex_sampler: textureNode, uv: new Nodes.UVNode() } ); materials.push( material ); 

WGSL是WebGPU进行GPU指令编程的语言。类似于OpenGL的GLSL, Direct3D的HLSL。

我们来看一个完整的例子,显示一个跳舞的小人,也不过100多行代码:

欢迎来到WebGPU的世界

 
    DOCTYPE html> <html lang="en"> <head> <title>three.js - WebGPU - Skinning 
      title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> <link type="text/css" rel="stylesheet" href="main.css"> <meta http-equiv="origin-trial" content="AoS1pSJwCV3KRe73TO0YgJkK9FZ/qhmvKeafztp0ofiE8uoGrnKzfxGVKKICvoBfL8dgE0zpkp2g/oEJNS0fDgkAAABeeyJvcmlnaW4iOiJodHRwczovL3RocmVlanMub3JnOjQ0MyIsImZlYXR1cmUiOiJXZWJHUFUiLCJleHBpcnkiOjE2NTI4MzE5OTksImlzU3ViZG9tYWluIjp0cnVlfQ==">  
       head> <body> <div id="info"> <a href="https://threejs.org" target="_blank" rel="noopener">three.js 
        a> WebGPU - Skinning  
         div> <script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"> 
          script> <script type="importmap"> { 
            "imports": { 
            "three": "../build/three.module.js", "three-nodes/": "./jsm/nodes/" } }  
           script> <script type="module"> import * as THREE from 'three'; import * as Nodes from 'three-nodes/Nodes.js'; import { 
             FBXLoader } from './jsm/loaders/FBXLoader.js'; import WebGPU from './jsm/capabilities/WebGPU.js'; import WebGPURenderer from './jsm/renderers/webgpu/WebGPURenderer.js'; import LightsNode from 'three-nodes/lights/LightsNode.js'; let camera, scene, renderer; let mixer, clock; init().then( animate ).catch( error ); async function init() { 
             if ( WebGPU.isAvailable() === false ) { 
             document.body.appendChild( WebGPU.getErrorMessage() ); throw new Error( 'No WebGPU support' ); } camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 1000 ); camera.position.set( 100, 200, 300 ); scene = new THREE.Scene(); camera.lookAt( 0, 100, 0 ); clock = new THREE.Clock(); // 光照 const light = new THREE.PointLight( 0xffffff ); camera.add( light ); scene.add( camera ); const lightNode = new LightsNode().fromLights( [ light ] ); const loader = new FBXLoader(); loader.load( 'models/fbx/Samba Dancing.fbx', function ( object ) { 
             mixer = new THREE.AnimationMixer( object ); const action = mixer.clipAction( object.animations[ 0 ] ); action.play(); object.traverse( function ( child ) { 
             if ( child.isMesh ) { 
             child.material = new Nodes.MeshStandardNodeMaterial(); child.material.lightNode = lightNode; } } ); scene.add( object ); } ); // 渲染 renderer = new WebGPURenderer(); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); document.body.appendChild( renderer.domElement ); window.addEventListener( 'resize', onWindowResize ); return renderer.init(); } function onWindowResize() { 
             camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize( window.innerWidth, window.innerHeight ); } function animate() { 
             requestAnimationFrame( animate ); const delta = clock.getDelta(); if ( mixer ) mixer.update( delta ); renderer.render( scene, camera ); } function error( error ) { 
             console.error( error ); }  
            script>  
             body>  
              html> 

在Babylon.js中使用WebGPU

Babylon.js的封装与Three.js大同小异,我们来看个PlayGround的效果:

欢迎来到WebGPU的世界

 const supportCS = engine.getCaps().supportComputeShaders; 

不过目前在macOS上,只有WebGPU支持计算着色器。

如果我们把环境切换成WebGL2,就变成下面这样了:

欢迎来到WebGPU的世界

顺便说一句,Babylon.js判断WebGL2和WebGL时也是同样的逻辑,有高就用高。

如果对于着色器不熟悉,Babylon.js提供了练习Vertex Shader和Pixel Shader的环境:https://cyos.babylonjs.com/ , 带语法高亮和预览。

欢迎来到WebGPU的世界

用WebGPU进行深度学习加速

除了3D界面和游戏,深度学习的推理器也是GPU的重度用户。所以Tensorflow.js也在还落不了地的时候就支持了WebGPU。实在是计算着色器太重要了。

写出来的加速代码就像下面一样,很多算子的实现最终是由WGSL代码来实现的,最终会转换成GPU的指令。

 getUserCode(): string { 
    const rank = this.xShape.length; const type = getCoordsDataType(rank); const start = this.xShape.map((_, i) => `uniforms.pad${ 
     i}[0]`).join(','); const end = this.xShape .map( (_, i) => `uniforms.pad${ 
     i}[0] + uniforms.xShape${ 
      rank > 1 ? `[${ 
       i}]` : ''}`) .join(','); const startValue = rank > 1 ? `${ 
     type}(${ 
     start})` : `${ 
     start}`; const endValue = rank > 1 ? `${ 
     type}(${ 
     end})` : `${ 
     end}`; const leftPadCondition = rank > 1 ? `any(outC < start)` : `outC < start`; const rightPadCondition = rank > 1 ? `any(outC >= end)` : `outC >= end`; const unpackedCoords = rank > 1 ? ['coords[0]', 'coords[1]', 'coords[2]', 'coords[3]'].slice(0, rank) : 'coords'; const userCode = ` ${ 
     getMainHeaderAndGlobalIndexString()} if (index < uniforms.size) { let start = ${ 
     startValue}; let end = ${ 
     endValue}; let outC = getCoordsFromIndex(index); if (${ 
     leftPadCondition} || ${ 
     rightPadCondition}) { setOutputAtIndex(index, uniforms.constantValue); } else { let coords = outC - start; setOutputAtIndex(index, getX(${ 
     unpackedCoords})); } } } `; return userCode; } 

无框架手写WebGPU代码

从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> 

画出来的结果如下:

欢迎来到WebGPU的世界

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

ctx.fillStyle = 'red'; 

也可以这么写:

ctx.fillStyle = '#F00'; 

还可以这么写:

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

从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> 

欢迎来到WebGPU的世界

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

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

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

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

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

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

 float pointSize = sqrt(20.0); gl_PointSize=pointSize; 
 void main(){ mediump vec4 pointColor; pointColor.r = 1.0; pointColor.a = 1.0; gl_FragColor = pointColor; } 
  • 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; } 

更现代的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 
          
            { var pos = array 
           
             , 3>( vec2 
            
              (0.0, 0.5), vec2 
             
               (-0.5, -0.5), vec2 
              
                (0.5, -0.5)); return vec4 
               
                 (pos[VertexIndex], 0.0, 1.0); } 
                
               
              
             
            
          `; const redFragWGSL = ` @stage(fragment) fn main() -> @location(0) vec4 
          
            { return vec4 
           
             (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.

欢迎来到WebGPU的世界

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

欢迎来到WebGPU的世界

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

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

 @stage(vertex) fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4 
  
    { var pos = array 
   
     , 3>( vec2 
    
      (0.0, 0.5), vec2 
     
       (-0.5, -0.5), vec2 
      
        (0.5, -0.5)); return vec4 
       
         (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) } } 

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

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

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

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

小结

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

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/199862.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月20日 下午12:02
下一篇 2026年3月20日 下午12:03


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号