家喻户晓,在 GPU 跑可编程管线的时候,着色器是并行运行的,每个着色器入口函数都会在 GPU 中并行执行。每个着色器对一大片对立格局的数据进行冲锋,体现 GPU 多外围的劣势,能够小核同时解决数据;不过,有的数据对每个着色器都是一样的,这种数据的类型是“uniform”,也叫做对立值。
这篇文章列举了原生 WebGL 1/2 中的 uniform 材料,以及 WebGPU 中的 uniform 材料,有一些例子供参考,以用来比对它们之间的差别。
1. WebGL 1.0 Uniform
1.1. 用 WebGLUniformLocation 寻址
在 WebGL 1.0 中,通常是在 JavaScript 端保留 WebGLUniformLocation
以向着色器程序传递 uniform 值的。
应用 gl.getUniformLocation()
办法获取这个 location,有如下几种形式
- 全名:
gl.getUniformLocation(program, 'u_someUniformVar')
- 重量:通常是向量的一部分,譬如
gl.getUniformLocation(program, 'u_someVec3[0]')
是获取第 0 个元素(元素类型是 vec3)的 location - 构造体成员:
gl.getUniformLocation(program, 'u_someStruct.someMember')
下面三种状况与之对应的着色器代码:
// 全名uniform float u_someUniformVar;// 重量uniform vec3 u_someVec3[3]; // 留神,这里是 3 个 vec3// 构造体成员struct SomeStruct { bool someMember;};uniform SomeStruct u_someStruct;
传值分三类,标量/向量、矩阵、采样纹理,见下文。
1.2. 矩阵赋值用 uniformMatrix[234]fv
对于矩阵,应用 gl.uniformMatrix[234]fv()
办法即可传递,其中,f 代表 float,v 代表 vector,即传入参数要是一个向量(即数组);
以传递一个 4×4 的矩阵为例:
// 获取 location(初始化时)const matrixLocation = gl.getUniformLocation(program, "u_matrix")// 创立或更新列主序变换矩阵(渲染时)const matrix = [/* ... */]// 传递值(渲染时)gl.uniformMatrix4fv(matrixLocation, false, matrix)
1.3. 标量与向量用 uniform[1234][fi][v]
对于一般标量和向量,应用 gl.uniform[1234][fi][v]()
办法即可传递,其中,1、2、3、4 代表标量或向量的维度(1就是标量啦),f/i 代表 float 或 int,v 代表 vector(即你传递的数据在着色器中将解析为向量数组)。
举例:
- 语句1,
gl.uniform1fv(someFloatLocation, [4.5, 7.1])
- 语句2,
gl.uniform4i(someIVec4Location, 5, 2, 1, 3)
- 语句3,
gl.uniform4iv(someIVec4Location, [5, 2, 1, 3, 2, 12, 0, 6])
- 语句4,
gl.uniform3f (someVec3Location, 7.1, -0.8, 2.1)
上述 4 个赋值语句对应的着色器中的代码为:
// 语句 1 能够适配 1~N 个浮点数// 只传单元素数组时,可间接申明 uniform float u_someFloat;uniform float u_someFloat[2];// 语句 2 适配一个 ivec4uniform ivec4 u_someIVec4;// 语句 3 适配 1~N 个 ivec4// 只传单元素数组时,可间接申明 uniform float u_someIVec4;uniform ivec4 u_someIVec4[2];// 语句 4 适配一个 vec3uniform vec3 u_someVec3;
到了 WebGL 2.0,在组分值类型会有一些裁减,请读者自行查阅相干文档。
1.4. 传递纹理
在顶点着色器阶段,能够应用顶点的纹理坐标对纹理进行采样:
attribute vec3 a_pos;attribute vec2 a_uv;uniform sampler2D u_texture;varying vec4 v_color;void main() { v_color = texture2D(u_texture, a_uv); gl_Position = a_pos; // 假如顶点不须要变换}
那么,在 JavaScript 端,能够应用 gl.uniform1i()
来通知着色器我把纹理刚刚传递到哪个纹理坑位上了:
const texture = gl.createTexture()const samplerLocation = gl.getUniformLocation(/* ... */)// ... 设置纹理数据 ...gl.activeTexture(gl[`TEXTURE${5}`]) // 通知 WebGL 应用第 5 个坑上的纹理gl.bindTexture(gl.TEXTURE_2D, texture)gl.uniform1i(samplerLocation, 5) // 通知着色器待会读纹理的时候去第 5 个坑位读
2. WebGL 2.0 Uniform
2.1. 标量/向量/矩阵传值办法裁减
WebGL 2.0 的 Uniform 系统对非方阵类型的矩阵提供了反对,例如
const mat2x3 = [ 1, 2, 3, 4, 5, 6,]gl.uniformMatrix2x3fv(loc, false, mat2x3)
上述办法传递的是 4×3
的矩阵。
而对于单值和向量,额定提供了无符号数值的办法,即由 uniform[1234][fi][v]
变成了 uniform[1234][f/ui][v]
,也就是上面 8 个新增办法:
gl.uniform1ui(/* ... */) // 传递数据至 1 个 uintgl.uniform2ui(/* ... */) // 传递数据至 1 个 uvec2gl.uniform3ui(/* ... */) // 传递数据至 1 个 uvec3gl.uniform4ui(/* ... */) // 传递数据至 1 个 uvec4gl.uniform1uiv(/* ... */) // 传递数据至 uint 数组gl.uniform2uiv(/* ... */) // 传递数据至 uvec2 数组gl.uniform3uiv(/* ... */) // 传递数据至 uvec3 数组gl.uniform4uiv(/* ... */) // 传递数据至 uvec4 数组
对应 GLSL300 中的 uniform 为:
#version 300 es#define N ? // N 取决于你的须要,JavaScript 传递的数量也要匹配 uniform uint u_someUint;uniform uvec2 u_someUVec2;uniform uvec3 u_someUVec3;uniform uvec4 u_someUVec4;uniform uint u_someUintArr[N];uniform uvec2 u_someUVec2Arr[N];uniform uvec3 u_someUVec3Arr[N];uniform uvec4 u_someUVec4Arr[N];
须要额定留神的是,uint/uvec234
这些类型在高版本的 glsl 能力应用,也就是说不向下兼容 WebGL 1.0 及 GLSL100.
然而,WebGL 2.0 带来的不单单只是这些小修小补,最重要的莫过于 UBO 了,马上开始。
2.1. 什么是 UniformBlock 与 UniformBuffer 的创立
在 WebGL 1.0 的时候,任意品种的对立值一次只能设定一个,如果一帧内 uniform 有较多更新,对于 WebGL 这个状态机来说不是什么坏事,会带来额定的 CPU 至 GPU 端的传递开销。
在 WebGL 2.0,容许一次发送一堆 uniform,这一堆 uniform 的聚合体,就叫做 UniformBuffer,具体到代码中:
先是 GLSL 300
uniform Light { highp vec3 lightWorldPos; mediump vec4 lightColor;};
而后是 JavaScript
const lightUniformBlockBuffer = gl.createBuffer()const lightUniformBlockData = new Float32Array([ 0, 10, 30, 0, // vec3, 光源地位, 为了 8 Byte 对齐填充一个尾 0 1, 1, 1, 1, // vec4, 光的色彩])gl.bindBuffer(gl.UNIFORM_BUFFER, lightUniformBlockBuffer);gl.bufferData(gl.UNIFORM_BUFFER, lightUniformBlockData, gl.STATIC_DRAW);gl.bindBufferBase(gl.UNIFORM_BUFFER, 0, lightUniformBlockBuffer)
先别急着问为什么,一步一步来。
首先你看到了,在 GLSL300 中容许应用相似构造体一样的块状语法申明多个 Uniform 变量,这里用到了光源的坐标和光源的色彩,别离应用了不同的精度和数据类型(vec3、vec4)。
随后,在 JavaScript 端,你看到了用新增的办法 gl.bindBufferBase()
来绑定一个 WebGLBuffer
到 0 号地位,这个 lightUniformBlockBuffer
其实就是汇合了两个 Uniform 变量的 UniformBufferObject (UBO)
,在着色器中那块被命名为 Light
的花括号区域,则叫 UniformBlock
.
其实,创立一个 UBO
和创立一般的 VBO
是一样的,绑定、赋值操作也简直统一(第一个参数有不同)。只不过 UBO 可能更须要思考数值上的设计,例如 8 字节对齐等,通常会在设计着色器的时候把雷同数据类型的 uniform 变量放在一起,达到内存应用上的最佳化。
2.2. 状态绑定
在 WebGL 2.0 中,JavaScript 端容许你把着色器程序中的 UniformBlock 地位绑定到某个变量中:
const viewUniformBufferIndex = 0;const materialUniformBufferIndex = 1;const modelUniformBufferIndex = 2;const lightUniformBufferIndex = 3;gl.uniformBlockBinding(prg, gl.getUniformBlockIndex(prg, 'View'), viewUniformBufferIndex);gl.uniformBlockBinding(prg, gl.getUniformBlockIndex(prg, 'Model'), modelUniformBufferIndex);gl.uniformBlockBinding(prg, gl.getUniformBlockIndex(prg, 'Material'), materialUniformBufferIndex);gl.uniformBlockBinding(prg, gl.getUniformBlockIndex(prg, 'Light'), lightUniformBufferIndex);
这里,应用的是 gl.getUniformBlockIndex()
获取 UniformBlock 在着色器程序中的地位,而把这个地位绑定到你喜爱的数字上的是 gl.uniformBlockBinding()
办法。
这样做有个益处,你能够在你的程序里人为地规定各个 UniformBlock 的程序,而后用这些 index 来更新不同的 UBO.
// 应用不同的 UBO 更新 materialUniformBufferIndex (=1) 指向的 UniformBlockgl.bindBufferBase(gl.UNIFORM_BUFFER, 1, redMaterialUBO)gl.bindBufferBase(gl.UNIFORM_BUFFER, 1, greenMaterialUBO)gl.bindBufferBase(gl.UNIFORM_BUFFER, 1, blueMaterialUBO)
当然,WebGL 2.0 对 Uniform 还有别的裁减,此处不再列举。
bindBufferBase 的作用相似于 enableVertexAttribArray,通知 WebGL 我马上就要用哪个坑了。
2.3. 着色器中的 Uniform
着色器应用 GLSL300 语法能力应用 UniformBlock 和 新的数据类型,除此之外和 GLSL100 没啥区别。当然,GLSL300 有很多新语法,这里只捡一些对于 Uniform 的来写。
对于 uint/uvec234
类型,在 2.1 节曾经有例子了,这里不赘述。
而对于 UniformBlock,还有一点须要补充的,那就是“命名”问题。
UniformBlock 的语法如下:
uniform <BlockType> { <BlockBody>} ?<blockName>;// 举例:具名定义uniform Model { mat4 world; mat4 worldInverseTranspose;} model;// 举例:不具名定义uniform Light { highp vec3 lightWorldPos; mediump vec4 lightColor;};
如果应用具名定义,那么拜访 Block 内的成员就须要应用它的 name 了,例如 model.world
、model.worldInverseTranspose
等。
举残缺的例子如下:
#version 300 esprecision highp float;precision highp int;// uniform 块的布局管制layout(std140, column_major) uniform;// 申明 uniform 块:Transform,命名为 transform 供主程序应用// 也能够不命名,就间接用 mvpMatrix 即可uniform Transform{ mat4 mvpMatrix;} transform;layout(location = 0) in vec2 pos;void main() { gl_Position = transform.mvpMatrix * vec4(pos, 0.0, 1.0);}
留神,即便给 UniformBlock 命名为 transform,然而立面的 mvpMatrix 是不能与其它 Block 外面的成员共名的,transform 没有命名空间的作用。
再看 JavaScript:
//#region 获取着色器程序中的 uniform 地位并绑定const uniformTransformLocation = gl.getUniformBlockIndex(program, 'Transform')gl.uniformBlockBinding(program, uniformTransformLocation, 0)//endregion//#region 创立 uboconst uniformTransformBuffer = gl.createBuffer()//#endregion//#region 创立矩阵所需的 ArrayBufferView,列主序const transformsMatrix = new Float32Array([ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0])//#endregion//#region 传递数据给 WebGLBuffergl.bindBuffer(gl.UNIFORM_BUFFER, uniformTransformBuffer)gl.bufferData(gl.UNIFORM_BUFFER, transformsMatrix, gl.DYNAMIC_DRAW);gl.bindBuffer(gl.UNIFORM_BUFFER, null)//#endregion// ---------- 在你须要绘制时 ----------//#region 绑定 ubo 到 0 号索引上的 uniformLocation 以供着色器应用gl.bindBufferBase(gl.UNIFORM_BUFFER, 0, uniformTransformBuffer)// ... 渲染// -------------
2.4. 传递纹理
纹理与 WebGL 1.0 统一,然而 GLSL300 的纹理函数有变,读者请自行查找材料比对。
3. WebGPU Uniform
WebGPU 有三个类型的 Uniform 资源:标量/向量/矩阵、纹理、采样器。
各自有各自的容器,第一种对立应用 GPUBuffer
,也就是所谓的 UBO;第二和第三种应用 GPUTexture
和 GPUSampler
.
3.1. 三类资源的创立与打组传递
上述三类资源,把它们通过打成一组,也就是 GPUBindGroup
,我叫它资源绑定组,进而传递给组织了着色器模块(GPUShaderModule
)的各种管线(GPURenderPipeline
、GPUComputePipeline
)。
对立起来好办事,这里为节约篇幅,数据传递就不再细说,着重看看它们的打组成绑定组的代码:
const someUbo = device.createBuffer({ /* 留神 usage 要有 UNIFORM */ })const texture = device.createTexture({ /* 创立惯例纹理 */ })const sampler = device.createSampler({ /* 创立惯例采样器 */ })// 布局对象分割管线布局和绑定组自身const bindGroupLayout = device.createBindGroupLayout({ entries: [ { binding: 0, // <- 绑定在 0 号资源 visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } }, { binding: 1, // <- 绑定在 1 号资源 visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } }, { binding: 2, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } } ]})const bindGroup = device.createBindGroup({ layout: bindGroupLayout, entries: [ { binding: 0, resource: sampler, // <- 传入采样器对象 }, { binding: 1, resource: texture.createView() // <- 传入纹理对象的视图 }, { binding: 2, resource: { buffer: someUbo // <- 传入 UBO } } ]})// 管线const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout]})const renderingPipeline = device.createRenderPipeline({ layout: pipelineLayout // ... 其它配置})// ... renderPass 切换 pipeline 和 bindGroup 进行绘制 ...
3.2. 更新 Uniform 与绑定组的意义
更新 Uniform 资源其实很简略。
如果是 UBO,个别会更新前端批改的灯光、材质、工夫帧参数以及单帧变动的矩阵等,应用 device.queue.writeBuffer
即可:
device.queue.writeBuffer( someUbo, // 传给谁 0, buffer, // 传递 ArrayBuffer,即以后帧中的新数据 byteOffset, // 从哪里开始 byteLength // 取多长)
应用 writeBuffer 就能够保障用的还是原来创立那个 GPUBuffer,它与绑定组、管线的绑定关系还在;不必映射、解映射的形式传值是缩小 CPU/GPU 双端通信老本
如果是纹理,那就用 图像拷贝操作 中的几个办法进行纹理对象更新;
个别不间接对采样器和纹理的更新,而是在编码器上切换不同的绑定组来切换管线所需的资源。尤其是纹理,若频繁更新数据,CPU/GPU 双端通信老本会减少的。
提早渲染、离屏绘制等须要更新色彩附件的,其实只须要创立新的 colorAttachments 对象即可实现“上一帧绘制的下一帧我能用”,不须要间接从 CPU 内存再刷入数据到 GPU 中。
更新 Uniform 须要对每一帧简直都要改的、简直不变的资源进行正当分组,分到不同的绑定组中,这样就能够有针对性地更新,而无需把管线、绑定组重设一次,仅仅在通道编码器上进行切换即可。
3.3. 着色器中的 Uniform
此处不波及太多 WGSL 语法。
与 UniformBlock 相似,须要指定“一块货色”,WGSL 间接应用的构造体。
首先,是 UBO:
// -- 顶点着色器 --// 申明一个构造体类型struct Uniforms { modelViewProjectionMatrix: mat4x4<f32>;};// 申明指定其绑定ID是0,绑定组序号是0@binding(2)@group(0)var<uniform> myUniforms: Uniforms;// —— 而后这个 myUniforms 变量就能够在函数中调用了 ——
而后是纹理和采样器:
@group(0)@binding(1)var mySampler: sampler;@group(0)@binding(2)var myTexture: texture_2d<f32>;// ... 片元着色器主函数中进行纹理采样textureSample(myTexture, mySampler, fragUV);
4. 比照总结
WebGL 以 2 为比对基准,它与 WebGPU 相比,没有资源绑定组,没有采样器对象(采样参数通过另外的办法设置)。
比起 WebGPU 的传 descriptor 式的写法,应用一条条办法切换 UniformBlock、纹理等资源可能会有所脱漏,这是全局状态写法的特点之一。当然,下层封装库会帮咱们屏蔽这些问题的。
与语法格调相比,其实 WebGPU 改良的更多的是这些 uniform 在每一帧更新时 CPU 到GPU 的负载问题,它是当时由编码器编码成指令缓冲最初一次性发送的,比起 WebGL 一条一条发送是更优的,在图形渲染、GPU运算这种中央,千里之行;始于足下,性能就高了起来。
对于 WebGL 2.0 的 Uniform 和 GLSL300 我学识不精,若有谬误请指出。
5. 参考资料
- WebGL2Fundamentals - StateDiagram - UniformBuffers
- Gist - A simple WebGL2 UniformBuffer Tutorial
- CSDN - WebGL2 UniformBlock
- Austin - WebGPUSamples