58

我对启用顶点属性数组的范围并不完全清楚。我有几个不同的着色器程序,它们具有不同数量的顶点属性。glEnableVertexAttribArray着色器程序的调用是本地调用还是全局调用?

现在,我在创建着色器程序时启用了顶点属性数组,并且从不禁用它们,而且一切似乎都有效,但似乎我应该在绘制调用之前/之后启用/禁用它们。对此有影响吗?

(碰巧我在 WebGL 中,所以我们真的在谈论gl.enableVertexAttribArrayand gl.disableVertexAttribArray。我还要注意,橙皮书OpenGL Shading Language对这些调用非常缺乏信息。)

4

4 回答 4

47

启用顶点属性数组的状态可以绑定到顶点数组对象 (VAO),也可以是全局的。

如果您使用 VAO,则不应禁用属性数组,因为它们封装在 VAO 中。

但是,对于全局顶点属性数组启用状态,您应该禁用它们,因为如果它们保持启用状态,OpenGL 将尝试从数组中读取,这可能会绑定到无效指针,如果指针指向客户端地址,这可能会使您的程序崩溃空间,或者如果它指出超出绑定顶点缓冲区对象的限制,则会引发 OpenGL 错误。

于 2012-09-14T15:56:58.977 回答
15

WebGL 与 OpenGL 不同。

在 WebGL 中,只要有一个附加到属性的缓冲区,并且(a)如果它被使用,它就足够大以满足绘图调用,或者(b)它没有被使用,就明确允许启用数组。

与 OpenGL ES 2.0 不同,WebGL 不允许客户端数组。

证明:

const gl = document.querySelector("canvas").getContext("webgl");

const vsUses2Attributes = `
attribute vec4 position;
attribute vec4 color;
  
varying vec4 v_color;

void main() {
  gl_Position = position;
  gl_PointSize = 20.0;
  v_color = color;
}
`;
const vsUses1Attribute = `
attribute vec4 position;
  
varying vec4 v_color;
  
void main() {
  gl_Position = position;
  gl_PointSize = 20.0;
  v_color = vec4(0,1,1,1);
}
`
const fs = `
precision mediump float;
varying vec4 v_color;

void main() {
  gl_FragColor = v_color;
}
`;

const program2Attribs = twgl.createProgram(gl, [vsUses2Attributes, fs]);
const program1Attrib  = twgl.createProgram(gl, [vsUses1Attribute, fs]);

function createBuffer(data) {
  const buf = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buf);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
  return buf;
}

const buffer3Points = createBuffer([
  -0.7, 0.5,
   0.0, 0.5,
   0.7, 0.5,
]);
const buffer3Colors = createBuffer([
  1, 0, 0, 1,
  0, 1, 0, 1,
  0, 0, 1, 1,
]);

const buffer9Points = createBuffer([
  -0.8, -0.5,
  -0.6, -0.5,
  -0.4, -0.5,
  -0.2, -0.5,
   0.0, -0.5,
   0.2, -0.5,
   0.4, -0.5,
   0.6, -0.5,
   0.8, -0.5,
]);

// set up 2 attributes
{
  const posLoc = gl.getAttribLocation(program2Attribs, 'position');
  gl.enableVertexAttribArray(posLoc);
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer3Points);
  gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);

  const colorLoc = gl.getAttribLocation(program2Attribs, 'color');
  gl.enableVertexAttribArray(colorLoc);
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer3Colors);
  gl.vertexAttribPointer(colorLoc, 4, gl.FLOAT, false, 0, 0);
}

// draw
gl.useProgram(program2Attribs);
gl.drawArrays(gl.POINTS, 0, 3);

// setup 1 attribute (don't disable the second attribute
{
  const posLoc = gl.getAttribLocation(program1Attrib, 'position');
  gl.enableVertexAttribArray(posLoc);
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer9Points);
  gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
}

// draw
gl.useProgram(program1Attrib);
gl.drawArrays(gl.POINTS, 0, 9);
const err = gl.getError();
console.log(err ? `ERROR: ${twgl.glEnumToString(gl, err)}` : 'no WebGL errors');
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<p>
1st it draws 3 points (3 vertices, 2 attributes)<br/>
2nd it draws 9 points (9 vertices, 1 attribute)<br/>
It does NOT call gl.disableVertexAttrib so on the second draw call one of the attributes is still enabled. It is pointing to a buffer with only 3 vertices in it even though 9 vertices will be drawn. There are no errors.
</p>
<canvas></canvas>

另一个例子,只需启用所有属性,然后使用不使用属性的着色器进行绘制(无错误),并使用使用 1 个属性的着色器进行绘制(再次无错误),无需调用gl.disbleVertexAttribArray

const gl = document.querySelector("canvas").getContext("webgl");

const vsUses1Attributes = `
attribute vec4 position;

void main() {
  gl_Position = position;
  gl_PointSize = 20.0;
}
`;
const vsUses0Attributes = `
void main() {
  gl_Position = vec4(0, 0, 0, 1);
  gl_PointSize = 20.0;
}
`
const fs = `
precision mediump float;
void main() {
  gl_FragColor = vec4(1, 0, 0, 1);
}
`;

const program0Attribs = twgl.createProgram(gl, [vsUses0Attributes, fs]);
const program1Attrib  = twgl.createProgram(gl, [vsUses1Attributes, fs]);

