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
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  LWJGL Tutorial Series - Creating a 3D Camera  (Read 9719 times)
0 Members and 2 Guests are viewing this topic.
Offline 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.

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
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?

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.

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.

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();

    // 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
ShaderProgram.java
shader.vert
shader.frag

Offline 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!

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

Thanks!

Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline 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?
Offline 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?

Offline Nouht

Senior Newbie





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

@SHC Thanks for the reply.

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.
Offline 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.

Offline 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.
Offline 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 Smiley
Offline 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,
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline 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.

Offline 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
List of Learning Resources
by Longarmx
2014-04-08 03:14:44

Good Examples
by matheus23
2014-04-05 13:51:37

Good Examples
by Grunnt
2014-04-03 15:48:46

Good Examples
by Grunnt
2014-04-03 15:48:37

Good Examples
by matheus23
2014-04-01 18:40:51

Good Examples
by matheus23
2014-04-01 18:40:34

Anonymous/Local/Inner class gotchas
by Roquen
2014-03-11 15:22:30

Anonymous/Local/Inner class gotchas
by Roquen
2014-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
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!