OpenGL和WebGL2中的顶点数组是什么?

Det*_*ned 1 opengl-es webgl webgl2

我已经使用WebGL1一段时间了,但是现在我对WebGL2有了更多的了解,我对Vertex Array实际的工作感到困惑。例如,在下面的示例中,我可以删除对其的所有引用(例如,创建,绑定,删除),并且该示例继续运行。

gma*_*man 5

这已经在其他地方进行了解释,但是您可以考虑WebGL1和WebGL2都具有顶点数组。默认情况下,这只是WebGL1,只有WebGL1可以在其中创建多个顶点数组(尽管所有WebGL1实现的99.9%都将其支持为扩展)

顶点数组是所有属性状态加上ELEMENT_ARRAY_BUFFER绑定的集合。

您可以想到这样的WebGL状态

function WebGLRenderingContext() {
   // internal WebGL state
   this.lastError: gl.NONE,
   this.arrayBuffer = null;
   this.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 },
       ...
     ],
   }
   ...
Run Code Online (Sandbox Code Playgroud)

而且您可以想到gl.bindBuffer这样实现

   // 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;
         this.arrayBuffer = buffer;
         break;
       case gl.ELEMENT_ARRAY_BUFFER;
         this.vertexArray.elementArrayBuffer = buffer;
         break;
       default:
         this.lastError = gl.INVALID_ENUM;
         break;
     }
   };
Run Code Online (Sandbox Code Playgroud)

因此,您可以在上面看到,gl.bindBuffergl.ELEMENT_ARRAY_BUFFERset 调用elementArray当前部分vertexArray

您还可以看到vertexArray具有许多属性。它们定义了如何从缓冲区中提取数据以提供给顶点着色器。调用gl.getAttribLocation(someProgram, "nameOfAttribute")告诉您顶点着色器将查看哪个属性以从缓冲区中获取数据。

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

他们已经有效地实施了这样的事情

this.enableVertexAttribArray = function(location) {
  const attribute = this.vertexArray.attributes[location];
  attribute.enabled = true;  // true means get data from attribute.buffer 
};

this.disableVertexAttribArray = function(location) {
  const attribute = this.vertexArray.attributes[location];
  attribute.enabled = false; // false means get data from attribute.value
};

this.vertexAttribPointer = function(location, size, type, normalized, stride, offset) {
  const attribute = this.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     = this.arrayBuffer;
};

this.vertexAttrib4f = function(location, x, y, z, w) {
  const attribute = this.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系统知道要如何从为提供顶点着色器而制作的缓冲区中提取数据时。看到这里如何工作

然后有3个函数将管理与相连的所有状态this.vertexArray。他们是gl.createVertexArraygl.bindVertexArraygl.deleteVertexArray。在WebGL1中,可以在OES_vertex_array_object稍微重命名的扩展上使用它们 。在WebGL2上,它们仅在默认情况下可用,这也是WebGL 2.0的功能。

调用将gl.createVertexArray创建新的顶点数组。调用gl.bindVertexArrayset this.vertexArray指向您传递的set 。您可以想象它是这样实现的

 this.bindVertexArray = function(vao) {
   this.vertexArray = vao ? vao : defaultVertexArray;
 }    
Run Code Online (Sandbox Code Playgroud)

好处应该是显而易见的。在绘制每件事之前,需要设置所有属性。设置每个属性至少需要为每个使用的属性调用一次。通常,每个属性3个调用。一个调用gl.bindBuffer将一个缓冲区绑定到ARRAY_BUFFER一个对象gl.vertexAttribPointer,然后调用一次将那个缓冲区绑定到一个特定的属性,并设置如何提取数据,一个调用gl.enableVertexAttribArray打开一个属性从缓冲区中获取数据。

对于具有9个调用的位置,法线和纹理坐标的典型模型,如果您使用索引并且需要将缓冲区绑定到,则需要再+1 ELEMENT_ARRAY_BUFFER

对于顶点数组,所有这些调用都在初始化时发生。您为要绘制的每个对象创建一个顶点数组,然后为该对象设置属性。在绘制时,只需调用一次gl.bindVertexArray即可设置所有属性和ELEMENT_ARRAY_BUFFER

如果您只想始终使用顶点数组,则可以在WebGL1中使用 polyfill。如果扩展存在或模仿它,它将使用内置的。当然,仿真速度较慢,但​​是任何需要仿真的GPU可能已经太慢了。

请注意,如果您正在寻找样品,可以将https://webglfundamentals.orghttps://webgl2fundamentals.org上的相应示例进行比较。WebGL2站点到处都使用顶点数组。您将在绘制之前的WebGL1示例中注意到,对于每一个顶点数据,都将绑定该数据的缓冲区,然后设置该数据的属性。在WebGL2示例中,它发生在初始化时间而不是绘制时间。在抽奖时间,所有发生的事情都在呼唤gl.bindVertexArray

关于顶点数组要注意的一件事是它们通常需要更多的组织。如果要使用不同的着色器程序多次绘制同一对象,则一个着色器程序可能会对同一数据使用不同的属性。换句话说,在没有额外组织的情况下,shaderprogram1可以将属性3用于位置,而将shaderprogram2可以将属性2用于位置。在那种情况下,相同的顶点数组将不适用于两个程序以获取相同的数据。

解决方案是手动分配位置。您可以在WebGL2的着色器中自己执行此操作。您也可以通过gl.bindAttribLocation在链接WebGL1和WebGL2中的每个着色器程序的着色器之前进行调用来实现。我倾向于认为使用gl.bindAttribLocation它比在GLSL中做得更好,因为它更干燥