New to Graphics Programming, need help drawing a triangle

Started by blobjim, March 03, 2016, 04:42:58

Previous topic - Next topic

blobjim

Basically I have this code. It doesn't crash but nothing shows up on the window except for the clear color. I'm assuming that my code is pretty messed up and I'll need to make plenty of edits... or perhaps not?

package main;
import org.lwjgl.opengl.*;
import org.lwjgl.glfw.*;
import static org.lwjgl.glfw.GLFW.*;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL12.*;
import static org.lwjgl.opengl.GL13.*;
import static org.lwjgl.opengl.GL14.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL21.*;
import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.opengl.GL31.*;
import static org.lwjgl.opengl.GL32.*;
import static org.lwjgl.opengl.GL33.*;
import static org.lwjgl.opengl.GL40.*;
import static org.lwjgl.opengl.GL41.*;
import static org.lwjgl.opengl.GL42.*;
import static org.lwjgl.opengl.GL43.*;
import static org.lwjgl.opengl.GL44.*;
import static org.lwjgl.opengl.GL45.*;

import java.io.File;
import java.io.FileNotFoundException;
import java.nio.ByteBuffer;
import java.util.Scanner;

public class Main {
	
	public static void main(String[] args) {
		
		//start GLFW
		glfwInit();
		
		//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
		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 off window resizing
		glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
		
		//create a GLFW window and store its id in the window variable
		long window = GLFW.glfwCreateWindow(1600, 900, "GLFW Window", glfwGetPrimaryMonitor(), 0);
		
		//enables opengl
		glfwMakeContextCurrent(window);
		
		//create GLCapabilities instance because it's required (stupid, I know)
		GLCapabilities capabilities = GL.createCapabilities();
		System.out.println("OpenGL 4.5 Supported: " + capabilities.OpenGL45);
		
		//show the window
		glfwShowWindow(window);
		
		//make the opengl screen 1600 pixels wide and 900 pixels tall.
		glViewport(0, 0, 1600, 900);
		
		//the vertex data (x and y)
		double[] triangle = {
				  0,	 0.5,
			   -0.5,	-0.5,
				0.5,	-0.5
		};
		
		//turn the triangle array into a ByteBuffer
		ByteBuffer vertices = storeArrayInBuffer(triangle);
		
		//VAO: stores all of the vbos
		//VBO: stores data (vertex coordinates, colors, indices)
		
		//tell the gpu to make a vbo and store the returned id into the vbo int
		int vbo = glGenBuffers();
		
		//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);
		
		//TODO make a comment here
		glBindBuffer(GL_ARRAY_BUFFER, vbo);
		
		//uploads vertex data to the GPU, tells some information about the vao so that it can work as efficiently as possible
		glBufferData(GL_ARRAY_BUFFER, vertices.capacity(), vertices, GL_STATIC_DRAW);
		
		//returns the vertex buffer to modify? I just decided to overwrite the vertices buffer with the returned one from this function 'cause reasons
		vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE);
		
		//second value is 2 because we only have an x and y (no color, etc.)
		//specifies the location and data format of the array of generic vertex attributes
		glVertexAttribPointer(0, 2, GL_DOUBLE, false, 0, 0);
		
		//unbind the currently bound vao (this should actually be done every frame)
		glBindVertexArray(0);
		
		//sets the background clear color
		glClearColor(1.0f, 0.0f, 0f, 1.0f);
		
		//load the vertex shader from the file
		int vertexShader = loadShader(new File("src/main/vertexShader"), GL_VERTEX_SHADER);
		
		//load the fragment shader from the file
		int fragmentShader = loadShader(new File("src/main/fragmentShader"), GL_FRAGMENT_SHADER);
		
		//create a program object and store its id in the program int
		int program = glCreateProgram();
		
		//links the index (second value) with the name of the variable within the shader (third value)
		glBindAttribLocation(vertexShader, 0, "in_Position");
		glBindAttribLocation(vertexShader, 1, "in_Color");
		
		//attach the vertex shader to the program
		glAttachShader(program, vertexShader);
		glAttachShader(program, fragmentShader);
		
		//set the current program
		glUseProgram(program);
		
		//set the attributes' values
		glVertexAttrib3f(0, 1.0f, 1.0f, 1.0f);
		glVertexAttrib3f(0, 0.0f, 1.0f, 1.0f);
		
