[OpenGL Part 4] 3D Basics

Introduction


So far we’ve only been dealing with 2D objects, we’ve also had a look at shaders. But we’ve only done stuff in two dimensions, now it’s time to make the step into the 3D world. In order to do this, there is a few things we need to do. The main thing is moving the object to the correct “space”. The spaces are an important, yet confusing, part of 3D rendering. Think of them as a separate coordinate system or a separate world. Any object has a coordinate in all of the spaces.

Object space


In this part we’ll be making a cube and rendering it. To make our cube we need 8 coordinates which make up the 8 corners of the cube :

The center of the object is often [0, 0, 0] and each of the vectors is usualy somgething close to zero. In our case, the cube has one corner that’s [-1, -1, -1] and another one that’s [1, 1, 1] and so on…

blogpic_object_space

So basically, this is the coordinate that describe how the model looks.

World space


Let’s look at the cube again. It needs to have a position that says where it is in the game world, so that it will appear in one specific spot. If you’ve ever used a map editing program, you can see the exact position of every object. This is the world space coordinates. When programming the gameplay elements like collision detection, this is the coordinate system we’ll be using. The idea behind it, is that every obejct has their own position in the world space, and that position is the same no matter how you look at it.

blogpic_world_space

This is an example of a world space that has a cube close to the center and a player to the left of it. The example is for 2D worlds for simplicity, but it would be exactly the same in 3D, only with an extra dimension.

View space / camera space


Whereas the world space location is universal and the same for everyone, the view/camera space is different. It basically tells where the objects are in relation to the player where the player is looking. It is similar to pointing a camera at an object. The center of the image would have the position [0, 0, 0] and every other coordinate is defined around that. These are known as camera or view space coordinates.

blogpic_view_space

