Time for Action: Rendering a Square Using a VAO

Let's refactor a previous example using VAOs:

  1. Open up ch02_01_square.html in your editor.
  2. First, we update our global variables:
// Global variables that are set and used
// across the application
let gl,
program,
squareVAO,
squareIndexBuffer,
indices;
  1. We've replaced squareVertexBuffer with squareVAO, as we no longer need to reference the vertex buffer directly.
  1. Next, we update the initBuffers functions as follows:
// Set up the buffers for the square
function initBuffers() {
/*
V0 V3
(-0.5, 0.5, 0) (0.5, 0.5, 0)
X---------------------X
| |
| |
| (0, 0) |
| |
| |
X---------------------X
V1 V2
(-0.5, -0.5, 0) (0.5, -0.5, 0)
*/
const vertices = [
-0.5, 0.5, 0,
-0.5, -0.5, 0,
0.5, -0.5, 0,
0.5, 0.5, 0
];

// Indices defined in counter-clockwise order
indices = [0, 1, 2, 0, 2, 3];

// Create VAO instance
squareVAO = gl.createVertexArray();

// Bind it so we can work on it
gl.bindVertexArray(squareVAO)
;

const squareVertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices),
gl.STATIC_DRAW);

// Provide instructions for VAO to use data later in draw
gl.enableVertexAttribArray(program.aVertexPosition);
gl.vertexAttribPointer(program.aVertexPosition, 3, gl.FLOAT,
false, 0, 0);

// Setting up the IBO
squareIndexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareIndexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices),
gl.STATIC_DRAW);

// Clean
gl.bindVertexArray(null);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
  1. We create a new VAO instance using gl.createVertexArray(); and assign it to squareVAO.
  2. Then, we bind squareVAO with gl.bindVertexArray(squareVAO); so that all of our attribute settings will apply to that set of attribute state.
  3. After the squareVertexBuffer has been configured, we instruct the currently bound VAO (i.e. squareVAO) on how to extract data given the instructions for aVertexPosition. These instructions are the same ones that previously sat inside of the draw function; but now, they happen once during initialization.
  4. Lastly, we need to use this VAO in our draw function:
// We call draw to render to our canvas
function draw() {
// Clear the scene
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

// Bind the VAO
gl.bindVertexArray(squareVAO)
;

gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareIndexBuffer);

// Draw to the scene using triangle primitives
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT,
0);

// Clean
gl.bindVertexArray(null);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
  1. The updated draw function is far simpler! We simply bind the VAO (i.e. squareVAO) and allow for it to handle the instructions we provided it inside of initBuffers.
  1. Lastly, it's good practice to unbind buffers and VAOs after usage by providing null values.
  2. Save the file and open it in your browser. You should see the same square being rendered using a VAO:

  1. The source code for this exercise can be found in ch02_03_square-vao.html.

Given that we're currently rendering a single geometry, using a VAO may seem unnecessarily complex. That is a reasonable assessment! However, as the complexity of our application grows, using VAOs becomes a foundational feature.