我有时会发现自己在以不同顺序声明缓冲区(使用createBuffer / bindBuffer / bufferdata)并在代码的其他部分重新绑定它们之间挣扎,通常是在绘制循环中。
如果我在绘制数组之前没有重新绑定顶点缓冲区,控制台会抱怨尝试访问范围顶点。我怀疑是最后一个绑定对象是在指针处传递然后传递给drawarrays但是当我在代码开头更改顺序时,没有任何变化。有效的方法是在绘制循环中重新绑定缓冲区。所以,我无法真正理解背后的逻辑。你什么时候需要重新绑定?你为什么需要重新绑定?什么是attribute0指的是什么?
答案 0 :(得分:22)
我不知道这是否会有所帮助。正如有些人所说,GL / WebGL有一堆内部状态。您调用的所有函数都会设置状态。完成所有设置后,您可以调用drawArrays
或drawElements
,并且所有状态都用于绘制内容
这在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;
}
};
...
}();
之后其他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
};
与那些绑定点分开的属性数量。默认情况下,属性也是全局状态。它们定义了如何从缓冲区中提取数据以提供给顶点着色器。调用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;
};
现在,当您调用gl.drawArrays
或gl.drawElements
时,系统会知道您希望如何从您为顶点着色器提供的缓冲区中提取数据。 See here for how that works
由于属性为全局状态,这意味着每次调用drawElements
或drawArrays
时,如何设置属性就是如何使用它们。如果将属性#1和#2设置为缓冲区,每个缓冲区都有3个顶点,但是要求用gl.drawArrays
绘制6个顶点,则会出现错误。类似地,如果您创建一个绑定到gl.ELEMENT_ARRAY_BUFFER
绑定点的索引缓冲区,并且该缓冲区的指标是> 2您将收到index out of range
错误。如果您的缓冲区只有3个顶点,那么唯一有效的索引是0
,1
和2
。
通常,每次绘制不同的东西时,都会重新绘制绘制该东西所需的所有属性。绘制一个有位置和法线的立方体?使用位置数据绑定缓冲区,设置用于位置的属性,使用普通数据绑定缓冲区,设置用于法线的属性,现在绘制。接下来,绘制具有位置,顶点颜色和纹理坐标的球体。绑定包含位置数据的缓冲区,设置用于位置的属性。绑定包含顶点颜色数据的缓冲区,设置用于顶点颜色的属性。绑定包含纹理坐标的缓冲区,设置用于纹理坐标的属性。
你唯一没有重新绑定缓冲区的时间就是你不止一次地绘制同一个东西。例如,绘制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;
}
这样做的好处是可以让你在初始时设置所有属性和缓冲区,然后在绘制时只需1个WebGL调用就可以设置所有缓冲区和属性。