WebGPU是一门神奇的技术,在浏览器支持率0%,标准还没有定稿的情况下,就已经被Three.js和Babylon.js等主流3D和游戏框架支持了。而且被Tensorflow.js用来加速手机端的深度学习,比起WebGL能带来20~30倍的显著提升。
使用Three.js的封装,我们可以直接生成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<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多行代码:
<!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的封装与Three.js大同小异,我们来看个PlayGround的效果:
不同之处在于处理WebGPU的支持情况时,Babylon.js并不判断整体上支不支持WebGPU,而是只看具体功能。
比如上面的例子,只判断是不是支持计算着色器。
const supportCS = engine.getCaps().supportComputeShaders;
不过目前在macOS上,只有WebGPU支持计算着色器。
如果我们把环境切换成WebGL2,就变成下面这样了:
顺便说一句,Babylon.js判断WebGL2和WebGL时也是同样的逻辑,有高就用高。
如果对于着色器不熟悉,Babylon.js提供了练习Vertex Shader和Pixel Shader的环境:https://cyos.babylonjs.com/ , 带语法高亮和预览。
针对需要通过写手机应用的场景,Babylon.js提供了与React Native结合的能力:
除了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代码。
不管是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>
画出来的结果如下:
我们要修改画出来的图的颜色怎么办? ctx有fillStyle属性,支持CSS的颜色字符串。
比如我们设成红色,可以这么写:
ctx.fillStyle = 'red';
也可以这么写:
ctx.fillStyle = '#F00';
还可以这么写:
ctx.fillStyle = 'rgb(255,0,0,1)';
从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是什么东西:
我们可以看到,它是一个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种类型:
另外还有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来进行绘制了。
跨越了从 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.
三角形画出来的结果如下:
现在的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二进制格式: .
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等支持率更成问题的新技术一起,成为未来几年前端的主要工具。
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/SZvqpGJKGNeQfIa9fcp8XA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。