Vertex Arrays Objects allow us to send large meshes to the GPU. It is a bit technical, but this is something we only have to do once.

*This post is part of the OpenGL 2D Facade series*

## Mesh data

In the previous post, we created three Numpy arrays:

`vertices`

: a [verticeCount, 2] float array with vertex coordinates;`colors`

: a [verticeCount, 3] float array with vertex colors;`faces`

: a [faceCount, 3] unsigned int array with vertex indices for each face:`faces[f, i]`

is the index of vertex`i`

of face`f`

.

That’s all we need to make our scene; all that follows presents the OpenGL API which creates the structures.

As I explained previously, I go straight to Vertex Array Objects (VAOs) because it is very efficient compared to drawing using OpenGL direct calls. It is more complicated, but we will “hide” this complexity behind a facade. This facade will have a simple API for 2D games while providing fast rendering.

## Vertex Array Object

To embed all our data, we must first create a Vertex Array Object (VAO):

vaoId = glGenVertexArrays(1)

The argument of the `glGenVertexArrays()`

is the number of VAO we want: in this example, only one.

We tell that we want to work on this new VAO:

glBindVertexArray(vaoId)

Remember that OpenGL is a state machine; every operation depends on the current context.

VAOs can contain one or more Vertex Buffer Object (VBO). In our example, we need three VBOs:

vboIds = glGenBuffers(3) vertexVboId = vboIds[0] colorVboId = vboIds[1] indexVboId = vboIds[2]

The `glGenBuffers()`

function with argument 3 returns an array with three VBO ids. I create three variables `vertexVboId`

, `colorVboId`

and `indexVboId`

to store those ids.

## Vertices VBO

Let’s now define the VBO that contains all vertex coordinates:

glBindBuffer(GL_ARRAY_BUFFER, vertexVboId)

The `glBindBuffer()`

function tells that the next VBO operations will be on `vertexVboId`

. Furthermore, the first argument `GL_ARRAY_BUFFER`

tells that this VBO contains vertex attributes. In this case, it includes the vertex coordinates, the most usual attribute.

The next lines send the data inside the Numpy array to the VBO:

vertices = np.ascontiguousarray(vertices.flatten()) glBufferData(GL_ARRAY_BUFFER, 4 * len(vertices), vertices, GL_STATIC_DRAW)

The first line builds a 1D array, so all values are contiguous, as in a buffer. There is a high chance that Numpy already stores data in this format, but we need to call these functions to be 100% sure of it.

The first argument of `glBufferData()`

is consistent with the VBO format.

The second argument is the size of the data in bytes. Since float needs 4 bytes, the total size of the buffer is four times the number of values in the array.

The third argument is the Numpy array with all values.

The last argument is a hint for OpenGL. `GL_STATIC_DRAW`

means that we don’t plan to update the data. Then, the GPU can enable optimizations based on this assumption.

Finally, we set the attributes for this VBO:

glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, None)

The first argument defines an index for this VBO. A usual setup is to set the first VBO with index 0, the second one with index 1, and so on.

The second argument defines the number of values in each vertex. We are working with 2D vertices, so there are two coordinates per vertex.

The third argument defines the type of values. In our case, we use 32-bit float numbers.

The fourth argument tells if our values should be normalized. We don’t need this feature in our example, so we set `GL_FALSE`

.

The fifth argument defines the stride. The stride is the size of a “jump” between two vertices. A zero value means that data is contiguous.

The last argument is another advanced feature we don’t need. A `None`

value means that we don’t need it.

## Colors VBO

The Vertex Buffer Object is almost the same than the vertices one:

glBindBuffer(GL_ARRAY_BUFFER, colorVboId) colors = np.ascontiguousarray(colors.flatten()) glBufferData(GL_ARRAY_BUFFER, 4 * len(colors), colors, GL_STATIC_DRAW) glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, None)

Note the two first arguments of the `glVertexAttribPointer()`

: the first one is 1 instead of 0, and the second one is 3 because there are 3 values per vertex (red, green and blue).

## Faces VBO

The Vertex Buffer Object for faces is different from the two other ones:

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexVboId) faces = np.ascontiguousarray(faces.flatten()) glBufferData(GL_ELEMENT_ARRAY_BUFFER, 4 * len(faces), faces, GL_STATIC_DRAW)

The type of this VBO is `GL_ELEMENT_ARRAY_BUFFER`

. It tells OpenGL that it does not contain values but an index. You can only have one index per VAO, and OpenGL uses it for all other VBOs. In our case, OpenGL uses it for the vertex coordinates and the vertex colors. So, every time OpenGL needs the attributes of the ith vertex, it computes `vertices[faces[i]]`

and `colors[faces[i]]`

.

Note that there is no call to `glVertexAttribPointer()`

because you can only have one index buffer. Furthermore, the properties of this buffer are set during the drawing (see below).

## Release the VAO

Once the VAO and all its VBOs are set, we must release them, using binding functions with a zero id:

glBindBuffer(GL_ARRAY_BUFFER, 0) glBindVertexArray(0)

## Draw the content of the VAO

