阅读视图

发现新文章,点击刷新页面。

javascript - webgl中绑定(bind)缓冲区的逻辑是什么?

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

如果在绘制数组之前不重新绑定(bind)顶点缓冲区,则控制台会提示尝试访问超出范围的顶点。我的怀疑是最后一个绑定(bind)对象在指针处传递,然后传递到drawarrays,但是当我在代码开头更改顺序时,什么都没有改变。有效的方法是在绘制循环中重新绑定(bind)缓冲区。因此,我无法真正理解其背后的逻辑。您何时需要重新绑定(bind)?为什么需要重新绑定(bind)? attribute0指的是什么?

最佳答案

我不知道这是否有帮助。就像有些人说的那样,GL/WebGL有很多内部状态。您调用的所有功能均会设置状态。完成所有设置后,您可以调用drawArraysdrawElements,所有状态都用于绘制内容

SO的其他地方对此进行了解释,但是绑定(bind)缓冲区只是在WebGL内设置2个全局变量中的1个。之后,通过缓冲区的绑定(bind)点引用该缓冲区。

你可以这样想

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
   };

与这些绑定(bind)点分开的是,有许多属性。默认情况下,属性也是全局状态。它们定义了如何从缓冲区中提取数据以提供给顶点着色器。调用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 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时,系统知道如何将数据从为提供顶点着色器而制作的缓冲区中拉出。 See here for how that works。

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

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

唯一不重新绑定(bind)缓冲区的情况是绘制同一件事的次数不止一次。例如,绘制10个多维数据集。您将重新绑定(bind)缓冲区,然后为一个立方体设置制服,绘制它,为下一个立方体设置制服,绘制它,重复。

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

调用gl.createVertexArray会使其中的一种成为新的。调用gl.bindVertexArray会将全局attributes设置为指向绑定(bind)的vertexArray中的那个。

调用gl.bindVertexArray将是

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

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

这是一个webgl state diagram,可以帮助更好地可视化它。

❌