The Quad with Projection, View and Model matrices

Introduction
In OpenGL we have to do our own matrix calculations for translating, rotating and scaling the camera view and our model. Legacy OpenGL had a number of included matrix methods and helper functions but OpenGL is a graphics library with a main responsibility to draw things and not to provide a set of mathematical functions, now it is up to us to do these matrix manipulations ourself. In this tutorial we are going to use our shaders to calculate the position for each vertex according to some predefined transformation matrices.

So... what's a matrix?
If this is a question you’re asking it might be best to brush up on your mathematics, this tutorial will not explain what a matrix is, however it will show you how to approach the problem of moving an object. A better explanation (still with OpenGL in mind) can be found here.

3 Matrices
To move our model and manipulate our “camera” we will split the calculations up in 3 matrix multiplication, these are:
 * Projection matrix
 * View matrix
 * Model matrix

Projection matrix
The projection matrix is responsible for defining the area that is visible for the camera in 3D, it’s also called the frustum. You can think of it as a pyramid with the top cut off. You are looking into the pyramid from top to bottom so to speak, the width and height (usually) has the exact same aspect ratio as the rectangle we are drawing to (the viewport). We can define the depth of the pyramid, anything inside this pyramid will get drawn to the screen, the rest will be discarded by OpenGL to save performance. The bottom of the pyramid is a scaled version of the top (still has the same  aspect ratio), this scaling is defined by our “field of view”.

Think of the projection matrix as a mathematical representation of a camera lens. With this matrix we can define things such as the aspect ratio of the final image, a zoom factor and also how far into the distance we want to see. Graphically it looks like this:



If you remember our default OpenGL coordinate system had a range of [-1, 1] for each axis. Using the projection matrix we change these ranges. As an example, if we are using a widescreen format with an aspect ratio of 16/10 our height can still be of range [-1, 1] but our width will be grown to [-1,6; 1,6] to compensate. If we would not use the correct aspect ratio for our matrix the final image will look stretched.

The matrix itself looks like this:



In code:

 // Setup projection matrix projectionMatrix = new Matrix4f; float fieldOfView = 60f; float aspectRatio = (float)WIDTH / (float)HEIGHT; float near_plane = 0.1f; float far_plane = 100f;

float y_scale = this.coTangent(this.degreesToRadians(fieldOfView / 2f)); float x_scale = y_scale / aspectRatio; float frustum_length = far_plane - near_plane;

projectionMatrix.m00 = x_scale; projectionMatrix.m11 = y_scale; projectionMatrix.m22 = -((far_plane + near_plane) / frustum_length); projectionMatrix.m23 = -1; projectionMatrix.m32 = -((2 * near_plane * far_plane) / frustum_length); projectionMatrix.m33 = 0;

The 2 extra functions “cotangent” and “degreesToRadians” are simply helper functions. The cotangent of an angle is simply “1/Math.tan(angle)” and because we provide the field of view in degrees we have to convert it to radians (as the Java Math.tan function wants the angles in radians). Converting degrees to radians is simply multiplying by a factor of “PI/180”.

View matrix
Whereas the projection matrix can be viewed as the lens of the camera, the view matrix is the camera itself. We can rotate and position the camera accordingly. Multiplying the projection matrix with the view matrix will give us the complete mathematical definition of our camera.

Model matrix
Our final matrix is different for each model, namely our model matrix. Just like our camera we can also rotate, position and scale our model.

Manipulating matrices
LWJGL provides a set of static functions that can manipulate a matrix. In this example we will be using a 4x4 float matrix. The class is called “Matrix4f”. Using this class we can apply rotation, scale and translation (among other handy functions) like this:

 // Translate camera Matrix4f.translate(cameraPos, viewMatrix, viewMatrix);

In this code example we are translating (moving) our view matrix over a certain vector (cameraPos is of a Vector3 type), the resulting matrix will overwrite our current view matrix. Rotation and scaling are done in the same manner.

Moving, rotating and scaling our object
To manipulate our matrices we need Vector3 objects. We’ll define a Vector3 for each kind of manipulation:

 private Vector3f modelPos = null; private Vector3f modelAngle = null; private Vector3f modelScale = null;

When defining our quad we’ll also give some standard values for these vector:

 // Set the default quad rotation, scale and position values modelPos = new Vector3f(0, 0, 0); modelAngle = new Vector3f(0, 0, 0); modelScale = new Vector3f(1, 1, 1); cameraPos = new Vector3f(0, 0, -1);

