The Quad interleaved

From LWJGL
Jump to: navigation, search

Introduction

We’ve been splitting our vertex data into different VBO’s. We could also “interleave” or “mix” these values together. Leaving us with 1 VBO that has all the vertex data mixed together. If you followed along you might think “we only need to use 1 attribute list”. No, we still need to split the different data parts in different lists, we only use 1 VBO to manage the data. Here’s how.

Splitting VBO's

As an overview we’ll do the same thing again but splitting up our VBO’s. As an example we’ve got 2 FloatBuffers, one for the color and one for the position. A color is defined by 4 components, position by 3:

float[] positions = new float[] {1, 1, 1};
float[] colors = new float[] {1, 1, 1, 1};

// Put our data in the proper format: byte buffers
FloatBuffer positionBuffer = BufferUtils.createFloatBuffer(positions.length);
positionBuffer.put(positions); positionBuffer.flip();
FloatBuffer colorBuffer = BufferUtils.createFloatBuffer(colors.length);
colorBuffer.put(colors); colorBuffer.flip();

// Create a new VAO
int vaoID = GL30.glGenVertexArrays();
GL30.glBindVertexArray(vaoID);

// Create a new VBO for our positions and link it to attribute list 0
int posVboID = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, posVboID);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, positionBuffer, GL15.GL_STATIC_DRAW);
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 0, 0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);

// Create a new VBO for our colors and link it to attribute list 1
int colVboID = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, colVboID);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, positionBuffer, GL15.GL_STATIC_DRAW);
GL20.glVertexAttribPointer(1, 4, GL11.GL_FLOAT, false, 0, 0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);

GL30.glBindVertexArray(0);

For those that have followed along with the previous tutorials, when I’m using “glDrawElements” what am I missing (if anything)?

Interleaving VBO's

Let’s now combine the color and position VBO’s into 1 VBO. We’ll use the same example as before with our made up positions and colors. A more practical example will be shown later.

float[] positions = new float[] {1, 1, 1};
float[] colors = new float[] {1, 1, 1, 1};

// Interleave the data in the proper format: byte buffer
FloatBuffer interleavedBuffer = BufferUtils.createFloatBuffer(positions.length + 
		colors.length);
interleavedBuffer.put(positions); 	// Buffer contents: X, Y, Z
interleavedBuffer.put(colors);		// Buffer contents: X, Y, Z, R, G, B, A
interleavedBuffer.flip();

// Create a new VAO
int vaoID = GL30.glGenVertexArrays();
GL30.glBindVertexArray(vaoID);

// Create a new VBO for our interleaved data
int interVboID = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, interVboID);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, interleavedBuffer, GL15.GL_STATIC_DRAW);

// -- We'll need to know some numbers beforehand, I'm using variables for these
// -- so it's easier to see where they come from
// There are 4 bytes in a float
int floatByteSize = 4;
// We use 3 floats for our position
int positionFloatCount = 3;
// We use 4 floats for our color
int colorFloatCount = 4;
// So the total amount of floats used is ...
int floatsPerVertex = positionFloatCount + colorFloatCount;
// So the total amount of bytes per vertex used is (this is the 'stride') ...
int vertexFloatSizeInBytes = floatByteSize * floatsPerVertex;

// -- Now we can split our interleaved data over 2 attribute lists
// First up is our positional information in list 0
GL20.glVertexAttribPointer(0, positionFloatCount, GL11.GL_FLOAT, false, 
		vertexFloatSizeInBytes, 0);
// Second is our color information in list 1, for this we also need the offset
int byteOffset = floatByteSize * positionFloatCount;
GL20.glVertexAttribPointer(1, colorFloatCount, GL11.GL_FLOAT, false, 
		vertexFloatSizeInBytes, byteOffset);

GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL30.glBindVertexArray(0);

