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



我已经使用WebGL1一段时间了,但现在我正在学习更多关于WebGL2的知识,我对Vertex Array实际上做了什么感到困惑。例如,在下面的示例中,我可以删除对它们的所有引用(例如创建、绑定、删除(,并且该示例继续工作。

这在其他地方已经解释过了,但你可以考虑WebGL1和WebGL2都有一个顶点数组。默认情况下,WebGL1只有一个WebGL2可以创建多个顶点数组(尽管99.9%的WebGL1实现都支持它们作为扩展(

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

你可以这样想WebGL状态

class WebGLRenderingContext {
constructor() {
// 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 },
...
],
}
...

你可以认为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;
}
};

所以你可以在上面看到,用gl.ELEMENT_ARRAY_BUFFER调用gl.bindBuffer设置了当前vertexArrayelementArray部分

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

有 4 个函数可用于配置属性如何从缓冲区获取数据。gl.enableVertexAttribArraygl.disableVertexAttribArraygl.vertexAttribPointergl.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;
};

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

然后有 3 个函数将管理连接到this.vertexArray的所有状态。它们是gl.createVertexArray的,gl.bindVertexArray的和gl.deleteVertexArray的。在WebGL1中,它们在稍作重命名的OES_vertex_array_object扩展中可用。在WebGL2上,它们只是默认可用 这也是WebGL 2.0的一个特性。

调用gl.createVertexArray会创建新的顶点数组。调用gl.bindVertexArraythis.vertexArray设置为指向您传入的那个。你可以想象它是这样实现的

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

好处应该是显而易见的。在要绘制的每个内容之前,您需要设置所有属性。设置每个属性至少需要对每个使用的属性进行一次调用。更常见的是每个属性 3 次调用。对gl.bindBuffer的一次调用将缓冲区绑定到ARRAY_BUFFER,对gl.vertexAttribPointer的调用一次调用将该缓冲区绑定到特定属性并设置如何提取数据,对gl.enableVertexAttribArray调用一次以打开从属性的缓冲区获取数据。

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

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

如果你想总是使用顶点数组,你可以在WebGL1中使用此填充码。如果扩展存在,它使用内置的扩展,否则模拟它。当然,仿真速度较慢,但任何需要仿真的 GPU 可能已经太慢了。

注意 如果您正在寻找示例,可以将 https://webglfundamentals.org 上的相应示例与 https://webgl2fundamentals.org 进行比较。WebGL2站点在任何地方都使用顶点数组。您会注意到,在绘制之前的 WebGL1 示例中,对于每段顶点数据,都会绑定该数据的缓冲区,然后设置该数据的属性。在 WebGL2 示例中,它发生在初始化时而不是绘制时。在绘制时,发生的所有事情都是调用gl.bindVertexArray

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

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

最新更新