I've been working with WebGL1 for some time but now that I'm learning more about WebGL2, I'm confused what Vertex Array
s actually do. For example, in the following example, I can remove all references to them (e.g. creation, binding, deletion) and the example continues to work.
1 Answers
This has been explained elsewhere but you can consider both WebGL1 and WebGL2 to have a vertex array. It's just WebGL1 by default only has one where as WebGL2 you can create multiple vertex arrays (although 99.9% of all WebGL1 implementations support them as an extension)
A Vertex Array is the collection of all attribute state plus the ELEMENT_ARRAY_BUFFER
binding.
You can think of WebGL state like this
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 },
...
],
}
...
And you can think of gl.bindBuffer
as implemented like this
// 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;
}
};
So you can see above, calling gl.bindBuffer
with gl.ELEMENT_ARRAY_BUFFER
sets the elementArray
part of the current vertexArray
You can also see vertexArray
has a number of attributes. 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.
There's 4 functions that you use to configure how an attribute will get data from a buffer. gl.enableVertexAttribArray
, gl.disableVertexAttribArray
, gl.vertexAttribPointer
, and gl.vertexAttrib??
.
They're effectively implemented something like this
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;
};
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. See here for how that works.
There's then 3 functions that will manage all state connected to this.vertexArray
. They are gl.createVertexArray
, gl.bindVertexArray
and gl.deleteVertexArray
. In WebGL1 they are available on the OES_vertex_array_object
extension slightly renamed. On WebGL2 they are just available by default
which is also a feature of WebGL 2.0.
Calling gl.createVertexArray
makes new vertex array. Calling gl.bindVertexArray
sets this.vertexArray
to point to the one you pass in. You can imagine it implemented like this
this.bindVertexArray = function(vao) {
this.vertexArray = vao ? vao : defaultVertexArray;
}
The benefit should be obvious. Before each thing you want to draw you need to set all the attributes. Setting each attribute requires a minimum of one call per used attribute. More commonly 3 calls per attribute. One call to gl.bindBuffer
to bind a buffer to ARRAY_BUFFER
and one call to gl.vertexAttribPointer
to then bind that buffer to a specific attribute and set how to pull data out and one call to gl.enableVertexAttribArray
to turn on getting data from a buffer for the attribute.
For a typical model with positions, normals, and texture coordinates that's 9 calls, +1 more if you're using indices and need to bind a buffer to ELEMENT_ARRAY_BUFFER
.
With vertex arrays all those calls happen at init time. You make a vertex array for each thing you want to draw then setup the attributes for that thing. At draw time it then only takes one call to gl.bindVertexArray
to setup all the attributes and the ELEMENT_ARRAY_BUFFER
.
If you'd like to just always use vertex arrays you can use this polyfill in WebGL1. It uses the built in one if the extension exists or else emulates it. Of course emulation is slower but any GPU that would need the emulation is probably already too slow.
note if you're looking for samples maybe compare the corresponding examples on https://webglfundamentals.org to https://webgl2fundamentals.org. The WebGL2 site uses vertex arrays everywhere. You'll notice in the WebGL1 examples just before drawing, for each piece of vertex data the buffer for that data is bound and then the attribute for that data is setup. In the WebGL2 examples that happens at init time instead of draw time. At draw time all that happens is calling gl.bindVertexArray
One extra thing about vertex arrays to be aware of is that they generally require more organization. If you're going to draw the same object more than once with different shader programs then it's possible one shader program will use different attributes for the same data. In other words, with no extra organization shaderprogram1 might use attribute 3 for position where as shaderprogram2 might attribute 2 for position. In that case the same vertex array would not work with both programs for the same data.
The solution is to manually assign the locations. You can do this in the shaders themselves in WebGL2. You can also do it by calling gl.bindAttribLocation
before linking the shaders for each shaderprogram in both WebGL1 and WebGL2. I tend to think using gl.bindAttribLocation
is better than doing it in GLSL because it's more D.R.Y.

