The Quad colored

From LWJGL
Jump to: navigation, search

Contents

Introduction

In this tutorial we’ll introduce colors. Each quad corner will have a different color assigned to it, this means we’ll have to use shaders. Again we are basing this tutorial on a previous tutorial that uses “glDrawElements” to draw our quad. We’ll see how we can define color for our vertices and how to apply them in our shaders. Let’s get to it.

4 components instead of 3?

OpenGL internally uses vectors that have 4 values, when defining a position we only need to declare the X, Y and Z components. But actually we have a 4th that also in use namely W. The same goes for colors, while we can have an R, G and B component for each pixel we also have an alpha value (A). Our shaders eventually will deal with a 4 component vector so therefore we’ll use them in our application code as well.

Vertex position and color definitions

Our vertex will have 2 types of data one for the position and one for the colors. Up until now we’ve always used one VBO that had our positional information, for “glDrawElements” to work we had to create another VBO that had the indices or element data. Now we’ll create yet another VBO to hold our color information.

As we’ll use 4 components for each position and color here’s how we define them, remember position is XYZW and color is RGBA:

// Vertices, the order is not important. XYZW instead of XYZ
float[] vertices = {
		-0.5f, 0.5f, 0f, 1f,
		-0.5f, -0.5f, 0f, 1f,
		0.5f, -0.5f, 0f, 1f,
		0.5f, 0.5f, 0f, 1f
};
FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length);
verticesBuffer.put(vertices);
verticesBuffer.flip();

float[] colors = {
		1f, 0f, 0f, 1f,
		0f, 1f, 0f, 1f,
		0f, 0f, 1f, 1f,
		1f, 1f, 1f, 1f,
};
FloatBuffer colorsBuffer = BufferUtils.createFloatBuffer(colors.length);
colorsBuffer.put(colors);
colorsBuffer.flip();

Because we have 16 attribute lists and we’ve got one already in use for our position information (attribute list 0) we’ll now need to use a new list for our colors (attribute list 1):

// Create a new VBO for the indices and select it (bind) - COLORS
vbocId = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbocId);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, colorsBuffer, GL15.GL_STATIC_DRAW);
GL20.glVertexAttribPointer(1, 4, GL11.GL_FLOAT, false, 0, 0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);

Again we’re using an GL_ARRAY_BUFFER but instead of 3 elements for RGB we’re using 4: RGBA. So we’ll have to tell this to OpenGL in our “glVertexAttribPointer” method, we’re still using floats. The VBO for our positional information is the same as before but now also with 4 values per definition instead of 3.

Shaders

Shaders are like mini applications running inside of your main program. There are different kinds of shaders we are going to use a vertex and fragment shader. The vertex shader will get run for each individual vertex. The Fragment shader will run for each individual pixel. The fragment shader will get run after all of the vertices have been processed. We can however push information from our vertex shaders to our fragment shaders.

The programming language used for these shaders is called GLSL (OpenGL Shading Language). As we are using OpenGL 3.2 the GLSL will be 1.5.0, also we’re using the core profile so our GLSL will also be the core version.

How the files are named and which extension is used is of no importance, I prefer to make the extension “glsl”.

Vertex Shader

In our vertex shader we are going to do 2 things, define the position of the shader and apply a color to it (I’ve named my vertex shader ‘vertex.glsl’). To do these 2 things we’ll have to push our vertex information that resides in our main application to the vertex shader. We can do this by defining “in” variables (note that they are a vector type with 4 components):

in vec4 in_Position;
in vec4 in_Color;

There are also “out” variables, these variables will get pushed to our fragment shader. Our vertex shader will do nothing, we’ll use the position we’ve defined in our program and we’ll pass the color to our fragment shader. Here’s the complete vertex shader:

#version 150 core

in vec4 in_Position;
in vec4 in_Color;

out vec4 pass_Color;

void main(void) {
	gl_Position = in_Position;
	pass_Color = in_Color;
}

Fragment shader

If we want to retrieve the “pass_Color” we’ll also have to define it as an “in” variable in our shader:

in vec4 pass_Color;

I’ve named my fragment shader ‘fragment.glsl’, here’s the complete shader:

#version 150 core

in vec4 pass_Color;

out vec4 out_Color;

void main(void) {
	out_Color = pass_Color;
}

Again we just apply the color as is.

Shader program

To use these 2 shaders and allow communication between them we’ll have to set up a shader “program”. This program will be made up of the different shaders that will be working together. We’ll have to compile our shaders (don’t worry this compilation is done via the driver so we don’t need any additional tools), attach them to the program and link them together.

Loading a shader is simply enough, we just have to read the shader source line by line. Create a shader ID (just like we have to create ID’s for other objects), put the source in the defined shader and compile it. A generic shader loading method is the following:

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);
	
	return shaderID;
}

