├── README.md └── src ├── engineTester └── MainGameLoop.java └── renderEngine ├── DisplayManager.java ├── Loader.java ├── RawModel.java └── Renderer.java /README.md: -------------------------------------------------------------------------------- 1 | # OpenGL-Tutorial-2 2 | Code from the OpenGL tutorial on Youtube: https://youtu.be/WMiggUPst-Q 3 | 4 | This code requires the lwjgl.jar and lwjgl_utils.jar from LWJGL 2, along with the relevant natives. Here is a tutorial about setting up the project: https://youtu.be/Jdkq-aSFEA0 5 | 6 | In this tutorial we look at loading model data into a VAO and rendering it to the screen. 7 | 8 | This tutorial covers: 9 | 10 | -Loading geometry data into VAOs/VBOs. 11 | 12 | -Rendering geometry using VAO. 13 | -------------------------------------------------------------------------------- /src/engineTester/MainGameLoop.java: -------------------------------------------------------------------------------- 1 | package engineTester; 2 | 3 | import org.lwjgl.opengl.Display; 4 | 5 | import renderEngine.DisplayManager; 6 | import renderEngine.Loader; 7 | import renderEngine.RawModel; 8 | import renderEngine.Renderer; 9 | 10 | /** 11 | * This class contains the main method and is used to test the engine. 12 | * 13 | * @author Karl 14 | * 15 | */ 16 | public class MainGameLoop { 17 | 18 | 19 | /** 20 | * Loads up the position data for two triangles (which together make a quad) 21 | * into a VAO. This VAO is then rendered to the screen every frame. 22 | * 23 | * @param args 24 | */ 25 | public static void main(String[] args) { 26 | 27 | DisplayManager.createDisplay(); 28 | Loader loader = new Loader(); 29 | Renderer renderer = new Renderer(); 30 | 31 | float[] vertices = { 32 | // Left bottom triangle 33 | -0.5f, 0.5f, 0f, 34 | -0.5f, -0.5f, 0f, 35 | 0.5f, -0.5f, 0f, 36 | // Right top triangle 37 | 0.5f, -0.5f, 0f, 38 | 0.5f, 0.5f, 0f, 39 | -0.5f, 0.5f, 0f 40 | }; 41 | 42 | RawModel model = loader.loadToVAO(vertices); 43 | 44 | while (!Display.isCloseRequested()) { 45 | // game logic 46 | renderer.prepare(); 47 | renderer.render(model); 48 | DisplayManager.updateDisplay(); 49 | } 50 | 51 | loader.cleanUp(); 52 | DisplayManager.closeDisplay(); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/renderEngine/DisplayManager.java: -------------------------------------------------------------------------------- 1 | package renderEngine; 2 | 3 | import org.lwjgl.LWJGLException; 4 | import org.lwjgl.opengl.ContextAttribs; 5 | import org.lwjgl.opengl.Display; 6 | import org.lwjgl.opengl.DisplayMode; 7 | import org.lwjgl.opengl.GL11; 8 | import org.lwjgl.opengl.PixelFormat; 9 | 10 | /** 11 | * This class contains all the methods needed to set-up, maintain, and close a LWJGL display. 12 | * 13 | * @author Karl 14 | * 15 | */ 16 | public class DisplayManager { 17 | 18 | private static final int WIDTH = 1280; 19 | private static final int HEIGHT = 720; 20 | private static final int FPS_CAP = 60; 21 | private static final String TITLE = "Our First Display"; 22 | 23 | /** 24 | * Creates a display window on which we can render our game. The dimensions 25 | * of the window are determined by setting the display mode. By using 26 | * "glViewport" we tell OpenGL which part of the window we want to render 27 | * our game onto. We indicated that we want to use the entire window. 28 | */ 29 | public static void createDisplay() { 30 | ContextAttribs attribs = new ContextAttribs(3, 2).withForwardCompatible(true).withProfileCore(true); 31 | try { 32 | Display.setDisplayMode(new DisplayMode(WIDTH, HEIGHT)); 33 | Display.create(new PixelFormat(), attribs); 34 | Display.setTitle(TITLE); 35 | } catch (LWJGLException e) { 36 | e.printStackTrace(); 37 | } 38 | GL11.glViewport(0, 0, WIDTH, HEIGHT); 39 | } 40 | 41 | /** 42 | * This method is used to update the display at the end of every frame. When 43 | * we have set up a rendering process this method will display whatever 44 | * we've been rendering onto the screen. The "sync" method is used here to 45 | * cap the frame rate. Without this the computer would just try to run the 46 | * game as fast as it possibly can, doing more work than it needs to. 47 | */ 48 | public static void updateDisplay() { 49 | Display.sync(FPS_CAP); 50 | Display.update(); 51 | } 52 | 53 | /** 54 | * This closes the window when the game is closed. 55 | */ 56 | public static void closeDisplay() { 57 | Display.destroy(); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/renderEngine/Loader.java: -------------------------------------------------------------------------------- 1 | package renderEngine; 2 | 3 | import java.nio.FloatBuffer; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import org.lwjgl.BufferUtils; 8 | import org.lwjgl.opengl.GL11; 9 | import org.lwjgl.opengl.GL15; 10 | import org.lwjgl.opengl.GL20; 11 | import org.lwjgl.opengl.GL30; 12 | 13 | /** 14 | * Handles the loading of geometry data into VAOs. It also keeps track of all 15 | * the created VAOs and VBOs so that they can all be deleted when the game 16 | * closes. 17 | * 18 | * @author Karl 19 | * 20 | */ 21 | public class Loader { 22 | 23 | private List vaos = new ArrayList(); 24 | private List vbos = new ArrayList(); 25 | 26 | /** 27 | * Creates a VAO and stores the position data of the vertices into attribute 28 | * 0 of the VAO. 29 | * 30 | * @param positions 31 | * - The 3D positions of each vertex in the geometry (in this 32 | * example a quad). 33 | * @return The loaded model. 34 | */ 35 | public RawModel loadToVAO(float[] positions) { 36 | int vaoID = createVAO(); 37 | storeDataInAttributeList(0, positions); 38 | unbindVAO(); 39 | return new RawModel(vaoID, positions.length / 3); 40 | } 41 | 42 | /** 43 | * Deletes all the VAOs and VBOs when the game is closed. VAOs and VBOs are 44 | * located in video memory. 45 | */ 46 | public void cleanUp() { 47 | for (int vao : vaos) { 48 | GL30.glDeleteVertexArrays(vao); 49 | } 50 | for (int vbo : vbos) { 51 | GL15.glDeleteBuffers(vbo); 52 | } 53 | } 54 | 55 | /** 56 | * Creates a new VAO and returns its ID. A VAO holds geometry data that we 57 | * can render and is physically stored in memory on the GPU, so that it can 58 | * be accessed very quickly during rendering. 59 | * 60 | * Like most objects in OpenGL, the new VAO is created using a "gen" method 61 | * which returns the ID of the new VAO. In order to use the VAO it needs to 62 | * be made the active VAO. Only one VAO can be active at a time. To make 63 | * this VAO the active VAO (so that we can store stuff in it) we have to 64 | * bind it. 65 | * 66 | * @return The ID of the newly created VAO. 67 | */ 68 | private int createVAO() { 69 | int vaoID = GL30.glGenVertexArrays(); 70 | vaos.add(vaoID); 71 | GL30.glBindVertexArray(vaoID); 72 | return vaoID; 73 | } 74 | 75 | /** 76 | * Stores the position data of the vertices into attribute 0 of the VAO. To 77 | * do this the positions must first be stored in a VBO. You can simply think 78 | * of a VBO as an array of data that is stored in memory on the GPU for easy 79 | * access during rendering. 80 | * 81 | * Just like with the VAO, we create a new VBO using a "gen" method, and 82 | * make it the active VBO (so that we do stuff to it) by binding it. 83 | * 84 | * We then store the positions data in the active VBO by using the 85 | * glBufferData method. We also indicate using GL_STATIC_DRAW that this data 86 | * won't need to be changed. If we wanted to edit the positions every frame 87 | * (perhaps to animate the quad) then we would use GL_DYNAMIC_DRAW instead. 88 | * 89 | * We the connect the VBO to the VAO using the glVertexAttribPointer() 90 | * method. This needs to know the attribute number of the VAO where we want 91 | * to put the data, the number of floats used for each vertex (3 floats in 92 | * this case, because each vertex has a 3D position, an x, y, and z value), 93 | * the type of data (in this case we used floats) and then some other more 94 | * complicated stuff for storing the data in more fancy ways. Don't worry 95 | * about the last 3 parameters for now, we don't need them here. 96 | * 97 | * Now that we've finished using the VBO we can unbind it. This isn't 98 | * totally necessary, but I think it's good practice to unbind the VBO when 99 | * you're done using it. 100 | * 101 | * @param attributeNumber 102 | * - The number of the attribute of the VAO where the data is to 103 | * be stored. 104 | * @param data 105 | * - The geometry data to be stored in the VAO, in this case the 106 | * positions of the vertices. 107 | */ 108 | private void storeDataInAttributeList(int attributeNumber, float[] data) { 109 | int vboID = GL15.glGenBuffers(); 110 | vbos.add(vboID); 111 | GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboID); 112 | FloatBuffer buffer = storeDataInFloatBuffer(data); 113 | GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW); 114 | GL20.glVertexAttribPointer(attributeNumber, 3, GL11.GL_FLOAT, false, 0, 0); 115 | GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); 116 | } 117 | 118 | /** 119 | * Unbinds the VAO after we're finished using it. If we want to edit or use 120 | * the VAO we would have to bind it again first. 121 | */ 122 | private void unbindVAO() { 123 | GL30.glBindVertexArray(0); 124 | } 125 | 126 | /** 127 | * Before we can store data in a VBO it needs to be in a certain format: in 128 | * a buffer. In this case we will use a float buffer because the data we 129 | * want to store is float data. If we were storing int data we would use an 130 | * IntBuffer. 131 | * 132 | * First and empty buffer of the correct size is created. You can think of a 133 | * buffer as basically an array with a pointer. After putting the necessary 134 | * data into the buffer the pointer will have increased so that it points at 135 | * the first empty element of the array. This is so that we could add more 136 | * data to the buffer if we wanted and it wouldn't overwrite the data we've 137 | * already put in. However, we're done with storing data and we want to make 138 | * the buffer ready for reading. To do this we need to make the pointer 139 | * point to the start of the data, so that OpenGL knows where in the buffer 140 | * to start reading. The "flip()" method does just that, putting the pointer 141 | * back to the start of the buffer. 142 | * 143 | * @param data 144 | * - The float data that is going to be stored in the buffer. 145 | * @return The FloatBuffer containing the data. This float buffer is ready 146 | * to be loaded into a VBO. 147 | */ 148 | private FloatBuffer storeDataInFloatBuffer(float[] data) { 149 | FloatBuffer buffer = BufferUtils.createFloatBuffer(data.length); 150 | buffer.put(data); 151 | buffer.flip(); 152 | return buffer; 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/renderEngine/RawModel.java: -------------------------------------------------------------------------------- 1 | package renderEngine; 2 | 3 | /** 4 | * Represents a loaded model. It contains the ID of the VAO that contains the 5 | * model's data, and holds the number of vertices in the model. 6 | * 7 | * @author Karl 8 | * 9 | */ 10 | public class RawModel { 11 | 12 | private int vaoID; 13 | private int vertexCount; 14 | 15 | public RawModel(int vaoID, int vertexCount) { 16 | this.vaoID = vaoID; 17 | this.vertexCount = vertexCount; 18 | } 19 | 20 | /** 21 | * @return The ID of the VAO which contains the data about all the geometry 22 | * of this model. 23 | */ 24 | public int getVaoID() { 25 | return vaoID; 26 | } 27 | 28 | /** 29 | * @return The number of vertices in the model. 30 | */ 31 | public int getVertexCount() { 32 | return vertexCount; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/renderEngine/Renderer.java: -------------------------------------------------------------------------------- 1 | package renderEngine; 2 | 3 | import org.lwjgl.opengl.GL11; 4 | import org.lwjgl.opengl.GL20; 5 | import org.lwjgl.opengl.GL30; 6 | 7 | /** 8 | * Handles the rendering of a model to the screen. 9 | * 10 | * @author Karl 11 | * 12 | */ 13 | public class Renderer { 14 | 15 | /** 16 | * This method must be called each frame, before any rendering is carried 17 | * out. It basically clears the screen of everything that was rendered last 18 | * frame (using the glClear() method). The glClearColor() method determines 19 | * the colour that it uses to clear the screen. In this example it makes the 20 | * entire screen red at the start of each frame. 21 | */ 22 | public void prepare() { 23 | GL11.glClearColor(1, 0, 0, 1); 24 | GL11.glClear(GL11.GL_COLOR_BUFFER_BIT); 25 | } 26 | 27 | /** 28 | * Renders a model to the screen. 29 | * 30 | * Before we can render a VAO it needs to be made active, and we can do this 31 | * by binding it. We also need to enable the relevant attributes of the VAO, 32 | * which in this case is just attribute 0 where we stored the position data. 33 | * 34 | * The VAO can then be rendered to the screen using glDrawArrays(). We tell 35 | * it what type of shapes to render and the number of vertices that it needs 36 | * to render. 37 | * 38 | * After rendering we unbind the VAO and disable the attribute. 39 | * 40 | * @param model 41 | * - The model to be rendered. 42 | */ 43 | public void render(RawModel model) { 44 | GL30.glBindVertexArray(model.getVaoID()); 45 | GL20.glEnableVertexAttribArray(0); 46 | GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, model.getVertexCount()); 47 | GL20.glDisableVertexAttribArray(0); 48 | GL30.glBindVertexArray(0); 49 | } 50 | 51 | } 52 | --------------------------------------------------------------------------------