During the logic part of our program loop we’ll create the complete model matrix, this has to be done in a specific order: scale, translate and rotate.

 // Scale, translate and rotate model Matrix4f.scale(modelScale, modelMatrix, modelMatrix); Matrix4f.translate(modelPos, modelMatrix, modelMatrix); Matrix4f.rotate(this.degreesToRadians(modelAngle.z), new Vector3f(0, 0, 1), 		modelMatrix, modelMatrix); Matrix4f.rotate(this.degreesToRadians(modelAngle.y), new Vector3f(0, 1, 0), 		modelMatrix, modelMatrix); Matrix4f.rotate(this.degreesToRadians(modelAngle.x), new Vector3f(1, 0, 0), 		modelMatrix, modelMatrix);

Uniform shader variables
What we are trying to do is calculate the correct position for each individual vertex, according to the 3 matrices. Therefore our vertex shader must have this information beforehand. We do this with “uniform” variables. A uniform variable is a variable that stays the same for the current render pipeline. Which is what we want, before we start rendering we’ll have to upload the new matrix data. We define the uniform matrices:


 * 1) version 150 core

uniform mat4 projectionMatrix; uniform mat4 viewMatrix; uniform mat4 modelMatrix;

in vec4 in_Position;

In our main method we’ll calculate the new vertex position, in_Position is the original vertex position:

 gl_Position = projectionMatrix * viewMatrix * modelMatrix * in_Position;

Our fragment shader does not have to know anything ever happened. “in” variables are linked to attribute lists, uniform variables however are not. We push data into these variables, so we’ll need to know where these variables reside in our shader (memory). We can request this information like so:

 // Get matrices uniform locations projectionMatrixLocation = GL20.glGetUniformLocation(pId, "projectionMatrix"); viewMatrixLocation = GL20.glGetUniformLocation(pId, "viewMatrix"); modelMatrixLocation = GL20.glGetUniformLocation(pId, "modelMatrix");

The locations are integer values that do not change once you’ve created the shader. So we have them globally defined as we’ll need this information when uploading the data.

Uploading data to uniform variables
So we’ve got our projection, view and model matrices ready. All we need to do now is upload it to the uniform variables. There are a few things we need to do first:


 * Store the Matrix4f data in a FloatBuffer, this FloatBuffer must hold 16 values
 * Flip the FloatBuffer...
 * Send that FloatBuffer to a uniform location

Storing the matrix is easy, we’ve defined a global FloatBuffer which can hold 16 values. Our matrices have a function called “store” that takes a FloatBuffer and stores the matrix information in that buffer. Flipping the FloatBuffer is something we always have to do and should be second nature by now.

The uniform location is the integer value we’ve requested when we created our shaders. We can upload the FloatBuffer using the “glUniformMatrix4” function. We’ll store, flip and upload our 3 matrices, we must also not forget to bind the shader:

 // Upload matrices to the uniform variables GL20.glUseProgram(pId);

projectionMatrix.store(matrix44Buffer); matrix44Buffer.flip; GL20.glUniformMatrix4(projectionMatrixLocation, false, matrix44Buffer); viewMatrix.store(matrix44Buffer); matrix44Buffer.flip; GL20.glUniformMatrix4(viewMatrixLocation, false, matrix44Buffer); modelMatrix.store(matrix44Buffer); matrix44Buffer.flip; GL20.glUniformMatrix4(modelMatrixLocation, false, matrix44Buffer);

GL20.glUseProgram(0);

The result


Note that while our viewport is not square, the quad is still rendered correctly without stretching. This is of course because we are using the projection matrix and it has adjusted it’s values thanks to the aspect ratio we’ve used.

Vertex shader

 * 1) version 150 core

uniform mat4 projectionMatrix; uniform mat4 viewMatrix; uniform mat4 modelMatrix;

in vec4 in_Position; in vec4 in_Color; in vec2 in_TextureCoord;

out vec4 pass_Color; out vec2 pass_TextureCoord;

void main(void) { gl_Position = in_Position; // Override gl_Position with our new calculated position gl_Position = projectionMatrix * viewMatrix * modelMatrix * in_Position; pass_Color = in_Color; pass_TextureCoord = in_TextureCoord; }

Fragment shader

 * 1) version 150 core

uniform sampler2D texture_diffuse;

in vec4 pass_Color; in vec2 pass_TextureCoord;

out vec4 out_Color;

