跳到主要内容

缓冲顶点与VAO

WebGL2 的顶点状态核心是 VAO,不是每帧把 bindBuffer + vertexAttribPointer 散装一遍。VAO 记下:各 attribute 的 buffer、步长、偏移、是否启用,以及 ELEMENT_ARRAY_BUFFER。热路径只 bindVertexArraydraw*

1. 为什么不能散装 attribute

ARRAY_BUFFER 绑定本身 不会 被 VAO 记住;VAO 记住的是调用 vertexAttribPointer 当时 绑在 ARRAY_BUFFER 上的那个 buffer。每帧重复 pointer 设置等于:

  • 把 CPU 同步点塞进每一趟 draw
  • 上一网格的 attrib 漏关,下一网格读到脏指针
  • instance / 静态网格共用一套全局 attrib,divisor 串台
export type Attrib = {
location: number;
size: number;
type: number;
normalized: boolean;
stride: number;
offset: number;
divisor?: number;
};

export function createVao(
gl: WebGL2RenderingContext,
vertex: WebGLBuffer,
attribs: readonly Attrib[],
index?: WebGLBuffer,
): WebGLVertexArrayObject {
const vao = gl.createVertexArray();
if (!vao) throw new Error("createVertexArray failed");
gl.bindVertexArray(vao);
gl.bindBuffer(gl.ARRAY_BUFFER, vertex);
for (const a of attribs) {
if (a.location < 0) continue;
gl.enableVertexAttribArray(a.location);
gl.vertexAttribPointer(
a.location,
a.size,
a.type,
a.normalized,
a.stride,
a.offset,
);
if (a.divisor !== undefined) {
gl.vertexAttribDivisor(a.location, a.divisor);
}
}
if (index) gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index);
gl.bindVertexArray(null);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
return vao;
}

export function drawVao(
gl: WebGL2RenderingContext,
vao: WebGLVertexArrayObject,
mode: number,
count: number,
indexType?: number,
): void {
gl.bindVertexArray(vao);
if (indexType === undefined) {
gl.drawArrays(mode, 0, count);
} else {
gl.drawElements(mode, count, indexType, 0);
}
gl.bindVertexArray(null);
}

stride / offset字节 计。交错 float x,y,u,v 的 stride 是 16,不是 4

2. 更新数据,不要重建 VAO

几何没变、只改顶点值:对已有 buffer bufferSubData(或 orphan:bufferData 同尺寸再写)。VAO 仍指向同一对象。

export function writeBuffer(
gl: WebGL2RenderingContext,
buffer: WebGLBuffer,
data: ArrayBufferView,
usage: number,
): void {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, data);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
void usage;
}

容量不够再 bufferData 扩容,然后 不必 重绑 pointer——除非你换了另一个 buffer 对象。换 buffer 才需要重新 vertexAttribPointer 一次。

适用:静态网格一次上传;CPU 动画顶点低频 bufferSubData
不适用:每顶点一次 drawArrays;WebGL1 生产路径(那里 VAO 是扩展,本体系不走 experimental-webgl)。

3. WebGPU 对照

没有 VAO 对象。GPUVertexBufferLayout 写进 pipeline,绘制时 pass.setVertexBuffer(slot, buffer)。布局编译期定死,热路径同样不要每 draw 重建 pipeline。

4. 丢失

webglcontextlost 之后 VAO / buffer 句柄全部作废。资源表按「几何源数据还在 CPU」重建,不要缓存旧 WebGLBuffer。见 上下文生命周期

5. 失败形态

症状原因
第一网格对、第二网格花没 VAO,attrib 从上一趟漏下来
全黑location === -1,或没 enableVertexAttribArray
顶点拧成条带stride/offset 按 float 个数而不是字节
实例全叠在一起vertexAttribDivisor 仍是 0
恢复后立刻 INVALID_OPERATION丢失后还在 bindVertexArray(旧句柄)

权威资料

核对日期:2026-08-26