bufferSubData()
是 WebGL2 中用于更新缓冲区对象部分数据的方法,它不会重新分配缓冲区内存,只是替换指定范围内的数据。bufferSubData()
是 WebGL2 中高效更新缓冲区数据的重要工具,特别适合动态数据和渐进式更新场景。
基本语法
js
void gl.bufferSubData(target, offset, srcData, srcOffset, length);
参数说明
参数 | 类型 | 描述 |
---|---|---|
target |
GLenum |
指定缓冲区类型,必须是 gl.ARRAY_BUFFER 或 gl.ELEMENT_ARRAY_BUFFER |
offset |
GLintptr |
缓冲区中的字节偏移量,从哪里开始替换数据 |
srcData |
ArrayBufferView |
数据源,可以是 ArrayBuffer 、TypedArray 或 DataView |
srcOffset (可选) |
GLuint |
从 srcData 的哪个元素开始读取 |
length (可选) |
GLuint |
要复制的元素数量 |
使用示例
基本用法
js
const gl = canvas.getContext('webgl2');
const buffer = gl.createBuffer();
// 初始化缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(100), gl.STATIC_DRAW);
// 更新部分数据
const newData = new Float32Array([1, 2, 3, 4]);
gl.bufferSubData(gl.ARRAY_BUFFER, 16, newData); // 从第16字节开始更新
使用 srcOffset 和 length
js
const fullData = new Float32Array([0,1,2,3,4,5,6,7]);
// 只更新第2-4个元素(索引1-3)
gl.bufferSubData(gl.ARRAY_BUFFER, 0, fullData, 1, 3);
完整示例
以下是一个可以直接运行的简单示例, 可以从中体会bufferSubData()
的具体用法:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>bufferSubData()方法学习</title>
</head>
<body>
<canvas id="canvas" width="600" height="400"></canvas>
<script>
/* ---------- 着色器 ---------- */
const vs = `#version 300 es
in vec2 a_pos;
void main() {
gl_Position = vec4(a_pos, 0.0, 1.0);
}`;
const fs = `#version 300 es
precision mediump float;
out vec4 fragColor;
void main() {
fragColor = vec4(0.2, 0.6, 1.0, 1.0);
}`;
/* ---------- 初始化 WebGL2 ---------- */
const gl = document.getElementById('canvas').getContext('webgl2');
if (!gl) {
throw new Error('WebGL2 not supported! ');
}
function createShader(shaderSource, shaderType) {
const shader = gl.createShader(shaderType);
gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
throw new Error('Shader compile error! ');
}
return shader;
}
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(gl.getProgramInfoLog(program));
throw new Error('Program link error! ');
}
return program;
}
const vertexShader = createShader(vs, gl.VERTEX_SHADER);
const fragmentShader = createShader(fs, gl.FRAGMENT_SHADER);
const program = createProgram(vertexShader, fragmentShader);
const posLoc = gl.getAttribLocation(program, 'a_pos');
/* ---------- 缓冲区 ---------- */
const vertexData = new Float32Array([
// 第一个三角形
-0.5, -0.5,
0.0, -0.5,
-0.5, 0.0,
// 第二个三角形(稍后会被 bufferSubData 修改)
0.0, 0.0,
0.5, 0.0,
0.0, 0.5
]);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
// 分配并初始化整个缓冲区
gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.DYNAMIC_DRAW);
/* ---------- 顶点数组对象 VAO ---------- */
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
gl.enableVertexAttribArray(posLoc);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
/* ---------- 渲染 ---------- */
gl.useProgram(program);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0, 0, 0, 1);
function draw() {
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindVertexArray(vao);
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
draw();
/* ---------- 定时用 bufferSubData 更新数据 ---------- */
setInterval(() => {
// 修改第二个三角形(索引 3*2=6 开始,共 3 个顶点)
const offset = 6 * Float32Array.BYTES_PER_ELEMENT;
const delta = 0.05; // 每次右移的量
for (let i = 0; i < 3; ++i) {
vertexData[6 + i*2] += delta; // 只改 x 坐标
}
// 把更新后的子数据刷进 GPU
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferSubData(gl.ARRAY_BUFFER, offset, vertexData.subarray(6, 12));
draw();
}, 1000);
</script>
</body>
</html>
注意事项
-
偏移量对齐:偏移量必须是数据类型字节大小的倍数(如 Float32Array 必须是4的倍数)
-
范围检查:确保 offset + srcData.byteLength 不超过缓冲区大小,否则会抛出错误
-
性能考虑:
- 比重新创建整个缓冲区更高效
- 频繁的小更新可能影响性能,考虑批量更新
-
同步操作 :
bufferSubData()
是同步的,大量数据更新时可能会阻塞
与 bufferData() 的区别
方法 | 描述 |
---|---|
bufferData() |
重新创建并初始化整个缓冲区 |
bufferSubData() |
只更新缓冲区的一部分数据 |