Java-Gaming.org
 Featured games (91) games approved by the League of Dukes Games in Showcase (576) games submitted by our members Games in WIP (498) games currently in development
 News: Read the Java Gaming Resources, or peek at the official Java tutorials
Pages: [1]
 ignore  |  Print
 LWJGL Tutorial Series - Creating a 3D Camera  (Read 9719 times) 0 Members and 2 Guests are viewing this topic.
SHC
 « Posted 2013-09-30 08:31:14 »

# 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
 `i`
and
 `j`
so in the previous example, the value of
 `A00`
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.

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
 `A x B != B x A`

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
 `w`
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
 `w`
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{    // Field Of View    private float fov;    // Aspect Ratio    private float aspect;    // Near Plane    private float zNear;    // Far Plane    private float zFar;    // Projection matrix    private Matrix4f projection;    // View matrix    private Matrix4f view;    // Camera position    private Vector3f position;    // Camera rotation    private Vector3f rotation;    // Vectors for axes    private Vector3f xAxis, yAxis, zAxis;`

You can understand everything except the two matrix variables. The
 `projection`
is the projection matrix. It is initialised only once with perspective projection. The next
 `view`
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){    // Set the local variables    this.fov = fov;    this.aspect = aspect;    this.zNear = zNear;    this.zFar = zFar;    // Create matrices    projection = MatrixUtil.createPerspectiveProjection(fov, aspect, zNear, zFar);    view = MatrixUtil.createIdentityMatrix();    // Initialize position and rotation vectors    position = new Vector3f(0, 0, 0);    rotation = new Vector3f(0, 0, 0);    // Create normalized axis vectors    xAxis = new Vector3f(1, 0, 0);    yAxis = new Vector3f(0, 1, 0);    zAxis = new Vector3f(0, 0, 1);    // Enable depth testing    glEnable(GL_DEPTH_TEST);}`

Here, we come across our utility class called
 `MatrixUtil`
which, We'll make soon. The next thing is the
 `apply()`
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(){    // Make the view matrix an identity.    view.setIdentity();    // Rotate the view    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);    // Move the camera    Matrix4f.translate(position, view, view);}`

One thing to remember here is that the
 `rotate()`
method of the
 `Matrix4f`
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
 `sin`
function to calculate distance in the forward and backward directions and
 `cos`
function to calculate distance is left and right directions? We're applying the same here. The parameter
 `amount`
will be the amount of distance to move and the parameter
 `direction`
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
 `toFloatBuffer()`
. 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
 `toFloatBuffer()`
. 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?

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.

 `shader.vert`

 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
 `gl_FragColor`
, we send the original color of the vertex to it with the help of the varying called as
 `vColor`
and in the vertex shader, we'll simply pass that.

 `shader.frag`

 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
 `ShaderProgram`
class to include a method called
 `setUniform()`
. 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
 `glUniformMatrix4()`
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
 `glGetUniformLocation()`
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(){    // Set Camera View    camera.apply();    // Bind the shaders    shaderProgram.bind();    // Set the uniforms    shaderProgram.setUniform("projection", camera.getProjectionMatrix());    shaderProgram.setUniform("view", camera.getViewMatrix());    // Clean both color and depth buffers    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    // Bind the vertex VBO    glBindBuffer(GL_ARRAY_BUFFER, vboVertexID);    glVertexPointer(3, GL_FLOAT, 0, 0);    // Bind the color VBO    glBindBuffer(GL_ARRAY_BUFFER, vboColorID);    glColorPointer(3, GL_FLOAT, 0, 0);    // Draw the cube with triangle strip    glDrawArrays(GL_TRIANGLE_STRIP, 0, 24);    // Unbind the shaders    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.

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();    // Look up    if (isKeyDown(KEY_UP))        camera.addRotation(-1f, 0, 0);    // Look down    if (isKeyDown(KEY_DOWN))        camera.addRotation(1f, 0, 0);    // Turn left    if (isKeyDown(KEY_LEFT))        camera.addRotation(0, -1f, 0);    // Turn right    if (isKeyDown(KEY_RIGHT))        camera.addRotation(0, 1f, 0);    // Move front    if (isKeyDown(KEY_W))        camera.move(0.1f, 1);    // Move back    if (isKeyDown(KEY_S))        camera.move(-0.1f, 1);    // Strafe left    if (isKeyDown(KEY_A))        camera.move(0.1f, 0);    // Strafe right    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.java
Camera.java
MatrixUtil.java

opiop65

JGO Kernel

Medals: 123
Projects: 7
Exp: 3 years

Team Alluminum

 « Reply #1 - Posted 2013-09-30 11:57:50 »

Wow, nice explanation of matrices!

SHC
 « Reply #2 - Posted 2013-09-30 11:58:29 »

Thanks!

Nouht

Senior Newbie

 « Reply #3 - Posted 2013-11-22 23:46:24 »

Hello. Thanks for this brilliant tutorial.

One query: Is it possible to not send the matrix to the OpenGL using the shader but user glMultMatrix to rotate the rotation matrix?
SHC
 « Reply #4 - Posted 2013-11-23 13:49:32 »

@Nouht

It may be possible but you should use the
 `gl_ModelViewProjectionMatrix`
in the shaders. But it is not recommended to mix the programming pipeline with the immediate mode. Also can I ask you why you wanted to mix them?

Nouht

Senior Newbie

 « Reply #5 - Posted 2013-11-23 16:22:01 »

The reason I'm doing that is because I don't really want to keep enabling/ disabling my shaders when doing other stuff such as light/shadow mapping using the texture matrix. Also I want to use the inverse of the rot matrix to get the pick.
SHC
 « Reply #6 - Posted 2013-11-24 03:02:38 »

@Nouht

You can use multiple shaders in an OpenGL application and there won't be any performance loss but when compared to the immediate mode, there is a huge performance increase, so I recommend using multiple shaders. For picking, I think you then have to use two separate matrices for rotation and translation (and scaling of-course) and combine them at the shader by simply multiplying them. Note that the order is important here too, you need to do like this.

 1 `finalMat = projMat * rotMat * transMat;`

And then you can use the final matrix for transforming matrices. To invert matrices, you can use
 `inverse`
function in GLSL or you can check out the methods of the
 `Matrix4f`
class.

Nouht

Senior Newbie

 « Reply #7 - Posted 2013-12-06 17:28:19 »

@SHC

Thank you for the reply. I know it sounds odd but I don't want to use uniforms and shaders. Is it possible just to get the matrix from OpenGL by using glGetFloat() and put that buffer into my Matrix class. Then to transform that matrix as appropriate and use glLoadMatrixf() / glMultMatrix() to send it back? Do you think you could provide me with some guidance with that? Additionally, I don't want to user glRotatef() or glTranslate() for the world transformation as that has some perfomance issues.

The main reason I want to do this is my game is doing a high level of lighting calculations in immediate mode. Shader's won't suffice. Thanks.
AullenVerch

Senior Newbie

The univertse is vast!!

 « Reply #8 - Posted 2014-01-23 03:35:26 »

I don't understand where the Game class is coming from in your source :/

EDIT: ok, I found your gitHub. You should think about adding a like to the Game class source for the once that need it. Or maybe just a link to the Contents page..

Thank you for the lessen
AullenVerch

Senior Newbie

The univertse is vast!!

 « Reply #9 - Posted 2014-01-23 05:00:48 »

@SHC

I don't understand how the camera variables such as "Vector3f position" and "Vector3f zAxis" are not the boxes variables?? Every time I draw anything in relationship to those camera variables it draws them in relation to the boxes position instead... Why is that?? Is the box somehow the 'camera', I'm really stumped by this.. Thank you,
SHC
 « Reply #10 - Posted 2014-01-25 16:42:20 »

@AullenVerch

A
 `Camera`
is an object in the space that represents your eye. This doesn't change the position of the object in the world. It just says the location of the eye and in what direction it is looking. Hope you get it now.

Nouht

Senior Newbie

 « Reply #11 - Posted 2014-04-01 20:12:08 »

Hi, I'm kinda new to this but how would you implement lighting? If you create a new set of shaders would you need these uniforms in them? I know how to do the hard part (create the lighting shader) I just don't know if I put it all in one shader or put it in a separate. I have tried it but it doesn't work.

EDIT: Oh, I apologise. I never saw your next post. Thanks for doing a great job in explaining it!
Pages: [1]
 ignore  |  Print

 Add your game by posting it in the WIP section, or publish it in Showcase. The first screenshot will be displayed as a thumbnail.
 xsi3rr4x (18 views) 2014-04-15 18:08:23 BurntPizza (15 views) 2014-04-15 03:46:01 UprightPath (28 views) 2014-04-14 17:39:50 UprightPath (13 views) 2014-04-14 17:35:47 Porlus (29 views) 2014-04-14 15:48:38 tom_mai78101 (54 views) 2014-04-10 04:04:31 BurntPizza (111 views) 2014-04-08 23:06:04 tom_mai78101 (212 views) 2014-04-05 13:34:39 trollwarrior1 (181 views) 2014-04-04 12:06:45 CJLetsGame (187 views) 2014-04-01 02:16:10
 princec 33x Riven 29x Rayvolution 20x BurntPizza 13x Gibbo3771 13x saucymeatman 13x kpars 12x Drenius 12x Roquen 11x ctomni231 11x trollwarrior1 10x matheus23 10x theagentd 9x HeroesGraveDev 9x JFixby 8x SHC 8x
 List of Learning Resourcesby Longarmx2014-04-08 03:14:44Good Examples2014-04-05 13:51:37Good Examplesby Grunnt2014-04-03 15:48:46Good Examplesby Grunnt2014-04-03 15:48:37Good Examples2014-04-01 18:40:51Good Examples2014-04-01 18:40:34Anonymous/Local/Inner class gotchasby Roquen2014-03-11 15:22:30Anonymous/Local/Inner class gotchasby Roquen2014-03-11 15:05:20
 java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org