That’s it, what we have done is put all information in one big array and said to OpenGL that the information for one vertex spans over a certain amount of bytes. Even more we’ve said that all the bytes for one vertex can be also split up, attribute list 0 must only look at the first X amount of bytes. While attribute list 1 must only look at the Y amount of bytes following the previous X. We do this by setting out “stride” and “offset”.

  • The stride tells OpenGL how many bytes should be grouped together. In essence it splits our buffer up in smaller buffers. The length of these (imaginary) smaller buffers is the stride value itself.
  • When working with multiple lists that work with the same buffer we can also tell OpenGL what the offset is in the stride for that list. This offset is also a number of bytes, so if the previous list had 3 items each having 4 bytes. The offset for the next list will be 12 bytes + whatever offset the previous list already had (assuming they are all using the same buffer)

Graphically it looks like this:

Interleaved buffer.png

The entire buffer is split up in smaller segments, the size is the stride. This segment can be segmented again in the different groups of bytes, where each group defines a different property of the vertex. The previous image is a visual representation of the buffer used in the former examples where the position property had 3 floats (one float is 4 bytes) and the colors had 4 floats.

Practical management of vertices

When adding different properties to a vertex we quickly get a lot of different float arrays and other variables. To avoid having to manage all these buffers and arrays one by one it’s better to handle it more “object oriented” we’re using Java after all. So as a first improvement we’re going to create a vertex class that holds our different properties namely our position and our color. It will also hold a few numbers so it’s easier when setting up the attribute lists.

public class Vertex {
	// Vertex data
	private float[] xyzw = new float[] {0f, 0f, 0f, 1f};
	private float[] rgba = new float[] {1f, 1f, 1f, 1f};
	
	// The amount of elements that a vertex has
	public static final int elementCount = 8;	
	// The amount of bytes an element has
	public static final int elementBytes = 4;
	// The size of a vertex in bytes, like in C/C++: sizeof(Vertex)
	public static final int sizeInBytes = elementBytes * elementCount;
	
	// Setters
	public void setXYZ(float x, float y, float z) {
		this.setXYZW(x, y, z, 1f);
	}
	
	public void setRGB(float r, float g, float b) {
		this.setRGBA(r, g, b, 1f);
	}
	
	public void setXYZW(float x, float y, float z, float w) {
		this.xyzw = new float[] {x, y, z, w};
	}
	
	public void setRGBA(float r, float g, float b, float a) {
		this.rgba = new float[] {r, g, b, 1f};
	}
	
	// Getters	
	public float[] getXYZW() {
		return new float[] {this.xyzw[0], this.xyzw[1], this.xyzw[2], this.xyzw[3]};
	}
	
	public float[] getRGBA() {
		return new float[] {this.rgba[0], this.rgba[1], this.rgba[2], this.rgba[3]};
	}
}

Now that we’ve got a Vertex class it’s easier to assign colors and positions. Let’s see how we can recreate our quad using these Vertex objects:

// We'll define our quad using 4 vertices of the custom 'Vertex' class
Vertex v0 = new Vertex(); v0.setXYZ(-0.5f, 0.5f, 0f); v0.setRGB(1, 0, 0);
Vertex v1 = new Vertex(); v1.setXYZ(-0.5f, -0.5f, 0f); v1.setRGB(0, 1, 0);
Vertex v2 = new Vertex(); v2.setXYZ(0.5f, -0.5f, 0f); v2.setRGB(0, 0, 1);
Vertex v3 = new Vertex(); v3.setXYZ(0.5f, 0.5f, 0f); v3.setRGB(1, 1, 1);

And now we simply create a new “FloatBuffer” and for each “Vertex” put the information in the buffer:

Vertex[] vertices = new Vertex[] {v0, v1, v2, v3};
// Put each 'Vertex' in one FloatBuffer
FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length *
		Vertex.elementCount);
for (int i = 0; i < vertices.length; i++) {
	verticesBuffer.put(vertices[i].getXYZW());
	verticesBuffer.put(vertices[i].getRGBA());
}
verticesBuffer.flip();

Now for my question from before, when using “glDrawElements” we have to create a new VBO that has the “element” order or indices. This is not changed from when we were not interleaving the order is still the same:

// OpenGL expects to draw vertices in counter clockwise order by default
byte[] indices = {
		0, 1, 2,
		2, 3, 0
};

