Rendering with glDrawArrays - Fish-In-A-Suit/Conquest GitHub Wiki
Prerequisites:
Code flow - from start to finish:
This class (better said, code example) doesn't follow any principles of object oriented programming. It's just here to show a general flow of the program to render a quad onto the window, from start to finish.
import java.nio.FloatBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.opengl.GL;
import static org.lwjgl.system.MemoryUtil.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.GL_FALSE;
import static org.lwjgl.opengl.GL11.GL_TRUE;
import static org.lwjgl.opengl.GL.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;
public class Quad {
public static void main(String[] args) {
new Quad().start();
}
private long window;
private final String WINDOW_TITLE = "The Quad: glDrawArrays";
private final int WIDTH = 1920;
private final int HEIGHT = 1080;
//quad variables
private int vaoId = 0;
private int vboId = 0;
private int vertexCount = 0;
private boolean running = false;
public void start() {
running = true;
setupOpenGL();
setupQuad();
while (running) {
loopCycle();
update();
if(glfwWindowShouldClose(window)) {
running = false;
}
}
destroyOpenGL();
}
public void setupOpenGL() {
if(!glfwInit()) {
throw new IllegalStateException("Unable to initialize GLFW!");
}
glfwDefaultWindowHints();
glfwWindowHint(GLFW_VISIBLE, GL_FALSE);
glfwWindowHint(GLFW_RESIZABLE, GL_TRUE);
window = glfwCreateWindow(WIDTH, HEIGHT, WINDOW_TITLE, NULL, NULL);
if (window == NULL) {
throw new RuntimeException("Cannot create window!");
}
glfwMakeContextCurrent(window);
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
glfwSetWindowPos(window, (vidmode.width() - WIDTH) / 2, (vidmode.height() - HEIGHT) / 2);
glfwShowWindow(window);
GL.createCapabilities();
}
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
};
FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length);
verticesBuffer.put(vertices);
verticesBuffer.flip();
vertexCount = 6;
vaoId = glGenVertexArrays();
glBindVertexArray(vaoId);
vboId = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
public void loopCycle() {
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBindVertexArray(vaoId);
glEnableVertexAttribArray(0);
glDrawArrays(GL_TRIANGLES, 0, vertexCount);
glDisableVertexAttribArray(0);
glBindVertexArray(0);
}
public void destroyOpenGL() {
// Disable the VBO index from the VAO attributes list
glDisableVertexAttribArray(0);
// Delete the VBO
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteBuffers(vboId);
// Delete the VAO
glBindVertexArray(0);
glDeleteVertexArrays(vaoId);
glfwDestroyWindow(window);
glfwTerminate();
}
public void update() {
glfwSwapBuffers(window);
glfwPollEvents();
}
}
Implementation
First, create a Mesh class, whose main job is to collect an array of vertices (points in 3D space) and store them in the video RAM (VRAM) on the GPU.
package renderEngine;
import java.nio.FloatBuffer;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;
import org.lwjgl.BufferUtils;
public class Mesh {
private int vaoID;
private int verticesVboID;
private int vertexCount;
private FloatBuffer verticesBuffer;
public Mesh(float[] vertices) {
// FloatBuffer verticesBuffer = null;
vertexCount = vertices.length/3;
vaoID = glGenVertexArrays();
glBindVertexArray(vaoID);
verticesVboID = glGenBuffers();
verticesBuffer = BufferUtils.createFloatBuffer(vertices.length);
verticesBuffer.put(vertices);
checkVerticesBufferContent(vertices);
verticesBuffer.flip();
System.out.println("State of verticesBuffer after being flipped: " + verticesBuffer.toString());
System.out.println("Number of vertices: " + vertexCount);
glBindBuffer(GL_ARRAY_BUFFER, verticesVboID);
glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
public int getVaoID() {
return vaoID;
}
public int getVertexCount() {
return vertexCount;
}
public int getVerticesVboID() {
return verticesVboID;
}
public void checkVerticesBufferContent(float vertices[]) {
int i = 0;
System.out.print("The contents of vertices array: ");
for (float vertex : vertices) {
System.out.print("[" + ++i + "]" + verticesBuffer.get((int)vertex) + ", ");
}
System.out.println();
System.out.println("State of verticesBuffer after putting data in but prior to flipping it: " +
verticesBuffer.toString());
/*System.out.println("The contents of verticesBuffer:"
+ "[0]: " + verticesBuffer.get(0) + ", "
+ "[1]: " + verticesBuffer.get(1) + ", "
+ "[2]: " + verticesBuffer.get(2) + ", "
+ "[3]: " + verticesBuffer.get(3) + ", "
+ "[4]: " + verticesBuffer.get(4) + ", "
+ "[5]: " + verticesBuffer.get(5) + ", "
+ "[6]: " + verticesBuffer.get(6) + ", "
+ "[7]: " + verticesBuffer.get(7) + ", "
+ "[8]: " + verticesBuffer.get(8) + ", "
+ "[9]: " + verticesBuffer.get(9) + ", "
+ "[10]: " + verticesBuffer.get(10) + ", "
+ "[11]: " + verticesBuffer.get(11) + ", "
);*/
if(verticesBuffer.hasArray()) {
System.out.println("verticesBuffer is backed by an accessible float array");
} else {
System.out.println("verticesBuffer isn't backed by an accessible float array!");
}
}
public void cleanUp() {
glDisableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteBuffers(verticesVboID);
glBindVertexArray(0);
glDeleteVertexArrays(vaoID);
}
}
EXPLANATION FOR THE MESH CLASS HERE.
Then, create a Renderer class. It's main duty is to render the vertices submitted to it through an instance of Mesh (an instance of mesh with loaded vertices is passed to the constructor of Renderer class):
package renderEngine;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;
/**
*
* @author Aljoša
*
* This class deals with rendering to the window
*/
public class Renderer {
public void clear() {
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
public void render(Window window, Mesh mesh) {
if (window.isResized()) {
glViewport(0, 0, window.getWidth(), window.getHeight());
window.setResized(false);
}
//System.out.println("vaoID of mesh prior to binding it in the render(...) method: " + mesh.getVaoID());
glBindVertexArray(mesh.getVaoID());
glEnableVertexAttribArray(0);
//System.out.println("vertexCount of mesh prior to glDrawArrays: " + mesh.getVertexCount());
glDrawArrays(GL_TRIANGLES, 0, mesh.getVertexCount());
glDisableVertexAttribArray(0);
glBindVertexArray(0);
}
}
EXPLANATION FOR THE RENDERER CLASS HERE!
Then, in the Main class, first create a "test" float array of vertices as an instance field. Inside the run() method, prior to the main game loop, create a new instance of Mesh and pass in that array of floats. Then, inside the game loop, call renderer.clear() and renderer.render(window, mesh).
The main class:
package main;
import static org.lwjgl.glfw.GLFW.*;
import renderEngine.Mesh;
import renderEngine.Renderer;
//import input.Input;
import renderEngine.Window;
public class Main implements Runnable {
private boolean running = false;
private boolean assertions = false;
private Thread renderingThread;
private Window window = new Window();
private Renderer renderer = new Renderer();
private Mesh mesh;
float positions[] = {
// 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
};
private void start() {
running = true;
renderingThread = new Thread(this, "renderingThread");
renderingThread.start();
}
public void run() {
window.init();
setupMesh(positions);
long lastTime = System.nanoTime();
double delta = 0.0;
double ns = 1000000000.0 / 60.0;
long timer = System.currentTimeMillis();
int updates = 0;
int frames = 0;
while(running) {
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
if (delta >= 1.0) {
update();
updates ++;
delta--;
}
renderer.clear();
renderer.render(window, mesh);
frames++;
if (System.currentTimeMillis() - timer > 1000) {
timer += 1000;
System.out.println(updates + " ups, " + frames + " fps");
updates = 0;
frames = 0;
if (assertions == true) {
runAssertions();
}
}
if(glfwWindowShouldClose(window.windowHandle)) {
glfwTerminate();
running = false;
glfwDestroyWindow(window.windowHandle);
window.keycallback.free();
}
}
}
public void update() {
glfwSwapBuffers(window.windowHandle);
glfwPollEvents();
if (window.keys[GLFW_KEY_SPACE] == true) {
System.out.println("SPACEBAR was pressed");
}
}
public void setupMesh(float[] vertices) {
mesh = new Mesh(vertices);
}
public void runAssertions() {
if (mesh == null) {
System.out.println("Mesh == null!");
} else {
System.out.println("Mesh exists");
}
System.out.println("vaoID of mesh: " + mesh.getVaoID());
System.out.println("Width: " + window.getWidth() + ", Height: " + window.getHeight() );
}
public static void main(String[] args) {
new Main().start();
}
}