void main(void) { out_Color = pass_Color; // Override out_Color with our texture pixel out_Color = texture2D(texture_diffuse, pass_TextureCoord); }

VertexData class
 public class VertexData { // Vertex data private float[] xyzw = new float[] {0f, 0f, 0f, 1f}; private float[] rgba = new float[] {1f, 1f, 1f, 1f}; private float[] st = new float[] {0f, 0f}; // The amount of bytes an element has public static final int elementBytes = 4; // Elements per parameter public static final int positionElementCount = 4; public static final int colorElementCount = 4; public static final int textureElementCount = 2; // Bytes per parameter public static final int positionBytesCount = positionElementCount * elementBytes; public static final int colorByteCount = colorElementCount * elementBytes; public static final int textureByteCount = textureElementCount * elementBytes; // Byte offsets per parameter public static final int positionByteOffset = 0; public static final int colorByteOffset = positionByteOffset + positionBytesCount; public static final int textureByteOffset = colorByteOffset + colorByteCount; // The amount of elements that a vertex has public static final int elementCount = positionElementCount + colorElementCount + textureElementCount; // The size of a vertex in bytes, like in C/C++: sizeof(Vertex) public static final int stride = positionBytesCount + colorByteCount + textureByteCount; // 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 setST(float s, float t) { this.st = new float[] {s, t}; }	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[] getElements { float[] out = new float[VertexData.elementCount]; int i = 0; // Insert XYZW elements out[i++] = this.xyzw[0]; out[i++] = this.xyzw[1]; out[i++] = this.xyzw[2]; out[i++] = this.xyzw[3]; // Insert RGBA elements out[i++] = this.rgba[0]; out[i++] = this.rgba[1]; out[i++] = this.rgba[2]; out[i++] = this.rgba[3]; // Insert ST elements out[i++] = this.st[0]; out[i++] = this.st[1]; return out; }	public float[] getXYZW { return new float[] {this.xyzw[0], this.xyzw[1], this.xyzw[2], this.xyzw[3]}; }	public float[] getXYZ { return new float[] {this.xyzw[0], this.xyzw[1], this.xyzw[2]}; }	public float[] getRGBA { return new float[] {this.rgba[0], this.rgba[1], this.rgba[2], this.rgba[3]}; }	public float[] getRGB { return new float[] {this.rgba[0], this.rgba[1], this.rgba[2]}; }	public float[] getST { return new float[] {this.st[0], this.st[1]}; } }

Application
 import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.FloatBuffer;

import org.lwjgl.BufferUtils; import org.lwjgl.LWJGLException; import org.lwjgl.input.Keyboard; import org.lwjgl.opengl.ContextAttribs; import org.lwjgl.opengl.Display; import org.lwjgl.opengl.DisplayMode; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL13; 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; import org.lwjgl.util.vector.Matrix4f; import org.lwjgl.util.vector.Vector3f;

import de.matthiasmann.twl.utils.PNGDecoder; import de.matthiasmann.twl.utils.PNGDecoder.Format;

public class TheQuadExampleMoving { // Entry point for the application public static void main(String[] args) { new TheQuadExampleMoving; }	// Setup variables private final String WINDOW_TITLE = "The Quad: Moving"; private final int WIDTH = 320; private final int HEIGHT = 200; private final double PI = 3.14159265358979323846; // Quad variables private int vaoId = 0; private int vboId = 0; private int vboiId = 0; private int indicesCount = 0; private VertexData[] vertices = null; private ByteBuffer verticesByteBuffer = null; // Shader variables private int pId = 0; // Texture variables private int[] texIds = new int[] {0, 0}; private int textureSelector = 0; // Moving variables private int projectionMatrixLocation = 0; private int viewMatrixLocation = 0; private int modelMatrixLocation = 0; private Matrix4f projectionMatrix = null; private Matrix4f viewMatrix = null; private Matrix4f modelMatrix = null; private Vector3f modelPos = null; private Vector3f modelAngle = null; private Vector3f modelScale = null; private Vector3f cameraPos = null; private FloatBuffer matrix44Buffer = null; public TheQuadExampleMoving { // Initialize OpenGL (Display) this.setupOpenGL; this.setupQuad; this.setupShaders; this.setupTextures; this.setupMatrices; 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; }

private void setupMatrices { // Setup projection matrix projectionMatrix = new Matrix4f; float fieldOfView = 60f; float aspectRatio = (float)WIDTH / (float)HEIGHT; float near_plane = 0.1f; float far_plane = 100f; float y_scale = this.coTangent(this.degreesToRadians(fieldOfView / 2f)); float x_scale = y_scale / aspectRatio; float frustum_length = far_plane - near_plane; projectionMatrix.m00 = x_scale; projectionMatrix.m11 = y_scale; projectionMatrix.m22 = -((far_plane + near_plane) / frustum_length); projectionMatrix.m23 = -1; projectionMatrix.m32 = -((2 * near_plane * far_plane) / frustum_length); projectionMatrix.m33 = 0; // Setup view matrix viewMatrix = new Matrix4f; // Setup model matrix modelMatrix = new Matrix4f; // Create a FloatBuffer with the proper size to store our matrices later matrix44Buffer = BufferUtils.createFloatBuffer(16); }

private void setupTextures { texIds[0] = this.loadPNGTexture("assets/images/stGrid1.png", GL13.GL_TEXTURE0); texIds[1] = this.loadPNGTexture("assets/images/stGrid2.png", GL13.GL_TEXTURE0); this.exitOnGLError("setupTexture"); }

private 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("setupOpenGL"); }	private void setupQuad { // We'll define our quad using 4 vertices of the custom 'TexturedVertex' class VertexData v0 = new VertexData; v0.setXYZ(-0.5f, 0.5f, 0); v0.setRGB(1, 0, 0); v0.setST(0, 0); VertexData v1 = new VertexData; v1.setXYZ(-0.5f, -0.5f, 0); v1.setRGB(0, 1, 0); v1.setST(0, 1); VertexData v2 = new VertexData; v2.setXYZ(0.5f, -0.5f, 0); v2.setRGB(0, 0, 1); v2.setST(1, 1); VertexData v3 = new VertexData; v3.setXYZ(0.5f, 0.5f, 0); v3.setRGB(1, 1, 1); v3.setST(1, 0); vertices = new VertexData[] {v0, v1, v2, v3}; // Put each 'Vertex' in one FloatBuffer verticesByteBuffer = BufferUtils.createByteBuffer(vertices.length * 				VertexData.stride); FloatBuffer verticesFloatBuffer = verticesByteBuffer.asFloatBuffer; for (int i = 0; i < vertices.length; i++) { // Add position, color and texture floats to the buffer verticesFloatBuffer.put(vertices[i].getElements); }		verticesFloatBuffer.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, verticesFloatBuffer, GL15.GL_STREAM_DRAW); // Put the position coordinates in attribute list 0 GL20.glVertexAttribPointer(0, VertexData.positionElementCount, GL11.GL_FLOAT, 				false, VertexData.stride, VertexData.positionByteOffset); // Put the color components in attribute list 1 GL20.glVertexAttribPointer(1, VertexData.colorElementCount, GL11.GL_FLOAT, 				false, VertexData.stride, VertexData.colorByteOffset); // Put the texture coordinates in attribute list 2 GL20.glVertexAttribPointer(2, VertexData.textureElementCount, GL11.GL_FLOAT, 				false, VertexData.stride, VertexData.textureByteOffset); 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); // Set the default quad rotation, scale and position values modelPos = new Vector3f(0, 0, 0); modelAngle = new Vector3f(0, 0, 0); modelScale = new Vector3f(1, 1, 1); cameraPos = new Vector3f(0, 0, -1); this.exitOnGLError("setupQuad"); }	private void setupShaders { // Load the vertex shader int vsId = this.loadShader("assets/shaders/moving/vertex.glsl", GL20.GL_VERTEX_SHADER); // Load the fragment shader int fsId = this.loadShader("assets/shaders/moving/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"); // Textute information will be attribute 2 GL20.glBindAttribLocation(pId, 2, "in_TextureCoord");

GL20.glLinkProgram(pId); GL20.glValidateProgram(pId);

// Get matrices uniform locations projectionMatrixLocation = GL20.glGetUniformLocation(pId,"projectionMatrix"); viewMatrixLocation = GL20.glGetUniformLocation(pId, "viewMatrix"); modelMatrixLocation = GL20.glGetUniformLocation(pId, "modelMatrix");

this.exitOnGLError("setupShaders"); }	private void logicCycle { //-- Input processing float rotationDelta = 15f; float scaleDelta = 0.1f; float posDelta = 0.1f; Vector3f scaleAddResolution = new Vector3f(scaleDelta, scaleDelta, scaleDelta); Vector3f scaleMinusResolution = new Vector3f(-scaleDelta, -scaleDelta, 				-scaleDelta); while(Keyboard.next) { // Only listen to events where the key was pressed (down event) if (!Keyboard.getEventKeyState) continue; // Switch textures depending on the key released switch (Keyboard.getEventKey) { case Keyboard.KEY_1: textureSelector = 0; break; case Keyboard.KEY_2: textureSelector = 1; break; }			// Change model scale, rotation and translation values switch (Keyboard.getEventKey) { // Move case Keyboard.KEY_UP: modelPos.y += posDelta; break; case Keyboard.KEY_DOWN: modelPos.y -= posDelta; break; // Scale case Keyboard.KEY_P: Vector3f.add(modelScale, scaleAddResolution, modelScale); break; case Keyboard.KEY_M: Vector3f.add(modelScale, scaleMinusResolution, modelScale); break; // Rotation case Keyboard.KEY_LEFT: modelAngle.z += rotationDelta; break; case Keyboard.KEY_RIGHT: modelAngle.z -= rotationDelta; break; }		}		//-- Update matrices // Reset view and model matrices viewMatrix = new Matrix4f; modelMatrix = new Matrix4f; // Translate camera Matrix4f.translate(cameraPos, viewMatrix, viewMatrix); // Scale, translate and rotate model Matrix4f.scale(modelScale, modelMatrix, modelMatrix); Matrix4f.translate(modelPos, modelMatrix, modelMatrix); Matrix4f.rotate(this.degreesToRadians(modelAngle.z), new Vector3f(0, 0, 1), 				modelMatrix, modelMatrix); Matrix4f.rotate(this.degreesToRadians(modelAngle.y), new Vector3f(0, 1, 0), 				modelMatrix, modelMatrix); Matrix4f.rotate(this.degreesToRadians(modelAngle.x), new Vector3f(1, 0, 0), 				modelMatrix, modelMatrix); // Upload matrices to the uniform variables GL20.glUseProgram(pId); projectionMatrix.store(matrix44Buffer); matrix44Buffer.flip; GL20.glUniformMatrix4(projectionMatrixLocation, false, matrix44Buffer); viewMatrix.store(matrix44Buffer); matrix44Buffer.flip; GL20.glUniformMatrix4(viewMatrixLocation, false, matrix44Buffer); modelMatrix.store(matrix44Buffer); matrix44Buffer.flip; GL20.glUniformMatrix4(modelMatrixLocation, false, matrix44Buffer); GL20.glUseProgram(0); this.exitOnGLError("logicCycle"); }	private void renderCycle { GL11.glClear(GL11.GL_COLOR_BUFFER_BIT); GL20.glUseProgram(pId); // Bind the texture GL13.glActiveTexture(GL13.GL_TEXTURE0); GL11.glBindTexture(GL11.GL_TEXTURE_2D, texIds[textureSelector]); // Bind to the VAO that has all the information about the vertices GL30.glBindVertexArray(vaoId); GL20.glEnableVertexAttribArray(0); GL20.glEnableVertexAttribArray(1); GL20.glEnableVertexAttribArray(2); // 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); GL20.glDisableVertexAttribArray(2); GL30.glBindVertexArray(0); GL20.glUseProgram(0); this.exitOnGLError("renderCycle"); }	private void loopCycle { // Update logic this.logicCycle; // Update rendered frame this.renderCycle; this.exitOnGLError("loopCycle"); }	private void destroyOpenGL { // Delete the texture GL11.glDeleteTextures(texIds[0]); GL11.glDeleteTextures(texIds[1]); // Delete the shaders GL20.glUseProgram(0); 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); this.exitOnGLError("destroyOpenGL"); Display.destroy; }	private 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); }		this.exitOnGLError("loadShader"); return shaderID; }	private int loadPNGTexture(String filename, int textureUnit) { ByteBuffer buf = null; int tWidth = 0; int tHeight = 0; try { // Open the PNG file as an InputStream InputStream in = new FileInputStream(filename); // Link the PNG decoder to this stream PNGDecoder decoder = new PNGDecoder(in); // Get the width and height of the texture tWidth = decoder.getWidth; tHeight = decoder.getHeight; // Decode the PNG file in a ByteBuffer buf = ByteBuffer.allocateDirect(					4 * decoder.getWidth * decoder.getHeight); decoder.decode(buf, decoder.getWidth * 4, Format.RGBA); buf.flip; in.close; } catch (IOException e) { e.printStackTrace; System.exit(-1); }		// Create a new texture object in memory and bind it		int texId = GL11.glGenTextures; GL13.glActiveTexture(textureUnit); GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId); // All RGB bytes are aligned to each other and each component is 1 byte GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); // Upload the texture data and generate mip maps (for scaling) GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, tWidth, tHeight, 0, 				GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buf); GL30.glGenerateMipmap(GL11.GL_TEXTURE_2D); // Setup the ST coordinate system GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT); GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT); // Setup what to do when the texture has to be scaled GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, 				GL11.GL_LINEAR); GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, 				GL11.GL_LINEAR_MIPMAP_LINEAR); this.exitOnGLError("loadPNGTexture"); return texId; }	private float coTangent(float angle) { return (float)(1f / Math.tan(angle)); }	private float degreesToRadians(float degrees) { return degrees * (float)(PI / 180d); }	private 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)