function createBuffer(data) {
  const buf = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buf);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
  return buf;
}

const buffer3Points = createBuffer([
  -0.7, 0.5,
   0.0, 0.5,
   0.7, 0.5,
]);

const buffer0Points = createBuffer([]);

// enable all the attributes and bind a buffer to them
const maxAttrib = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
for (let i = 0; i < maxAttrib; ++i) {
  gl.enableVertexAttribArray(i);
  gl.vertexAttribPointer(i, 2, gl.FLOAT, false, 0, 0);
}

gl.useProgram(program0Attribs);
gl.drawArrays(gl.POINTS, 0, 1);

gl.useProgram(program1Attrib);
const posLoc = gl.getAttribLocation(program1Attrib, 'position');
gl.bindBuffer(gl.ARRAY_BUFFER, buffer3Points);
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.POINTS, 0, 3);

const err = gl.getError();
console.log(err ? `ERROR: ${twgl.glEnumToString(gl, err)}` : 'no WebGL errors');
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<p>
1st it enables all attributes<br/>
2nd it draws 1 point that needs no attributes (no error)<br/>
3rd it draws 3 points that need 1 attribute (no error)<br/>
It does NOT call gl.disableVertexAttribArray on any of the attributes so they are all still enabled.  There are no errors.
</p>
<canvas></canvas>

于 2012-09-15T09:22:09.560 回答
3

对于 webGL,我将选择yes,调用 gl.disableVertexAttribArray 很重要。

Chrome 给了我这个警告:

WebGL: INVALID_OPERATION: drawElements: attribs not setup correctly

当程序更改为使用少于最大属性数的程序时,就会发生这种情况。显然,解决方案是在绘制之前禁用未使用的属性。

如果您的所有程序都使用相同数量的属性,那么您可能会在初始化时调用 gl.enableVertexAttribArray 一次。否则,您需要在更改程序时对其进行管理。

于 2013-11-13T06:55:49.550 回答
-2

将其视为属性是 VAO 的本地属性,而不是着色器程序。VBO 在 GPU 内存中。

现在考虑一下,在 WebGL 中,WebGL 默认使用一个默认的 VAO。(它也可以是程序员创建的 VAO,同样的概念适用)。此 VAO 包含一个名为 ARRAY_BUFFER 的目标,GPU 内存中的任何 VBO 都可以绑定到该目标。此 VAO 还包含具有固定数量的属性槽的属性数组(数量取决于实现和平台,这里假设为 8,这是 Webgl 规范要求的最小值)。此外,此 VAO 将具有 ELEMENT_ARRAY_BUFFER 目标,任何索引数据缓冲区都可以绑定到该目标。

现在,当您创建着色器程序时,它将具有您指定的属性。当您链接着色器程序时,Webgl 会将可能的属性槽“编号”之一分配给程序中指定的所有属性。现在属性将使用 VAO 中的相应属性槽来访问绑定到 VAO 中的 ARRAY_BUFFER 或 ELEMENT_ARRAY_BUFFER 目标的数据。现在,当您使用函数 gl.enableVertexAttribArray(location) 和 gl.vertexAttribPointer(location,....) 时,您不会更改着色器程序中属性的任何特征(它们只是有一个属性编号,它引用其中一个他们将用于访问数据的 VAO 中的属性槽)。您实际上正在做的是使用其位置编号修改 VAO 中属性槽的状态。因此,为了让程序中的属性能够访问数据,必须启用其在 VAO 中的相应属性槽 (gl.enableVertexAttribArray())。我们必须配置属性槽,以便它可以正确地从绑定到 ARRAY_BUFFER 的缓冲区中读取数据(gl.vertexAttribPointer())。一个 VBO 设置为一个不会改变的插槽,即使我们将它与目标解除绑定,只要它在 GPU 内存中,属性插槽 csn 仍然从 VBO 变为红色。此外,必须有一些缓冲区绑定到 VAO 的目标 (gl.bindBuffer())。所以 gl.enableVertexAttribArray(location) 将启用当前 VAO 中 'location' 指定的属性槽。gl.disableVertexAttribArray(location) 将禁用它。但它与着色器程序无关。即使您使用不同的着色器程序,

因此,如果两个不同的着色器程序使用相同的属性槽,则不会出现任何错误,因为 VAO 中对应的属性槽已经处于活动状态。但是如果需要属性在两个着色器程序中以不同方式解释数据,则可能会错误地读取来自目标的数据。现在考虑,如果两个着色器程序使用不同的属性槽,那么您可能会启用第二个着色器程序所需的属性槽,并认为您的程序应该可以工作。但是已经启用的属性槽(由之前的着色器程序启用)仍将启用但不会使用。这会导致错误。

因此,在更改着色器程序时,我们必须确保必须禁用该着色器程序不会使用的 VAO 中启用的属性槽。尽管我们现在可以显式指定任何 VAO,但 Webgl 默认情况下是这样工作的。

一种方法是在 javascript 端维护启用的属性列表,并在切换程序时禁用所有启用的属性槽,同时仍使用相同的 VAO。处理此问题的另一种方法是创建仅由一个着色器程序访问的自定义 VAO。但效率较低。还有一种方法是在使用 gl.bindAttribLocation() 链接着色器程序之前将属性位置绑定到固定槽。

于 2020-11-26T16:43:50.927 回答