Uniforms Buffer Object (UBO) 是 WebGL2 引入的一项重要功能,它允许开发者更高效地管理和传递着色器中的 uniform 变量。
基本概念
UBO 是一种特殊的缓冲区对象,用于存储和组织着色器中的 uniform 数据。与传统的逐个设置 uniform 变量不同,UBO 允许你将一组相关的 uniform 变量作为一个整体来处理。
主要优势
- 性能提升:减少 WebGL API 调用次数
- 数据共享:多个着色器程序可以共享同一个 UBO
- 大容量支持:支持比传统 uniform 更大的数据量
- 结构化数据:可以更好地组织复杂的 uniform 数据
核心组件
-
GLSL 中的 Uniform Block:
glsllayout(std140) uniform TransformBlock { mat4 modelViewMatrix; mat4 projectionMatrix; };
-
JavaScript 中的缓冲区对象:
jsconst ubo = gl.createBuffer();
-
绑定系统:通过绑定点连接 uniform block 和 buffer
基本工作流程
-
创建 Uniform Block(在着色器中)
-
创建并填充缓冲区:
jsgl.bindBuffer(gl.UNIFORM_BUFFER, ubo); gl.bufferData(gl.UNIFORM_BUFFER, data, gl.DYNAMIC_DRAW);
-
获取并绑定 Uniform Block:
jsconst blockIndex = gl.getUniformBlockIndex(program, "TransformBlock"); gl.uniformBlockBinding(program, blockIndex, 0);
-
绑定缓冲区到绑定点:
jsgl.bindBufferBase(gl.UNIFORM_BUFFER, 0, ubo);
内存布局选项
UBO 支持不同的内存布局规范:
-
std140:标准布局(webgl2 - glsl 300 es版本只可以使用这个)
glsllayout(std140) uniform MyBlock { ... };
使用场景
- 矩阵和变换数据
- 光照参数
- 材质属性
- 场景全局设置
- 大量需要频繁更新的 uniform 数据
示例
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>uniforms buffer 学习示例</title>
<style>
body {
margin: 0;
overflow: hidden;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<canvas id="glCanvas"></canvas>
<script>
// 顶点着色器 - 使用 uniform block
const vertexShaderSource = `#version 300 es
in vec2 aPosition;
layout(std140) uniform TransformBlock {
mat4 uModelViewMatrix;
mat4 uProjectionMatrix;
};
void main() {
gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 0.0, 1.0);
}`;
// 片段着色器 - 使用 uniform block
const fragmentShaderSource = `#version 300 es
precision highp float;
layout(std140) uniform ColorBlock {
vec4 uColor;
};
out vec4 fragColor;
void main() {
fragColor = uColor;
}`;
// 1. 获取渲染上下文
const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl2');
if (!gl) {
alert('WebGL2 不支持!');
throw new Error('WebGL2 不支持!');
}
// 检查 UBO 支持
const uboSupported = gl.getParameter(gl.MAX_UNIFORM_BUFFER_BINDINGS) > 0;
if (!uboSupported) {
alert('ubo 不支持!');
throw new Error('UBO 不支持!');
}
// 调整画布大小
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, canvas.width, canvas.height);
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// 2. 创建并编译着色器
// 编译着色器
function createShader(gl, source, type) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error('Shader compilation error: ', gl.getShaderInfoLog(shader));
gl.deleteShader(shadr);
return null;
}
return shader;
}
// 3. 创建渲染程序
function createProgram(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('Program linking error: ', gl.getProgramInfoLog(program));
return null;
}
return program;
}
const vertexShader = createShader(gl, vertexShaderSource, gl.VERTEX_SHADER);
const fragmentShader = createShader(gl, fragmentShaderSource, gl.FRAGMENT_SHADER);
const program = createProgram(vertexShader, fragmentShader);
gl.useProgram(program);
// 4. 传输 attrib
const vertices = new Float32Array([
0.0, 0.5, // 顶部顶点
-0.5, -0.5, // 左下顶点
0.5, -0.5 // 右下顶点
]);
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const positionAtributeLocation = gl.getAttribLocation(program, 'aPosition');
gl.enableVertexAttribArray(positionAtributeLocation);
gl.vertexAttribPointer(positionAtributeLocation, 2, gl.FLOAT, false, 0, 0);
// 5. 传输 uniform block
const transformBlockIndex = gl.getUniformBlockIndex(program, 'TransformBlock');
const colorBlockIndex = gl.getUniformBlockIndex(program, 'ColorBlock');
const transformBlockSize = gl.getActiveUniformBlockParameter(program, transformBlockIndex, gl.UNIFORM_BLOCK_DATA_SIZE);
const colorBlockSize = gl.getActiveUniformBlockParameter(program, colorBlockIndex, gl.UNIFORM_BLOCK_DATA_SIZE);
// 创建 ubo
const transformUniformBuffer = gl.createBuffer();
gl.bindBuffer(gl.UNIFORM_BUFFER, transformUniformBuffer);
gl.bufferData(gl.UNIFORM_BUFFER, transformBlockSize, gl.DYNAMIC_DRAW);
// 创建 color uniform buffer
const colorUniformBuffer = gl.createBuffer();
gl.bindBuffer(gl.UNIFORM_BUFFER, colorUniformBuffer);
gl.bufferData(gl.UNIFORM_BUFFER, colorBlockSize, gl.DYNAMIC_DRAW);
// 绑定 uniform block 到 binding point
gl.uniformBlockBinding(program, transformBlockIndex, 0);
gl.uniformBlockBinding(program, colorBlockIndex, 1);
// 绑定 uniform block 到 binding point
gl.bindBufferBase(gl.UNIFORM_BUFFER, 0, transformUniformBuffer);
gl.bindBufferBase(gl.UNIFORM_BUFFER, 1, colorUniformBuffer);
// 设置初始颜色
const initialColor = new Float32Array([1.0, 0.0, 0.0, 1.0]);
gl.bindBuffer(gl.UNIFORM_BUFFER, colorUniformBuffer);
gl.bufferSubData(gl.UNIFORM_BUFFER, 0, initialColor);
// 6. 动画循环 与 渲染
let angle = 0;
function animate() {
angle += 0.01;
// 计算模型视图矩阵(旋转)
const modelViewMatrix = new Float32Array(16);
const cosA = Math.cos(angle);
const sinA = Math.sin(angle);
modelViewMatrix[0] = cosA;
modelViewMatrix[1] = sinA;
modelViewMatrix[4] = -sinA;
modelViewMatrix[5] = cosA;
modelViewMatrix[10] = 1.0;
modelViewMatrix[15] = 1.0;
// 计算投影矩阵(正交投影)
const projectionMatrix = new Float32Array(16);
const aspect = canvas.width / canvas.height;
// 简单的正交投影
projectionMatrix[0] = 1.0 / aspect;
projectionMatrix[5] = 1.0;
projectionMatrix[10] = 1.0;
projectionMatrix[15] = 1.0;
// 更新 transform uniform buffer
gl.bindBuffer(gl.UNIFORM_BUFFER, transformUniformBuffer);
gl.bufferSubData(gl.UNIFORM_BUFFER, 0, modelViewMatrix);
gl.bufferSubData(gl.UNIFORM_BUFFER, 16 * 4, projectionMatrix);
// 随时间改变颜色
const time = Date.now() * 0.001;
const color = new Float32Array([
(Math.sin(time) + 1) / 2,
(Math.sin(time * 0.7) + 1) / 2,
(Math.sin(time * 0.3) + 1) / 2,
1.0
]);
gl.bindBuffer(gl.UNIFORM_BUFFER, colorUniformBuffer);
gl.bufferSubData(gl.UNIFORM_BUFFER, 0, color);
// 清除画布并绘制
gl.clearColor(0.1, 0.1, 0.1, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 3);
requestAnimationFrame(animate);
}
animate();
</script>
</body>
</html>
与传统 uniform 的比较
特性 | 传统 uniform | UBO |
---|---|---|
数据量限制 | 较小 | 较大 |
更新效率 | 单个变量更新 | 批量更新 |
共享能力 | 不能共享 | 可以共享 |
代码复杂度 | 简单 | 较复杂 |