ARRAY_BUFFER 在 WebGL 中 VAO 绑定为 null 后丢失

ARRAY_BUFFER lost after VAO binded to null in WebGL

我创建了一个 VAO 并为 VAO 绑定了 VBO 以记住这些绑定:

bufferV = gl.createBuffer ();
bufferI = gl.createBuffer ();
vertexArray = gl.createVertexArray ();
gl.bindVertexArray (vertexArray);
gl.bindBuffer (gl.ELEMENT_ARRAY_BUFFER, bufferI);
gl.bindBuffer (gl.ARRAY_BUFFER, bufferV);
gl.bindVertexArray (null);

在动画循环中,我重新绑定 VAO 以重新绑定 VBO,并在渲染之前将一些顶点和索引加载到其中(我已经创建了着色器、程序并激活了程序):

gl.bindVertexArray (vertexArray);
gl.bufferData (gl.ARRAY_BUFFER, new Float32Array (vertices), gl.STATIC_DRAW);
gl.bufferData (gl.ELEMENT_ARRAY_BUFFER, new Uint16Array (indices), gl.STATIC_DRAW);
// Call to gl.drawElements () here
gl.bindVertexArray (null);

然后,因为我想这样做(不要问我为什么),在动画循环结束时,我取消绑定 VBO:

gl.bindBuffer (gl.ELEMENT_ARRAY_BUFFER, null);
gl.bindBuffer (gl.ARRAY_BUFFER, null);

奇怪的事情发生了。在第一个循环之后,WebGL 报告:

WebGL warning: bufferData: Buffer for `target` is null.

我查过了。这意味着在第一个循环之后,绑定 VAO 会 NOT 恢复 ARRAY_BUFFER VBO 绑定。但它仍然恢复 ELEMENT_ARRAY_BUFFER VBO 绑定。有人可以解释为什么吗?我在 Firefox 90.0.2 上测试了 Windows.

帮助人们发现 VAO 州在绑定 ARRAY_BUFFER 和 ELEMENT_ARRAY_BUFFER

方面如何运作的示例

感谢我的问题的答案,我很高兴给出这个独立的小例子,它展示了这个 VAO 状态的东西是如何工作的。

var program, vs, fs, bufferXY, bufferRGB, bufferI, vao, location, buffer;
var vertices = [    // x, y
    0.0, 1.0,
    1.0, -1.0,
    -1.0, -1.0
];
var colors = [  // r, g, b
     1.0, 0.0, 0.0,
      0.0, 1.0, 0.0,
       0.0, 0.0, 1.0
];
var indices = [
    0, 1, 2
];
var nbIndicesPerPrimitive=3, nbPrimitives=1;

// We create two shaders and a program.

vs = gl.createShader (gl.VERTEX_SHADER);
gl.shaderSource (vs, "#version 300 es\nin lowp vec2 A_xy;in lowp vec3 A_rgb;out lowp vec4 V_rgba;void main (void) { gl_Position = vec4 (A_xy, 0.0, 1.0); V_rgba = vec4 (A_rgb, 1.0); }");
gl.compileShader (vs);
if (!gl.getShaderParameter (vs, gl.COMPILE_STATUS))
    console.log (gl.getShaderInfoLog (vs));
fs = gl.createShader (gl.FRAGMENT_SHADER);
gl.shaderSource (fs, "#version 300 es\nin lowp vec4 V_rgba;out lowp vec4 rgba;void main (void) { rgba = V_rgba; }");
gl.compileShader (fs);
if (!gl.getShaderParameter (fs, gl.COMPILE_STATUS))
    console.log (gl.getShaderInfoLog (fs));
program = gl.createProgram ();
gl.attachShader (program, vs);
gl.attachShader (program, fs);
gl.linkProgram (program);
gl.useProgram (program);

// We create some VBOs and a VAO.

bufferXY = gl.createBuffer ();
bufferRGB = gl.createBuffer ();
bufferI = gl.createBuffer ();
vao = gl.createVertexArray ();

// We bind bufferXY to ARRAY_BUFFER. We can do it before the VAO is bound, because the ARRAY_BUFFER binding is global state. This means that whatever VBO is bound to ARRAY_BUFFER when vertexAttribPointer () is called, it will be memorized in the VAO state for the attribute which location is passed to this function.

gl.bindBuffer (gl.ARRAY_BUFFER, bufferXY);
gl.bufferData (gl.ARRAY_BUFFER, new Float32Array (vertices), gl.STATIC_DRAW);

