属性

属性是全局状态(*)。如果它们是用 JavaScript 实现的,那么它们看起来就像这样

 // pseudo code
 gl = {
   ARRAY_BUFFER: null,
   vertexArray: {
     attributes: [
       { enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
       { enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
       { enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
       { enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
       { enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
       { enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
       { enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
       { enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
     ],
     ELEMENT_ARRAY_BUFFER: null,
   },
 }

如上所示,有 8 属性,它们是全局状态。

当你调用 gl.enableVertexAttribArray(location)gl.disableVertexAttribArray 时,你可以这样想

// pseudo code
gl.enableVertexAttribArray = function(location) {
  gl.vertexArray.attributes[location].enable = true;
};

gl.disableVertexAttribArray = function(location) {
  gl.vertexArray.attributes[location].enable = false;
};

换句话说,location 直接引用属性的索引。

同样地,gl.vertexAttribPointer 将实现类似的东西

// pseudo code
gl.vertexAttribPointer = function(location, size, type, normalize, stride, offset) {
  var attrib = gl.vertexArray.attributes[location];
  attrib.size = size;
  attrib.type = type;
  attrib.normalize = normalize;
  attrib.stride = stride ? stride : sizeof(type) * size;
  attrib.offset = offset;
  attrib.buffer = gl.ARRAY_BUFFER;  // !!!! <-----
};

请注意,attrib.buffer 设置为当前 gl.ARRAY_BUFFER 设置的任何值。gl.ARRAY_BUFFER 是通过调用 gl.bindBuffer(gl.ARRAY_BUFFER, someBuffer) 设置的。

所以,接下来我们有顶点着色器。在顶点着色器中,你声明属性。例

attribute vec4 position;
attribute vec2 texcoord;
attribute vec3 normal;

...

void main() {
  ...
}

通过调用 gl.linkProgram(someProgram) 链接顶点着色器和片段着色器 WebGL(驱动程序/ GPU /浏览器)决定自己使用哪个索引/位置用于每个属性。你不知道他们会选择哪一个。这是浏览器/驱动程序/ GPU。所以,你必须问它你用于 positiontexcoordnormal 的属性是什么?。你可以通过拨打 gl.getAttribLocation 来做到这一点

var positionLoc = gl.getAttribLocation(program, "position");
var texcoordLoc = gl.getAttribLocation(program, "texcoord");
var normalLoc = gl.getAttribLocation(program, "normal");

让我们说 positionLoc = 5。这意味着当顶点着色器执行时(当你调用 gl.drawArraysgl.drawElements 时),顶点着色器希望你具有正确的 typesizeoffsetstridebuffer 等的 setup 属性 5。

请注意,链接程序之前,你可以通过调用 gl.bindAttribLoction(program, location, nameOfAttribute) 来选择位置。例:

// Tell `gl.linkProgram` to assign `position` to use attribute #7
gl.bindAttribLocation(program, 7, "position");

完整的属性状态

缺少上述描述是每个属性也有一个默认值。它被遗漏在上面,因为使用它并不常见。

attributes: [
   { enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?
     value: [0, 0, 0, 1], },
   { enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?
     value: [0, 0, 0, 1], },
   ..

你可以使用各种 gl.vertexAttribXXX 功能设置该值。当 enablefalse 时使用 value。当 enable 为 true 时,属性的数据将从指定的 buffer 中提取。

顶点数组对象

WebGL 有一个扩展名 OES_vertex_array_object

在上图中,OES_vertex_array_object 允许你创建和替换 vertexArray。换一种说法

var vao = ext.createVertexArrayOES();

在上面的伪代码中创建你看到附加到 gl.vertexArray 的对象。调用 ext.bindVertexArrayOES(vao) 将创建的顶点数组对象指定为当前顶点数组。

// pseudo code
ext.bindVertexArrayOES = function(vao) {
  gl.vertexArray = vao;
}

这样你就可以在当前的 VAO 中设置所有属性和 ELEMENT_ARRAY_BUFFER,这样当你想要绘制一个叫 ext.bindVertexArrayOES 的地方时,如果没有扩展名,那么每个属性最多可以调用一次 gl.bindBuffer gl.vertexAttribPointer(可能还有 gl.enableVertexAttribArray)。