基本语法
layout
语法的基本形式如下:
scss
layout(layout_qualifier) qualifier variable_name;
layout_qualifier
:指定布局属性的值,例如location
、binding
等。qualifier
:变量的类型,例如in
、out
、uniform
等。variable_name
:变量的名称。
版本支持情况
在webgl2 - glsl 300 es 版本中, layout 语法只支持 location 和 std140。
location
layout location语法 从 GLSL ES 300(对应 WebGL 2 / OpenGL ES 3.0)开始支持
location
用于指定顶点着色器的输入变量和片段着色器的输出变量的位置。这在顶点属性传递和片段输出时非常有用。
-
顶点着色器中的输入变量:
inilayout(location = 0) in vec3 vertexPosition; layout(location = 1) in vec3 vertexColor;
这里,
vertexPosition
和vertexColor
分别绑定到位置 0 和位置 1,对应 WebGL 中的gl.VERTEX_ATTRIB_ARRAY
。 -
片段着色器中的输出变量:
inilayout(location = 0) out vec4 fragColor;
这里,
fragColor
绑定到片段输出位置 0,对应 WebGL 中的默认颜色附件。
layout location用法 示例
下方是一完整的可直接运行的例子, 可以从中体会layout location
的用法
ini
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GLSL ES 3.00 layout</title>
<style>
canvas {
width: 800px;
height: 600px;
}
</style>
</head>
<body>
<canvas id="glCanvas"></canvas>
<script>
//// 1. 获取上下文
const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl2');
if (!gl) {
alert('WebGL2 不支持!');
throw new Error('WebGL2 不支持!');
}
//// 2. 创建着色器
// 顶点着色器 - 使用 layout(location)
const vertexShaderSource = `#version 300 es
layout(location = 0) in vec3 aPosition;
void main() {
gl_Position = vec4(aPosition, 1.0);
}`;
// 片段着色器 - 使用 layout(location)
const fragmentShaderSource = `#version 300 es
precision mediump float;
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = vec4(1.0, 0.0, 0.0, 1.0);
}`;
// 创建着色器
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error('着色器编译错误: ', gl.getSaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
//// 3. 创建程序对象
// 创建程序
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('程序链接错误: ', gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return null;
}
return program;
}
// 创建着色器和程序
const vertexSader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
const program = createProgram(gl, vertexSader, fragmentShader);
//// 4. 使用程序
// 使用程序
gl.useProgram(program);
//// 5. 传输属性
// 创建顶点缓冲区
const positions = [
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0,
0.0, 0.5, 0.0
];
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
// 绑定顶点属性
const positionAttribLoc = 0; // 使用 layout(location = 0), 省略了原来的getAttribLocation()调用
gl.enableVertexAttribArray(positionAttribLoc);
gl.vertexAttribPointer(positionAttribLoc, 3, gl.FLOAT, false, 0, 0);
//// 6. 绘制
// 绘制
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 3);
</script>
</body>
</html>
使用layout location 可省略 getAttribLocation()调用
在 GLSL 300 ES 及更高版本中,如果使用 layout(location = X)
显式指定顶点属性(attribute)的位置,就可以 省略 JavaScript 中的 gl.getAttribLocation()
调用,直接通过已知的 location
绑定属性。这是现代 GLSL 的特性之一,用于简化代码并提升可维护性。
std140
std140
在 GLSL ES 300(OpenGL ES 3.0 / WebGL 2)开始支持
std140 是 OpenGL 着色语言 (GLSL) 中定义的一种统一缓冲区对象 (UBO) 和着色器存储缓冲区对象 (SSBO) 的内存布局规范。它规定了数据在缓冲区中的精确排列方式,确保 CPU 和 GPU 对数据结构的解释一致。
基本规则
std140 布局遵循以下核心规则:
-
基础对齐规则:
- 标量类型 (bool, int, float):对齐到自身大小 (4字节)
- 向量类型:对齐到 vec4 的大小 (16字节)
- 数组:每个元素对齐到 vec4 (16字节)
- 结构体:对齐到其最大成员的对齐值,且大小必须填充为对齐值的整数倍
- 矩阵:视为列向量数组,每列对齐到 vec4
-
填充规则:
- 任何不符合对齐要求的情况都会自动插入填充字节
具体类型对齐
1. 标量类型
arduino
layout(std140) uniform Example {
float a; // 偏移 0,占用 4 字节
int b; // 偏移 4,占用 4 字节
bool c; // 偏移 8,占用 4 字节
}; // 总大小 12 字节
2. 向量类型
less
layout(std140) uniform Vectors {
vec2 a; // 偏移 0,占用 8 字节
vec3 b; // 偏移 16 (不是 8!),占用 12 字节
vec4 c; // 偏移 32 (不是 28!),占用 16 字节
}; // 总大小 48 字节
注意 vec3 会被填充到 16 字节边界。
3. 数组
scss
layout(std140) uniform Arrays {
float a[3]; // 每个元素 16 字节,总 48 字节
}; // 总大小 48 字节
4. 结构体
scss
struct Light {
vec3 position; // 16 字节 (实际使用 12,填充 4)
vec3 color; // 16 字节 (实际使用 12,填充 4)
float intensity;// 4 字节
}; // 总大小 36 字节 (填充到 48)
layout(std140) uniform Lights {
Light lights[2]; // 每个 Light 48 字节 (填充到 16 倍数)
}; // 总大小 96 字节
5. 矩阵
scss
layout(std140) uniform Matrices {
mat3 m; // 3 个 vec4 (48 字节)
mat4 n; // 4 个 vec4 (64 字节)
}; // 总大小 112 字节
实际应用注意事项
- 性能考虑:虽然 std140 确保了可预测的布局,但可能因填充而浪费内存。对于频繁更新的数据,应考虑内存使用效率。
- 跨平台兼容性:std140 保证了不同硬件/驱动上的一致行为。
- 调试技巧 :使用
glGetActiveUniformsiv
查询偏移量验证布局是否符合预期。
layout std140用法 示例
下方是一个完整的示例
ini
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GLSL ES 3.00 layout std140</title>
<style>
body {
margin: 0;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<canvas id="glCanvas"></canvas>
<script>
//// 1. 获取渲染上下文
const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl2');
if (!gl) {
alert('获取webgl2上下文失败!');
throw new Error('获取webgl2上下文失败!');
}
//// 2. 创建着色器
const vertexShaderSource = `#version 300 es
precision highp float;
layout(std140) uniform TransformBlock {
mat4 modelMatrix;
mat4 viewMatrix;
mat4 projectionMatrix;
vec4 color;
float intensity;
};
in vec2 aPosition;
out vec4 vColor;
void main() {
vColor = color * intensity;
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(aPosition, 0.0, 1.0);
}`;
const fragmentShaderSource = `#version 300 es
precision mediump float;
in vec4 vColor;
out vec4 fragColor;
void main() {
fragColor = vColor;
}`;
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error('着色器编译错误: ', gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
//// 3. 创建渲染程序
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('程序链接错误: ', gl.getProgramInfoLog(program));
throw new Error('程序链接错误!');
return null;
}
return program;
}
const program = createProgram(gl, vertexShader, fragmentShader);
//// 4. 设置属性
const positions = new Float32Array([
0.0, 0.5, // 顶点1
-0.5, -0.5, // 顶点2
0.5, -0.5 // 顶点3
]);
// 创建并绑定顶点缓冲区
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
const positionAttribLoc = gl.getAttribLocation(program, 'aPosition');
gl.enableVertexAttribArray(positionAttribLoc);
gl.vertexAttribPointer(positionAttribLoc, 2, gl.FLOAT, false, 0, 0);
//// 5. 设置 uniform
// uniform block 传值步骤 (1). 获取 uniform block 传值所需要的信息
// 获取 TransformBlock 在 program 中的索引(就相当于在program中的位置, 可以通过这个索引找到该 uniform block)
const uniformBlockIndex = gl.getUniformBlockIndex(program, 'TransformBlock');
// 获取 std140 布局下的 uniform block大小
const blockSize = gl.getActiveUniformBlockParameter(program, uniformBlockIndex, gl.UNIFORM_BLOCK_DATA_SIZE);
console.log('Uniform block 大小: ', blockSize, '字节');
// 获取 uniform 在 block 中的偏移量
const uniformIndices = gl.getActiveUniformBlockParameter(program, uniformBlockIndex, gl.UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES);
const offsets = gl.getActiveUniforms(program, uniformIndices, gl.UNIFORM_OFFSET);
console.log('Uniform偏移量: ', offsets);
// uniform block 传值步骤 (2). 创建 uniform buffer object, 用来保存数据
const uniformBuffer = gl.createBuffer();
gl.bindBuffer(gl.UNIFORM_BUFFER, uniformBuffer);
/*********************下方一直到 bufferData()都是设置数据*******/
// 创建 ArrayBuffer 来存储 uniform
const buffer = new ArrayBuffer(blockSize);
const view = new DataView(buffer);
// 设置 modelMatrix (mat4 - 16个 float)
const modelMatrix = 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
]);
// 设置 viewMatrix (mat4)
const viewMatrix = 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
]);
// 设置 projectionMatrix (mat4)
const aspect = canvas.width / canvas.height;
const projectionMatrix = new Float32Array([
1.0/aspect, 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
]);
// 设置 color (vec4)
const color = [Math.random(), Math.random(), Math.random(), 1.0];
// 设置 intensity (float)
const intensity = 0.5 + Math.random() * 0.5;
// 将数据写入 buffer
// modelMatrix (偏移量 0)
modelMatrix.forEach((val, i) => {
view.setFloat32(offsets[0] + i * 4, val, true);
});
// viewMatrix (偏移量 64, 因为 mat4 占用 64 字节)
viewMatrix.forEach((val, i) => {
view.setFloat32(offsets[1] + i * 4, val, true);
});
//projectionMatrix (偏移量 128)
projectionMatrix.forEach((val, i) => {
view.setFloat32(offsets[2] + i * 4, val, true);
});
// color (偏移量 192)
color.forEach((val, i) => {
view.setFloat32(offsets[3] + i * 4, val, true);
});
// intensity (偏移量 208)
view.setFloat32(offsets[4], intensity, true);
// 将上面设置的数据传入uniformbuffer中
gl.bufferData(gl.UNIFORM_BUFFER, view, gl.DYNAMIC_DRAW);
// uniform block 传值步骤 (3). 将program中的TransformBlock 与 上面创建的UBO绑定起来, 通过绑定点
const bindingPoint = 0;
// 将program中的TransformBlock绑定到绑定点
gl.uniformBlockBinding(program, uniformBlockIndex, bindingPoint);
// 将UBO绑定到绑定点
gl.bindBufferBase(gl.UNIFORM_BUFFER, bindingPoint, uniformBuffer);
// 调整画布大小
function resizeCanvas() {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
gl.viewport(0, 0, canvas.width, canvas.height);
}
// 6.渲染循环
function render() {
resizeCanvas();
gl.clearColor(0.1, 0.1, 0.1, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(program);
gl.drawArrays(gl.TRIANGLES, 0, 3);
requestAnimationFrame(render);
}
render();
</script>
</body>
</html>
300 es版本中 uniform block 前面必须指定布局限定符
在 GLSL 300 ES (OpenGL ES Shading Language version 3.00) 中,uniform block 前面必须指定布局限定符,这是与旧版本 GLSL 的一个重要区别。
布局限定符要求
-
强制要求:
- 在 GLSL 300 ES 中,所有的 uniform block 都必须使用
layout
限定符显式声明其布局方式(300 es只支持 std140), 所以没有默认的布局方式, 因为必须显式声明布局方式 - 不允许省略布局限定符(这与 GLSL 1.00/1.10 不同)
- 在 GLSL 300 ES 中,所有的 uniform block 都必须使用
-
可用布局选项:
scsslayout(std140) uniform MyBlock { ... }; // 显式 std140 布局
如果没有指定布局会发生什么?
如果省略 layout
限定符,GLSL 300 ES 编译器会报错。例如:
scss
// 错误写法(GLSL 300 ES 中不合法)
uniform MyBlock {
vec4 color;
float size;
};
// 正确写法(必须指定布局)
layout(std140) uniform MyBlock {
vec4 color;
float size;
};
为什么这样设计?
GLSL 300 ES 强制要求显式布局声明有几个原因:
- 可预测性:确保不同硬件/驱动上的一致行为
- 性能优化:让开发者明确选择内存布局策略
- 与现代GLSL规范对齐:与桌面版GLSL保持一致
绑定点与缓冲对象的关系
在 WebGL2 中,Uniform 块绑定点(Uniform Block Binding Point) 和 Uniform 缓冲对象(Uniform Buffer Object, UBO) 共同构成了高效传递 Uniform 数据的机制。以下是它们的详细关系和区别:
1. Uniform 块绑定点(Binding Point)
定义
-
绑定点是一个 全局的、整数索引的槽位(类似"插槽"),用于关联:
- 着色器程序中的 Uniform 块 (GLSL 中定义的
uniform BlockName { ... }
) - Uniform 缓冲对象(UBO) (存储实际数据的 GPU 缓冲区)。
- 着色器程序中的 Uniform 块 (GLSL 中定义的
-
绑定点是 WebGL2 上下文共享的,不同程序可以访问相同的绑定点。
作用
- 解耦 Uniform 块和具体缓冲区,允许动态切换 UBO 数据,而无需重新链接着色器程序。
绑定流程
-
获取 Uniform 块的索引:
iniconst blockIndex = gl.getUniformBlockIndex(program, "LightBlock");
-
将 Uniform 块绑定到某个绑定点 (例如
0
):inigl.uniformBlockBinding(program, blockIndex, 0);
-
将 UBO 绑定到相同的绑定点:
inigl.bindBufferBase(gl.UNIFORM_BUFFER, 0, uboBuffer);
2. Uniform 缓冲对象(UBO)
定义
- UBO 是一块 GPU 缓冲区 (通过
gl.createBuffer()
创建),用于存储 Uniform 数据(如矩阵、光照参数等)。 - 数据布局需与 GLSL 中的
std140
等规则匹配。
特点
- 高效更新大量 Uniform 数据 (避免逐字段调用
gl.uniformXXX()
)。 - 多个程序共享同一 UBO(通过绑定点)。
3. 绑定点与 UBO 的关系
类比:插槽与电池
- 绑定点 类似设备上的电池插槽(如
插槽0
、插槽1
)。 - UBO 类似电池,可以插入任意插槽。
- Uniform 块 类似设备电路,需要声明它使用哪个插槽的电源。
关键关系
-
绑定点是桥梁:
- 着色器通过
uniformBlockBinding
声明:"我要用绑定点X
的数据"。 - 开发者通过
bindBufferBase
将 UBO 绑定到X
,完成数据关联。
- 着色器通过
-
动态切换:
-
可以随时更换绑定点
X
对应的 UBO,无需修改着色器代码。 -
例如:
scss// 切换 UBO(同一绑定点) gl.bindBufferBase(gl.UNIFORM_BUFFER, 0, uboBuffer1); // 使用数据1 gl.bindBufferBase(gl.UNIFORM_BUFFER, 0, uboBuffer2); // 切换为数据2
-
4. 完整流程示例
GLSL 代码(GLSL 300 ES)
ini
#version 300 es
layout(std140) uniform LightBlock { // 未指定 binding,需通过 JS 绑定
vec3 lightColor;
float intensity;
};
JavaScript 代码
ini
// 1. 创建 UBO 并填充数据
const uboBuffer = gl.createBuffer();
gl.bindBuffer(gl.UNIFORM_BUFFER, uboBuffer);
gl.bufferData(gl.UNIFORM_BUFFER, new Float32Array([1, 1, 0, 2.0]), gl.STATIC_DRAW);
// 2. 获取 Uniform 块索引
const blockIndex = gl.getUniformBlockIndex(program, "LightBlock");
// 3. 将 Uniform 块绑定到绑定点 0
gl.uniformBlockBinding(program, blockIndex, 0);
// 4. 将 UBO 绑定到绑定点 0
gl.bindBufferBase(gl.UNIFORM_BUFFER, 0, uboBuffer);
5. 常见问题
Q: 为什么需要绑定点?直接关联 UBO 和 Uniform 块不行吗?
- 解耦设计:绑定点允许 UBO 和 Uniform 块动态关联,避免重新编译着色器。
- 资源共享:多个程序可通过同一绑定点共享 UBO(如全局光照数据)。
Q: 绑定点数量有限制吗?
- 可通过
gl.getParameter(gl.MAX_UNIFORM_BUFFER_BINDINGS)
查询(通常 ≥ 12)。
总结
概念 | 作用 |
---|---|
Uniform 块绑定点 | 全局槽位,关联 Uniform 块和 UBO。 |
Uniform 缓冲对象 | 存储 Uniform 数据的 GPU 缓冲区。 |
关系 | 绑定点是"插槽",UBO 是"电池",Uniform 块声明自己要用哪个插槽的电源。 |
这种设计大幅提升了 Uniform 数据管理的灵活性和性能! 🚀