webgl中绑定缓冲区的逻辑是什么?

IT技术 javascript webgl
2021-02-28 09:15:51

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

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

1个回答

我不知道这是否会有所帮助。正如一些人所说,GL/WebGL 有一堆内部状态您调用的所有函数都设置了状态。当所有设置完成后,您调用drawArraysordrawElements并且所有该状态都用于绘制事物

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

你可以这样想

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, value: [0, 0, 0, 1], buffer: null },
       { enabled: false, type: gl.FLOAT, size: 3, normalized: false, 
         stride: 0, offset: 0, value: [0, 0, 0, 1], buffer: null },
       { enabled: false, type: gl.FLOAT, size: 3, normalized: false, 
         stride: 0, offset: 0, value: [0, 0, 0, 1], buffer: null },
       { enabled: false, type: gl.FLOAT, size: 3, normalized: false, 
         stride: 0, offset: 0, value: [0, 0, 0, 1], buffer: null },
       { enabled: false, type: gl.FLOAT, size: 3, normalized: false, 
         stride: 0, offset: 0, value: [0, 0, 0, 1], buffer: null },
       ...
     ],
   }
   ...

   // 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.vertexAttribPointer、 和gl.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 attribute.value
};

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 attribute = vertexArray.attributes[location];
  attribute.value[0] = x;
  attribute.value[1] = y;
  attribute.value[2] = z;
  attribute.value[3] = w;
};

现在,当您调用gl.drawArraysgl.drawElements系统知道您希望如何从您为提供顶点着色器而制作的缓冲区中提取数据时。请参阅此处了解其工作原理

由于属性是全局状态,这意味着每次调用drawElementsdrawArrays设置属性的方式都是使用它们的方式。如果您将属性 #1 和 #2 设置为每个都有 3 个顶点的缓冲区,但您要求绘制 6 个顶点,gl.drawArrays则会出现错误。同样,如果您创建一个索引缓冲区并将其绑定到绑定点,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 状态图,可能有助于更好地可视化。

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