# LWJGL Tutorial Series - Creating a 3D Camera

Welcome to the thirteenth part of the LWJGL Tutorial Series. In this tutorial, we are going to create a three dimensional camera and before we do, we'll learn some matrices stuff that we'll use to setup projections and view transformations. If there are any mistakes in this tutorial, please notify them to me with comments. Now, let's start with some matrix math.

# Matrix Math

In this tutorial, I'm gonna explain only the things needed for us to create the camera because this is more of a programming tutorial than a math class. So don't please expect doing a math degree by reading this. Okay, enough of some useless conversation, let's get started. Now that we are ready, I'll start by showing a simple matrix to you.

This is a simple matrix. A matrix is simply a rectangular array containing elements arranged in rows and columns. The elements are indexed by row and colums with

and

so in the previous example, the value of

will be 1 where the first zero refers to the row and the second zero refers to the column. Now let's see how to add and subtract matrices. They are relatively easy so I'm just showing them.

Matrix addition is simply adding the appropriate elements.

The same is with subtraction.

Now let's do matrix multiplication. The order of multiplication is important here so I'm colouring the elements to get it easily. In matrix multiplication, we multiply every row of one matrix with every column of another matrix. See it too.

One thing you need to remember is the order of matrix is very important because in matrices

OpenGL actually operates on 4 by 4 matrices which means that the matrix contains four rows and four columns. So enough of these basics and now let's see examples of matrix-vector multiplication.

# Matrix-Vector Multiplication

How to do multiplication between a matrix and a vector? For this purpose, we'll represent vector as a column matrix, a matrix which has only one column. This is because OpenGL expects column-major matrices. So, let's multiply a vector with an identity matrix.

What! we got the same? This is because we multiplied our vector with an identity matrix. Now let's learn how to scale a vector. Also you may ask that what will be the value of

since we are only using 3D vectors. That component is for making them able to multiply but for the time-being, the value of the fourth parameter

will be 1.

# Translation

To translate a vector from (x, y, z) by (X, Y, Z), we need to build a translation matrix. The matrix will be constructed as

See, this translates our vector from (x, y, z) to (x+X, y+Y, z+Z) by translating with (X, Y, Z). This is used to place our models in 3D space effectively.

# Scaling

I'm just giving the matrices from hereon since this is just for information. All these operations will be done with LWJGL's utility classes for us. Now let me show how scaling works.

This scales the vector (x, y, z) with (SX, SY, SZ) and produces (SX.x, SY.y, SZ.z).

# Rotation

I'm not going into much details here but am just listing the images of the matrices for the rotations on different axes. First, rotation on X-Axis.

Rotation on Y-Axis:

And finally rotation on Z-Axis:

Don't worry about understanding them, they will be computed for us by the utility classes. Enough of this boring math and let's get into something useful.

# The Camera Class

Now, let's start with our camera. Let me list some code and explain it step by step. First, let's take some class-level variables.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Camera { private float fov; private float aspect; private float zNear; private float zFar;
private Matrix4f projection; private Matrix4f view;
private Vector3f position; private Vector3f rotation;
private Vector3f xAxis, yAxis, zAxis; |

You can understand everything except the two matrix variables. The

is the projection matrix. It is initialised only once with perspective projection. The next

is the view matrix. It contains the transformations of the camera itself, i.e., it moves and rotates the camera to give the correct view. Now, let's see the constructor. It is simple as well.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public Camera(float fov, float aspect, float zNear, float zFar) { this.fov = fov; this.aspect = aspect; this.zNear = zNear; this.zFar = zFar;
projection = MatrixUtil.createPerspectiveProjection(fov, aspect, zNear, zFar); view = MatrixUtil.createIdentityMatrix();
position = new Vector3f(0, 0, 0); rotation = new Vector3f(0, 0, 0);
xAxis = new Vector3f(1, 0, 0); yAxis = new Vector3f(0, 1, 0); zAxis = new Vector3f(0, 0, 1);
glEnable(GL_DEPTH_TEST); } |

Here, we come across our utility class called

which, We'll make soon. The next thing is the

method of the camera. It is in this method that we transform our matrices. Here's the code.

1 2 3 4 5 6 7 8 9 10 11 12 13
| public void apply() { view.setIdentity();
Matrix4f.rotate((float) Math.toRadians(rotation.x), xAxis, view, view); Matrix4f.rotate((float) Math.toRadians(rotation.y), yAxis, view, view); Matrix4f.rotate((float) Math.toRadians(rotation.z), zAxis, view, view);
Matrix4f.translate(position, view, view); } |

One thing to remember here is that the

method of the

class takes the angle in radians and not degrees as OpenGL expects. So we've to convert from degrees to radians every time we need it. Next is that we've move method here. If you remember your high school physics, this shouldn't be too hard.

1 2 3 4 5
| public void move(float amount, float direction) { position.z += amount * Math.sin(Math.toRadians(rotation.y + 90 * direction)); position.x += amount * Math.cos(Math.toRadians(rotation.y + 90 * direction)); } |

Do you remember that you use the

function to calculate distance in the forward and backward directions and

function to calculate distance is left and right directions? We're applying the same here. The parameter

will be the amount of distance to move and the parameter

