Notes

Thoughts, write-ups, and things worth remembering.

VAO and VBO

#graphics#opengl Updated Oct 13, 2026

A VBO holds vertex data. A VAO holds no data; it stores the wiring that connects VBOs to shader inputs, so one bind restores it all.

VBO (Vertex Buffer Object)

A buffer bound to GL_ARRAY_BUFFER holding per-vertex data: positions, normals, UVs, colors.

A buffer holding indices is called an IBO or EBO (index / element buffer). Same kind of object, bound to GL_ELEMENT_ARRAY_BUFFER instead.

glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, bytes, data, GL_STATIC_DRAW);

VAO (Vertex Array Object)

While a VAO is bound, it records:

Recorded stateSet by
Which shader inputs are enabledglEnableVertexAttribArray
Which VBO each input reads, and how (components, type, stride, offset)glVertexAttribPointer
Which index buffer to useglBindBuffer(GL_ELEMENT_ARRAY_BUFFER)

Analogy: VBOs are warehouses of material; the VAO is the wiring diagram saying which pipe runs from which warehouse to which input of the machine.

Why it remembers after the binding changes

The GL_ARRAY_BUFFER slot is only a temporary cursor for setup. glVertexAttribPointer captures whatever VBO is in that slot at that moment and saves it into the VAO. Rebinding the slot afterwards does not change what the VAO saved.

Two objects, each with its own VAO:

// init
glBindVertexArray(vaoSquare);
glBindBuffer(GL_ARRAY_BUFFER, vboSquare);
glEnableVertexAttribArray(posLoc);
glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); // saves: posLoc ← vboSquare
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboSquare);                 // saves: indices ← iboSquare

glBindVertexArray(vaoTri);
glBindBuffer(GL_ARRAY_BUFFER, vboTri);                            // slot now holds vboTri
glEnableVertexAttribArray(posLoc);
glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); // saves: posLoc ← vboTri
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboTri);

// every frame
glBindVertexArray(vaoSquare);
glDrawElements(GL_TRIANGLES, squareIndexCount, GL_UNSIGNED_INT, nullptr);
glBindVertexArray(vaoTri);
glDrawElements(GL_TRIANGLES, triIndexCount, GL_UNSIGNED_INT, nullptr);

vaoSquare still reads vboSquare even though the slot ended on vboTri.

What goes where

OperationWhereWhy
Bind VBO + Enable + PointerInitWiring, saved in the VAO
Bind index bufferInitSaved in the VAO
glUseProgramEvery frameVAO does not store the program
glUniform*Every frameVAO does not store uniforms
glBindVertexArrayEvery frameSelect the object to draw
glDrawElementsEvery frameThe draw
glDisableVertexAttribArrayNot usedWould overwrite the saved wiring

Without VAOs every object redoes the full wiring before each draw. With 4 attributes and hundreds of objects that is thousands of calls per frame; with VAOs it is one bind per object.

In hw04

OpenGL 3.2 Core requires a bound VAO to draw, so initializeGL creates one and binds it once. The assignment then redoes Enable / Pointer / Disable inside drawGeometry every frame, which rewrites the same wiring into that one VAO. It works and practices the calls, but does not use what a VAO is for.

OpenGL Hub · OpenGL buffers and targets · OpenGL frame workflow · OpenGL API naming and call order