Compare the previous image with this. In the previous image, the cube ( [-1, -1] ) is to the left and behind the player ( [-2, 0] ). So if you look at it the world space from above, that’s how it looks. But if you look at it from the view space of the player, the player will be in the center, and the cube ( which is still at ( [-1, -1] in world space ) will be to the right. Note that the object hasn’t moved around in the world and the player hasn’t moved either. All we did was looking at it with the player as the center instead of the center of the world as the center.

Another thing about the camera space is that it’s going to be relative to the direction the player or camera is facing. So imagine the player is looking along the x-axis ( towards the world space center. ) Then the player stars rotating right. Soon he’ll see the object. Since he’s rotating left, he’ll see the object moving to his right. Now imagine him stopping. What he can see, is the world in his own view space. Another player at another location looking at another point would see the world in his own view space.

This might be a bit confusing, but it’ll get clearer soon.

Projection space


Finally we have the projection space. This is a little different, it describes the final position on the screen the vertex will have. Unlike the other spaces, this is always a 2D coordinate, because the screen is a 2D surface. You can look at this like the lens of the camera. The camera looks at a 3D world and the lens enables it to create a 2D image. You can look at it like the 2d version of the view space. When the camera looks at an object, it sees the view space. But what ends up on the screen is in 2d, and that is what we refer to as the projection space.

Just like cameras can have different lenses, so is there different ways of convert camera space coordinates to projection space. We will look at this later when we look at how to convert from space to space

An illustration of view and projection space


Below is an illustration of the view and projection space. Hopefully it’ll help make things clearer :

View space and camera space

The big pyramid is the view space. It’s all that we can see. In this case it’s just three cubes.

The 2d plane with the 3 cubes represented in 2d is the projection space. As you can see, it’s the exact same as the rest of the view space, only in 2d.

Matrices


In order to transform the the vectors from one space to another, we use a matrix ( plural : matrices ). A matrix can be used to change an object in various ways, including moving, rotating and scaling. A matrix is a 2 dimensional mathematical structure, quite similar to a table :

\begin{bmatrix} 1\quad0\quad0\quad0 \\0\quad1\quad0\quad0 \\0\quad0\quad1\quad0\\0\quad0\quad0\quad1\end{bmatrix}

This is what’s called an identity matrix. You can look at it like like a skeleton or an “empty” matrix. It won’t change the object at all. So when we intialize a matrix, this is what we initialize it to.

If we had initialized it to just 0 for all values, it would have changed the object. We’ll look into the math involved for matrices in the next part. For now just remember than an idenity matrix is a default matrix that doesn’t change the object it’s used on.

Instead we’ll look at how to work with matrices. And for that purpose, we use glm.

glm


In order to do graphics programming, we will eventually need to do more mathematics stuff involving vectors and matrices. We really don’t want to do this manually, because there is lots of operations we’d have to implement ourselves. Instead, we’ll use a tried and tested library that does the mathematics operatiofor us. glm, or OpenGL Mathematics, is a library made for doing the maths for graphics programming. It’s widely used and does just about everything we need. It is also 100% platform independent so we can use it on Linux, Windows and Mac.

Installation


The libraries we have been dealing with up until now has required both header files, and library files. glm however, only requires header files. This makes installation very easy, even on Windows.

Linux + Mac ( the automatic way)


Both Linux and Mac might have glm available from package manager. If that’s the case, the process is the same as for SDL. Just open the terminal and install glm just like you would with any other program or package. If the package is not found, we need to “install” it ourselves.

Windows + ( Linux and Mac the slightly harder way)


If you’re on Windows ( or Linux / Mac and the first step didn’t work, ) we need to install the library ourselves. Fortunately this is relatively easy.

Downloading

The first step is to download glm. You can do that here.. Scroll to the bottom and download the format you want ( .zip or .7. ) If you have a tool for dealing with package files, you should have to problems extracting it. Windows has built in support for .zip so choose this if you’re unsure. If none of the options work you can install winrar or 7zip.

Installing

Now extract the package anywhere you want and open the folder. You should find another folder name glm. In it there should be a lot of .hpp files ( think of these as your regular header ( .h ) files. )

For Windows :
Take the folder name glm ( the one containing the .hpp files ) and copy it to where you put the SDL2 header files so that it now contains both the SDL2 header file folder and the glm header file folder. Once that’s done, you should be able to use it directly ( since we’ve already specified the folder with all our includes. )

For Linux and Mac:
Take the folder name glm ( the one containing the .hpp files ) and copy it to /usr/include/ so that you end up with a folder called /usr/include/glm/ that contains all the glm header files.

Since this is a system directory, you won’t be able to put them here the regular way. But there is a few options.

If your file browser has a root mode, you can use that ( just be careful! )
If you can’t find it, you need to use the terminal ( after all, you are on Linux! )

You can use the cp command to do this :

Most likely you can do it like this

What does this do?

sudo is short for “Super User DO”. This is needed because it’s a system folder. sudo basically tells the operating system that “I know what I’m doing” Use it with caution!

The cp is the terminal command for copying.

The -r option is short for recursive it makes the cp command also copy all the sub folders and their files ( wihtout it, it’ll only copy the files inside the glm folders but it’d ignore all sub folders )

[collapse]

In order to make sure you got it right, run the command sudo ls /usr/include/glm it should now list the .hpp folders just like in the folder we looked at earlier.

( Please tell me if this doesn’t work on Mac, I haven’t been able to test it there yet… )

We can now include them in the same way as the SDL2 header files : #include <glm/vec4.hpp>. And since glm only uses header files, we don’t need to change our compile command!

Using glm to do matrix operations


Using the OpenGL Matmetics library ( glm ) is quite easy. There’s just a few simple functions we need to do what we want.

First of all, it’s just a mathematics library, so there’s no initialization code. That means we can jump straight to the mathematical functions.

Matricies and verticies


Fundamentally, vertecies and matricies are very simple constructions in glm. They’re just arrays with one element for each value. So a 3d vector has 3 elements, a 4d vector has 4 and so on. And similar for matrices.

A 3×3 matrix has a 9 element matrix. It’s arranged in a 2d array like so : float matr33[3][3] and similarly a 4×4 matrix has 16 values and can look like this : float matr4[4][4]. glm uses float types instead of double but this can be changed if you want to.

Let’s have a look at the various functions we can use with the vectors in glm

Creating a vector


The vector object in glm has several constructors, but we’re just gonna look at the simplest one :

This will set all the values of the vector to value. So

gives you the vector [1.3, 1.3, 1.3, 1.3]

Creating an identity matrix


When it comes to matrices we will be dealing with several different types of matrices. First we’ll look at creating an identity matrix ( like we saw above )

The simplest type of matrix is the identity matrix ( as we saw above. ) There are two simple ways to making them :

Or for 3×3 matrices :

Both of these produce a idenity matrix, which you can look at as a default value for matricies. It can also be used for reseting a matrix.

translatation matrix


In addition to identity matrix, we’ll be looking at translation matrices . A translation matrix is used to move an object by a certain amount. Remember above when talking about world space we saw that each object needs its own position in the world space? This is what the translation matrix is for. We use it to move a single object to the position it’ll have in the world space. Every object in your game world needs to have a be moved to a position in the world space, and to move it we use a translation matrix.

In addition to translating ( or moving ) an object, we can also scale and rotate it. All of these operations that works on a single object is called the model matrix. We’ll be using the name model matrix, but wel’ll be looking at rotating and scaling in a later post.

glm::translate


Here is how we use glm to create a translation matrix :

Parameters :

  • glm::vec3 d – the distance to move

The vec3 vector specifies the distance to move in each direction. So, for instance, [ 1, 0, -2 ] creates a matrix that can move an object

  • 1 unit in x direction
  • 0 units in y direction
  • -2 units in z direction

If you specify the vector [ 0, 0, 0 ] you’ll end up with a matrix that doesn’t translate the object at all. Nor does it change it in any way. So in effect you’ll end up with just a identity matrix.

Let’s look at a very simple example on how to create a translation matrix :

So how do we use it? Well that’s a bit more complicated so we’ll look at this later in the post.

view matrix


Now that we’ve placed the object in world space, we need to place it in the camera/view space. This is a bit more tricky because we need to set both position and where the camera is pointing.

It also has what’s called an up vector. This is used to set which direction is up vector. We’ll just leave it at [0, -1, 0] which is the most common value. Since we won’t use it, it’s not something you need to read. But if you want to know more about it, check out the spoiler text

The up vector

Think of it as how the camera itself is rotated. For instance, the camera could be turned up and down. Or tilted to the side. Doing so, would also change how the coordinate systems work Which is logical. If you turn the camera upside down, positive x would be towards the left and negative towards the right!

A possible use for this is if the player is hanging upside down. Then you could just change the up vector, which would rotate everything the player sees.

[collapse]

Parameters :

  • glm::vec3 position – the position of the camera
  • glm::vec3 center – the direction the camera is facing ( first paragraph )
  • glm::vec3 up – the tilt of the camera ( second paragraph )

Here’s the setup we’ll use :

  • position = [ 0, 0, -5 ]
    • x and y is at center, z is 5 units backwards
  • eye = [ 0, 0, 0 ]
    • Looking straight ahead
  • up = [ 0, -1, 0 ]
    • Upside-down, same as in SDL2

And here’s the code for creating that matrix :

When we’re rendering the scene, we’ll multiply all vertexes by this matrix. When we do that, things will be moved like we saw above. [ 0, 0, 0] is now what the camera is looking at ( like we saw above )

projection matrix


The view matrix dictates the position and orientation of the game camera ( what will end up on screen. ) But there is another matrix we need in order to do 3d, the projection matrix.

Just like a camera can have many different lenses, so can a projection matrix be set up in different ways

Parameters :

  • float const &fov – the field of view ( how far out to the sides the player can see )
  • float const &aspect – same as screen formats ( 16:9, 16:10, 3:4, etc… ) Changing this will stretch the image.
  • float const &near – the closest to camera something can be. Anything closer will be cut off.
  • float const &far – the furthest away something can be. Anything further away will be cut off.

The fov paramater is said to be spcified in degrees, and that’s what we’ll use. But it seems some have issues with glm wanting radians instead. Radians is just an alternative to degrees. You can read more about it here.. So if it doesn’t work, you can try specifying 3.14 * 0.25 = 0.785 for 45º.

Tip : if you own Minecraft you can experiment with this by going to options and changing the fov there!

The near and far far arguments will cut whatever is smaller than near or larger than far. It doesn’t cut off whole vertexes, just the pixels than are not between near and far

So, even though there are a few parameters here, they are relatively easy to comprehend. We’ll look more into how the actual matrix looks and what different types of projection matrices we can make ( yes, there are others ) in a later post.

Let’s take a look at a simple example

Combining them


We can combine all the matrices into one, so that we only have to multiply each of the vertexes by one matrix instead of all three. But when doing matrix operations it’s important to notice that the operations are not commutative. That means that the order in which you multiply them matters. This is in some cases very useful, but it can also lead to weird behavior.

In general, the order is left to right. The thing you want to happen first, should be the first part of your multiplication chain. Let’s assume we have three matrices. One for moving, one for scaling and one for rotating. If you wanted to scale, then rotate, then move, you’d do mat = scale * rotate * move.

But… When it comes to the transitioning between spaces, it’s opposite! So we start out with the projection, then we multiply by view and then by model.

I won’t go into the details of why ( would take too long time ) but it’s important to remember this for later.

I use the name modelViewProjection because that’s the most common name for this matrix. It is also sometimes shortened to mvp

Shaders and uniforms


Now that we know the basics of the matricies, we can finally have a look at how to use them in order to move objects, do projection and get something 3d on the screen. In order to do this, we must pass our matrix on to the shader. And this is where the benefit of having just one comes in. We can now just send one matrix for each object we are rendering, which means we have to send less to the GPU and the rendering will be faster.

Unifroms are global variables within the shader. It is a constant value so it does not change from rendering call to rendering call. You can’t actually change it from inside your shader at all. Doing so would cause a compiliation error. This is very practical because if there is an issue due to a uniform, we know it’s being changed somewhere in our main source code.

ID of a uniform


In order to be able to change a uniform in a shader from the source code, we need to have something to refer to it by. So in OpenGL, your uniforms automatically gets an ID. This is usually just the order in which you declare them. But this again raises a different issue ; how do we get the ID of the uniform on the shader. So we decalre an uniform on the shader, now we need to change it from our source code. How do we get the ID? By using the function glGetUniformLocation. Here’s what it looks like :

Parameters :

  • GLint location – The id of the shader program
  • const GLchar *name – The name of the variable in the shader

So if we have a shader that looks like this :

We can get the value like this :

Quite simple. And now that we have the ID, we can move on to the next step

Changing a uniform


A uniform can be any type of variable, a bool, and int, a float it can also be array type like vectors and matrices and even used defined structs! No matter what type, there is a group of functions we use for setting it, glUniform*. We’ll go into more details about the ones for single values and vectors in a later part. Instead we’ll jump straight into the ones for setting matricies

glUniformMatrix*


The function for setting a matrix in OpenGL is glUniformMatrix. There are a lot of varieties of this depending on the type ( whether the induvidual values are ints or floats ) and size ( 4×4, 3×3, 2×3, 4×3, … ). To make this part shorter, we’ll only be focusing on the one we’ll actually be using, glUniformMatrix4fv

Parameters :

  • GLint location – The location of the matrix ( the result of glGetUniformLocation )
  • GLsizei count – The number of matrices. In most cases this will be 1
  • GLboolean transpose – Whether OpenGL should transpose the matrix.See below
  • const GLfloat *value – The actual values of the matrix as an array

Matrix transpose


OpenGL expects the matrix in a spcific way. But in some cases, we might have the matrix transposed ( or “rotated” ). So instead of :

\begin{bmatrix} 1\quad3\quad5\quad7 \\2\quad4\quad6\quad8 \\3\quad4\quad5\quad6\\4\quad6\quad8\quad9\end{bmatrix}

It might look like this :

\begin{bmatrix} 1\quad2\quad3\quad4 \\3\quad4\quad4\quad6 \\5\quad6\quad5\quad8\\7\quad8\quad6\quad9\end{bmatrix}

The flag tells OpenGL that it needs to transpose the matrix first. Note : Some versions of OpenGL does not support this operation. In these cases the parameter must be set to false. This applies to OpenGL for mobile devices, OpenGL ES

And now for an example

In the vertex shader

And in your main source code :

Quite simple, and now the matrix is set and we can ( finally ) render 3d!

The results


So now after all that work, let’s see what we ended up with….

depth_fail

What?! That’s not right, the colors are weird and the front is missing…

The depth buffer


Remember from my previous part where I talked about the last step of the rendering piepline, per-sample operations?* I mentioned the depth test and how it determines if something is obscured / invisible and should not be rendered. What you see above is the consequence of not enabling the depth test.

( * : It was in there, but I forgot to mention it also checks if something is covered by another object. Sorry about that! )

Let’s take a close look at what’s happening, but this time we render one side of the cube at a time :

fail_1_side

This is the front side of the cube. So far it all looks good!


Let’s draw the bottom…

fail_2_side

Here’s the back and the bottom, and here’s where it goes wrong. The front should cover the bottom. But here the bottom is covering the front. This is because we don’t have depth test enabled so OpenGL just draws the bottom on top of it.


Let’s look at the next steps and see what happens

fail_3_side

Here we’ve added the the next side. If you compare with the inital part, this is what gets covered up. But why just this?


Let’s render the next triangle

Fail half

Here we’ve added half of the front. From this we can see that it is covering the bottom and right sides.


Let’s render the next triangle ( the second half of the front )

depth_whole

It covers up everything. This is because it’s the last thing we drew, so it gets drawn last, on top of everything.


And if we now draw the sides…

depth_fail

… we end up with what we saw earlier. The back gets drawn and covers everything. Then the top and left sides gets drawn on top of that.

Enabling depth test


Now let’s look at this with the depth test enabled

win

Here we’ve everything, including the front drawn the front. It completely covers the cube. It might seem wrong, all we can see is a blue square! But if we just move it a little…

win_moved

.. we see that it actually IS a 3D object! Finally!

The depth test


So how does all of this work? It’s quite simple. You can tell OpenGL to create a buffer ( think of it as a 2d array ) that has one value per pixel that says something about how far that pixel is from the camera.

Each time you draw something, OpenGL checks the value for that pixel in the array. This way, OpenGL can determine if what you’re trying to draw is closer to the camera than what’s already there. If it is, then the new pixel will be draw instead of the old one and the distance value in the buffer will be updated with the value of the new pixel. That way this buffer will always keep a buffer that contains the value of the information about the closest pixel that has been checked so far.

depth_test

Here’s how it worked when we drew the front ( blue ) over the rest. For every pixel it compares the previous value ( left ) value with the current ( right ) value. In this case, the blue one is closer and is drawn over the yellow. This happens for every single pixel we try to draw. Luckily, OpenGL is pretty quick at this.

How to enable it


Enabling it is quite simple. There are two functions we need for that :

Parameters :

  • GLenum cap – the OpenGL capability we want to enable. In our case its’s GL_DEPTH_TEST.

This basic function is used for turning OpenGL features on. You can see a full list of the possible values here.

Setting the depth function


We also need to see how the depth comparison works

Parameters :

  • GLenum func – the function to use

Here we tell OpenGL what function to use for depth testing. We will be using GL_LEQUAL, you can find more information about it and the others here.

Clearing the depth buffer


Finally, we need to tell OpenGL to clear the depth buffer for us. This is so that we can start we a clean slate every time we render. Without it, the depth test could fail because of leftover values making OpenGL not render something that should have been rendered. We’ll be doing this in our glClear function :

The | character is a way of combining the two values so that we clear GL_DEPTH_BUFFER_BIT and GL_COLOR_BUFFER_BIT in one call.

The source code


For the source code, I’ve taken the liberty of organizing it a little. I made a helper class for shaders, one for rendering in general, and one for dealing with models and their model matrix. In addition, I included a class I use for input ( that uses SDL2. )

Renderer


In charge of most rendering functionality :

  • Creating and initializing windows
  • Setting up OpenGL options
  • Setting up view and projection matricies

Shader


A class that keeps track of Shaders.

  • Can keep track of one shader of each type ( vertex, geometry, tesselation, fragment. )
  • Represents a single, whole shader program
  • Does everything needed to create a single shader program
  • Also used for setting uniforms like the model view projection matrix

Model


A class that holds a single object. In our case this is the cube:

  • creates VAO and VBO for the object from a file
  • Keeps the model matrix, which contains the position / scale and roation of the object
  • Keeps a reference to the Shader that the object uses
  • Has a Render() function so that it can set all the VAOs and VBOs and render itself

EventHandler


This is a class I wrote some time for keeping track of SDL events like quit, button presses, mouse move, mouse position, etc… It is not dirctly related to OpenGL, we just use it to make our interaction with SDL a tiny bit easier.

Math


A very simple Math helper class. It simply takes an EventHandler and creates a vec3 of the movement based on the arrow keys and w and s. So if you you press left, it’ll create a vector with a negative x value. This means that when we use glm::translate with the vector as the argument, we’ll get a matrix that moves the object left. It’ll be the same for every direction. Pressing w will move the object closer, s will move it away “into the screen”.

main.ccp


Controls everything.

  • Initializes Renderer
  • Creates a Shader
  • Creates a Model
  • Checks for keyboard events and tells Model to update matrix accordingly ( move or reset )
  • Renders the Model by calling functions in Renderer

As you can see, main.cpp doesn’t do anything to OpenGL. In fact, it doesn’t even include any OpenGL or SDL stuff. This is completely intentional. main.cpp should only control stuff.

Since the code is quite long and too big to put in this post ( unless you really like scrolling! ) I’ve put it in the Github repo for this code.

I’ve also created a zip file in case you don’t want to deal with git. You can find it here.

Compiling


Since we have the new .cpp file, EventHandler.cpp, we need to add it to our compilation call :

For clang :

clang++ main.cpp EventHandler.cpp -lSDL2 -lGL -lGLEW -std=c++11 -o Part4

For gcc:

g++ main.cpp EventHandler.cpp -lSDL2 -lGL -lGLEW -std=c++11 -o Part4


And NOW we’ve covered everything we need to know in order to do basic 3d rendering. It has taken me a long time to write all of this and it is quite long. But I hope you enjoyed it and that it helps you understand 3d rendering. If not, feel free to ask, I’m happy to help if I can.


Feel free to comment if you have anything to say or ask questions if anything is unclear. I always appreciate getting comments.

You can also email me : olevegard@headerphile.com