		while(glfwWindowShouldClose(window) == 0) {
			
			//clear the window
			glClear(GL_COLOR_BUFFER_BIT);
			
			//set the current vao...
			glBindVertexArray(vao);
			
			//draw it!
			glDrawArrays(GL_TRIANGLES, 0, 2); //draws the currently bound vertex array object
			
			//unbind the vao
			glBindVertexArray(0);
			
			//swap the frame to show the rendered image
			glfwSwapBuffers(window);
			
			//poll for window events (resize, close, button presses, etc.)
			glfwPollEvents();
		}
		
		glDeleteShader(vertexShader);
		glDeleteProgram(program);
	}
	
	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) {
		
		//Double.BYTES is the same as 64 bits / 8 bytes (64 bits in a double, 8 bits in a byte)
		ByteBuffer buffer = ByteBuffer.allocateDirect(array.length * 8);
		
		for(double i : array) {
			buffer.putDouble(i);
		}
		
		buffer.position(0);
		
		return buffer;
	}
	
}


Here is the vertex shader:

#version 150
// in_Position was bound to attribute index 0 and in_Color was bound to attribute index 1
in  vec2 in_Position;
in  vec3 in_Color;

// We output the ex_Color variable to the next shader in the chain
out vec3 ex_Color;
void main(void) {
    // Since we are using flat lines, our input only had two points: x and y.
    // Set the Z coordinate to 0 and W coordinate to 1

    gl_Position = vec4(in_Position.x, in_Position.y, 0.0, 1.0);

    // GLSL allows shorthand use of vectors too, the following is also valid:
    // gl_Position = vec4(in_Position, 0.0, 1.0);
    // We're simply passing the color through unmodified

    ex_Color = in_Color;
}


...and here is the fragment shader:

#version 150
// It was expressed that some drivers required this next line to function properly
precision highp float;

in  vec3 ex_Color;
out vec4 gl_FragColor;

void main(void) {
    // Pass through our original color with full opacity.
    gl_FragColor = vec4(ex_Color,1.0);
}


I don't need someone to tell me how to optimize the code, I'll figure that out eventually. All I want to know is what is the least number of edits to my code that will produce a triangle on the screen.

Kai

There are quite some issues:
- you are not creating a ByteBuffer with native endianness (use LWJGL'S BufferUtils instead)
- you are not enabling the generic vertex attribute index 0 with glEnableVertexAttribArray(0) within the VAO
- you are binding a generic vertex array index which you did not specify a buffer for (this will crash the process once you enable that vertex attribute!)
- you hold your VBO in a mapped state via glMapBuffer but don't unmap it before rendering and are not using persistent, coherent mapped buffers
- you are only drawing 2 vertices. a triangles obviously requires at least 3
- you do not link the shader program via glLinkProgram
- you do not check for any shader compilation (and linking errors once you actually link the program)
- you do not check for any GL errors at all (your code will throw many errors at you once you check it with glGetError() or enable OpenGL debug callback)

blobjim

Thanks for the reply!

I'm unclear as to the role of VBOs vs VAOs. All I want to do in the program right now is have an array of x and y coordinates (plus any other values I want to add later) , store that array in a ByteBuffer, ship it to the GPU, and use shaders to tell the GPU how to use that array to draw a triangle.

Questions I still have:
- Am I doing too much or too little in the program, do I need to use a vbo if I want to use anything more than vertices?
- What is glLinkProgram for when we already have glUseProgram (what is the difference)?

Thanks again for the reply, I really appreciate it. :)

I've fixed everything, my code compiles, and IT DRAWS A TRIANGLE, here's the code for proof:

package main;

import org.lwjgl.opengl.*;
import org.lwjgl.BufferUtils;
import static org.lwjgl.glfw.GLFW.*;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL12.*;
import static org.lwjgl.opengl.GL13.*;
import static org.lwjgl.opengl.GL14.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL21.*;
import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.opengl.GL31.*;
import static org.lwjgl.opengl.GL32.*;
import static org.lwjgl.opengl.GL33.*;
import static org.lwjgl.opengl.GL40.*;
import static org.lwjgl.opengl.GL41.*;
import static org.lwjgl.opengl.GL42.*;
import static org.lwjgl.opengl.GL43.*;
import static org.lwjgl.opengl.GL44.*;
import static org.lwjgl.opengl.GL45.*;

