欢迎来到 WebGPU 的世界 上

简介: 欢迎来到 WebGPU 的世界


图片.png


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

图片.png


在主流框架中 WebGPU 的例子


1、在Three.js中使用WebGPU

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

02.gif

我们照猫画虎引入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<f32>, tex_sampler: sampler, uv:vec2<f32> ) -> vec4<f32> {
      return textureSample( tex, tex_sampler, uv ) * vec4<f32>( 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多行代码:

003.gif

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


2、在Babylon.js中使用WebGPU

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

image.gif图片.png

不同之处在于处理WebGPU的支持情况时,Babylon.js并不判断整体上支不支持WebGPU,而是只看具体功能。

比如上面的例子,只判断是不是支持计算着色器。

    const supportCS = engine.getCaps().supportComputeShaders;

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

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

image.gif图片.png

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

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

image.gif图片.png

针对需要通过写手机应用的场景,Babylon.js提供了与React Native结合的能力:

image.gif图片.png


3、用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;
  }




相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
Web App开发 移动开发 Rust
欢迎来到 WebGPU 的世界 下
欢迎来到 WebGPU 的世界
848 0
|
JavaScript
🚀从零开始学习JS基础6️⃣🚀
在一个程序执行的过程中,各条代码的执行顺序对程序的结果是有直接影响的。很多时候我们要通过控制代码的执行顺序来实现我们要完成的功能。简单来说,流程控制就是来控制代码按照一定结构顺序来执行。
109 0
🚀从零开始学习JS基础6️⃣🚀
|
JavaScript 程序员 编译器
🚀从零开始学习JS基础4️⃣🚀(下)
计算机不能直接理解任何除机器语言以外的语言,所以必须要把程序员所写的程序语言翻译成机器语言才能执行程序。程序语言翻译成机器语言的工具,被称为翻译器。
107 0
🚀从零开始学习JS基础4️⃣🚀(下)
|
存储 JavaScript 前端开发
🚀从零开始学习JS基础3️⃣🚀
变量简单来说就是一个装东西的盒子,专业来说变量是用于存放数据的容器。 我们通过 变量名 获取数据,甚至数据可以修改。 变量是程序在内存中申请的一块用来存放数据的空间。类似我们酒店的房间,一个房间就可以看做是一个变量。
152 0
🚀从零开始学习JS基础3️⃣🚀
|
JavaScript 前端开发 开发者
🚀从零开始学习JS基础5️⃣🚀
运算符(operator)也被称为操作符,**是用于实现赋值、比较和执行算数运算等功能的符号。**JavaScript中常用的运算符有: 算数运算符 递增和递减运算符 比较运算符 逻辑运算符 赋值运算符
132 0
🚀从零开始学习JS基础5️⃣🚀
|
存储 JavaScript 前端开发
🚀从零开始学习JS基础9️⃣🚀
 在 JS 里面,可能会定义非常多的相同代码或者功能相似的代码,这些代码可能需要大量重复使用。虽然 for循环语句也能实现一些简单的重复操作,但是比较具有局限性,此时我们就可以使用 JS 中的函数。     简答来说,函数就是封装了一段可被重复调用执行的代码块。通过此代码块可以实现大量代码的重复使用。
129 0
🚀从零开始学习JS基础9️⃣🚀
|
存储 JavaScript 前端开发
🚀从零开始学习JS基础🔟🚀
在 JavaScript 中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等。
200 0
🚀从零开始学习JS基础🔟🚀
|
JavaScript
🚀从零开始学习JS基础7️⃣🚀
断点调试是指自己在程序的某一行设置一个断点,调试时,程序运行到这一行就会停住,然后你可以一步一步往下调试,调试过程中可以看各个变量当前的值,出错的话,调试到出错的代码行即显示错误,停下。断点调试可以帮助观察程序的运行过程。
134 0
🚀从零开始学习JS基础7️⃣🚀
|
存储 JavaScript 索引
🚀从零开始学习JS基础8️⃣🚀
数组是指一组数据的集合,其中的每个数据被称作元素,在数组中可以存放任意类型的元素。数组是一种将一组数据存储在单个变量名下的优雅方式。数组可以把一组相关的数据一起存放,并提供方便的访问(获取)方式。
136 0
🚀从零开始学习JS基础8️⃣🚀
|
存储 JavaScript 前端开发
🚀从零开始学习JS基础4️⃣🚀
在计算机中,不同的数据所需占用的存储空间是不同的,为了便于把数据分成所需内存大小不同的数据,充分利用存储空间,于是定义了不同的数据类型。 简单来说,数据类型就是数据的类别型号。比如姓名“张三”,年龄18,这些数据的类型是不一样的。
189 0
🚀从零开始学习JS基础4️⃣🚀

热门文章

最新文章