【问题标题】:Is it important to call glDisableVertexAttribArray()?调用 glDisableVertexAttribArray() 重要吗?
【发布时间】:2012-09-14 15:47:11
【问题描述】:

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

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

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

【问题讨论】:

    标签: webgl vertex-shader


    【解决方案1】:

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

    如果您正在使用 VAO,那么您应该禁用属性数组,因为它们被封装在 VAO 中。

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

    【讨论】:

    • 感谢指向 VAO 的指针!如果您不使用它们,危险在于如果着色器 A 有两个顶点属性数组,而着色器 B 有一个,从 A 切换到 B,B 程序仍将访问 A 的第二个数组。我有这个权利吗?
    • WebGL 不允许“全局数组”或更恰当地称为“客户端数组”。你不会崩溃,因为 WebGL 明确不允许它们。
    • @gman:我知道,但是 OP 询问了 OpenGL-ES 和 WebGL,我认为我的答案适用于两者。虽然它不会崩溃,但它仍然会引发错误情况。
    • @Grumdrig:问题是另一个问题。假设使用着色器 1,您有属性 A 和 B,每个属性都有 500 个顶点。然后切换到着色器 2,它只有属性 A,但这次绘制 1000 个顶点。如果您不禁用属性 B,OpenGL 会尝试从 B 的缓冲区中读取 1000 个顶点,如果没有取消绑定、更改或禁用,则将对 B 中仅有的 500 个属性进行越界访问。
    • @Dreamer:gman 指的不是“顶点数组对象”而是“顶点缓冲区对象”,是的,这是 WebGL 的要求。不幸的是,他的命名法已关闭。他的意思是,WebGL 不支持客户端顶点数组,即将客户端地址空间传递给 glVertexAttribPointer 函数。在 WebGL 和 OpenGL-3.3 及更高版本的核心配置文件中,您必须将顶点数据放在服务器端的顶点缓冲区对象中。他就是这个意思。
    【解决方案2】:

    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>

    【讨论】:

    • 很高兴知道!它会使用资源来启用该属性吗? (我认为 GPU 可能会进行额外的计算,虽然我不确定我会看到它是怎样的。)
    • 你的代码中的 gl.enableVertexAttribArray 在哪里?
    • ...查看 /twgljs.org/docs/ 需要一些时间
    • 不应该有错误,我的例子就是证明没有错误。此处的其他 2 个答案声称他们的意愿。他们错了。请适当地对他们投反对票。至于“客户端数组”的含义,它是指在 CPU 内存(客户端)中的顶点数据,而不是 GPU 内存(服务器)中的顶点数据。 GPU 内存由缓冲区表示。见the spec,搜索“客户”。在 OpenGL ES 中,您可以让系统直接从 CPU 内存(客户端数组)向属性提供数据。 WebGL 不允许客户端数组
    【解决方案3】:

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

    Chrome 给了我这个警告:

    WebGL: INVALID_OPERATION: drawElements: attribs not setup correctly
    

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

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

    【讨论】:

    • 我还面临另一个错误:INVALID TEXTURE,通过禁用未使用的属性以同样的方式解决。
    • 在初始化时只调用一次 gl.enableVertexAttribArray () 是有效的,但是对于学习 WebGL 的人来说:确保你在初始化时调用它。这很容易忘记。如果屏幕上没有显示任何内容,请检查您是否调用了它(这将是调试清单的一部分)。
    • 这个答案是完全错误的。无论您遇到什么问题,都与致电disableVertexAttribArray 无关。我已经在我的回答中发布了证据,证明您不需要禁用它们。我不是说你没有问题。显然你做到了。我并不是说禁用阵列​​并不能为您解决问题。相反,我是说你实际上并没有理解真正的问题。
    【解决方案4】:

    将其视为属性是 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() 链接着色器程序之前将属性位置绑定到固定槽。

    【讨论】:

    • 这是错误的。无需禁用属性。将我的回答视为证据。此外,您的答案也是错误的。 VAO 不包含 ARRAY_BUFFER 目标。 VAO 仅包含 ELEMENT_ARRAY_BUFFER 目标。 ARRAY_BUFFER 目标是全局的。
    猜你喜欢
    • 2012-07-31
    • 2018-03-31
    • 1970-01-01
    • 1970-01-01
    • 2020-06-10
    • 2010-12-07
    • 2021-08-11
    • 2011-11-20
    • 1970-01-01
    相关资源
    最近更新 更多