We only need to run the creation of VAO once (except if we want to change its content). Then, we can draw its content in the main loop using the following lines:

glBindVertexArray(vaoId) glEnableVertexAttribArray(0) glEnableVertexAttribArray(1) glDrawElements(GL_QUADS, 4 * faceCount, GL_UNSIGNED_INT, None) glDisableVertexAttribArray(0) glDisableVertexAttribArray(1) glBindVertexArray(0)

Line 1 tells that we want to work on our vaoId.

Lines 2-3 enable the two VBO with vertex coordinates and colors, telling OpenGL that we want to use them for drawing. The first argument in each case is the index we defined during the creating of those VBOs.

Line 4 is the drawing of all rectangles thanks to the `glDrawElements()`

function.

The first argument `GL_QUADS`

indicates that faces have four vertices (replace by `GL_TRIANGLES`

if you want to render triangles).

The second argument is the total number of vertices to draw.

The third argument indicates that the index values are unsigned integers.

The last argument is an advanced options we don’t need now, `None`

means that we don’t use it.

## Delete the buffers

At the end of the program, after the main loop, we delete the VBOs and the VAO:

glDeleteBuffers(3, vboIds) glDeleteVertexArrays(1, vaoId)

It is not mandatory when you leave the program. Anyway, it shows you how to do it if you need to delete/create buffers.

## Final program

Here is the final program:

import os import numpy as np import pygame from OpenGL.GL import glClear, GL_COLOR_BUFFER_BIT, GL_QUADS, glFlush, \ glClearColor, glGenVertexArrays, glBindVertexArray, glGenBuffers, GL_ARRAY_BUFFER, glBindBuffer, glBufferData, \ ctypes, GL_STATIC_DRAW, glVertexAttribPointer, GL_FLOAT, GL_FALSE, GL_ELEMENT_ARRAY_BUFFER, \ glEnableVertexAttribArray, glDrawElements, GL_UNSIGNED_INT, glDisableVertexAttribArray, glDeleteBuffers, \ glDeleteVertexArrays os.environ['SDL_VIDEO_CENTERED'] = '1' windowSize = (1280, 800) pygame.display.set_mode(windowSize, pygame.DOUBLEBUF | pygame.OPENGL) pygame.display.set_caption("OpenGL 2D Facade - https://www.patternsgameprog.com/") glClearColor(0.0, 0.0, 0.0, 1.0) # All components of our scene vertices = np.array([ [-0.5, -0.5], [-0.5, 0.5], [0.5, 0.5], [0.5, -0.5], [-0.3, -0.7], [-0.3, 0.3], [0.7, 0.3], [0.7, -0.7] ], dtype=np.float32) colors = np.array([ [0.0, 0.0, 1.0], [0.0, 0.0, 1.0], [0.0, 0.0, 1.0], [0.0, 0.0, 1.0], [0.0, 1.0, 0.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [1.0, 0.0, 0.0] ], dtype=np.float32) faces = np.array([ [0, 1, 2, 3], [4, 5, 6, 7] ], dtype=np.uint) faceCount = faces.shape[0] # Create one Vertex Array Object (VAO) vaoId = glGenVertexArrays(1) # We will be working on this VAO glBindVertexArray(vaoId) # Create three Vertex Buffer Objects (VBO) vboIds = glGenBuffers(3) # Commodity variables to memorize the id of each VBO vertexVboId = vboIds[0] colorVboId = vboIds[1] indexVboId = vboIds[2] # Copy vertices data to GPU glBindBuffer(GL_ARRAY_BUFFER, vertexVboId) vertices = np.ascontiguousarray(vertices.flatten()) glBufferData(GL_ARRAY_BUFFER, 4 * len(vertices), vertices, GL_STATIC_DRAW) glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, None) # Copy colors data to GPU glBindBuffer(GL_ARRAY_BUFFER, colorVboId) colors = np.ascontiguousarray(colors.flatten()) glBufferData(GL_ARRAY_BUFFER, 4 * len(colors), colors, GL_STATIC_DRAW) glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, None) # Copy index data to GPU glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexVboId) faces = np.ascontiguousarray(faces.flatten()) glBufferData(GL_ELEMENT_ARRAY_BUFFER, 4 * len(faces), faces, GL_STATIC_DRAW) # Release focus on VBO and VAO glBindBuffer(GL_ARRAY_BUFFER, 0) glBindVertexArray(0) # Main loop clock = pygame.time.Clock() running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False break elif event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: running = False break glClear(GL_COLOR_BUFFER_BIT) glBindVertexArray(vaoId) glEnableVertexAttribArray(0) glEnableVertexAttribArray(1) glDrawElements(GL_QUADS, 4 * faceCount, GL_UNSIGNED_INT, None) glDisableVertexAttribArray(0) glDisableVertexAttribArray(1) glBindVertexArray(0) glFlush() pygame.display.flip() clock.tick(60) glDeleteBuffers(3, vboIds) glDeleteVertexArrays(1, vaoId) pygame.quit() quit()

If you run this program, you get white rectangles:

It is as expected, we need shaders to color vertices, I’ll show that in the next post.