The Quad with DrawArrays

From LWJGL
Jump to: navigation, search

Introduction

With the release of OpenGL 3.0 a “deprecated” model has been introduced, functions that would be removed in future versions of OpenGL were flagged as deprecated so programmers could avoid using them and start pulling them out of current implementations. Many of the “helper” functions were removed such as matrix/stack operations and default lighting. But more important for defining primitives you can no longer use “glVertex”. OpenGL instead forces us to use VAO’s (Vertex Array Object) and VBO’s (Vertex Buffer Object). We’ll use these to draw a quad on the screen.

“Vertex Array Object” and “Vertex Buffer Object”

A way to speed up rendering algorithms is to put the data on the GPU instead of sending them from the CPU to the GPU every time. This is what the VAO’s and VBO’s accomplish, you use a certain amount of memory on the graphics card itself.

Think about a VAO as a complete object definition, and it is made out of different VBO’s. Each VBO can hold specific data (mixing or interleaving is also possible), you could have a VBO for: vertex position, color, normal, texture coordinates, … . VAO’s can hold these different VBO’s in what is called an “attribute list”. By default 16 of these attribute lists can be used in one VAO from 0 to 15.

Setting up a VAO, or any other object for that matter, follows the following rules:

  • Request a memory location (returns integer number, the ID, of the object)
  • Bind the object using the ID
  • Manipulate the object (the object is whichever object OpenGL is currently bound to)
  • Unbind the object using the ID

In code (VAO):

int vaoId = GL30.glGenVertexArrays();
GL30.glBindVertexArray(vaoId);
// Do something with it
GL30.glBindVertexArray(0);

In code (VBO):

int vboId = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
// Do something with it
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);

Setting up the quad VBO and VAO

When putting a VBO in an attribute list of a certain VAO you should first bind the VAO and then call “glVertexAttributePointer” which needs the ID of the list (0 to 15 by default). In our example we are going to setup a quad using one VAO and one VBO. The VBO will hold the vertex positional data and the VAO will function as the main object (the quad) definition.

First we have to declare our vertices, OpenGL draws everything in triangles. A triangle is drawn by defining the vertices in counter clockwise order by default. This means that if you specify the vertices in counter clockwise order and you are defining which side is the front and which is the back. This is used so OpenGL can “cull” invisible faces in an optimization stage of the rendering pipeline. You could change this default behavior but it’s rather trivial so I will not go into depth now.

Going back to our vertex definition, a quad is not a triangle but it can be made from 2 triangles. So to define our quad we will need 6 vertices instead of 4 (we’ll solve this inefficiency another time):

QuadVertices.png

Our default OpenGL coordinate system goes from -1 to 1 in all axis. And this is mapped to the entire screen. Like this:

Defaultcoordinates.png

We’ll define our vertices using 3 numbers, one for each axis (X, Y, Z). Behind the scenes OpenGL works with 4 numbers (X, Y, Z, W). We are not concerned with the W value, just make sure it’s 1 when declaring it. The numbers are floats and we’ll have to wrap them in a bytebuffer (of the type FloatBuffer) before using it in OpenGL methods.

// OpenGL expects vertices to be defined counter clockwise by default
float[] vertices = {
		// Left bottom triangle
		-0.5f, 0.5f, 0f,
		-0.5f, -0.5f, 0f,
		0.5f, -0.5f, 0f,
		// Right top triangle
		0.5f, -0.5f, 0f,
		0.5f, 0.5f, 0f,
		-0.5f, 0.5f, 0f
};
// Sending data to OpenGL requires the usage of (flipped) byte buffers
FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length);
verticesBuffer.put(vertices);
verticesBuffer.flip();

vertexCount = 6;

We’ll keep track of the vertex count, as we’ll need to let OpenGL know how many vertices it needs to use when drawing the triangles later using the “glDrawArrays” method.

Now that we have our vertices in a usable format we can define our VAO and VBO. In the VBO we’ll put our positional data, in our VAO we’ll put our VBO in the attribute list 0. Linking a VBO with a VAO’s attribute list is fairly easy (when not interleaving data). All we need to do is use “glVertexAttribPointer” as parameters it needs to know the following:

  • Index of the attribute list (0 in our case)
  • How many values define one data definition (3 floats is 1 position definition in our case)
  • What type the values have (floats in our case)
  • The stride and the offset (we are not using those yet, they have to do with interleaving data)


To put the data in the VBO we’ll use the “glBufferData” method. It has also a number of parameters:

  • The type (we’re using a GL_ARRAY_BUFFER, a default definition for generic data)
  • The buffer (our FloatBuffer that holds the vertices positions)
  • The usage (our vertices will not move or change so the usage is simply GL_STATIC_DRAW)


We must not forget to “unbind” our objects once we no longer need to manipulate them. If we combine all the previous steps in code it looks like this:

// Create a new Vertex Array Object in memory and select it (bind)
// A VAO can have up to 16 attributes (VBO's) assigned to it by default
vaoId = GL30.glGenVertexArrays();
GL30.glBindVertexArray(vaoId);

// Create a new Vertex Buffer Object in memory and select it (bind)
// A VBO is a collection of Vectors which in this case resemble the location of each vertex.
vboId = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, verticesBuffer, GL15.GL_STATIC_DRAW);
// Put the VBO in the attributes list at index 0
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 0, 0);
// Deselect (bind to 0) the VBO
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);

// Deselect (bind to 0) the VAO
GL30.glBindVertexArray(0);

vaoId and vboId (and also vertexCount) are integers variables defined globally.

Rendering with glDrawArrays

To actually draw our quad we’ll use, as mentioned before, the “glDrawArrays” method. It needs to know the following:

  • How to draw these vertices (we’re using simple GL_TRIANGLES)
  • The first index (we’ll start from the beginning at 0)
  • The vertex count (we’ve held that number in our vertexCount variable)

OpenGL must also have the VAO (and as a result the linked VBO’s) active in memory so we’ll have to bind them before drawing. Our VBO is linked with the attribute list 0 of our VAO, so we’ll have to enable that list as well. When we’ve drawn our quad we’ll unbind and disable everything again. The render code is this:

GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);

// Bind to the VAO that has all the information about the quad vertices
GL30.glBindVertexArray(vaoId);
GL20.glEnableVertexAttribArray(0);

// Draw the vertices
GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, vertexCount);

// Put everything back to default (deselect)
GL20.glDisableVertexAttribArray(0);
GL30.glBindVertexArray(0);

Cleaning up our memory

And finally before exiting our application we’ll do some memory management:

// Disable the VBO index from the VAO attributes list
GL20.glDisableVertexAttribArray(0);

// Delete the VBO
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL15.glDeleteBuffers(vboId);

// Delete the VAO
GL30.glBindVertexArray(0);
GL30.glDeleteVertexArrays(vaoId);

The result

And there we have it, our quad:

Result quadDrawArrays.png

Complete source code

import java.nio.FloatBuffer;

import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.ContextAttribs;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.PixelFormat;
import org.lwjgl.util.glu.GLU;

public class TheQuadExampleDrawArrays {
	// Entry point for the application
	public static void main(String[] args) {
		new TheQuadExampleDrawArrays();
	}
	
	// Setup variables
	private final String WINDOW_TITLE = "The Quad: glDrawArrays";
	private final int WIDTH = 320;
	private final int HEIGHT = 240;
	// Quad variables
	private int vaoId = 0;
	private int vboId = 0;
	private int vertexCount = 0;
	
	public TheQuadExampleDrawArrays() {
		// Initialize OpenGL (Display)
		this.setupOpenGL();
		
		this.setupQuad();
		
		while (!Display.isCloseRequested()) {
			// Do a single loop (logic/render)
			this.loopCycle();
			
			// Force a maximum FPS of about 60
			Display.sync(60);
			// Let the CPU synchronize with the GPU if GPU is tagging behind
			Display.update();
		}
		
		// Destroy OpenGL (Display)
		this.destroyOpenGL();
	}
	