- 100,619
- 31
- 269
- 393
-
Are there other resources that explain these concepts as well as you did? Explaining the low level functionality, especially in terms of JS, is extremely useful. – Detuned May 09 '18 at 18:53
-
1Here is a [webgl state diagram](https://webglfundamentals.org/webgl/lessons/resources/webgl-state-diagram.html) and a [webgl2 state diagram](https://webgl2fundamentals.org/webgl/lessons/resources/webgl-state-diagram.html) – gman Feb 23 '20 at 17:27
-
this.enableVertexAttribArray put attribute.enabled=true. this.vertexAttribPointer put other attribute value, (attribute.size, .type, ...) but attribute.enabled. Why ? – oceanGermanique Oct 22 '20 at 13:16
-
1because whoever designed the API made it that way. It make some sense though. enabled = false means the vertex attribute gets its value from attribute.value (set with `gl.vertexAttrib(1-4)f`). enabled = true means the vertex attribute gets its values from a buffer specified with `gl.vertexAttribPointer`. – gman Oct 22 '20 at 17:27
-
ok i understand now!! in the examples of your WebGL State diagram there is no disableVertexAttribArray. ;) :) https://webglfundamentals.org/webgl/lessons/resources/webgl-state-diagram.html – oceanGermanique Oct 23 '20 at 08:02
-
1@PhilippeOceangermanique??? https://i.imgur.com/wOvrKnK.png – gman Oct 23 '20 at 08:11
-
@gman , is it possible to edit (change) the code of the examples in your webgl_state_diagram ? – oceanGermanique Oct 26 '20 at 19:25
-
1You should probably ask this on github. The short answer is you can download the source and try. JS has no way to step through lines of code so it just works by splitting by semicolon. (vs adding an entire javascript parser). Further there is no way to keep a stack so it can't step through functions or loops (unless again you parsed the js yourself and maybe emitted generator versions of the code). You could try to turn it into a devtools extension but I have not and besides it doesn't handle 100% of state. – gman Oct 27 '20 at 02:39
-
I did not understand well (my English is not good enough). This is the source? : https://github.com/gfxfundamentals/webgl-fundamentals/blob/master/webgl/lessons/resources/webgl-state-diagram.html – oceanGermanique Oct 27 '20 at 12:45
-
1Yes, that's the source – gman Oct 27 '20 at 13:07
-
have you the pseudo-code for gl.framebufferTexture2D ? – oceanGermanique Nov 08 '20 at 15:16
-
1You can imagine the pseudo code if you look at the state diagram and pick [an example that uses a framebuffer](https://webglfundamentals.org/webgl/lessons/resources/webgl-state-diagram.html?exampleId=draw-cube-on-cube#no-help) but basically `framebufferTexture2D(target, attachment, textureTarget, texture, level) { fb = context[target]; fb.attacments[attachment] = {texture, textureTarget, level}; )` – gman Nov 08 '20 at 15:55
-
in the state diagram where is the "context" array ? – oceanGermanique Nov 09 '20 at 19:01
-
1`context` in the pseudo code above isn't an array, it's an object of all global state. You'd normally call `bindFramebuffer(gl.FRAMEBUFFER, someFramebuffer)` which could be implemented as `bindFramebuffer(target, framebuffer) { context[target] = fb; }`. It would show up in the diagram in global state under `FRAMEBUFFER_BINDING` – gman Nov 10 '20 at 04:24
-
is that correct ? : class WebGLRenderingContext { constructor() { .... this.framebufferBinding = null; ..... } .... this.bindFramebuffer(target, framebuffer) { framebuffer = framebuffer || defaultFramebufferForCanvas; // if null use canvas switch (target) { case: gl.FRAMEBUFFER: this.framebufferBinding = framebuffer; break; default: ... error ... } } } – oceanGermanique Nov 25 '20 at 21:26
-
@gman, in my local webgl_state_diagram,I did not succeed in implanting requestAnimationFrame (and any other function : const f=function() {...} ). Why ? – oceanGermanique Dec 01 '20 at 20:41