Skip to content
ivanorsolic edited this page Apr 17, 2016 · 3 revisions

#This class loads the 3D models into memory

And it does it by storing positional data about the models into VAOs.

#Basics

A VAO - Vertex Array Object is just an object in which you can store data about a 3D model. It has a lot of slots in which you can save data and they are called the attribute lists. Usually you store different sets of data into different attribute lists. E.G.:

  • 0 Vertex positions
  • 1 Vertex colors
  • 2 Normal vectors
  • 3 Texture coordinates
  • 4 etc...

And these data sets are stored in the attribute lists as VBOs - Vertex Buffer Objects. A VBO is just data. It's pretty much just an array of data, and it could be anything from colors, coordinates and it's stored in a attribute list in a VAO. If for an example we wanted to access normal vectors from the example up we would access it with the index 2.

But how do you represent a 3D model as a bunch of data stored in a VAO?

Every 3D model we're dealing with just has a bunch of triangles, and each of these triangles is represented by three points in three-dimensional space - three verteces. And each of these points has three coordinates - x, y, z. And if we collect the coordinates from all of the triangles that represent our model, we can store them all in an array (VBO), and then store that in a VAO in an attribute list and that's pretty much it.

#OpenGL official Vertex Specification ##VAOs

A Vertex Array Object (VAO) is an OpenGL Object that stores all of the state needed to supply vertex data (with one minor exception noted below). It stores the format of the vertex data as well as the Buffer Objects (see below) providing the vertex data arrays. Note that VAOs do not copy, freeze or store the contents of the referenced buffers - if you change any of the data in the buffers referenced by an existing VAO, those changes will be seen by users of the VAO.

As OpenGL Objects, VAOs have the usual creation, destruction, and binding functions: glGenVertexArrays​, glDeleteVertexArrays​, and glBindVertexArray​. The latter is different, in that there is no "target" parameter; there is only one target for VAOs, and glBindVertexArray​ binds to that target.

Note: VAOs cannot be shared between OpenGL contexts. Vertex attributes are numbered from 0 to GL_MAX_VERTEX_ATTRIBS - 1. Each attribute can be enabled or disabled for array access. When an attribute's array access is disabled, any reads of that attribute by the vertex shader will produce a constant value (see below) instead of a value pulled from an array.

A newly-created VAO has array access disabled for all attributes. Array access is enabled by binding the VAO in question and calling:

void glEnableVertexAttribArray​(GLuint index​);

There is a similar glDisableVertexAttribArray​ function to disable an enabled array.

Remember: all of the state below is part of the VAO's state, except where it is explicitly stated that it is not. A VAO must be bound when calling any of those functions, and any changes caused by these function will be captured by the VAO.

The compatibility OpenGL profile makes VAO object 0 a default object. The core OpenGL profile makes VAO object 0 not an object at all. So if VAO 0 is bound in the core profile, you should not call any function that modifies VAO state. This includes binding the GL_ELEMENT_ARRAY_BUFFER with glBindBuffer​.

##VBOs

A Vertex Buffer Object (VBO) is a Buffer Object which is used as the source for vertex array data. It is no different from any other buffer object, and a buffer object used for Transform Feedback or asynchronous pixel transfers can be used as source values for vertex arrays.

There are two ways to use buffer objects as the source for vertex data. This section describes the combined format method. A method that separates the format specification from buffers is described below. The two are functionally equivalent, but the separate method (and easier to understand); however, it requires OpenGL 4.3 or ARB_vertex_attrib_binding.

The format and source buffer for an attribute array can be set by doing the following. First, the buffer that the attribute comes from must be bound to GL_ARRAY_BUFFER.

Note: The GL_ARRAY_BUFFER binding is NOT part of the VAO's state! I know that's confusing, but that's the way it is. Once the buffer is bound, call one of these functions:

 void glVertexAttribPointer​( GLuint index​, GLint size​, GLenum type​,
   GLboolean normalized​, GLsizei stride​, const void *offset​);
 void glVertexAttribIPointer​( GLuint index​, GLint size​, GLenum type​,
   GLsizei stride​, const void *offset​ );
 void glVertexAttribLPointer​( GLuint index​, GLint size​, GLenum type​,
   GLsizei stride​, const void *offset​ );

