简体   繁体   English

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

[英]What is the logic of binding buffers in webgl?

I sometimes find myself struggling between declaring the buffers (with createBuffer/bindBuffer/bufferdata) in different order and rebinding them in other parts of the code, usually in the draw loop.我有时发现自己在以不同的顺序声明缓冲区(使用 createBuffer/bindBuffer/bufferdata)和在代码的其他部分重新绑定它们之间挣扎,通常是在绘制循环中。

If I don't rebind the vertex buffer before drawing arrays, the console complains about an attempt to access out of range vertices.如果我在绘制数组之前不重新绑定顶点缓冲区,控制台会抱怨试图访问超出范围的顶点。 My suspect is the the last bound object is passed at the pointer and then to the drawarrays but when I change the order at the beginning of the code, nothing changes.我怀疑是最后一个绑定对象在指针处传递,然后传递给 drawarrays 但是当我在代码的开头更改顺序时,没有任何变化。 What effectively works is rebinding the buffer in the draw loop.有效的方法是重新绑定绘制循环中的缓冲区。 So, I can't really understand the logic behind that.所以,我真的无法理解这背后的逻辑。 When do you need to rebind?什么时候需要重新绑定? Why do you need to rebind?为什么需要重新绑定? What is attribute0 referring to? attribute0 指的是什么?

I don't know if this will help.我不知道这是否会有所帮助。 As some people have said, GL/WebGL has a bunch of internal state .正如一些人所说,GL/WebGL 有一堆内部状态 All the functions you call set up the state.您调用的所有函数都设置了状态。 When it's all setup you call drawArrays or drawElements and all of that state is used to draw things当所有设置完成后,您调用drawArraysdrawElements并且所有这些状态都用于绘制事物

This has been explained elsewhere on SO but binding a buffer is just setting 1 of 2 global variables inside WebGL.这已在 SO 的其他地方进行了解释,但绑定缓冲区只是在 WebGL 中设置 2 个全局变量之一。 After that you refer to the buffer by its bind point.之后,您通过其绑定点引用缓冲区。

You can think of it like this你可以这样想

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;
     }
   };
...
}();

After that other WebGL functions reference those.之后其他 WebGL 函数引用这些函数。 For example gl.bufferData might do something like例如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
   };

Separate from those bindpoints there's number of attributes.除了这些绑定点之外,还有许多属性。 The attributes are also global state by default.默认情况下,这些属性也是全局状态。 They define how to pull data out of the buffers to supply to your vertex shader.它们定义了如何从缓冲区中提取数据以提供给您的顶点着色器。 Calling gl.getAttribLocation(someProgram, "nameOfAttribute") tells you which attribute the vertex shader will look at to get data out of a buffer.调用gl.getAttribLocation(someProgram, "nameOfAttribute")告诉您顶点着色器将查看哪个属性以从缓冲区中获取数据。

So, there's 4 functions that you use to configure how an attribute will get data from a buffer.因此,您可以使用 4 个函数来配置属性如何从缓冲区获取数据。 gl.enableVertexAttribArray , gl.disableVertexAttribArray , gl.vertexAttribPointer , and gl.vertexAttrib?? gl.enableVertexAttribArraygl.disableVertexAttribArraygl.vertexAttribPointergl.vertexAttrib?? . .

They're effectively implemented something like this他们有效地实施了这样的事情

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

Now, when you call gl.drawArrays or gl.drawElements the system knows how you want to pull data out of the buffers you made to supply your vertex shader.现在,当您调用gl.drawArraysgl.drawElements ,系统知道您希望如何从为提供顶点着色器而制作的缓冲区中提取数据。 See here for how that works . 请参阅此处了解其工作原理

Since the attributes are global state that means every time you call drawElements or drawArrays how ever you have the attributes setup is how they'll be used.由于属性是全局状态,这意味着每次调用drawElementsdrawArrays如何设置属性就是如何使用它们。 If you set up attributes #1 and #2 to buffers that each have 3 vertices but you ask to draw 6 vertices with gl.drawArrays you'll get an error.如果您将属性 #1 和 #2 设置为每个都有 3 个顶点的缓冲区,但您要求使用gl.drawArrays绘制 6 个顶点, gl.drawArrays出现错误。 Similarly if you make an index buffer which you bind to the gl.ELEMENT_ARRAY_BUFFER bindpoint and that buffer has an indice that is > 2 you'll get that index out of range error.类似地,如果您创建一个索引缓冲区并将其绑定到gl.ELEMENT_ARRAY_BUFFER绑定点,并且该缓冲区的index out of range > 2,您将得到该index out of range错误。 If your buffers only have 3 vertices then the only valid indices are 0 , 1 , and 2 .如果您的缓冲区只有 3 个顶点,那么唯一有效的索引是012

Normally, every time you draw something different you rebind all the attributes needed to draw that thing.通常,每次绘制不同的东西时,都会重新绑定绘制该东西所需的所有属性。 Drawing a cube that has positions and normals?绘制具有位置和法线的立方体? Bind the buffer with position data, setup the attribute being used for positions, bind the buffer with normal data, setup the attribute being used for normals, now draw.将缓冲区与位置数据绑定,设置用于位置的属性,将缓冲区与普通数据绑定,设置用于法线的属性,现在绘制。 Next you draw a sphere with positions, vertex colors and texture coordinates.接下来绘制一个带有位置、顶点颜色和纹理坐标的球体。 Bind the buffer that contains position data, setup the attribute being used for positions.绑定包含位置数据的缓冲区,设置用于位置的属性。 Bind the buffer that contains vertex color data, setup the attribute being used for vertex colors.绑定包含顶点颜色数据的缓冲区,设置用于顶点颜色的属性。 Bind the buffer that contains texture coordinates, setup the attribute being used for texture coordinates.绑定包含纹理坐标的缓冲区,设置用于纹理坐标的属性。

The only time you don't rebind buffers is if you're drawing the same thing more than once.唯一不重新绑定缓冲区的情况是您多次绘制相同的东西。 For example drawing 10 cubes.例如绘制 10 个立方体。 You'd rebind the buffers, then set the uniforms for one cube, draw it, set the uniforms for the next cube, draw it, repeat.您将重新绑定缓冲区,然后为一个立方体设置制服,绘制它,为下一个立方体设置制服,绘制它,重复。

I should also add that there's an extension [ OES_vertex_array_object ] which is also a feature of WebGL 2.0.我还应该补充一点,有一个扩展 [ OES_vertex_array_object ] 这也是 WebGL 2.0 的一个特性。 A Vertex Array Object is the global state above called vertexArray which includes the elementArrayBuffer and all the attributes.顶点数组对象是上面称为vertexArray的全局状态,它包括elementArrayBuffer和所有属性。

Calling gl.createVertexArray makes new one of those.调用gl.createVertexArray使新的其中之一。 Calling gl.bindVertexArray sets the global attributes to point to the one in the bound vertexArray.调用gl.bindVertexArray将全局attributes为指向绑定的 vertexArray 中的一个。

Calling gl.bindVertexArray would then be然后调用gl.bindVertexArray

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

This has the advantage of letting you set up all attributes and buffers at init time and then at draw time just 1 WebGL call will set all buffers and attributes.这样做的好处是让您在初始化时设置所有属性和缓冲区,然后在绘制时只需 1 个 WebGL 调用即可设置所有缓冲区和属性。

Here is a webgl state diagram that might help visualize this better.这是一个webgl 状态图,可能有助于更好地可视化。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM