1 WebGPU学习开发环境配置
WebGPU的环境配置比较简单,不需要vite或webpack配置一个复杂的开发环境,直接使用.html文件即可。
1.1 支持WebGPU的浏览器
Chrome 113 beta测试版开始默认支持WebGPU。
1.2 index.html文件
创建index.html文件,<script>标签设置type=“module”,就可以在模块中编写ES6语法,不需要webpack和vite进行编译处理。
1.3 vscode编辑器
借助live server插件,就可以在代码页面直接右键在live server中打开,默认浏览器中打开执行。
测试是否支持webgpu
if (navigator.gpu) {console.log("support webgpu");
} else {console.log("not support webgpu");
}
2 WebGPU API和Canvas画布
WebGPU提供了很多相关的API,通过这些WebGPU API可以控制你的显卡GPU渲染3D场景或计算数据。
WebGPU API文档:https://www.w3.org/TR/webgpu/
GPU:图形处理器,即电脑上的显卡,如果为了追求更好的性能,一般会在电脑上安装独立显卡。
2.1 使用WebGPU API获取到GPU设备对象
//浏览器请求GPU适配器
const adapter = await navigator.gpu.requestAdapter();
//获取GPU设备对象,通过GPU设备对象就可以使用WebGPU API控制GPU渲染过程
const device = await adapter.requestDevice();
获取GPU设备对象非常简单,执行上面两步操作即可。
***注意***
- requestAdapter()和requestDevice都是异步函数,函数前需要加上es6语法关键字await。
device设备对象有很多方法和属性,可以去控制物理上的显卡GPU。
2.2 device设备对象的属性和方法
device.createRenderPipeline();//创建渲染管线
device.createComputePipeline();//创建计算管线
device.createShaderModule();//创建着色器模块
device.createCommandEncoder();//创建命令对象(绘制和计算命令)
device.createBuffer();//创建缓冲区对象
device.createCommandEncoder();//创建命令对象(绘制和计算命令),不是创建编码器。
2.3 获取WebGPU上下文
Canvas元素作为WebGPU的画布
const canvas = document.getElementById("player");
const context = canvas.getContext("webgpu");
2.4 配置WebGPU上下文
关联Canvas对象和GPU设备对象device。这样就能把Canvas元素作为WebGPU的画布,用来呈现3D渲染效果。
//配置webgpu上下文(canvas上下文和device绑定在一起):device设备,format颜色格式,
const format = navigator.gpu.getPreferredCanvasFormat();//获取浏览器canvas默认的颜色格式
context.configure({device:device,format:format,
})
3 创建顶点缓冲区、渲染管线
如果想渲染一个物体,需要先通过顶点坐标来定义该物体的几何形状。通过WebGPU的顶点缓冲区来创建顶点数据。
3.1 WebGPU坐标系
坐标原点是Canvas画布的中间位置,x轴水平向右,y轴竖直向上,Z轴与canvas画布垂直,朝向屏幕内。x,y取值范围【-1,1】,z取值范围【0,1】。(归一化)
用x,y分量可绘制2D平面图。3D效果会用到z坐标、投影矩阵、视图矩阵、模型矩阵等深入概念。
3.2 类型化数组Float32Array表示顶点坐标
3.2.1 JavaScript类型化数组
JavaScript类型化数组不同于普通的数组,类型化数组就是数组的元素可以设置数字的类型,比如浮点数、无符号整数..
3.2.2 类型化数组Float32Array表示顶点坐标
const vertexArray = new Float32Array([//三角形三个顶点坐标的x,y,z值0.0,0.0,0.0,//顶点1坐标1.0,0.0,0.0,//顶点2坐标0.0,1.0,0.0,//顶点3坐标
]);
3.2.3 创建顶点缓冲区.createBuffer
通过GPU设备对象的.createBuffer()方法可以创建一个顶点缓冲区。当device.createBuffer执行的时候,会在你的电脑显卡GPU的内存(显存)中开辟一片存储空间,用来存储顶点数据,你可以把这个开辟的存储空间,称为顶点缓冲区。
const vertexBuffer = device.createBuffer();
3.2.4 缓冲区设置
const vertexBuffer = device.createBuffer({size:vertexArray.byteLength,//长度:数据字节usage:GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,//用途:作为顶点缓冲区|可以写入顶点数据
});
3.2.5 顶点数据写入顶点缓冲区
把类型化数组中的数据写入创建的顶点缓冲区:GPU设备对象device队列属性.queue的方法.writeBuffer()。参数2表示从vertexArray获取顶点数据的偏移量(单位字节),0表示从vertexArray的数据开头读取数据。
device.queue.wirteBuffer(vertexBuffer,0,vertexArray);//把vertexArray里面的顶点数据写入到vertexBuffer对应的GPU显存缓冲区中。
3.2.6 创建渲染管线和设置参数
通过GPU设备对象的方法.createRenderPipeline创建一个WebGPU渲染管线。
可以理解为工厂中的流水线,通过流水线处理顶点缓冲区中的数据。
const pipeline = device.createRenderPipeline({vertex:{buffers:[{arrayStride:3*4,//一个顶点数据占用的字节长度attributes:[{//顶点缓冲区属性shaderLocation:0,//GPU显存上顶点缓冲区标记存储位置format:"float32x3",//格式:3个float32offset:0//arrayStride每组顶点数据间隔字节数}]}]}
});
4 着色器语言WGSL
WGSL语言是专门给WebGPU定制的着色器语言,就像WebGL OpenGL中使用的GLSL着色器语言。
WGSL英文文档:WebGPU Shading Language
WebGPU引擎Orillusion团队翻译:WebGPU Shading Language
WebGPU Shading Language
TypeScript、C语言等都在CPU执行,而WGSL主要在GPU上执行,有自身的特殊性。
4.1 WGSL基础类型
符号 | 数据类型 |
bool | 布尔 |
u32 | 无符号整数 |
i32 | 有符号整数 |
f32 | 32位浮点数 |
f16 | 16位浮点数 |
4.2 关键字声明变量
WGSL中使用var关键字声明变量。
//var关键字声明一个变量a,数据类型是无符号整数
var a:u32;
u32 = 2;//数据类型是32位浮点数
var a:f32;
a = 2.0;//声明时直接赋值
var a:f32 = 3.0;
如果不加u32或f32,WGSL会根据值进行自动推断。
4.3 变量简单运算
两个变量进行运算,需要保持一样的数据类型,否则报错。
//32位浮点数相加
var a:f32 = 2.0;
var b:f32 = 4.0;
var c:f32 = a + b;//无符号整数相加
var a:u32 = 2;
var b:u32 = 4;
var c:u32 = a + b;
4.4 声明函数的关键字
fn 函数名(参数1:数据类型,参数2:数据类型...){//代码
}//例如:
fn add(x:f32,y:f32){var z:f32 = x + y;
}
如果函数有返回值,设置符号->,后面注明返回值的数据类型
fn 函数名(参数1,参数2)-> 返回值数据类型{return 返回值;
}
//例如:fn add(x:f32,y:f32)->f32{return x + y;
}
4.5 if、for等语句
在WGSL中,if、for等语句,和JavaScript逻辑上差不多,区别就是注意数据类型即可。
var n:u32 = 10;
var s:f32 = 0.0;for(var i:u32 = 0;i<n;i++){s += 0.5;
}
4.6 向量表示颜色
四维向量有4个分量,可以用来表示颜色的R、G、B、A。
var color:vec4<f32> = vec4<f32>(1.0,0.0,0.0,1.0);//红色不透明//省略:vec4<f32>数据类型=>系统自动识别
var color = vec4<f32>(1.0,0.0,0.0,1.0);//先声明一个四维向量变量,再赋值,此时声明必须带着类型
var color:vec4<f32>;
color = vec4<f32>(1.0,0.0,0.0,1.0);
4.7 向量表示位置
三维向量vec3<f32>表示具有三个分量,可以用来表示顶点的xyz坐标。
var pos:vec3<f32>;
pos = vec3<f32>(1.0,2.0,3.0);
xyz的齐次坐标:前三个变量为x,y,z,最后一个分量是1.0
var pos:vec4<f32>;
pos = vec4<f32>(1.0,2.0,3.0,1.0);
一个三维向量转化为四维向量
var pos:vec3<f32>;
pos = vec3<f32>(1.0,2.0,3.0);
//转换为四维向量
var pos2 = vec4<f32>(pos,1.0);
一个二维向量转换为四维向量
var pos:vec2<f32>;
pos = vec2<f32>(1.0,2.0);
//转换为四维向量
var pos2 = vec4<f32>(pos,3.0,1.0);
4.8 结构体
struct pointLight {color:vec3<f32>,//光源颜色intensity:f32//光源强度
}
通过结构体生成一个光源,类似JavaScript中类执行new 实例化一个对象。
var linght1:pointLight;
light1.color = vec3<f32>(1.0,0.0,0.0);
light1.intensity = 0.6;
4.9 WGSL语句结尾分号
在JavaScript中,代码语句结尾的分号可以省略,但是WGSL中分号不能省略。
5 顶点着色器
你把渲染管线想象成为工厂的一条流水线,顶点着色器想象为流水线上的一个工位。
GPU渲染管线上提供的顶点着色器单元的功能就是计算顶点,即对顶点坐标x,y,z的值进行平移、旋转、缩放等各种操作。
5.1 顶点着色器代码
GPU渲染管线上的顶点着色器功能单元,可以执行WGSL着色器语言编写的代码。所有顶点数据经过顶点着色器时,都会执行顶点着色器代码中顶点计算的函数,比如平移顶点坐标。
5.2 WGSL着色器代码形式
在JavaScript或TypeScript写WebGPU代码时,按照语法要求,WGSL着色器的代码,要以字符串的形式存在。
如果直接在单引号或双引号表示的字符串里面写WGSL代码,实现字符串的多行书写,需要用+号连接,不是很方便。
使用ES6的语法模板字符串 ` `(反引号),实现字符串的多行书写很方便。
5.3 @vertex
@vertex表示字符串vertex里面的代码是顶点着色器代码,在GPU渲染管线的顶点着色器单元上执行。
const vertexShaderSource = `@vertex
`
5.4 fn关键字声明一个函数
fn 关键字声明一个函数,命名为main,作为顶点着色器代码的入口函数。fn关键字类似JavaScript语言的function关键字,用来声明一个函数。
@vertex
fn main(){}
5.5 location 关键字
location是WGSL语言的一个关键字,通常用来指定顶点缓冲区相关的顶点数据,使用location的时候需要加上@符号前缀,@location()小括号里面设置参数。
main函数的参数@location(0)表示GPU显存中标记为0(参数和创建渲染管线中的shaderLocation的参数一一对应)的顶点缓冲区中顶点数据。
@vertex
fn main(@location(0)){}
执行@location(0) pos给main函数参数@location(0)表示的顶点数据设置一个变量名pos。
@vertex
fn main(@location(0) pos){}
pos变量:通过location(0)拿到顶点缓冲区中的数据。
5.6 顶点变量的数据类型
执行@location(0) pos: vec3<f32>给main函数参数pos设置数据类型,vec3表示pos变量的数据类型是三维向量vec3,<f32>表示三维向量x,y,z四个属性的值都是32位浮点数。
@vertex
fn main(@location(0) pos:vec3<f32>){}
注意location(0)对应的WebGPU传过来的顶点是三个为一组(顶点缓冲区、渲染管线中的format设置),顶点着色器代码中的pos变量的数据类型用三维向量表示。如果WebGPU传过来的顶点数据两个为一组,书写形式为@location(0) pos:vec2<f32>
5.7 vec3顶点坐标转vec4齐次坐标
在WGSL顶点着色器代码中,很多时候会用四维向量vec4表示顶点的位置坐标,vec4第四个分量默认值一般是1.0,vec4比vec3多了一个分量,把vec4形式的坐标称为齐次坐标。
@vertex
fn main(@location(0) pos:vec3<f32>){var pos2 = vec4<f32>(pos,1.0);//pos转齐次坐标
}
5.8 顶点计算后,return返回顶点数据
实际开发,一般会在main函数中,进行顶点坐标的几何变换,例如:平移、缩放、旋转等。几何变换后,需要return返回,供下一个功能单元使用。
@vertex
fn main(@location(0) pos:vec3<f32>)->vec4<f32>{var pos2 = vec4<f32>(pos,1.0);pos2.x -=0.2;//偏移所有顶点的x坐标return pos2;
}
5.9 内置变量position和@builtin关键字
内置变量:WGSL默认提供的变量,不需要通过关键字var声明就可以使用。
position是WGSL语言的一个内置变量,表示顶点数据。main函数的返回是顶点数据,这时候除了要设置返回值类型,还需要设置@builtin(position)表明返回值是顶点位置数据。
@vertex
fn main(@location(0) pos:vec3<f32>)->@builtin(position) vec4<f32>{var pos2 = vec4<f32>(pos,1.0);pos2.x -=0.2;//偏移所有顶点的x坐标return pos2;
}
5.10 着色器代码块方法.createShaderModule()和指定入口函数
device.createShaderModule();
指定顶点着色器代码的入口函数,入口函数名称可以自定义。
//创建一个WebGPU渲染管线对象pipeline
const pipeline = device.createRenderPipeline({vertex:{module:device.createShaderModule({code:vertex}),entryPoint: "main",buffers:[{arrayStride:3*4,//一个顶点数据占用的字节长度attributes:[{//顶点缓冲区属性shaderLocation:0,//GPU显存上顶点缓冲区标记存储位置format:"float32x3",//格式:3个float32offset:0//arrayStride每组顶点数据间隔字节数}]}]}
});
6 图元装配和光栅化
渲染管线上的其他功能单元(图元装配、光栅化、片元着色器)。
6.1 图元装配
经过顶点着色器处理过的顶点数据,会进入图元装配环节,简单说就是如何通过顶点数据生成几何图形,例如三个点绘制一个三角形,两点可以绘制一条线段等。
通过渲染管线参数的primitive.topology属性可以设置WebGPU如何绘制顶点数据。
const pipeline = device.createRenderPipeline({vertex:{module:device.createShaderModule({code:vertex}),entryPoint: "main",buffers:[{arrayStride:3*4,//一个顶点数据占用的字节长度attributes:[{//顶点缓冲区属性shaderLocation:0,//GPU显存上顶点缓冲区标记存储位置format:"float32x3",//格式:3个float32offset:0//arrayStride每组顶点数据间隔字节数}]}]},primitive:{topology: "triangle-list",//绘制三角形、线line-strip(多个点连接成线)、point-list}});
6.2 光栅化(没有代码)
光栅化:生成几何图形对应的片元,你可以把片元类比为图像上一个个像素,例如绘制一个三角形,相当于在三角形内部,生成一个一个密集排列的片元(像素)。
经过光栅化的片元,是没有任何颜色的片元(像素),需要通过渲染管线上的片元着色器上色,片元着色器单元就像流水线上一个喷漆的工位一样,给物体设置外观颜色。
7 片元着色器
一个个片元就像一个个顶点一样。
片元着色器和顶点着色器类似,都是渲染管线上的一个着色器功能单元,可以执行WGSL着色器代码。
7.1 @fragment关键字
表示后续代码为片元着色器代码。
7.2 返回值和类型
片元着色器中的@location(0)和前面顶点着色器中的@location(0)虽然符号一样,但含义不同。
片元着色器中的@location(0)和顶点数据没关系。渲染管线片元着色器输出的片元像素数据,会存储在显卡内存上,@location(0)表示输出的片元数据存储到显卡内存上,并把存储位置标记为0,用于渲染管线的后续操作和处理。
const fragmentShaderSource = `@framentfn main() ->location(0) vec4<f32>{return vec4<f32>(1.0,0.0,0.0,1.0);//片元设置为红色 }
`
7.3 渲染管线设置fragment属性
module模块、entryPoint入口函数、targets的format格式。
const pipeline = device.createRenderPipeline({vertex:{module:device.createShaderModule({code:vertexShaderSource}),entryPoint: "main",buffers:[{arrayStride:3*4,//一个顶点数据占用的字节长度attributes:[{//顶点缓冲区属性shaderLocation:0,//GPU显存上顶点缓冲区标记存储位置format:"float32x3",//格式:3个float32offset:0//arrayStride每组顶点数据间隔字节数}]}]},fragment:{module:device.createShaderModule({code:fragmentShaderSource}),entryPoint: "main",targets: [{ format: format }]}primitive:{topology: "triangle-list",//绘制三角形、线line-strip(多个点连接成线)、point-list}});
学习WebGPU主要是控制渲染管线。
8 渲染命令
8.1 创建命令编码器和渲染通道
通过GPU设备对象的方法.createCommandEncoder()创建一个命令编码器对象。创建一个渲染通道对象renderPass,他可以控制渲染管线pipeline渲染输出像素数据。
canvas画布有一个默认的颜色缓冲区,可以直接使用,当然你也可以自己创建一个颜色缓冲区。
颜色缓冲区:通过WebGPU渲染管线各个功能处理后,会得到图形的片元数据、或像素数据,这些像素数据,会存储到显卡内存颜色缓冲区中。顶点缓冲区的功能是存储顶点数据,颜色缓冲区的功能是存储渲染管线输出的像素数据。
//创建GPU命令编码器对象
const commanEncoder = device.createCommandEncoder();
8.2 .beginRenderPass的参数对象
renderPass有很多属性对象。常用的有colorAttachments(颜色附件),
//创建一个渲染通道对象renderPass
const renderPass = commandEncoder.beginRenderPass({colorAttachments: [{view: context.getCurrentTexture().createView(),//指向用于Canvas画布的纹理视图对象(Canvas对应的颜色缓冲区)clearValue: [1.0, 0.0, 0.0, 1.0],//背景颜色loadOp: "clear",storeOp: "store",//像素数据写入颜色缓冲区},],
});
8.3 设置渲染通道的渲染管线
//设置渲染管线
renderPass.setPipeline(pipeline);
8.4 关联顶点缓冲区和渲染管线
将顶点缓冲区和createRenderPipeline中的shaderLocation创建联系。
renderPass.setVertexBuffer(0,vertexBuffer);
总结:
在渲染管线中配置顶点着色器的属性时,会设置shaderLocation:0,这表示GPU中0位置的数据;在顶点着色器中会使用@location(0)进行访问。
而顶点缓冲区并没有映射到GPU的0位置,因此需要setVertexBuffer进行设置。
8.5 绘制命令和结束命令
1、把渲染管线中的内容绘制到canvas上。
2、结束命令编码器的渲染通道对象
renderPass.draw(3);//绘制三个点
renderPass.end();//结束命令编码器的渲染通道对象
前面代码调用的WebGPU API,大部分都是用来控制GPU如何运行的,例如:device.createRenderPipeline()控制GPU创建一个渲染管线;.draw方法控制GPU如何绘制顶点数据,不过这些WebGPU API不能直接控制GPU的运行,需要转化为GPU指令,才能控制GPU运转。
8.6 命令编码器方法.finish()
命令编码器对象commandEncoder执行.finish()方法返回一个命令缓冲区对象,同时会把该编码器相关的WebGPU api编码为GPU指令,存入到返回的命令缓冲区对象中。
8.7 GPU设备命令队列.queue属性
GPU设备命令队列.queue的功能是用来存放控制GPU运转的指令(命令),简单说就是你命令编码器和渲染通道定义的一系列控制GPU运行的命令方法。
.submit()是GPU设备对象device队列属性.queue的一个提交方法。
提交方法.submit()的参数是一个数组,数组的元素是命令编码器执行.finish()生成的GPU命令缓冲区对象commandBuffer,数组元素可以包含多个命令缓冲区对象。
device.queue.submit([commandEncoder.finish()]);
待做:
vscode中WGSL插件
在/*wgsl*/` `;反引号中的内容会变成彩色。