LWJGL Forum

Programming => OpenGL => Topic started by: blobjim on April 27, 2016, 01:48:35

Title: A Simple OpenGL Triangle Demo
Post by: blobjim on April 27, 2016, 01:48:35
To learn OpenGL, I've made a simple-ish little demo program with short comments on each method call. I just wanted to post it in case anyone else is able to find it of use. When run, the program produces a full screen window with a white background and a "swimming" triangle with changing colors. However, it's definitely not perfect since I'm still just learning OpenGL.  :P

Application Code:
package main;

//import the OpenGL classes, Buffer utilities, and GLFW classes
import org.lwjgl.opengl.*;
import org.lwjgl.BufferUtils;
import static org.lwjgl.glfw.GLFW.*;

//import all of the necessary OpenGL functions statically so they can be easily accessed
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;

//these are some extra imports for handling the shader files and Buffers
import java.io.File;
import java.io.FileNotFoundException;
import java.nio.ByteBuffer;
import java.util.Scanner;

public class Main {

//a variable to hold the id of the GLFW window
static long window;

public static void main(String[] args) {

//start GLFW
glfwInit();

//get the screen resolution of the user's monitor
final int screenWidth = glfwGetVideoMode(glfwGetPrimaryMonitor()).width();
final int screenHeight = glfwGetVideoMode(glfwGetPrimaryMonitor()).height();

//set the window to use opengl version 4.5
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);

//use the core opengl profile, instead of the compatibility one, to make sure you're only using current features
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

//make this program work with newer versions of opengl
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);

//turn on window resizing
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);

//create a GLFW window and store its id in the window variable
window = glfwCreateWindow(screenWidth, screenHeight, "GLFW OpenGL Window", glfwGetPrimaryMonitor(), 0);

//enables opengl
glfwMakeContextCurrent(window);

//create GLCapabilities instance because it's required (stupid, I know) and use it to print out if OpenGL 4.5 is supported
System.out.println("OpenGL 4.5 Supported: " + GL.createCapabilities().OpenGL45);

//make the opengl screen 1600 pixels wide and 900 pixels tall.
glViewport(0, 0, screenWidth, screenHeight);

//show the window
glfwShowWindow(window);

//the vertex data (x and y)
double[] triangle = {
0.0, 0.5, //first x and y
   -0.5, -0.5, //second x and y
0.5, -0.5, //third x and y
};

//the color data (red, green, and blue)
double[] color = {
0.0, 1.0, 0.0, //first vertex color
1.0, 0.0, 0.0, //second vertex color
0.0, 0.0, 1.0, //third vertex color
};

//the order to render the vertices
int[] index = {
0,
1,
2,
};

//convert the vertex data arrays into ByteBuffers using a method I created down below
ByteBuffer vertices = storeArrayInBuffer(triangle);
ByteBuffer colors = storeArrayInBuffer(color);
ByteBuffer indices = storeArrayInBuffer(index);

//VAO: stores pointers to all of the vbos to keep 'em organized
//VBO: stores data (vertex coordinates, colors, indices, etc.) and a header that contains information about their format

//tell the GPU to make a single vertex array and store the returned id into the VBO int
int vao = glGenVertexArrays();

//set the current vertex array object
glBindVertexArray(vao);

//tell the gpu to make a VBO and store its ID in the 'coordVBO' varabile
int coordVBO = glGenBuffers();

//bind the 'coordVBO' VBO for use
glBindBuffer(GL_ARRAY_BUFFER, coordVBO);

//we are currently inside the vertex array so this VBO is associated with 'coordVBO'
//uploads VBO data (in this case, coords) to the GPU, tells some information about the VBO so that it can work as efficiently as possible
//we are using STATIC_DRAW because "The data store contents will be specified once by the application...
//...and used many times as the source for GL drawing and image specification commands."
glBufferData(GL_ARRAY_BUFFER, vertices.capacity(), vertices, GL_STATIC_DRAW);

//specifies information about the format of the VBO (number of values per vertex, data type, etc.)
glVertexAttribPointer(0, 2, GL_DOUBLE, false, 0, 0);

//enable vertex attribute array 0
glEnableVertexAttribArray(0);

//create a second VBO for the colors
int colorVBO = glGenBuffers();

//bind the 'colorVBO' VBO for use
glBindBuffer(GL_ARRAY_BUFFER, colorVBO);

//uploads VBO data (in this case, colors) to the GPU
glBufferData(GL_ARRAY_BUFFER, colors.capacity(), colors, GL_STATIC_DRAW);

//specifies information about the format of the VBO (number of values per vertex, data type, etc.)
glVertexAttribPointer(1, 3, GL_DOUBLE, false, 0, 0);

//enable vertex attribute array 1
glEnableVertexAttribArray(1);

//create a third VBO for the indices (tells the GPU which vertices to render and when)
int indicesVBO = glGenBuffers();

//bind the 'indicesVBO' for use
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesVBO);

//uploads VBO data (in this case, colors) to the GPU
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.capacity(), indices, GL_STATIC_DRAW);

//unbind the last bound VBO
glBindBuffer(GL_ARRAY_BUFFER, 0);

//unbind the currently bound VAO
glBindVertexArray(0);

//load the vertex shader from the file using a method I wrote down below
int vertexShader = loadShader(new File("src/main/vertexShader"), GL_VERTEX_SHADER);

//load the fragment shader from the file using a method I wrote down below
int fragmentShader = loadShader(new File("src/main/fragmentShader"), GL_FRAGMENT_SHADER);

//create a program object and store its ID in the 'program' variable
int program = glCreateProgram();

