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

7 javascript webgl

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

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

gma*_*man 25

我不知道这是否会有所帮助.正如有些人所说,GL/WebGL有一堆内部状态.您调用的所有函数都会设置状态.如果它是所有设置你打电话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, 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;
     }
   };
...
}();
Run Code Online (Sandbox Code Playgroud)

之后,其他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
   };
Run Code Online (Sandbox Code Playgroud)

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

因此,您可以使用4个函数来配置属性如何从缓冲区获取数据.gl.enableVertexAttribArray,gl.disableVertexAttribArray,gl.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;
};
Run Code Online (Sandbox Code Playgroud)

现在,当您调用gl.drawArraysgl.drawElements系统知道如何从您为顶点着色器提供的缓冲区中提取数据时.请看这里是如何工作的.

由于属性是全局状态,这意味着每次调用drawElements或者drawArrays如何设置属性都是如何使用它们.如果将属性#1和#2设置为缓冲区,每个缓冲区都有3个顶点,但是要求绘制6个顶点,gl.drawArrays则会出现错误.类似地,如果你创建一个绑定到绑定点的索引缓冲区,gl.ELEMENT_ARRAY_BUFFER并且该缓冲区的indice> 2,那么你将得到该index out of range错误.如果你的缓冲区只有3个顶点,那么唯一的有效的指标是0,12.

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

你没有重新绑定缓冲区的唯一一次是你不止一次地绘制同一个东西.例如,绘制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;
 }    
Run Code Online (Sandbox Code Playgroud)

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

  • 顺序并不重要,除非一种方法依赖于另一种方法的状态设置。例如,“vertexAttribPointer”将 ARRAY_BUFFER 上的任何缓冲区附加到该属性。bufferData 将数据放入您之前绑定的任何缓冲区中。等等...直到您调用“gl.draw???”之前,不会将数据发送到着色器 (2认同)