Now we have to do our VAO and VBO work:

// Create a new Vertex Array Object in memory and select it (bind)
vaoId = GL30.glGenVertexArrays();
GL30.glBindVertexArray(vaoId);

// Create a new Vertex Buffer Object in memory and select it (bind)
vboId = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, verticesBuffer, GL15.GL_STATIC_DRAW);
// Put the positions in attribute list 0
GL20.glVertexAttribPointer(0, 4, GL11.GL_FLOAT, false, Vertex.sizeInBytes, 0);
// Put the colors in attribute list 1
GL20.glVertexAttribPointer(1, 4, GL11.GL_FLOAT, false, Vertex.sizeInBytes, 
		Vertex.elementBytes * 4);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);

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

No further changes are required. We’re still using 2 attribute lists, the thing that changed is that we’re using data from 1 VBO for 2 lists.

The result

The look of our quad has not changed, we’re simply changing the data layout internally.

Result Interleaved.png

Complete source code

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.ByteBuffer;
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 TheQuadExampleInterleaved {
	// Entry point for the application
	public static void main(String[] args) {
		new TheQuadExampleInterleaved();
	}
	
	// Setup variables
	private final String WINDOW_TITLE = "The Quad: Interleaved";
	private final int WIDTH = 320;
	private final int HEIGHT = 240;
	// Quad variables
	private int vaoId = 0;
	private int vboId = 0;
	private int vboiId = 0;
	private int indicesCount = 0;
	// Shader variables
	private int vsId = 0;
	private int fsId = 0;
	private int pId = 0;
	
	public TheQuadExampleInterleaved() {
		// Initialize OpenGL (Display)
		this.setupOpenGL();
		
		this.setupQuad();
		this.setupShaders();
		
		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);
	}
	
	public void setupQuad() {
		// We'll define our quad using 4 vertices of the custom 'Vertex' class
		Vertex v0 = new Vertex(); v0.setXYZ(-0.5f, 0.5f, 0f); v0.setRGB(1, 0, 0);
		Vertex v1 = new Vertex(); v1.setXYZ(-0.5f, -0.5f, 0f); v1.setRGB(0, 1, 0);
		Vertex v2 = new Vertex(); v2.setXYZ(0.5f, -0.5f, 0f); v2.setRGB(0, 0, 1);
		Vertex v3 = new Vertex(); v3.setXYZ(0.5f, 0.5f, 0f); v3.setRGB(1, 1, 1);
		
		Vertex[] vertices = new Vertex[] {v0, v1, v2, v3};
		// Put each 'Vertex' in one FloatBuffer
		FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length *
				Vertex.elementCount);
		for (int i = 0; i < vertices.length; i++) {
			verticesBuffer.put(vertices[i].getXYZW());
			verticesBuffer.put(vertices[i].getRGBA());
		}
		verticesBuffer.flip();
		
		// OpenGL expects to draw vertices in counter clockwise order by default
		byte[] indices = {
				0, 1, 2,
				2, 3, 0
		};
		indicesCount = indices.length;
		ByteBuffer indicesBuffer = BufferUtils.createByteBuffer(indicesCount);
		indicesBuffer.put(indices);
		indicesBuffer.flip();
		
		// Create a new Vertex Array Object in memory and select it (bind)
		vaoId = GL30.glGenVertexArrays();
		GL30.glBindVertexArray(vaoId);
		
		// Create a new Vertex Buffer Object in memory and select it (bind)
		vboId = GL15.glGenBuffers();
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
		GL15.glBufferData(GL15.GL_ARRAY_BUFFER, verticesBuffer, GL15.GL_STATIC_DRAW);
		// Put the positions in attribute list 0
		GL20.glVertexAttribPointer(0, 4, GL11.GL_FLOAT, false, Vertex.sizeInBytes, 0);
		// Put the colors in attribute list 1
		GL20.glVertexAttribPointer(1, 4, GL11.GL_FLOAT, false, Vertex.sizeInBytes, 
				Vertex.elementBytes * 4);
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
		
		// Deselect (bind to 0) the VAO
		GL30.glBindVertexArray(0);
		
		// Create a new VBO for the indices and select it (bind) - INDICES
		vboiId = GL15.glGenBuffers();
		GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboiId);
		GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL15.GL_STATIC_DRAW);
		GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
	}
	
	private void setupShaders() {
		int errorCheckValue = GL11.glGetError();
		
		// Load the vertex shader
		vsId = this.loadShader("src/thequad/vertex.glsl", GL20.GL_VERTEX_SHADER);
		// Load the fragment shader
		fsId = this.loadShader("src/thequad/fragment.glsl", GL20.GL_FRAGMENT_SHADER);
		
		// Create a new shader program that links both shaders
		pId = GL20.glCreateProgram();
		GL20.glAttachShader(pId, vsId);
		GL20.glAttachShader(pId, fsId);

		// Position information will be attribute 0
		GL20.glBindAttribLocation(pId, 0, "in_Position");
		// Color information will be attribute 1
		GL20.glBindAttribLocation(pId, 1, "in_Color");
		
		GL20.glLinkProgram(pId);
		GL20.glValidateProgram(pId);
		
		errorCheckValue = GL11.glGetError();
		if (errorCheckValue != GL11.GL_NO_ERROR) {
			System.out.println("ERROR - Could not create the shaders:" + GLU.gluErrorString(errorCheckValue));
			System.exit(-1);
		}
	}
	
	public void loopCycle() {
		GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
		
		GL20.glUseProgram(pId);
		
		// Bind to the VAO that has all the information about the vertices
		GL30.glBindVertexArray(vaoId);
		GL20.glEnableVertexAttribArray(0);
		GL20.glEnableVertexAttribArray(1);
		
		// Bind to the index VBO that has all the information about the order of the vertices
		GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboiId);
		
		// Draw the vertices
		GL11.glDrawElements(GL11.GL_TRIANGLES, indicesCount, GL11.GL_UNSIGNED_BYTE, 0);
		
		// Put everything back to default (deselect)
		GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
		GL20.glDisableVertexAttribArray(0);
		GL20.glDisableVertexAttribArray(1);
		GL30.glBindVertexArray(0);
		GL20.glUseProgram(0);
	}
	
	public void destroyOpenGL() {
		// Delete the shaders
		GL20.glUseProgram(0);
		GL20.glDetachShader(pId, vsId);
		GL20.glDetachShader(pId, fsId);
		
		GL20.glDeleteShader(vsId);
		GL20.glDeleteShader(fsId);
		GL20.glDeleteProgram(pId);
		
		// Select the VAO
		GL30.glBindVertexArray(vaoId);
		
		// Disable the VBO index from the VAO attributes list
		GL20.glDisableVertexAttribArray(0);
		GL20.glDisableVertexAttribArray(1);
		
		// Delete the vertex VBO
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
		GL15.glDeleteBuffers(vboId);
		
		// Delete the index VBO
		GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
		GL15.glDeleteBuffers(vboiId);
		
		// Delete the VAO
		GL30.glBindVertexArray(0);
		GL30.glDeleteVertexArrays(vaoId);
		
		Display.destroy();
	}
	
	public int loadShader(String filename, int type) {
		StringBuilder shaderSource = new StringBuilder();
		int shaderID = 0;
		
		try {
			BufferedReader reader = new BufferedReader(new FileReader(filename));
			String line;
			while ((line = reader.readLine()) != null) {
				shaderSource.append(line).append("\n");
			}
			reader.close();
		} catch (IOException e) {
			System.err.println("Could not read file.");
			e.printStackTrace();
			System.exit(-1);
		}
		
		shaderID = GL20.glCreateShader(type);
		GL20.glShaderSource(shaderID, shaderSource);
		GL20.glCompileShader(shaderID);

		if (GL20.glGetShader(shaderID, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) {
			System.err.println("Could not compile shader.");
			System.exit(-1);
		}
		
		return shaderID;
	}
}

Credit

Mathias Verboven (moci)