// We bind the VAO so that it starts memorizing calls to a set of functions. For WebGL 1, those are bindBuffer () when binding a VBO to ELEMENT_ARRAY_BUFFER, vertexAttribPointer (), enableVertexAttribute () and disableVertexAttribute (). WebGL 2 adds some functions to this set: vertexAttribIPointer (), vertexAttribI4[u]i[v] () and vertexAttribDivisor ().

gl.bindVertexArray (vao);

// We bind bufferI to ELEMENT_ARRAY_BUFFER. The VAO memorizes this binding.

gl.bindBuffer (gl.ELEMENT_ARRAY_BUFFER, bufferI);
gl.bufferData (gl.ELEMENT_ARRAY_BUFFER, new Uint16Array (indices), gl.STATIC_DRAW);

// We configure attribute A_xy. The VAO memorizes this configuration, including the fact that bufferXY is bound to ARRAY_BUFFER when vertexAttribPointer () is called. So the VAO memorizes that A_xy should be read from bufferXY, 2 floats at a time, starting from offset 0, jumping 2 floats after one read.

location = gl.getAttribLocation (program, "A_xy");
gl.vertexAttribPointer (location, 2, gl.FLOAT, false, 2 * Float32Array.BYTES_PER_ELEMENT, 0);
gl.enableVertexAttribArray (location);

// We configure attribute A_rgb. For this, we must bind bufferRGB to ARRAY_BUFFER. The VAO memorizes this configuration, including the fact that bufferRGB is bound to ARRAY_BUFFER when vertexAttribPointer () is called. So the VBO memorizes that A_rgb should be read from bufferRGB, 3 floats at a time, starting from offset 0, jumping 3 floats after one read. Notice that binding bufferRGB to ARRAY_BUFFER does not change anything to what the VAO memorized about A_xyz previously.

gl.bindBuffer (gl.ARRAY_BUFFER, bufferRGB);
gl.bufferData (gl.ARRAY_BUFFER, new Float32Array (colors), gl.STATIC_DRAW);
location = gl.getAttribLocation (program, "A_rgb");
gl.vertexAttribPointer (location, 3, gl.FLOAT, false, 3 * Float32Array.BYTES_PER_ELEMENT, 0);
gl.enableVertexAttribArray (location);

// We unbind the VAO so that it stops memorizing.

gl.bindVertexArray (null);

// We bind null to ARRAY_BUFFER and to ELEMENT_ARRAY_BUFFER. This will show that all the bindings to ARRAY_BUFFER and ELEMENT_ARRAY_BUFFER will be remembered when the VAO is bound again before rendering.

gl.bindBuffer (gl.ARRAY_BUFFER, null);
gl.bindBuffer (gl.ELEMENT_ARRAY_BUFFER, null);

// We start the rendering process.

gl.clearColor (0.0, 0.0, 0.0, 1.0);
gl.clear (gl.COLOR_BUFFER_BIT);

// We bind the VAO so that it restores its state, among which the bindings of bufferI to ELEMENT_ARRAY_BUFFER, and the bindings of bufferXY to ARRAY_BUFFER for attribute A_xy and of bufferRGB to ARRAY_BUFFER for attribute A_rgb.

gl.bindVertexArray (vao);
gl.drawElements (gl.TRIANGLES, nbIndicesPerPrimitive * nbPrimitives, gl.UNSIGNED_SHORT, 0);
gl.bindVertexArray (null);

// We clean everything.

gl.useProgram (null);
gl.deleteProgram (program);
gl.deleteShader (vs);
gl.deleteShader (fs);
gl.deleteVertexArray (vao);
gl.deleteBuffer (bufferXY);
gl.deleteBuffer (bufferRGB);
gl.deleteBuffer (bufferI);

ARRAY_BUFFER 是全局状态。但是,ELEMENT_ARRAY_BUFFER 缓冲区绑定存储在顶点数组对象中。因此,当您想要更改 ELEMENT_ARRAY_BUFFER 绑定时,您需要绑定顶点数组对象:

gl.bindVertexArray (vertexArray);
gl.bindBuffer (gl.ELEMENT_ARRAY_BUFFER, null);

VAO 状态向量中规定的每个属性可能指的是不同的ARRAY_BUFFER。在调用 gl.vertexAttribPointer 时存储此引用。然后当前绑定到 ARRAY_BUFFER 目标的缓冲区与指定的属性索引相关联,缓冲区对象引用存储在当前绑定的 VAO 的状态向量中。
然而,索引缓冲区是 VAO 的一种状态。一个 VAO 只能引用 1 个索引缓冲区。如果缓冲区绑定到 ELEMENT_ARRAY_BUFFER 目标,缓冲区引用将分配给当前绑定的顶点数组对象。