【问题标题】:What is the logic of binding buffers in webgl?webgl中绑定缓冲区的逻辑是什么?
【发布时间】:2022-04-17 09:55:51
【问题描述】:

我有时会发现自己在以不同的顺序声明缓冲区(使用 createBuffer/bindBuffer/bufferdata)和在代码的其他部分(通常是在绘制循环中)重新绑定它们之间挣扎。

如果我在绘制数组之前没有重新绑定顶点缓冲区,控制台会抱怨尝试访问超出范围的顶点。我的怀疑是最后一个绑定对象在指针处传递,然后传递给 drawarrays 但是当我在代码开头更改顺序时,没有任何变化。有效的方法是在绘制循环中重新绑定缓冲区。所以,我无法真正理解这背后的逻辑。什么时候需要重新绑定?为什么需要重新绑定?属性0指的是什么?

【问题讨论】:

    标签: javascript webgl


    【解决方案1】:

    我不知道这是否会有所帮助。正如一些人所说,GL/WebGL 有一堆内部的state。您调用的所有函数都会设置状态。设置完成后,您调用 drawArraysdrawElements,所有这些状态都用于绘制东西

    这已在 SO 的其他地方进行了解释,但绑定缓冲区只是在 WebGL 中设置 2 个全局变量中的 1 个。之后,您可以通过绑定点引用缓冲区。

    你可以这样想

    gl = function() {
       // internal WebGL state
       let lastError;
       let arrayBuffer = null;
       let vertexArray = {
         elementArrayBuffer: null,
         attributes: [
           { enabled: false, type: gl.FLOAT, size: 3, normalized: false, 
             stride: 0, offset: 0, buffer: null },
           { enabled: false, type: gl.FLOAT, size: 3, normalized: false, 
             stride: 0, offset: 0, buffer: null },
           { enabled: false, type: gl.FLOAT, size: 3, normalized: false, 
             stride: 0, offset: 0, buffer: null },
           { enabled: false, type: gl.FLOAT, size: 3, normalized: false, 
             stride: 0, offset: 0, buffer: null },
           { enabled: false, type: gl.FLOAT, size: 3, normalized: false, 
             stride: 0, offset: 0, buffer: null },
           ...
         ],
       }
       // these values are used when a vertex attrib is disabled
       let attribValues = [
         [0, 0, 0, 1],
         [0, 0, 0, 1],
         [0, 0, 0, 1],
         [0, 0, 0, 1],
         [0, 0, 0, 1],
         ...
       ];
       ...
    
       // Implementation of gl.bindBuffer. 
       // note this function is doing nothing but setting 2 internal variables.
       this.bindBuffer = function(bindPoint, buffer) {
         switch(bindPoint) {
           case gl.ARRAY_BUFFER;
             arrayBuffer = buffer;
             break;
           case gl.ELEMENT_ARRAY_BUFFER;
             vertexArray.elementArrayBuffer = buffer;
             break;
           default:
             lastError = gl.INVALID_ENUM;
             break;
         }
       };
    ...
    }();
    

    之后,其他 WebGL 函数会引用这些函数。例如gl.bufferData 可能会做类似的事情

       // implementation of gl.bufferData
       // Notice you don't pass in a buffer. You pass in a bindPoint. 
       // The function gets the buffer one of its internal variable you set by
       // previously calling gl.bindBuffer
    
       this.bufferData = function(bindPoint, data, usage) {
    
         // lookup the buffer from the bindPoint
         var buffer;
         switch (bindPoint) {
           case gl.ARRAY_BUFFER;
             buffer = arrayBuffer;
             break;
           case gl.ELEMENT_ARRAY_BUFFER;
             buffer = vertexArray.elemenArrayBuffer;
             break;
           default:
             lastError = gl.INVALID_ENUM;
             break;
          }
    
          // copy data into buffer
          buffer.copyData(data);  // just making this up
          buffer.setUsage(usage); // just making this up
       };
    

    与这些绑定点分开,有许多属性。默认情况下,这些属性也是全局状态。它们定义了如何从缓冲区中提取数据以提供给顶点着色器。调用 gl.getAttribLocation(someProgram, "nameOfAttribute") 会告诉您顶点着色器将查看哪个属性以从缓冲区中获取数据。

    因此,您可以使用 4 个函数来配置属性如何从缓冲区获取数据。 gl.enableVertexAttribArraygl.disableVertexAttribArraygl.vertexAttribPointergl.vertexAttrib??

    他们有效地实现了这样的东西

    this.enableVertexAttribArray = function(location) {
      const attribute = vertexArray.attributes[location];
      attribute.enabled = true;  // true means get data from attribute.buffer 
    };
    
    this.disableVertexAttribArray = function(location) {
      const attribute = vertexArray.attributes[location];
      attribute.enabled = false; // false means get data from attribValues[location]
    };
    
    this.vertexAttribPointer = function(location, size, type, normalized, stride, offset) {
      const attribute = vertexArray.attributes[location];
      attribute.size       = size;       // num values to pull from buffer per vertex shader iteration
      attribute.type       = type;       // type of values to pull from buffer
      attribute.normalized = normalized; // whether or not to normalize
      attribute.stride     = stride;     // number of bytes to advance for each iteration of the vertex shader. 0 = compute from type, size
      attribute.offset     = offset;     // where to start in buffer.
    
      // IMPORTANT!!! Associates whatever buffer is currently *bound* to 
      // "arrayBuffer" to this attribute
      attribute.buffer     = arrayBuffer;
    };
    
    this.vertexAttrib4f = function(location, x, y, z, w) {
      const attrivValue = attribValues[location];
      attribValue[0] = x;
      attribValue[1] = y;
      attribValue[2] = z;
      attribValue[3] = w;
    };
    

    现在,当您调用gl.drawArraysgl.drawElements 时,系统知道您要如何从缓冲区中提取数据以提供顶点着色器。 See here for how that works.

    由于属性是全局状态,这意味着每次您调用drawElementsdrawArrays 时,您如何设置属性就是如何使用它们。如果您将属性#1 和#2 设置为每个具有3 个顶点的缓冲区,但您要求使用gl.drawArrays 绘制6 个顶点,您将收到错误消息。同样,如果您创建一个绑定到gl.ELEMENT_ARRAY_BUFFER 绑定点的索引缓冲区,并且该缓冲区的索引> 2,您将收到index out of range 错误。如果您的缓冲区只有 3 个顶点,那么唯一有效的索引是 012

    通常,每次绘制不同的东西时,都会重新绑定绘制该东西所需的所有属性。绘制一个有位置和法线的立方体?将缓冲区与位置数据绑定,设置用于位置的属性,将缓冲区与法线数据绑定,设置用于法线的属性,现在进行绘制。接下来绘制一个带有位置、顶点颜色和纹理坐标的球体。绑定包含位置数据的缓冲区,设置用于位置的属性。绑定包含顶点颜色数据的缓冲区,设置用于顶点颜色的属性。绑定包含纹理坐标的缓冲区,设置纹理坐标使用的属性。

    唯一不重新绑定缓冲区的情况是,如果您多次绘制相同的东西。例如绘制 10 个立方体。您将重新绑定缓冲区,然后为一个立方体设置制服,绘制它,为下一个立方体设置制服,绘制它,重复。

    我还应该补充一点,还有一个扩展名 [OES_vertex_array_object],这也是 WebGL 2.0 的一个特性。顶点数组对象是上面称为vertexArray 的全局状态,其中包括elementArrayBuffer 和所有属性。

    致电gl.createVertexArray 会成为其中之一。调用gl.bindVertexArray 将全局attributes 设置为指向绑定vertexArray 中的那个。

    然后调用gl.bindVertexArray

     this.bindVertexArray = function(vao) {
       vertexArray = vao ? vao : defaultVertexArray;
     }    
    

    这样做的好处是让您可以在初始化时设置所有属性和缓冲区,然后在绘制时只需 1 次 WebGL 调用即可设置所有缓冲区和属性。

    这是一个webgl state diagram,它可能有助于更好地形象化。

    【讨论】:

    • 这说明了很多。我只是想知道另外一件事。我试图通过单独的缓冲区加载不同的网格,这让我明白了你的观点----“如果你将属性 #1 和 #2 设置为缓冲区,每个缓冲区都有 3 个顶点,但你要求用 gl 绘制 6 个顶点。 drawArrays 你会得到一个错误“----这是否意味着如果我有单独的缓冲区,对于每个几何图形(我不知道这是否是最佳实践)我必须多次调用 drawxxx?
    • 每次你想画的时候,你要为每一个你想画的东西调用一次drawXXX。您使用多少缓冲区取决于您。大多数 WebGL 应用程序对每个网格的每种数据类型使用一个缓冲区。因此,如果一个立方体需要位置、法线和纹理坐标,它将使用 3 个缓冲区。如果你有一个带有位置和顶点颜色的球体。那将使用 2 个缓冲区。您可以将事物组合到同一个缓冲区中,但它更复杂,通常不值得努力。
    • 哇,谢谢。 ;) 我喜欢你如何使用 JS 将其分解为伪代码,这使得很多事情在开发人员层面上变得非常清晰。就像一张图片说一千个字,我得到的只是关于小细节的更多问题,某些点并没有真正连接 - 就像现在看到 'vertexAttribPointer ' 是如何工作的,我现在可以看到我必须首先绑定数组缓冲区。 ;)
    • 顺序重要吗?好的 bindbuffer 在第一个位置,但是:bufferdata,enableVertexAttribArray 和 vertexAttribPointer ? (换句话说,数据什么时候刷新到着色器中?)
    • 顺序并不重要,除非一种方法依赖于另一种方法的状态设置。例如,vertexAttribPointer 将 ARRAY_BUFFER 上的任何缓冲区附加到该属性。 bufferData 将数据放入您之前绑定的任何缓冲区中。等等...在您调用 gl.draw??? 之前,不会将数据发送到着色器
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-13
    • 2017-12-11
    • 1970-01-01
    • 2020-04-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多