All of these functions do more or less the same thing. The difference between them will be discussed later. Note that the last function is only available on OpenGL 4.1 or ARB_vertex_attrib_64bit.

These functions say that the attribute index index​ will get its attribute data from whatever buffer object is currently bound to GL_ARRAY_BUFFER. It is vital to understand that this association is made when this function is called. For example, let's say we do this:

glBindBuffer(GL_ARRAY_BUFFER, buf1);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);

The first line binds buf1​ to the GL_ARRAY_BUFFER binding. The second line says that attribute index 0 gets its vertex array data from buf1​, because that's the buffer that was bound to GL_ARRAY_BUFFER when the glVertexAttribPointer​ was called.

The third line binds the buffer object 0 to the GL_ARRAY_BUFFER binding. What does this do to the association between attribute 0 and buf1​?

Nothing! Changing the GL_ARRAY_BUFFER binding changes nothing about vertex attribute 0. Only calls to glVertexAttribPointer​ can do that.

Think of it like this. glBindBuffer​ sets a global variable, then glVertexAttribPointer​ reads that global variable and stores it in the VAO. Changing that global variable after it's been read doesn't affect the VAO. You can think of it that way because that's exactly how it works.

This is also why GL_ARRAY_BUFFER is not VAO state; the actual association between an attribute index and a buffer is made by glVertexAttribPointer​.

Note that it is an error to call the glVertexAttribPointer​ functions if 0 is currently bound to GL_ARRAY_BUFFER.

You can read the rest of the documentation here.

#My code

Firstly we have a method that takes in positions of the models verteces, load them in a VAO and then return the information about the VAO as a RawModel object.

public RawModel loadToVAO(float[] positions, int[] indices){}

And there are three steps we have to take to carry out this method:

  • Create a new empty VAO
  • Store the data in one of the Attribute Lists of the VAO
  • Unbind the VAO - when you're finished using the VAO you have to unbind it

So we define three methods:

private int createVAO()
private void unbindVAO(){}
private void storeDataInAttributeList()

The first thing we need to do is:

  • To create an empty VAO
  • Store the positional data in one of the attribute lists as a VBO, we'll use 0
  • After we're finished using the VAO, we'll unbind it
  • And we will return the data, the information about the VAO as a RawModel
public RawModel loadToVAO(float[] positions, int[] indices){
	int vaoID = createVAO();
	bindIndicesBuffer(indices); //Update two
	storeDataInAttributeList(0,positions);
	unbindVAO();
	return new RawModel(vaoID, indices.length); //Update two
}

To create the VAO we need to implement the abstract method we mentioned above, so we do:

private int createVAO(){
	int vaoID = GL30.glGenVertexArrays(); // creates an empty VAO and it returns it ID
	vaos.add(vaoID); // memory management, bottom of page
	// if we want to do anything with the VAO we have to activate it first with this method
	GL30.glBindVertexArray(vaoID); 
	return vaoID; // we return the ID
}

The VAO will stay bound until we unbind it, so we implement the second method from above:

private void unbindVAO(){
	GL30.glBindVertexArray(0); // unbinds the current VAO - 0
}

And now we need to store the data into an attribute list, so we implement the third abstract method from above:

private void storeDataInAttributeList(int attributeNumber, float[] data){
	int vboID = GL15.glGenBuffers(); // we create an empty VBO and get its ID
	// we have to bind it in order to use it, we specify the type as an ARRAY_BUFFER
	GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboID); 
}

One more thing, data needs to be stored into a VBO as a FloatBuffer, so we need to implement another method that converts our float array of data into a float buffer - so it takes in the array we want to convert it and returns a FloatBuffer:

private FloatBuffer storeDataInFloatBuffer(float[] data){
	FloatBuffer buffer = BufferUtils.createFloatBuffer(data.length); // creating an empty buffer
	buffer.put(data); // putting the data into the buffer
	//we have to prepare the buffer to be read from, because now it expects to be written into 
	buffer.flip(); // so we use .flip() so it knows we're done writing to it
	return buffer;
}

So let's call that method now:

private void storeDataInAttributeList(int attributeNumber, float[] data){
	int vboID = GL15.glGenBuffers();
	vaos.add(vaoID); // memory management, bottom of page
	GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboID); 
	FloatBuffer buffer = storeDataInFloatBuffer(data); // converting our data into a FloatBuffer
	GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW); //Storing it into a VBO
	GL20.glVertexAttribPointer(attributeNumber, 3, GL11.GL_FLOAT, false, 0, 0); // Put the VBO into a VAO atr. list
	GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); //Unbinding it because we're done
}

And the last thing we need to do is some memory management because when we close the game we'd like to delete all of the VAOs and VBOs we put in memory while we ran the game, so we make two arrays in which we memorize them:

private List<Integer> vaos = new ArrayList<Integer>();
private List<Integer> vbos = new ArrayList<Integer>();

And after closing the game we call a method that deletes all of them:

public void cleanUp(){
	for(int vao:vaos){
		GL30.glDeleteVertexArrays(vao);
	}
			
	for(int vbo:vbos){
		GL15.glDeleteBuffers(vbo);
	}
}

##Update 2

In this update I've optimized the rendering method by adding an Index Buffer to save memory while using VBOs and to avoid redundancy of vertices.

So we need to add a method that's going to load up that buffer and bind it to the VAO we want to render:

private void bindIndicesBuffer(int[] indices){
	int vboID = GL15.glGenBuffers(); //create an empty VBO
	vbos.add(vboID); // memory management
	GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboID); // bind the buffer(VBO) to use it 
}

Now we want to store our data into the Buffer and we implement this method which is similiar to the FloatBuffer storeDataInFloatBuffer() method:

private IntBuffer storeDataInIntBuffer(int[] data){
	IntBuffer buffer = BufferUtils.createIntBuffer(data.length); //empty IntBuffer
	buffer.put(data); // putting the data in
	buffer.flip(); // finished with writing
	return buffer;
}

And now we will use that method to convert our array of indices to an IntBuffer so we can store it into the VBO:

private void bindIndicesBuffer(int[] indices){
	int vboID = GL15.glGenBuffers();
	vbos.add(vboID);
	GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboID); 
	IntBuffer buffer = storeDataInIntBuffer(indices); // converting to IntBuffer
	GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW); // Storing it into the VBO
}

##For quick reference, here is all of the code

package renderEngine;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;

public class Loader {

	private List<Integer> vaos = new ArrayList<Integer>();
	private List<Integer> vbos = new ArrayList<Integer>();
	public RawModel loadToVAO(float[] positions, int[] indices){
		int vaoID = createVAO();
		bindIndicesBuffer(indices);
		storeDataInAttributeList(0,positions);
		unbindVAO();
		return new RawModel(vaoID, indices.length);
	}
	public void cleanUp(){
			
		for(int vao:vaos){
			GL30.glDeleteVertexArrays(vao);
		}
			
		for(int vbo:vbos){
			GL15.glDeleteBuffers(vbo);
		}
			
	}
	private int createVAO(){
		int vaoID = GL30.glGenVertexArrays();
		vaos.add(vaoID);
		GL30.glBindVertexArray(vaoID);
		return vaoID;
	}
		
	private void storeDataInAttributeList(int attributeNumber, float[] data){
		int vboID = GL15.glGenBuffers();
		vbos.add(vboID);
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboID);
		FloatBuffer buffer = storeDataInFloatBuffer(data);
		GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
		GL20.glVertexAttribPointer(attributeNumber, 3, GL11.GL_FLOAT, false, 0, 0);
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
	}
		
	private void unbindVAO(){
		GL30.glBindVertexArray(0);
	}
	private IntBuffer storeDataInIntBuffer(int[] data){
		IntBuffer buffer = BufferUtils.createIntBuffer(data.length);
		buffer.put(data);
		buffer.flip();
		return buffer;
	}
	private void bindIndicesBuffer(int[] indices){
		int vboID = GL15.glGenBuffers();
		vbos.add(vboID);
		GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboID);
		IntBuffer buffer = storeDataInIntBuffer(indices);
		GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
	}
	private FloatBuffer storeDataInFloatBuffer(float[] data){
		FloatBuffer buffer = BufferUtils.createFloatBuffer(data.length);
		buffer.put(data);
		buffer.flip();
		return buffer;
	}
}

Clone this wiki locally