as everyone knows , stay GPU When running a programmable pipeline , Shaders run in parallel , Each shader entry function will be in GPU In parallel . Each shader charges a large piece of data in a unified format , reflect GPU The advantages of multi-core , Small cores can process data at the same time ; however , Some data is the same for every shader , The type of data is “uniform”, Also known as uniform value .
This article lists the original WebGL 1/2 Medium uniform Information , as well as WebGPU Medium uniform Information , There are some examples for reference , To compare the differences between them .
1. WebGL 1.0 Uniform
1.1. use WebGLUniformLocation Addressing
stay WebGL 1.0 in , Usually in JavaScript End save WebGLUniformLocation
To pass... To the shader program uniform It's worth it .
Use gl.getUniformLocation()
Method to get this location, There are several ways
- full name :
gl.getUniformLocation(program, 'u_someUniformVar')
- component : Usually part of a vector , for example
gl.getUniformLocation(program, 'u_someVec3[0]')
Is to get the 0 Elements ( The type of element is vec3) Of location - Structural members :
gl.getUniformLocation(program, 'u_someStruct.someMember')
The shader code corresponding to the above three cases :
// full name
uniform float u_someUniformVar;
// component
uniform vec3 u_someVec3[3]; // Be careful , Here is 3 individual vec3
// Structural members
struct SomeStruct {
bool someMember;
};
uniform SomeStruct u_someStruct;
There are three types of value transmission , Scalar / vector 、 matrix 、 Sample texture , See below .
1.2. Matrix assignment is done with uniformMatrix[234]fv
For matrices , Use gl.uniformMatrix[234]fv()
Method to pass , among ,f representative float,v representative vector, That is, if the incoming parameter is a vector ( It's an array );
To pass a 4×4 The matrix of :
// obtain location( On initialization )
const matrixLocation = gl.getUniformLocation(program, "u_matrix")
// Create or update the column main order transformation matrix ( When rendering )
const matrix = [/* ... */]
// Transfer value ( When rendering )
gl.uniformMatrix4fv(matrixLocation, false, matrix)
1.3. Scalar and vector use uniform[1234][fi][v]
For ordinary scalars and vectors , Use gl.uniform[1234][fi][v]()
Method to pass , among ,1、2、3、4 Represents the dimension of a scalar or vector (1 It's scalar ),f/i representative float or int,v representative vector( That is, the data you pass will be parsed into a vector array in the shader ).
give an example :
- sentence 1,
gl.uniform1fv(someFloatLocation, [4.5, 7.1])
- sentence 2,
gl.uniform4i(someIVec4Location, 5, 2, 1, 3)
- sentence 3,
gl.uniform4iv(someIVec4Location, [5, 2, 1, 3, 2, 12, 0, 6])
- sentence 4,
gl.uniform3f (someVec3Location, 7.1, -0.8, 2.1)
Above 4 The code in the shader corresponding to an assignment statement is :
// sentence 1 Can be adapted 1~N A floating point number
// When only flyer element arrays , You can directly declare uniform float u_someFloat;
uniform float u_someFloat[2];
// sentence 2 Fit one ivec4
uniform ivec4 u_someIVec4;
// sentence 3 adapter 1~N individual ivec4
// When only flyer element arrays , You can directly declare uniform float u_someIVec4;
uniform ivec4 u_someIVec4[2];
// sentence 4 Fit one vec3
uniform vec3 u_someVec3;
here we are WebGL 2.0, There will be some extensions in the component value type , Please refer to the relevant documents .
1.4. Transfer texture
In the vertex shader phase , You can sample textures using the texture coordinates of vertices :
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; // Suppose the vertices don't need to be transformed
}
that , stay JavaScript End , have access to gl.uniform1i()
To tell the shader which texture pit I just passed the texture to :
const texture = gl.createTexture()
const samplerLocation = gl.getUniformLocation(/* ... */)
// ... Set texture data ...
gl.activeTexture(gl[`TEXTURE${5}`]) // tell WebGL Use the 5 The texture of a pit
gl.bindTexture(gl.TEXTURE_2D, texture)
gl.uniform1i(samplerLocation, 5) // Tell the shader to read the texture later 5 A pit reading
2. WebGL 2.0 Uniform
2.1. Scalar / vector / Extension of matrix value transfer method
WebGL 2.0 Of Uniform The system supports matrices of non square matrix type , for example
const mat2x3 = [
1, 2, 3,
4, 5, 6,
]
gl.uniformMatrix2x3fv(loc, false, mat2x3)
The above method passes 4×3
Matrix .
And for single values and vectors , Additional methods for unsigned values are provided , by uniform[1234][fi][v]
Turned into uniform[1234][f/ui][v]
, It's down there 8 A new method :
gl.uniform1ui(/* ... */) // Transfer data to 1 individual uint
gl.uniform2ui(/* ... */) // Transfer data to 1 individual uvec2
gl.uniform3ui(/* ... */) // Transfer data to 1 individual uvec3
gl.uniform4ui(/* ... */) // Transfer data to 1 individual uvec4
gl.uniform1uiv(/* ... */) // Transfer data to uint Array
gl.uniform2uiv(/* ... */) // Transfer data to uvec2 Array
gl.uniform3uiv(/* ... */) // Transfer data to uvec3 Array
gl.uniform4uiv(/* ... */) // Transfer data to uvec4 Array
Corresponding GLSL300 Medium uniform by :
#version 300 es
#define N ? // N It depends on your needs ,JavaScript The number of passes should also match
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];
What needs extra attention is ,uint/uvec234
These types are in higher versions of glsl Can be used , In other words, it is not downward compatible WebGL 1.0 And GLSL100.
However ,WebGL 2.0 It's not just these minor repairs , The most important thing is UBO 了 , Start now .
2.1. What is? UniformBlock And UniformBuffer The creation of
stay WebGL 1.0 When , Only one uniform value of any kind can be set at a time , If within a frame uniform There are more updates , about WebGL It's not a good thing for this state machine , It will bring extra CPU to GPU The delivery overhead at the end .
stay WebGL 2.0, Allow sending a bunch at a time uniform, This pile uniform The polymer of , It's called UniformBuffer, Specific to the code :
First, GLSL 300
uniform Light {
highp vec3 lightWorldPos;
mediump vec4 lightColor;
};
And then there was JavaScript
const lightUniformBlockBuffer = gl.createBuffer()
const lightUniformBlockData = new Float32Array([
0, 10, 30, 0, // vec3, Light source location , in order to 8 Byte Align and fill a tail 0
1, 1, 1, 1, // vec4, The color of light
])
gl.bindBuffer(gl.UNIFORM_BUFFER, lightUniformBlockBuffer);
gl.bufferData(gl.UNIFORM_BUFFER, lightUniformBlockData, gl.STATIC_DRAW);
gl.bindBufferBase(gl.UNIFORM_BUFFER, 0, lightUniformBlockBuffer)
Don't rush to ask why , Step by step .
First you see , stay GLSL300 Multiple... Are allowed to be declared using a block syntax similar to a structure Uniform Variable , The coordinates of the light source and the color of the light source are used here , Different precision and data types are used respectively (vec3、vec4).
And then , stay JavaScript End , You see the new method gl.bindBufferBase()
To bind a WebGLBuffer
To 0 The location , This lightUniformBlockBuffer
It's actually a collection of two Uniform Variable UniformBufferObject (UBO)
, In the shader, that piece is named Light
Curly bracket area of , It's called UniformBlock
.
Actually , Create a UBO
And create ordinary VBO
It's the same , binding 、 The assignment operation is almost the same ( The first parameter is different ). It's just UBO It may be more necessary to consider numerical design , for example 8 Byte alignment, etc , The same data type is usually used when designing shaders uniform Put variables together , Optimize memory usage .
2.2. State binding
stay WebGL 2.0 in ,JavaScript The client allows you to put... In the shader program UniformBlock Position bound to a variable :
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);
here , It uses gl.getUniformBlockIndex()
obtain UniformBlock Position in shader program , And what binds this position to your favorite number is gl.uniformBlockBinding()
Method .
There's a benefit to this , You can artificially specify each in your program UniformBlock The order of , Then use these index To update different UBO.
// Use different UBO to update materialUniformBufferIndex (=1) Point to the UniformBlock
gl.bindBufferBase(gl.UNIFORM_BUFFER, 1, redMaterialUBO)
gl.bindBufferBase(gl.UNIFORM_BUFFER, 1, greenMaterialUBO)
gl.bindBufferBase(gl.UNIFORM_BUFFER, 1, blueMaterialUBO)
Of course ,WebGL 2.0 Yes Uniform There are other extensions , There are no more examples here .
bindBufferBase Is similar to enableVertexAttribArray, tell WebGL I'm going to use which pit .
2.3. In shader Uniform
Shader use GLSL300 Grammar can only be used UniformBlock and New data types , Besides, and GLSL100 Do not have what difference . Of course ,GLSL300 There are many new grammars , Here's just some information about Uniform To write .
About uint/uvec234
type , stay 2.1 There are already examples in this section , I won't repeat it here .
And about the UniformBlock, There is one more point to add , That's it “ name ” problem .
UniformBlock The grammar is as follows :
uniform <BlockType> {
<BlockBody>
} ?<blockName>;
// give an example : Named definition
uniform Model {
mat4 world;
mat4 worldInverseTranspose;
} model;
// give an example : Unnamed definition
uniform Light {
highp vec3 lightWorldPos;
mediump vec4 lightColor;
};
If you use a named definition , Then visit Block Members within need to use its name 了 , for example model.world
、model.worldInverseTranspose
etc. .
A complete example is as follows :
#version 300 es
precision highp float;
precision highp int;
// uniform Block layout control
layout(std140, column_major) uniform;
// Statement uniform block :Transform, Name it transform For the main program
// You can also not name , Use directly mvpMatrix that will do
uniform Transform
{
mat4 mvpMatrix;
} transform;
layout(location = 0) in vec2 pos;
void main() {
gl_Position = transform.mvpMatrix * vec4(pos, 0.0, 1.0);
}
Be careful , Even to UniformBlock Name it transform, But the facade mvpMatrix It can't be compared with other Block The members in it have a total of ,transform There is no namespace .
Look again JavaScript:
//#region Get... In shader program uniform Position and bind
const uniformTransformLocation = gl.getUniformBlockIndex(program, 'Transform')
gl.uniformBlockBinding(program, uniformTransformLocation, 0)
//endregion
//#region establish ubo
const uniformTransformBuffer = gl.createBuffer()
//#endregion
//#region Required to create matrix ArrayBufferView, Column main order
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 Transfer data to WebGLBuffer
gl.bindBuffer(gl.UNIFORM_BUFFER, uniformTransformBuffer)
gl.bufferData(gl.UNIFORM_BUFFER, transformsMatrix, gl.DYNAMIC_DRAW);
gl.bindBuffer(gl.UNIFORM_BUFFER, null)
//#endregion
// ---------- When you need to draw ----------
//#region binding ubo To 0 On the index number uniformLocation For use by shaders
gl.bindBufferBase(gl.UNIFORM_BUFFER, 0, uniformTransformBuffer)
// ... Rendering
// -------------
2.4. Transfer texture
Texture and WebGL 1.0 Agreement , however GLSL300 The texture function of has changed , Readers are requested to find out the information by themselves .
3. WebGPU Uniform
WebGPU There are three types of Uniform resources : Scalar / vector / matrix 、 texture 、 Sampler .
Each has its own container , The first unified use GPUBuffer
, It's called UBO; The second and third uses GPUTexture
and GPUSampler
.
3.1. Creation and group transmission of three types of resources
The above three types of resources , Group them by , That is to say GPUBindGroup
, I call it resource binding group , Then passed to the shader module (GPUShaderModule
) All kinds of pipelines (GPURenderPipeline
、GPUComputePipeline
).
Unified and easy to handle , Here to save space , Data transfer is no longer detailed , Focus on the code of their binding group :
const someUbo = device.createBuffer({ /* Be careful usage Want to have UNIFORM */ })
const texture = device.createTexture({ /* Create a regular texture */ })
const sampler = device.createSampler({ /* Create a general sampler */ })
// The layout object relates the pipeline layout and the binding group itself
const bindGroupLayout = device.createBindGroupLayout({
entries: [
{
binding: 0, // <- Binding in 0 Resource No
visibility: GPUShaderStage.FRAGMENT,
sampler: {
type: 'filtering'
}
},
{
binding: 1, // <- Binding in 1 Resource No
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, // <- Pass in the sampler object
},
{
binding: 1,
resource: texture.createView() // <- Pass in the view of the texture object
},
{
binding: 2,
resource: {
buffer: someUbo // <- Pass in UBO
}
}
]
})
// pipeline
const pipelineLayout = device.createPipelineLayout({
bindGroupLayouts: [bindGroupLayout]
})
const renderingPipeline = device.createRenderPipeline({
layout: pipelineLayout
// ... Other configuration
})
// ... renderPass Switch pipeline and bindGroup Drawing ...
3.2. to update Uniform The meaning of binding groups
to update Uniform Resources are actually very simple .
If it is UBO, Generally, the lights modified at the front end will be updated 、 texture of material 、 Time frame parameters and matrix of single frame change, etc , Use device.queue.writeBuffer
that will do :
device.queue.writeBuffer(
someUbo, // To whom
0,
buffer, // Pass on ArrayBuffer, That is, the new data in the current frame
byteOffset, // Where to start
byteLength // How long
)
Use writeBuffer You can ensure that the original creation is used GPUBuffer, It is associated with the binding group 、 The binding relationship of pipeline is still ; No mapping 、 The way of demapping is to reduce CPU/GPU Two terminal communication cost
If it's texture , Then use Image copy operation Several methods in texture object update ;
Generally, the sampler and texture are not updated directly , Instead, switch different binding groups on the encoder to switch the resources required by the pipeline . Especially the texture , If the data is updated frequently ,CPU/GPU The cost of two terminal communication will increase .
Deferred Shading 、 Off screen drawing and other color attachments that need to be updated , In fact, you just need to create a new colorAttachments Object can realize “ The next frame drawn from the previous frame I can use ”, You don't have to go directly from CPU The memory then brushes the data into GPU in .
to update Uniform You need to change almost every frame 、 Reasonably group almost unchanged resources , Into different binding groups , In this way, you can update , Without putting the pipeline 、 Bind group reset once , Just switch on the channel encoder .
3.3. In shader Uniform
Not much is involved here WGSL grammar .
And UniformBlock similar , You need to specify the “ A piece of stuff ”,WGSL Directly used structures .
First , yes UBO:
// -- Vertex shader --
// Declare a struct type
struct Uniforms {
modelViewProjectionMatrix: mat4x4<f32>;
};
// The declaration specifies its binding ID yes 0, Binding group serial number is 0
@binding(2)
@group(0)
var<uniform> myUniforms: Uniforms;
// —— And then this myUniforms Variables can be called in functions. ——
Then texture and sampler :
@group(0)
@binding(1)
var mySampler: sampler;
@group(0)
@binding(2)
var myTexture: texture_2d<f32>;
// ... Texture sampling in the main function of the slice shader
textureSample(myTexture, mySampler, fragUV);
4. conclusion
WebGL With 2 As a benchmark , It is associated with WebGPU comparison , No resource binding group , No sampler object ( The sampling parameters are set in another way ).
Compared with WebGPU Biography of descriptor The style of writing , Use one method after another to switch UniformBlock、 Textures and other resources may be missing , This is one of the characteristics of global state writing . Of course , The upper encapsulation library will help us shield these problems .
Compared with grammatical style , Actually WebGPU More improvements are these uniform At each frame update CPU To GPU The load problem of , It is encoded into instruction buffer by encoder in advance, and finally sent at one time , Compared with WebGL Sending one by one is better , In graphic rendering 、GPU Computing places like this , Many a little make a mickle , The performance is higher .
About WebGL 2.0 Of Uniform and GLSL300 I'm not very knowledgeable , If there is any mistake, please point out .