//these method calls link shader program variables to attribute locations so that they can be modified in Java code
glBindAttribLocation(program, 0, "position");
glBindAttribLocation(program, 1, "color");

//attach the vertex and fragment shaders to the program
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);

//link the program (whatever that does)
glLinkProgram(program);

//validate the program to make sure it wont blow up the program
glValidateProgram(program);

//check for compilation errors
System.out.println("Vertex Shader Compiled: " + glGetShaderi(vertexShader, GL_COMPILE_STATUS));
System.out.println("Fragment Shader Compiled: " + glGetShaderi(fragmentShader, GL_COMPILE_STATUS));
System.out.println("Program Linked: " + glGetProgrami(program, GL_LINK_STATUS));
System.out.println("Program Validated: " + glGetProgrami(program, GL_VALIDATE_STATUS));

//check for general OpenGL errors
int error = glGetError();
while(error != 0) {
System.out.println("OpenGL Error: " + error);
error = glGetError();
}

//sets the background clear color to white
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);

//get the 'colorMod and 'positionMod' variables so I can change them while drawing to create the animation
int colorMod = glGetUniformLocation(program, "colorMod");
int positionMod = glGetUniformLocation(program, "positionMod");
float i = 0.0f;

//set the current program
glUseProgram(program);

while(glfwWindowShouldClose(window) == GLFW_FALSE) {

//Animate the triangle by using sinusoids
if(i > Math.PI * 2) {
i = 0.0f;
}

else {
i += 0.1f;
}

//use the colorMod and positionMod variables to modify the fragment colors and positions to animate the triangle
glUniform4f(colorMod, (float)((Math.cos(i) + 1)/2), (float)((Math.sin(i) + 1)/2), (float)((Math.cos(i) + 1)/2), (float)((Math.sin(i) + 1)/2));
glUniform2f(positionMod, (float)((Math.cos(i) + 1)/2), (float)((Math.sin(i) + 1)/2));

//clear the window
glClear(GL_COLOR_BUFFER_BIT);

//set the current vao to the one we made earlier with all of the data
glBindVertexArray(vao);

//draw the current bound VAO/VBO using an index buffer
glDrawElements(GL_TRIANGLES, index.length, GL_UNSIGNED_INT, 0);

//unbind the vao if there's another one that will be used, just to get rid of any conflicts
glBindVertexArray(0);

//swap the frame to show the rendered image
glfwSwapBuffers(window);

//poll for window events (resize, close, button presses, etc.)
glfwPollEvents();
}

System.out.println("Window closed");

//disable the vertex attribute arrays
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);

//delete the vbos and vao
glDeleteBuffers(coordVBO);
glDeleteBuffers(colorVBO);
glDeleteVertexArrays(vao);

//detach the shaders from the program object
glDetachShader(program, vertexShader);
glDetachShader(program, fragmentShader);

//delete the shaders now that they are deatched
glDeleteShader(vertexShader);

//stop using the shader program
glUseProgram(0);

//delete the program now that the shaders are detached and the program isn't being used
glDeleteProgram(program);

//check for general OpenGL errors again to make sure the shutdown process worked
error = glGetError();
while(error != 0) {
System.out.println("OpenGL Error: " + error);
error = glGetError();
}
}

public static int loadShader(File file, int type) {
try {
Scanner sc = new Scanner(file);
StringBuilder data = new StringBuilder();

if(file.exists()) {
while(sc.hasNextLine()) {
data.append(sc.nextLine() + "\n");
}

sc.close();
}
int id = glCreateShader(type);
glShaderSource(id, data);
glCompileShader(id);
return id;
}

catch (FileNotFoundException e) {
e.printStackTrace();
return -1;
}
}

public static ByteBuffer storeArrayInBuffer(double[] array) {

//8 bytes (64-bits) in a double, multiplied by the number of values in the array
ByteBuffer buffer = BufferUtils.createByteBuffer(array.length * 8);

for(double i : array) {
buffer.putDouble(i);
}

buffer.position(0);

return buffer;
}

public static ByteBuffer storeArrayInBuffer(int[] array) {

//8 bytes (64-bits) in a double, multiplied by the number of values in the array
ByteBuffer buffer = BufferUtils.createByteBuffer(array.length * 4);

for(int i : array) {
buffer.putInt(i);
}

buffer.position(0);

return buffer;
}
}


Vertex Shader:
#version 450 core

//position is bound to attribute index 0 and color is bound to attribute index 1
in  vec2 position;
in  vec3 color;

//this vector is updated each frame to animate the triangle
uniform vec2 positionMod = vec2(1,1);

//output the outColor variable to the next shader in the chain
out vec3 outColor;

void main(void) {

//syntax: vec4(x, y, z, w);
    gl_Position = vec4(position.x * positionMod.x, position.y * positionMod.y, 0.0, 1.0);
   
    //pass the output color right to the fragment shader without changing it
    outColor = color;
}


Fragment Shader:
#version 450 core
// It was expressed that some drivers required this next line to function properly
precision highp float;

//use the outColor variable from the vertex shader as the input to the fragment shader
in vec3 outColor;

//this is updated every frame in the main loop
uniform vec4 colorMod;

//the built in pixel color variable
out vec4 gl_FragColor;

void main(void) {
    // Pass through our original color with full opacity.
    gl_FragColor = vec4(outColor.r * colorMod.r, outColor.g * colorMod.g, outColor.b * colorMod.b, 1.0 * colorMod.a);
}


Please take all of this with a grain of salt since this program probably isn't written in the best way possible  :)