Rendering a triangle using a perspective projection matrix

Started by BobbyLeChimp, June 07, 2015, 09:46:07

Previous topic - Next topic

BobbyLeChimp

Hallo everyone,

I have to admit that I am fairly new to LWJGL and just started with version 3.
I followed along a few getting started tutorials including the one from the lwjgl wiki, goharsha.com and the (in my opinion) excellent video tutorials by the cherno project(especially the maths part).

While I think I do undestand the math behind the projection matrix I can`t seem to get it to work.
While my triangle renders fine without the uniforms apllied to the vector position in the vertex shader, it seems to disappear when I apply it.

My workflow is as follows:

I create the window, callbacks, context etc. in the init method, create the vertex array object and the buffer object, create the shader program, link it and finally apply the unifroms:

private void init() {
		// set ErrorCallback
		// initialize GLFW
		// set windowhints
		// create the window
		// set KeyCallback
		// create the context  / "make it current"

		// //////////////////////////
		// create a vertex array object
		vao = glGenVertexArrays();
		glBindVertexArray(vao);

		// create a Buffer to store the vertex
		FloatBuffer verticesBuffer = BufferUtils
				.createFloatBuffer(createTriangle().length);
		verticesBuffer.put(createTriangle()).flip();

		// ////////////////////////////
		// create a vertex buffer object
		vbo = glGenBuffers();
		glBindBuffer(GL_ARRAY_BUFFER, vbo);
		glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);

		// ////////////////////////////
		// create a new shaderProgram
		shaderProgram = new shaderProgram();
		// create the shaders
		shaderProgram.attachVertexShader("Game/Shaders/vertexShader.txt");
		shaderProgram.attachFragmentShader("Game/Shaders/fragmentShader.txt");
		// link the shaderprogram with the shaders
		shaderProgram.link();
		// get the programID
		shaderID = shaderProgram.getShaderID();

		//uniforms
		glfwGetFramebufferSize(window, width, height);
		float widthF = width.get();
		float heightF = height.get();
		shaderProgram.getUniModel();
		shaderProgram.getUniView();
		shaderProgram.getUniProjection(widthF, heightF);

		// specify vertex attrributes
		specifyVertexAttributes();

		// unbind vertex array
		glBindVertexArray(0);

	}


The methods to set the uniforms look like this: For the model and view I apply the identity matrix. For the perspective projection matrix I feed in the field of view, width, height, near clipping and far clipping distance.

public void getUniModel(){
	
		int uniModel = glGetUniformLocation(programID, "model");
		
		Matrix4f model = Matrix4f.identity();
		glUniformMatrix4fv(uniModel, false, model.toFloatBuffer());

	}

	
	public void getUniView(){
	
		int uniView = glGetUniformLocation(programID, "view");
		Matrix4f view = Matrix4f.identity();
		glUniformMatrix4fv(uniView, false, view.toFloatBuffer());

	}
	
	
	public void getUniProjection(float width, float height){
		
		int uniProjection = glGetUniformLocation(programID, "projection");
		Matrix4f projection = Matrix4f.projection(70f, width, height, 0.1f, 1000f);
		glUniformMatrix4fv(uniProjection, false, projection.toFloatBuffer());
	}


My matrices look like this:

public static Matrix4f projection(float viewAngle, float width, float height,
			float nearClippingPlaneDistance, float farClippingPlaneDistance) {
		// convert angle from degree to radians
		final float radians = (float) (viewAngle * Math.PI / 180f);

		float halfHeight = (float) (Math.tan(radians / 2) * nearClippingPlaneDistance);

		float halfScaledAspectRatio = halfHeight * (width / height);

		Matrix4f projection = perspectiveFrustum(-halfScaledAspectRatio,
				halfScaledAspectRatio, -halfHeight, halfHeight,
				nearClippingPlaneDistance, farClippingPlaneDistance);

		return projection;
	}

	// result.elements[ROW + COLUMN] = value
	public static Matrix4f perspectiveFrustum(float left, float right, float bottom,
			float top, float near, float far) {
		Matrix4f result = new Matrix4f();

		result.elements[0 + 0 * 4] = (2f * near) / (right - left);
		result.elements[0 + 2 * 4] = (right + left) / (right - left);

		result.elements[1 + 1 * 4] = (2 * near) / (top - bottom);
		result.elements[1 + 2 * 4] = (top + bottom) / (top - bottom);

		result.elements[2 + 2 * 4] = -(far + near) / (far - near);
		result.elements[2 + 3 * 4] = -2 * (far * near) / (far - near);

		result.elements[3 + 2 * 4] = -1;
		result.elements[3 + 3 * 4] = 0;

		return result;
	}



Just in case it matters:
In the render method I follow the usual, I clear the screen, use the program, bind the vertex array object, draw it and unbind the vao and program.
I shortend some of the code so I won`t just code dump. When something important is missing I will post the complete code.

I hope someone can point me into the right direction. I have tried to get this to work for almost two weeks now!

Thanks,

BobbyLeChimp

Kai

Hello,

have a look at this wonderful page, which I always always always recommend to people learning about OpenGL coordinate spaces and transformations: www.songho.ca

To further explain: The default coordinate system used by the shader pipeline in OpenGL is the "Clip Coordinates."
Basically, this means you can do everything you want in your vertex shader, but in the end, what you need to set "gl_Position" to are vertices in the Clip Coordinates space.
If you do not do any projection in your shader, resulting in the vertices' W-component staying 1.0, then Clip Coordinates will be identical to Normalized Device Coordinates, which range from -1..+1 in all three dimensions. So everything in that range will be visible on the screen.

I am assuming your triangle is on z=0.0, so it will perfectly fall into that interval.

When you now use a perspective projection in the way that OpenGL conventionally defines it, you stick to the convention of the "camera" looking along the *negative* Z direction.
So everything that is between zNear and zFar away from the camera along the *negative* Z-direction will potentially be visible, provided it is in the visible X/Y-range.

So just make sure your triangle's Z-coordinates are between -zNear and -zFar.

BobbyLeChimp

Thanks for the link! The article is very well put together.
It actually explains everything so well that I was completely sure that my implementation should be working.
My vertecies already were in a position between -zNear and -zFar so this couldn`t be the problem either.

After I read through the article it took me about 10 seconds to realise how stupid I am...
I set up my shaders and calculated the martices but I never actually started the shaderprogram!
I only start and stop the shaderprogram in the render() method so when I set everything up in the init() method the shaderprogram wasn`t even running!
Somehow it never occurred to me that this could be a problem...

Thanks for reading through my post! Somehow it actually helps to get some distance from your code to realise where you went wrong.

~BobbyLeChimp

EDIT: Oh and.. solved!

Kai

Yes, using glUniformMatrix4fv requires a "bound" shader program to set the matrix to.
Very likely, without the shader program being bound, that call would have generated a GL error, which you could have queried via GL11.glGetError().
There is the EXTDirectStateAccess extension with glProgramUniformMatrix4fvEXT allowing you to set a shader's uniform variable without "binding" that shader first.

Furthermore, have a look at the LWJGL 3 Gears demo.
Specifically at that line 108. There it shows how you can make use of a debug context in OpenGL, which will print an error or warning message whenever you somehow wrongly used an OpenGL call.

BobbyLeChimp

You know what? That debug context is just what I needed. I made a test run without the bound shaderprogram and it immediately printed this error message: "GL_INVALID_OPERATION error generated. No active program."
Thanks again! This will come in handy in the future!