This method needs a type (is it a vertex, fragment, … shader?) and a filename. It will return the shader ID because that is all we’ll need once the shader is compiled – which this function does as well.

Again we’re going to save a number of ID’s for our shaders. 2 shader ID’s and 1 program ID. We’ll declare them globally as vsId, fsId and pId. Loading the shaders is easy when we’ve got our generic method ready:

// 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);

Don’t forget what kind of shader you are loading, as we’ll have to give it a correct shader type. Next up is creating our program and attaching our shaders to this program:

// Create a new shader program that links both shaders
pId = GL20.glCreateProgram();
GL20.glAttachShader(pId, vsId);
GL20.glAttachShader(pId, fsId);

Remember that we’ve declared 2 ‘in’ variables in our vertex shader, this information has to come from somewhere and OpenGL has to know where that somewhere is. So we’ll bind the attribute lists of our VAO’s to these variables. These lists are arrays but because we’ve told OpenGL how many items it needs to read for each vertex definition it will provide only the relevant information for each instance of the vertex shader (as each vertex will be processed, usually in parallel, by the vertex shader).

// Position information will be attribute 0
GL20.glBindAttribLocation(pId, 0, "in_Position");
// Color information will be attribute 1
GL20.glBindAttribLocation(pId, 1, "in_Color");

As a final step we can link and validate our program:

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

Ok we’ve got our shaders setup. On to rendering.

Rendering with shaders

Rendering with shaders is not much different. We just have to make sure we bind the shader before we draw our vertices, simple. We “bind” a specific shader by calling “glUseProgram” and giving it the program ID. To unbind the shader we simply use a program ID of 0:

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);

Cleaning up our memory

We’ll clean up a few more objects when exiting our application. Our color VBO, the shaders and the shader program:

// Delete the shaders
GL20.glUseProgram(0);
GL20.glDetachShader(pId, vsId);
GL20.glDetachShader(pId, fsId);

GL20.glDeleteShader(vsId);
GL20.glDeleteShader(fsId);
GL20.glDeleteProgram(pId);
// Delete the color VBO
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL15.glDeleteBuffers(vbocId);

The result

The colored quad is our reward:

Result Colored.png

Complete source code

Vertex shader

#version 150 core

in vec4 in_Position;
in vec4 in_Color;

out vec4 pass_Color;

void main(void) {
	gl_Position = in_Position;
	pass_Color = in_Color;
}

Fragment shader

#version 150 core

in vec4 pass_Color;

out vec4 out_Color;

void main(void) {
	out_Color = pass_Color;
}

Program

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 TheQuadExampleColored {
	// Entry point for the application
	public static void main(String[] args) {
		new TheQuadExampleColored();
	}
	
	// Setup variables
	private final String WINDOW_TITLE = "The Quad: colored";
	private final int WIDTH = 320;
	private final int HEIGHT = 240;
	// Quad variables
	private int vaoId = 0;
	private int vboId = 0;
	private int vbocId = 0;
	private int vboiId = 0;
	private int indicesCount = 0;
	// Shader variables
	private int vsId = 0;
	private int fsId = 0;
	private int pId = 0;
	
	public TheQuadExampleColored() {
		// 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() {
		// Vertices, the order is not important. XYZW instead of XYZ
		float[] vertices = {
				-0.5f, 0.5f, 0f, 1f,
				-0.5f, -0.5f, 0f, 1f,
				0.5f, -0.5f, 0f, 1f,
				0.5f, 0.5f, 0f, 1f
		};
		FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length);
		verticesBuffer.put(vertices);
		verticesBuffer.flip();
		
		float[] colors = {
				1f, 0f, 0f, 1f,
				0f, 1f, 0f, 1f,
				0f, 0f, 1f, 1f,
				1f, 1f, 1f, 1f,
		};
		FloatBuffer colorsBuffer = BufferUtils.createFloatBuffer(colors.length);
		colorsBuffer.put(colors);
		colorsBuffer.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) - VERTICES
		vboId = GL15.glGenBuffers();
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
		GL15.glBufferData(GL15.GL_ARRAY_BUFFER, verticesBuffer, GL15.GL_STATIC_DRAW);
		GL20.glVertexAttribPointer(0, 4, GL11.GL_FLOAT, false, 0, 0);
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
		
		// Create a new VBO for the indices and select it (bind) - COLORS
		vbocId = GL15.glGenBuffers();
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbocId);
		GL15.glBufferData(GL15.GL_ARRAY_BUFFER, colorsBuffer, GL15.GL_STATIC_DRAW);
		GL20.glVertexAttribPointer(1, 4, GL11.GL_FLOAT, false, 0, 0);
		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 color VBO
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
		GL15.glDeleteBuffers(vbocId);
		
		// 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);
		
		return shaderID;
	}
}

Credit

Mathias Verboven (moci)

Personal tools
Namespaces

Variants
Actions
Navigation
Tools