我什么时候应该在 WebGL/OpenGL 中 enable/disable 顶点位置属性?

When should I enable/disable vertex position attributes in WebGL/OpenGL?

我正在处理一些 WebGL 代码,其中包含多个 运行 按顺序排列的着色器程序。

以前,我在 gl 上下文和着色器初始化期间根据需要使用 gl.enableVertexAttribArray(...)。我假定调用此函数是针对 gl.useProgram(...)

选择的程序设置特定状态,这可能是错误的

现在,我的第一个着色器程序启用了两个属性数组,第二个着色器程序启用了一个。当第二个程序运行s时,我得到一个错误:

Error: WebGL: drawArrays: no VBO bound to enabled vertex attrib index 1!

所以这让我想到也许我需要在第一个程序中使用顶点属性 1 后禁用它,但我想验证这是我应该做的,并希望得到解释为什么这是正确的或不正确的。

对于每个数组位置,在每次使用前后的enableVertexAttribArray(...)disableVertexAttribArray的最佳实践是什么?

我一生中从未调用过 disableVertexAttribArray,而且我已经编写了 100 多个 WebGL 程序。调用它可能有也可能没有任何性能优势,但不调用它没有兼容性问题。

The spec says 如果属性被当前程序使用并且访问超出范围,或者没有缓冲区绑定到已启用的属性,您只会收到错误消息。

我们可以对其进行测试,看看它是否正常工作。

var vsThatUses2Attributes = `
  attribute vec4 position;
  attribute vec2 texcoord;
  
  varying vec2 v_texcoord;
  
  void main() {
    v_texcoord = texcoord;
    gl_Position = position;
  }
`;

var vsThatUses1Attribute = `
  attribute vec4 position;
  
  varying vec2 v_texcoord;
  
  void main() {
    v_texcoord = position.xy * 0.5 + 0.5;
    gl_Position = position + vec4(1, 0, 0, 0);
  }
`;

var fs = `
  precision mediump float;
  varying vec2 v_texcoord;
  
  void main () {
    gl_FragColor = vec4(v_texcoord, v_texcoord.x * v_texcoord.y, 1);
  }
`;

var gl = document.querySelector("canvas").getContext("webgl");
document.body.appendChild(gl.canvas);
var programThatUses2Attributes = twgl.createProgramFromSources(gl, [vsThatUses2Attributes, fs]);
var programThatUses1Attribute = twgl.createProgramFromSources(gl, [vsThatUses1Attribute, fs]);

var positionLocation2AttribProg = gl.getAttribLocation(programThatUses2Attributes, "position");
var texcoordLocation2AttribProg = gl.getAttribLocation(programThatUses2Attributes, "texcoord");

var positionLocation1AttribProg = gl.getAttribLocation(programThatUses1Attribute, "position");

var positionBufferFor2AttribPrg = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBufferFor2AttribPrg);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
  -1, -1,
 -.5,  1,
   0, -1,
]), gl.STATIC_DRAW);

var texcoordBufferFor2AttribPrg = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBufferFor2AttribPrg);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
  0, 0,
0.5, 1,
  1, 0,
]), gl.STATIC_DRAW);


var positionBufferFor1AttribPrg = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBufferFor1AttribPrg);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
  -1, -1,
   0, -1,
  -1,  1,
  -1,  1,
   0, -1,
   0,  1,
]), gl.STATIC_DRAW);


// turn on 2 attributes
gl.enableVertexAttribArray(positionLocation2AttribProg);
gl.enableVertexAttribArray(texcoordLocation2AttribProg);

gl.bindBuffer(gl.ARRAY_BUFFER, positionBufferFor2AttribPrg);
gl.vertexAttribPointer(positionLocation2AttribProg, 2, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBufferFor2AttribPrg);
gl.vertexAttribPointer(texcoordLocation2AttribProg, 2, gl.FLOAT, false, 0, 0);

// draw with 2 attributes enabled
gl.useProgram(programThatUses2Attributes);
gl.drawArrays(gl.TRIANGLES, 0, 3);

// setup for second program that uses only 1 attribute
gl.bindBuffer(gl.ARRAY_BUFFER, positionBufferFor1AttribPrg);
gl.vertexAttribPointer(positionLocation1AttribProg, 2, gl.FLOAT, false, 0, 0);

// NOTICE WE HAVE !NOT turned off other attribute
gl.useProgram(programThatUses1Attribute);
gl.drawArrays(gl.TRIANGLES, 0, 6);

log("glError:", gl.getError());

function log() {
  var pre = document.createElement("pre");
  pre.appendChild(document.createTextNode(Array.prototype.join.call(arguments, " ")));
  document.body.appendChild(pre);
}
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/twgl.min.js"></script>
<pre>
This example draws a triangle with 3 vertices using 2 attributes.

It then draws a quad using 6 vertices and 1 attribute 
<b>WITHOUT TURNING OFF THE NOW 2nd UNUSED ATTRIBUTE</b>.

That means not only is that attribute left on but it only has 3 vertices even 
though the draw will use 6 vertices. Because that attribute is not 'comsumed' by 
the current program it's ok according to the spec.
</pre>
<canvas width="150" height="30"></canvas>

所以,您的错误可能是其他原因。

请注意,删除缓冲区会将其与属性解除绑定,此时它将成为没有缓冲区的启用属性,除非您禁用它,否则会导致错误。

属性状态与您发现的程序状态是分开的。

你的错误就是它所说的意思。您尝试绘制,该程序需要属性 #1 的数据。你在某个时候用 gl.enableVertexAttribArray 启用了它,但你没有用 gl.vertexAttribPointer 给它任何数据。所以你得到了一个错误。

请注意,gl.vertexAttribPointer 将当前绑定到 gl.ARRAY_BUFFER 的缓冲区绑定到指定的属性。

您可能会发现此答案很有用

您永远不需要在 WebGL 中调用 disableVertexAttribArray()。我会注意到为什么这个方法首先实际存在。

如您所知,WebGL 只是 OpenGLES2 的一个移植库,而 OpenGLES2 是 OpenGL 的一个子集。
在 OpenGL 的 API 列表中,有一些其他方法可以将顶点缓冲区传递给 GPU 而无需将顶点缓冲区指定为指针。 例如,在 OpenGL 环境中,您可以通过调用 gl.begin()gl.Vertex()gl.end() 等来发送顶点缓冲区数据。
这样就不需要调用gl.enableVertexAttribArray().

但是如果不在 WebGL 中调用 enableVertexAttribArray() 就无法指定缓冲区。因此,您永远不需要调用该方法,这只是历史原因。

而且您不需要在每一帧中调用 enableVertexAttribArray()。这是非常繁重的操作,因为您不应该在每一帧都调用它。 您只需要在初始化缓冲区后立即调用 enableVertexAttribArray()

这个 Q/A 帮助我回答了我的问题:

Conflict when using two or more shaders with different number of attributes

为了使其正常工作,我需要在使用不使用它们的着色器程序进行绘制之前禁用未使用的属性。我无法解释为什么有些人说这不是必需的,但这样做可以解决我的问题。