import java.io.File;
import java.io.FileNotFoundException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Scanner;

public class Main {
	
	public static void main(String[] args) {
		
		//start GLFW
		glfwInit();
		
		//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
		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 off window resizing
		glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
		
		//create a GLFW window and store its id in the window variable
		long window = glfwCreateWindow(1600, 900, "GLFW OpenGL Window", glfwGetPrimaryMonitor(), 0);
		
		//enables opengl
		glfwMakeContextCurrent(window);
		
		//create GLCapabilities instance because it's required (stupid, I know)
		GLCapabilities capabilities = GL.createCapabilities();
		System.out.println("OpenGL 4.5 Supported: " + capabilities.OpenGL45);
		
		//show the window
		glfwShowWindow(window);
		
		//make the opengl screen 1600 pixels wide and 900 pixels tall.
		glViewport(0, 0, 1600, 900);
		
		//the vertex data (x and y)
		double[] triangle = {
				  0,	 0.5, //first x and y
			   -0.5,	-0.5, //second x and y
				0.5,	-0.5  //third x and y
		};
		
		double[] color = {
				0.0, 1.0, 0.0, //first vertex color: green
				1.0, 0.0, 1.0, //second vertex color: purple
				0.0, 0.0, 1.0  //third vertex color: blue
		};
		
		//turn the triangle array into a ByteBuffer
		ByteBuffer vertices = storeArrayInBuffer(triangle);
		
		ByteBuffer colors = storeArrayInBuffer(color);
		
		//VAO: stores pointers to all of the vbos to keep 'em organized
		//VBO: stores data (vertex coordinates, colors, indices)
		
		//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);

			//enable vertex attribute array 0
			glEnableVertexAttribArray(0);
			
			//tell the gpu to make a vbo and store the returned id into the vbo int
			int coordVBO = glGenBuffers();
			
			//bind the VBO for use
			glBindBuffer(GL_ARRAY_BUFFER, 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 currently inside the vertex array so this VBO is associated with the current VAO, the end of the vertex array is glBindVertexArray(0)
			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);
	
			//unbind the VBO
			glBindBuffer(GL_ARRAY_BUFFER, 0);
			
			//create a second VBO (colors) and upload its data
			int colorVBO = glGenBuffers();
			
			//bind the VBO for use
			glBindBuffer(GL_ARRAY_BUFFER, colorVBO);
			
			//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 currently inside the vertex array so this VBO is associated with the current VAO, the end of the vertex array is glBindVertexArray(0)
			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(0, 3, GL_DOUBLE, false, 0, 0);
	
			//unbind the VBO
			glBindBuffer(GL_ARRAY_BUFFER, 0);

		//unbind the currently bound VAO (this should actually be done every frame)
		glBindVertexArray(0);

		//load the vertex shader from the file
		int vertexShader = loadShader(new File("src/main/vertexShader"), GL_VERTEX_SHADER);
		
		//load the fragment shader from the file
		int fragmentShader = loadShader(new File("src/main/fragmentShader"), GL_FRAGMENT_SHADER);
		
		//create a program object and store its id in the program int
		int program = glCreateProgram();

		//links the index (second value) with the name of the variable within the shader (third value), 
		//these values must be set before linking, I won't be setting these values in java code explicitly, I sent them to the GPU
		glBindAttribLocation(vertexShader, 0, "in_Position");
		glBindAttribLocation(vertexShader, 1, "in_Color");

		//attach the vertex shader to the program
		glAttachShader(program, vertexShader);
		glAttachShader(program, fragmentShader);

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

		//set the current program
		glUseProgram(program);

		//how to set the attribute's values, this is just an example
		//glVertexAttrib3f(1, 0.0f, 1.0f, 0.0f);
		
		//validate 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));
		System.out.println("Program Shader Buffer: " 		+ glGetAttachedShaders(program));
		
		//check for general OpenGL errors
		int error = glGetError();
		while(error != 0) {
			System.out.println("OpenGL Error: " + error);
			error = glGetError();
		}
		
		//sets the background clear color
		glClearColor(1.0f, 0.0f, 0f, 1.0f);
		
		while(glfwWindowShouldClose(window) == 0) {
			
			//clear the window
			glClear(GL_COLOR_BUFFER_BIT);
			
			//set the current vao...
			glBindVertexArray(vao);
			
			//draw the currently bound VAO
			glDrawArrays(GL_TRIANGLES, 0, 3);
			
			//unbind the VAO
			glBindVertexArray(0);
			
			//swap the frame to show the rendered image
			glfwSwapBuffers(window);
			
			//poll for window events (resize, close, button presses, etc.)
			glfwPollEvents();
		}
		
		//delete the vbo and vao
		glDeleteBuffers(coordVBO);
		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);
		