	public void setupOpenGL() {
		// Setup an OpenGL context with API version 3.2
		try {
			PixelFormat pixelFormat = new PixelFormat();
			ContextAttribs contextAtrributes = new ContextAttribs(3, 2)
				.withForwardCompatible(true)
				.withProfileCore(true);
			
			Display.setDisplayMode(new DisplayMode(WIDTH, HEIGHT));
			Display.setTitle(WINDOW_TITLE);
			Display.create(pixelFormat, contextAtrributes);
			
			GL11.glViewport(0, 0, WIDTH, HEIGHT);
		} catch (LWJGLException e) {
			e.printStackTrace();
			System.exit(-1);
		}
		
		// Setup an XNA like background color
		GL11.glClearColor(0.4f, 0.6f, 0.9f, 0f);
		
		// Map the internal OpenGL coordinate system to the entire screen
		GL11.glViewport(0, 0, WIDTH, HEIGHT);
		
		this.exitOnGLError("Error in setupOpenGL");
	}
	
	public void setupQuad() {		
		// OpenGL expects vertices to be defined counter clockwise by default
		float[] vertices = {
				// Left bottom triangle
				-0.5f, 0.5f, 0f,
				-0.5f, -0.5f, 0f,
				0.5f, -0.5f, 0f,
				// Right top triangle
				0.5f, -0.5f, 0f,
				0.5f, 0.5f, 0f,
				-0.5f, 0.5f, 0f
		};
		// Sending data to OpenGL requires the usage of (flipped) byte buffers
		FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length);
		verticesBuffer.put(vertices);
		verticesBuffer.flip();
		
		vertexCount = 6;
		
		// Create a new Vertex Array Object in memory and select it (bind)
		// A VAO can have up to 16 attributes (VBO's) assigned to it by default
		vaoId = GL30.glGenVertexArrays();
		GL30.glBindVertexArray(vaoId);
		
		// Create a new Vertex Buffer Object in memory and select it (bind)
		// A VBO is a collection of Vectors which in this case resemble the location of each vertex.
		vboId = GL15.glGenBuffers();
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
		GL15.glBufferData(GL15.GL_ARRAY_BUFFER, verticesBuffer, GL15.GL_STATIC_DRAW);
		// Put the VBO in the attributes list at index 0
		GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 0, 0);
		// Deselect (bind to 0) the VBO
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
		
		// Deselect (bind to 0) the VAO
		GL30.glBindVertexArray(0);
		
		this.exitOnGLError("Error in setupQuad");
	}
	
	public void loopCycle() {
		GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
		
		// Bind to the VAO that has all the information about the quad vertices
		GL30.glBindVertexArray(vaoId);
		GL20.glEnableVertexAttribArray(0);
		
		// Draw the vertices
		GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, vertexCount);
		
		// Put everything back to default (deselect)
		GL20.glDisableVertexAttribArray(0);
		GL30.glBindVertexArray(0);
		
		this.exitOnGLError("Error in loopCycle");
	}
	
	public void destroyOpenGL() {		
		// Disable the VBO index from the VAO attributes list
		GL20.glDisableVertexAttribArray(0);
		
		// Delete the VBO
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
		GL15.glDeleteBuffers(vboId);
		
		// Delete the VAO
		GL30.glBindVertexArray(0);
		GL30.glDeleteVertexArrays(vaoId);
		
		Display.destroy();
	}
	
	public void exitOnGLError(String errorMessage) {
		int errorValue = GL11.glGetError();
		
		if (errorValue != GL11.GL_NO_ERROR) {
			String errorString = GLU.gluErrorString(errorValue);
			System.err.println("ERROR - " + errorMessage + ": " + errorString);
			
			if (Display.isCreated()) Display.destroy();
			System.exit(-1);
		}
	}
}

Credit

Mathias Verboven (moci)