诞生
谷歌Chrome浏览器113版本,加入的新图形API——WebGPU,目前在threeJs官网已经出现了相关教程。
WebGL相比,WebGPU将底层接口从老旧的OpenGL升级到了最新的Direct3D 12、Vulkan和Metal图形引擎,所以这也使得它既拥有了比过去高得多的执行效率,又使得未来的浏览器可以访问更多的显卡GPU底层功能
- WebGPU让“浏览器高效运行大型3D应用”变为可能。所以它确实可能促进一些高画质、全3D化的交互式网站的出现,比如全3D的电商平台、全3D的车辆或房屋展示网站等。理论上可以让网页端的大型3D应用执行得和本地程序一样快、一样好。换句话说,它消除了以往浏览器在3D性能上的天然约束。
使用
浏览器全局提供了一个属性navigator.gpu
创建GPU设备对象
// 请求GPU适配器
const adapter = await navigator.gpu.requestAdapter();
// 获取GPU设备对象,通过GPU设备对象device的WebGPU API可以控制GPU渲染过程
const device = await adapter.requestDevice();
console.log('adapter', adapter)
console.log('device', device)
把Canvas作为WebGpu的画布
<div>
<canvas id="myGpu" width="500" height="500" style="background-color: aqua" />
</div>
// 获取canvas实例
const canvas = document.getElementById('myGpu');
// 设置canvas用哪种方式绘图
const context = canvas.getContext('webgpu');
// 请求浏览器默认的着色器
const format = navigator.gpu.getPreferredCanvasFormat
// 将canvas画布与webGpu关联起来
context.configure({
device, // gpu设备对象
format // 浏览器默认着色器
})
关联画布,创建GPU缓冲区
x轴水平向右,y轴竖直向上,z轴垂直与Canvas画布,朝向屏幕内。
// 创建坐标数据
const demoData = new Float32Array([
// 坐标x,y,z 一个坐标4字节,共36字节
0.0, 0.0, 0.0, // 顶点1
1.4, 0.0, 0.0, // 顶点2
0.0, 1.0, 0.0, // 顶点3
])
// 创建顶点缓冲区,在GPU的显存(内存)中开辟一片空间,用来存储顶点数据
const demoBuffer = device.createBuffer({
size: demoData.byteLength, // 定义缓冲区
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, // 缓冲区用途,VERTEX用来存储顶点数据
});
// 将坐标数据写入创建的缓冲区
// 参数意思为[buffer:要写入的缓冲区,offset:写入到缓冲区的偏移量,data:要写入缓冲区的数据(可以是TypedArray或ArrayBufferView)]
device.queue.writeBuffer(demoBuffer, 0, demoData)
坐标数据常用类型化数组,是 ECMAScript 6 新增加的一种数据类型,它提供了一种类似于 C 语言中数组的存储方式,可以在内存中连续存储同一类型的数据。它的每一个元素都是原始二进制值,可以直接在内存中存储,不需要进行类型转换和拷贝,因此处理速度更快,尤其是在处理大量数据时。
GPUBufferUsage.COPY_DST是 WebGPU 中的一个常量,表示缓冲区用于接收从另一个缓冲区复制的数据。具体来说,它是一个位掩码,可以与其他 GPUBufferUsage 常量进行按位或运算来组合使用:
缓冲区用途usage的更多属性,请点击了解更多
WGSL(着色器语言)
变量声明
| 符号 | 数据类型 |
|---|---|
| bool | 布尔 |
| u32 | 无符号整数 |
| i32 | 有符号整数 |
| f32 | 32位浮点数 |
| f16 | 16位浮点数 |
// var关键字声明一个变量a,数据类型是32位浮点数
var a:f32;
a = 2.0;
函数声明
普通函数
fn add( x: f32, y:f32){
var z: f32 = x + y;
}
带返回值的函数
fn add( x: f32, y:f32) -> f32 {
return x + y;
}
向量
N维向量(vecN)
例如:
// 四维向量有四个分量,可以用来表示颜色的R、G、B、A
var color:vec4<f32> = vec4<f32>(1.0, 0.0, 0.0, 1.0);//红色不透明
向量转换
// 三维转四维
var pos:vec3<f32>;
pos = vec3<f32>(1.0, 2.0, 3.0);
//等价于vec4<f32>(1.0, 2.0, 3.0,1.0)
var pos2 = vec4<f32>(pos,1.0);
结构体
// 定义一个结构体表示点光源
struct pointLight {
color: vec3<f32>,//光源颜色
intensity: f32//光源强度
};
// 类似JavaScript中类执行new实例化一个对象
var light1:pointLight;
light1.color = vec3<f32>(1.0, 0.0, 0.0);
light1.intensity = 0.6;
顶点着色器
可以操控顶点作几何变换,如平移、缩放、旋转,在js中用WGSL书写着色器代码,需要以字符串的形式来编写
关键字@location
在函数中形参位置,声明@location,用来代表GPU显存上顶点缓冲区标记存储位置shaderLocation
@location(0)是一个装饰器,用于指定变量在渲染管线中的位置。
关键字@buildin
用来解析内置变量
// @location(代表GPU显存上顶点缓冲区标记存储位置shaderLocation)
// 从而获取到该缓冲区内,所有顶点坐标
// 函数的返回值,首先需要声明是顶点位置的数据,其次声明数据类型
const vertex = /* wgsl */`
@vertex
// @builtin(position) 装饰器告诉WGSL编译器,顶点着色器的输入中包含顶点位置信息,并且该信息应该被映射到WebGPU渲染管线中的特定位置。这个位置通常是屏幕空间中的顶点位置,用于后续的光栅化和片元着色器处理。
fn main(@location(0) pos: vec3<f32>) -> @builtin(position) vec4<f32> {
var pos2 = vec4<f32>(pos,1.0);//pos转齐次坐标
pos2.x -= 0.5;//偏移所有顶点的x坐标
pos2.y -= 0.2;//偏移所有顶点的y坐标
return pos2;
}
`
export { vertex };
片元着色器
// 片元着色器代码,返回像素颜色
const fragment = /* wgsl */ `
@fragment
fn main() -> @location(0) vec4<f32> {
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
}
`
开始配置渲染项
// 创建渲染管线,配置渲染项
const pipeLine = device.createRenderPipeline({
layout: 'auto',
// 顶点着色器相关配置
vertex: {
// 声明代码块
module: device.createShaderModule({ code: vertex }),
// 指定入口函数,要与config里面声明的函数名一致
entryPoint: 'main',
// 顶点所有的缓冲区模块设置
buffers: [
{
arrayStride: 3*4,//一个顶点数据占用的字节长度,该缓冲区一个顶点包含xyz三个分量,每个数字是4字节浮点数,3*4字节长度
// 顶点缓冲区属性
attributes: [{
shaderLocation: 0,//GPU显存上顶点缓冲区标记存储位置
format: "float32x3",//格式:loat32x3表示一个顶点数据包含3个32位浮点数
offset: 0//arrayStride表示每组顶点数据间隔字节数,offset表示读取改组的偏差字节数,没特殊需要一般设置0
}]
}
],
},
// 片元着色器
fragment: {
// 声明代码块
module: device.createShaderModule({ code: fragment }),
// 指定入口函数,要与config里面声明的函数名一致
entryPoint: 'main',
targets: [{
// 与webGpu上下文配置的颜色格式一致
format: format
}]
},
// 图元装配,组合点线面
primitive: {
// point-list,只保留点渲染
// line-strip,将顶点依次连接
// triangle-list,将顶点形成闭环,连接成三角形
topology: 'triangle-list'
}
})
命令编码器
创建命令编码器对象(createCommandEncoder)
// 创建命令编码器对象,控制渲染管线
const commandEncoder = device.createCommandEncoder();
const renderPass = commandEncoder.beginRenderPass({
colorAttachments: [
{
// 装载Canvas画布的纹理视图对象
view: context.getCurrentTexture().createView(),
storeOp: 'store', // 像素数据写入颜色缓冲区, 不写入为discard
loadOp: 'clear', // clearValue的颜色起作用,不起作用为load
// 设置背景色
clearValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 }
}
]
});
控制管线,开始渲染到GPU指定位置
// 渲染一条控制管线,实际开发会用到多个
renderPass.setPipeline(pipeLine);
// 关联顶点数据,设置渲染通道。第一个参数对应pipeLine的shaderLocation
renderPass.setVertexBuffer(0, demoBuffer);
// 控制渲染管线绘制图像
renderPass.draw(3);
// 绘制结束
renderPass.end();
// finish()创建命令缓冲区(生成GPU指令,存入缓冲区)
const commandBuffer = commandEncoder.finish();
device.queue.submit([commandBuffer]);