From a low level point of view, you might think that an array has two parts:
Information about the size, shape, and type of the array (for example, 32-bit floating-point numbers containing strings of vectors with four elements each).
Array data that is slightly larger than a large number of bytes.
Despite the fact that the low-level concept has basically remained the same, then, as you point out, arrays have changed several times over the years.
OpenGL 3.0 / ARB_vertex_array_object
This is what you should probably do today. It is very rare to find people who cannot run OpenGL 3.x, but still have money spent on your software.
An OpenGL buffer object is a large bit bit. Think of the “active” buffer as a global variable, and there are many functions that use the active buffer instead of using a parameter. These global state variables are the ugly side of OpenGL (up to direct access to the state, which is described below).
GLuint buffer; // Generate a name for a new buffer. // eg buffer = 2 glGenBuffers(1, &buffer); // Make the new buffer active, creating it if necessary. // Kind of like: // if (opengl->buffers[buffer] == null) // opengl->buffers[buffer] = new Buffer() // opengl->current_array_buffer = opengl->buffers[buffer] glBindBuffer(GL_ARRAY_BUFFER, buffer); // Upload a bunch of data into the active array buffer // Kind of like: // opengl->current_array_buffer->data = new byte[sizeof(points)] // memcpy(opengl->current_array_buffer->data, points, sizeof(points)) glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);
Now your typical vertex shader accepts vertices as input rather than a large bit bit. Therefore, you need to specify how the blob bit (buffer) is decoded to the top. This is an array task. Similarly, there is an “active” array that can be thought of as a global variable:
GLuint array; // Generate a name for a new array. glGenVertexArrays(1, &array); // Make the new array active, creating it if necessary. glBindVertexArray(array); // Make the buffer the active array buffer. glBindBuffer(GL_ARRAY_BUFFER, buffer); // Attach the active buffer to the active array, // as an array of vectors with 4 floats each. // Kind of like: // opengl->current_vertex_array->attributes[attr] = { // type = GL_FLOAT, // size = 4, // data = opengl->current_array_buffer // } glVertexAttribPointer(attr, 4, GL_FLOAT, GL_FALSE, 0, 0); // Enable the vertex attribute glEnableVertexAttribArray(attr);
OpenGL 2.0 (old way)
OpenGL 2.x did not have vertex arrays, and the data was just global. You still had to call glVertexAttribPointer() and glEnableVertexAttribArray() , but you had to call them every time you used the buffer. In OpenGL 3.x, you just set up the array once.
Going back to OpenGL 1.5, you can really use buffers, but you used a separate function to bind each data type. For example, glVertexPointer() was for vertex data, and glNormalPointer() was for normal data. There were no buffers before OpenGL 1.5, but you could use pointers in your application memory.
OpenGL 4.3 / ARB_vertex_attrib_binding
In 4.3, or if you have the extension ARB_vertex_attrib_binding, you can specify the attribute format and attribute data separately. This is good because it allows you to easily switch between a single vertex array between different buffers.
GLuint array; // Generate a name for a new array array. glGenVertexArrays(1, &array); // Make the new array active, creating it if necessary. glBindVertexArray(array); // Enable my attributes glEnableVertexAttribArray(loc_attrib); glEnableVertexAttribArray(normal_attrib); glEnableVertexAttribArray(texcoord_attrib); // Set up the formats for my attributes glVertexAttribFormat(loc_attrib, 3, GL_FLOAT, GL_FALSE, 0); glVertexAttribFormat(normal_attrib, 3, GL_FLOAT, GL_FALSE, 12); glVertexAttribFormat(texcoord_attrib, 2, GL_FLOAT, GL_FALSE, 24); // Make my attributes all use binding 0 glVertexAttribBinding(loc_attrib, 0); glVertexAttribBinding(normal_attrib, 0); glVertexAttribBinding(texcoord_attrib, 0); // Quickly bind all attributes to use "buffer" // This replaces several calls to glVertexAttribPointer() // Note: you don't need to bind the buffer first! Nice! glBindVertexBuffer(0, buffer, 0, 32); // Quickly bind all attributes to use "buffer2" glBindVertexBuffer(0, buffer2, 0, 32);
OpenGL 4.5 / ARB_direct_state_access
In OpenGL 4.5, or if you have the ARB_direct_state_access extension, you no longer need to call glBindBuffer() or glBindVertexArray() just to set the settings ... you directly specify arrays and buffers. You only need to bind the array at the end to draw it.
GLuint array; // Generate a name for the array and create it. // Note that glGenVertexArrays() won't work here. glCreateVertexArrays(1, &array); // Instead of binding it, we pass it to the functions below. // Enable my attributes glEnableVertexArrayAttrib(array, loc_attrib); glEnableVertexArrayAttrib(array, normal_attrib); glEnableVertexArrayAttrib(array, texcoord_attrib); // Set up the formats for my attributes glVertexArrayAttribFormat(array, loc_attrib, 3, GL_FLOAT, GL_FALSE, 0); glVertexArrayAttribFormat(array, normal_attrib, 3, GL_FLOAT, GL_FALSE, 12); glVertexArrayAttribFormat(array, texcoord_attrib, 2, GL_FLOAT, GL_FALSE, 24); // Make my attributes all use binding 0 glVertexArrayAttribBinding(array, loc_attrib, 0); glVertexArrayAttribBinding(array, normal_attrib, 0); glVertexArrayAttribBinding(array, texcoord_attrib, 0); // Quickly bind all attributes to use "buffer" glVertexArrayVertexBuffer(array, 0, buffer, 0, 32); // Quickly bind all attributes to use "buffer2" glVertexArrayVertexBuffer(array, 0, buffer2, 0, 32); // You still have to bind the array to draw. glBindVertexArray(array); glDrawArrays(...);
ARB_direct_state_access is good for many reasons. You can forget about binding arrays and buffers (except when you draw), so you don’t need to think about hidden global variables that OpenGL keeps track of for you. You can forget about the difference between “generating a name for an object” and “creating an object” because glCreateBuffer() and glCreateArray() both perform at the same time.
Vulkan
The volcano goes even further, and you write the code like the pseudocode I wrote above. So you will see something like:
// This defines part of a "vertex array", sort of VkVertexInputAttributeDescription attrib[3]; attrib[0].location = 0; // Feed data into shader input