我应该何时在WebGL/OOpenGL中启用/禁用顶点位置属性

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

本文关键字:顶点 属性 位置 启用 何时 WebGL OOpenGL 我应该      更新时间:2023-09-26

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

以前,我在gl上下文和着色器的初始化过程中根据需要使用gl.enableVertexAttribArray(...)。我可能错误地认为,调用此函数是为gl.useProgram(...) 选择的程序设置特定状态

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

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

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

每次使用前后,每个阵列位置的enableVertexAttribArray(...)disableVertexAttribArray的最佳实践是什么?

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

规范指出,只有当属性被当前程序使用并且访问超出范围,或者没有绑定到已启用属性的缓冲区时,才会出现错误。

我们可以测试一下,看看它能正常工作。

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的缓冲区绑定到指定的属性。

你可能会发现这个答案很有用

https://stackoverflow.com/a/27164577/128511

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

使用两个或多个属性数不同的着色器时发生冲突

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

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

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

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

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