		//delete the program now that the shaders are detached
		glDeleteProgram(program);
	}
	
	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 in a double (64-bits) 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;
	}
	
}

Kai

There are still some issues with that code, which makes it not render anything on my computer. Just a red window.
The issues are:
- you don't actually enable the generic vertex attribute with index 1 holding the vertex colors
- the first parameter to glBindAttribLocation() is NOT the vertex shader id (consult the OpenGL documentation)
- your second glVertexAttribPointer overwrites the generic vertex attribute with index 0, which is supposed to hold the vertex positions, with the vertex colors
Once you fix these three errors, your colorful triangle will show up. :)

abcdef

@blobjim

** Think of VBO's as pointers to per vertex data on the GPU.

- For every VBO you create you get an id, which uniquely identifies this dataset on the GPU.
- Then you can upload / edit data with the buffer commands.
- You can also then link this data to the vertex shader with glVertexAttribPointer, this basically references the vertex shader variable to map the data to and tells it the type of data in the data set and how many of them to map (ie if there are 3 floats making the positions of the vertex data then you would define the vertex attribute as such).

You can have multiple VBO's (position data, color data, normal data, uv data, skinning data etc) or you can interleave these in one data set. The glVertexAttribPointer allows you to define interleave indexes with strides values.

**Think of the VAO as a pointer to a set of VBO's

If you have 5 VBO's that are all bound to different sets of data then you can link them all together with the VAO. The advantage here is that you only need to bind the VAO once for each draw call and not for all the individual VBO's. There are both advantages and disadvantages to using VAO's and there are plenty opinions on the web which talk about this.

Cornix

The VAO also stores some additional management data about your VBO's. This helps to reduce the number of methods you have to call to set up your VBO's for drawing.
Look at the reference manual to know exactly what is stored and what is not stored in VAO state.

blobjim

Quote from: abcdef on March 04, 2016, 11:56:52
@blobjim

** Think of VBO's as pointers to per vertex data on the GPU.

- For every VBO you create you get an id, which uniquely identifies this dataset on the GPU.
- Then you can upload / edit data with the buffer commands.
- You can also then link this data to the vertex shader with glVertexAttribPointer, this basically references the vertex shader variable to map the data to and tells it the type of data in the data set and how many of them to map (ie if there are 3 floats making the positions of the vertex data then you would define the vertex attribute as such).

You can have multiple VBO's (position data, color data, normal data, uv data, skinning data etc) or you can interleave these in one data set. The glVertexAttribPointer allows you to define interleave indexes with strides values.

**Think of the VAO as a pointer to a set of VBO's

If you have 5 VBO's that are all bound to different sets of data then you can link them all together with the VAO. The advantage here is that you only need to bind the VAO once for each draw call and not for all the individual VBO's. There are both advantages and disadvantages to using VAO's and there are plenty opinions on the web which talk about this.

I'll definitely try out interleaving, that sounds like a nice organizational method (and probably a bit better for loading 3d models).

blobjim

Quote from: Kai on March 04, 2016, 09:08:56
There are still some issues with that code, which makes it not render anything on my computer. Just a red window.
The issues are:
- you don't actually enable the generic vertex attribute with index 1 holding the vertex colors
- the first parameter to glBindAttribLocation() is NOT the vertex shader id (consult the OpenGL documentation)
- your second glVertexAttribPointer overwrites the generic vertex attribute with index 0, which is supposed to hold the vertex positions, with the vertex colors
Once you fix these three errors, your colorful triangle will show up. :)

I forgot to check the thread and I ended up fixing these myself. If only I could have seen your answer and saved myself the toil ;( Thanks for everything though <3 (PS I've also figured out uniforms now and have a dancing disco triangle :P)