specifies which direction to move.

**If it's specified as 1, the camera will move forward and backward and if it's defined as 0, the camera will move left and right.** This completes our Camera class except for one part that you have to create the getters and setters for positions, matrices etc., which I'm not showing here.

# MatrixUtil Class

Now, let's make our utility class. It has two methods namely

createPerspectiveProjection() |

and

. The first method is used to create a matrix of perspective projection. The second method is used to convert a matrix to a FloatBuffer and is shown later in this tutorial. Before we make the matrix, I'm showing how the matrix looks here. Again I'm not going to explain it completely but if you want, you can read to more depth

here.

So, let me show that method here.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public static Matrix4f createPerspectiveProjection(float fov, float aspect, float zNear, float zFar) { Matrix4f mat = new Matrix4f();
float yScale = 1f / (float) Math.tan(Math.toRadians(fov / 2f)); float xScale = yScale / aspect; float frustumLength = zFar - zNear;
mat.m00 = xScale; mat.m11 = yScale; mat.m22 = -((zFar + zNear) / frustumLength); mat.m23 = -1; mat.m32 = -((2 * zFar * zNear) / frustumLength); mat.m33 = 0;
return mat; } |

That should create the matrix successfully and now, we need a way to send these matrices to the shader in order to get everything rendered as expected. Since OpenGL only works with buffers, we'll create another method called

. This is fairly simple so, I'm just listing it here.

1 2 3 4 5 6 7
| public static FloatBuffer toFloatBuffer(Matrix4f mat) { FloatBuffer buffer = BufferUtils.createFloatBuffer(16); mat.store(buffer); buffer.flip(); return buffer; } |

This gets our part done. We still have to send these to shaders, but how?

# Uniform Variables in Shaders

For this purpose, we are going to use uniforms in shaders. I've said in the previous tutorial also and in this tutorial, let's write shaders that does nothing. So here's the code goes.

1 2 3 4 5 6 7
| varying vec4 vColor;
void main() { vColor = gl_Color; gl_Position = gl_Vertex; } |

What does this do? Since our fragment shader expects the you write the color to the variable

, we send the original color of the vertex to it with the help of the varying called as

and in the vertex shader, we'll simply pass that.

1 2 3 4 5 6
| varying vec4 vColor;
void main() { gl_FragColor = vColor; } |

Okay, what about the matrices? We'll define them in the vertex shader as uniforms and multiply them with the vertex. Here's the complete source of the vertex shader.

1 2 3 4 5 6 7 8 9 10
| uniform mat4 projection; uniform mat4 view;
varying vec4 vColor;
void main() { vColor = gl_Color; gl_Position = projection * view * gl_Vertex; } |

Here we combine both the projection and view matrices by multiplying both of them and transform the vertex by multiplying it with the combined matrix. Now, we need a way to set the uniforms in the shaders with the values generated by our camera.

# Sending the Matrices to Shaders

How to send them to the shaders? For that purpose we change our

class to include a method called

. I'm just including it here since it's so simple.

1 2 3 4 5 6
| public void setUniform(String name, Matrix4f value) { glUniformMatrix4(glGetUniformLocation(programID, name), false, MatrixUtil.toFloatBuffer(value)); } |

With the help of OpenGL method

we send the matrix to the shader. It requires the location of the uniform in the shader to change the value which can be acquired with the

method. Here, we use our

MatrixUtil.toFloatBuffer() |

. Now, I'm going to show you the render method where we use this to send the matrices.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public void render() { camera.apply();
shaderProgram.bind();
shaderProgram.setUniform("projection", camera.getProjectionMatrix()); shaderProgram.setUniform("view", camera.getViewMatrix());
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindBuffer(GL_ARRAY_BUFFER, vboVertexID); glVertexPointer(3, GL_FLOAT, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, vboColorID); glColorPointer(3, GL_FLOAT, 0, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 24);
ShaderProgram.unbind(); } |

That solves most of it and when you run it, the cube appears again but you cannot still move around it. Now, let's add the movement.

# Adding Movement

This is so simple by just calling the move method of the camera I've shown you previously. So, I'm just listing the update method here.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public void update(long elapsedTime) { if (isKeyDown(KEY_ESCAPE)) end();
if (isKeyDown(KEY_UP)) camera.addRotation(-1f, 0, 0);
if (isKeyDown(KEY_DOWN)) camera.addRotation(1f, 0, 0);
if (isKeyDown(KEY_LEFT)) camera.addRotation(0, -1f, 0);
if (isKeyDown(KEY_RIGHT)) camera.addRotation(0, 1f, 0);
if (isKeyDown(KEY_W)) camera.move(0.1f, 1);
if (isKeyDown(KEY_S)) camera.move(-0.1f, 1);
if (isKeyDown(KEY_A)) camera.move(0.1f, 0);
if (isKeyDown(KEY_D)) camera.move(-0.1f, 0); } |

That's it and when you run it, you should have something like this on your screen.

If you have any problems, check the source code. In the next tutorial, we'll load some 3D models. If there are any mistakes in this tutorial, please notify me with comments.

# Source Code

Tutorial13.javaCamera.javaMatrixUtil.javaShaderProgram.javashader.vertshader.frag