WebGL创建新VBO时顶点数据被覆盖的问题排查
WebGL VBO顶点数据被覆盖问题解决
问题现象
创建多个Cube实例时,后实例化的Cube的顶点数据会覆盖先实例化的对象数据,导致所有Cube都显示最后一个实例的顶点属性(如颜色)。调整实例化顺序,被覆盖的对象也会随之变化。
问题代码
Cube构造函数
this.shader = shaderProgram(srcContainer.vert, srcContainer.frag); this.theta = 0; this.vbo = gl.createBuffer(); this.ibo = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ibo); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.indices), gl.STATIC_DRAW); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); gl.useProgram(this.shader); gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.verts), gl.STATIC_DRAW); // vertex position gl.vertexAttribPointer(0, /*size of attribute(x,y,z) in count*/3, gl.FLOAT, gl.FALSE, /*size of a vertex in bytes*/6*Float32Array.BYTES_PER_ELEMENT, /*offset to attribute in vertex*/0*Float32Array.BYTES_PER_ELEMENT); gl.enableVertexAttribArray(0); // vertex color gl.vertexAttribPointer(1, /*size of attribute(r,g,b) in count*/3, gl.FLOAT, gl.FALSE, /*size of a vertex in bytes*/6*Float32Array.BYTES_PER_ELEMENT, /*offset to attribute in vertex*/3*Float32Array.BYTES_PER_ELEMENT); gl.enableVertexAttribArray(1); gl.bindBuffer(gl.ARRAY_BUFFER, null);
实例化与渲染代码
let box, box2; let canv, gl, time; function setup() { canv = document.querySelector('#glCanvas'); gl = loadGL(canv); // 获取canvas的WebGL上下文 box2 = new Cube([ 1-0.4, 1-0.1, 1-0.8, 1-0.5, 1-0.0, 1-0.2, 1-0.6, 1-0.0, 1-0.0, 1-0.7, 1-0.2, 1-0.0, 1-0.6, 1-0.2, 1-0.9, 1-0.5, 1-0.1, 1-0.6, 1-0.7, 1-0.0, 1-0.5, 1-0.9, 1-0.5, 1-0.0], 0.5, 1/7); // 初始化颜色反转的立方体 box = new Cube([ 0.4, 0.1, 0.8, 0.5, 0.0, 0.2, 0.6, 0.0, 0.0, 0.7, 0.2, 0.0, 0.6, 0.2, 0.9, 0.5, 0.1, 0.6, 0.7, 0.0, 0.5, 0.9, 0.5, 0.0], 0.5, 1/10); // 初始化正常颜色的立方体,覆盖了box2的数据 box2.time = Math.PI/6; // 调整角度区分两个立方体 time = new Time(0); // 自定义时间类追踪deltaTime window.requestAnimationFrame(draw); // 启动渲染循环 } function draw(timestamp) { time.update(timestamp); background(0, 0, 0); box.show(); // 本应显示正常颜色 box2.show(); // 本应显示反转颜色 window.requestAnimationFrame(draw); }
show与bind方法
bind() { gl.useProgram(this.shader); gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ibo); }; show() { this.bind(); gl.drawElements(gl.TRIANGLES, this.indices.length, gl.UNSIGNED_SHORT, 0); this.unbind(); };
问题原因
WebGL中,gl.vertexAttribPointer()的配置是与当前激活的着色器程序绑定的全局状态,而非与VBO绑定。如果多个Cube实例共享同一个着色器程序(比如shaderProgram函数会缓存相同源码的程序实例),后创建的Cube在构造时调用vertexAttribPointer会覆盖着色器程序中属性0、1对应的VBO引用。渲染时,即使绑定了先创建的Cube的VBO,着色器仍会使用最后一次设置的VBO数据。
此外,当前bind()方法仅绑定缓冲区,未重新关联顶点属性与当前VBO,进一步导致属性配置与VBO不匹配。
解决方案
方案一:在bind()中重新配置顶点属性
将构造函数中的顶点属性配置逻辑移到bind()方法中,确保每次渲染时都将当前VBO与着色器属性正确关联:
修改后的构造函数
this.shader = shaderProgram(srcContainer.vert, srcContainer.frag); this.theta = 0; this.vbo = gl.createBuffer(); this.ibo = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ibo); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.indices), gl.STATIC_DRAW); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.verts), gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, null);
修改后的bind()方法
bind() { gl.useProgram(this.shader); gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ibo); // 重新关联顶点属性与当前VBO gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 6*Float32Array.BYTES_PER_ELEMENT, 0); gl.enableVertexAttribArray(0); gl.vertexAttribPointer(1, 3, gl.FLOAT, gl.FALSE, 6*Float32Array.BYTES_PER_ELEMENT, 3*Float32Array.BYTES_PER_ELEMENT); gl.enableVertexAttribArray(1); };
方案二:使用VAO管理顶点属性配置
顶点数组对象(VAO)可以保存所有顶点属性的配置状态,绑定VAO即可一键恢复属性与VBO的关联,更简洁高效:
修改后的构造函数
this.shader = shaderProgram(srcContainer.vert, srcContainer.frag); this.theta = 0; this.vbo = gl.createBuffer(); this.ibo = gl.createBuffer(); this.vao = gl.createVertexArray(); // 创建VAO // 绑定VAO,后续属性配置将被保存到VAO中 gl.bindVertexArray(this.vao); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ibo); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.indices), gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.verts), gl.STATIC_DRAW); // 配置顶点属性 gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 6*Float32Array.BYTES_PER_ELEMENT, 0); gl.enableVertexAttribArray(0); gl.vertexAttribPointer(1, 3, gl.FLOAT, gl.FALSE, 6*Float32Array.BYTES_PER_ELEMENT, 3*Float32Array.BYTES_PER_ELEMENT); gl.enableVertexAttribArray(1); // 解绑VAO与缓冲区 gl.bindVertexArray(null); gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
修改后的bind()方法
bind() { gl.useProgram(this.shader); gl.bindVertexArray(this.vao); // 绑定VAO,自动恢复所有属性配置与IBO绑定 };
注意:VAO需要WebGL 2.0支持,若使用WebGL 1.0需启用
OES_vertex_array_object扩展。
内容的提问来源于stack exchange,提问作者azavier
相关产品推荐
相关产品推荐

