🌍 Go Paperful

🔗 Subscribe via RSS

mid's site

you're logged in as loser

Journey into OpenGL: Spaces

The Series

  1. The Series
  2. the First Triangle
  3. the Framebuffer and Depth Buffer
  4. Transformations
  5. Spaces
  6. the Cube?
  7. Vertex Arrays
  8. Index Arrays
  9. ...

Now that we have learned matrices, we can move onto spaces other than clip space, which we already know. Pretty much all matrices you will be using can be said to move things between spaces.

As I said before, models are made in a so-called model space. In model space, the model is centered at the origin point, and its forward is some standard direction (likely Z+). When models are placed in the world, however, they clearly are not at the origin. They move, shift, spin, etc. In these cases, models have been moved into world space. This is done with what is known as the model matrix (M). Most if not all program logic is done in world space, because it can be considered global or unchanging. A real-world example would be our use of coordinates relative to Earth. In CG terms this would be "Earth space".

Let us use this new knowledge on our one-triangle example. I shall now incorporate my choice of library for linear algebra, cglm. You may include separate C headers for each offered type, or import the whole with #include<cglm/cglm.h>.

glMatrixMode(GL_MODELVIEW);
mat4 m; //Model matrix.
glm_mat4_identity(m);
glm_translate(m, (vec3) {0.5, 0, 0});
glLoadMatrixf((float*) m); //The cast to float* is a C-ism to prevent a warning.

glBegin(GL_TRIANGLES);
	glColor3f(1, 1, 1);
	glVertex2f(-0.2, -0.2);
	glVertex2f(+0.2, -0.2);
	glVertex2f(0, +0.2);
glEnd();

As you see, the vertices I've had passed to OpenGL are defined in model space. But I want the triangle to be not at the origin, but at (0.5, 0, 0), and so I use a model matrix to do that. glm_mat4_identity initializes the matrix to one with nil effect. The subsequent function adds translation to the transformation. The matrix is then loaded into the OpenGL state. After this, each vertex pushed is premultiplied with the matrix. The previous article should fill you in on how this works.

This is neat, but without the addition of a camera we will appear to be looking from (0, 0, 0) at all times. Let us imagine what it might look like when we move a camera from (0, 0, 0).

If we transform the camera by, say, moving it to the left, then by the principle of relativity it will appear as though the entire world moves right. This can be extrapolated to all transformations: if the camera is defined by a transformation matrix C, the effect on screen will be as though the entire world has been transformed by the inverse of C. This inverse is the view matrix (V) and it moves from world space to camera space.

So let's add the camera part. Recall that multiplication of matrices combines their effects. Because OpenGL takes in a single modelview matrix, we must use this property to pass the whole transformation.


mat4 m; //Model matrix.
glm_mat4_identity(m);
glm_translate(m, (vec3) {0.1, 0, 0});

mat4 c; //Camera matrix.
glm_mat4_identity(c);
glm_translate(c, (vec3) {0, 0.1, 0});

mat4 v; //View matrix.
glm_mat4_inv(c, v);

mat4 mv; //Modelview matrix.
glm_mat4_mul(v, m, mv);

glMatrixMode(GL_MODELVIEW);
glLoadMatrixf((float*) mv); //The cast to float* is a C-ism to prevent a warning.

glBegin(GL_TRIANGLES);
	glColor3f(1, 1, 1);
	glVertex2f(-0.2, -0.2);
	glVertex2f(+0.2, -0.2);
	glVertex2f(0, +0.2);
glEnd();

As you see, the view matrix defines the camera's position at (0, 0.1, 0). That means the world should move by (0, -0.1, 0) instead, and that is in fact the effect you see.

Not entirely accurate but gets the idea across.

Now we shall move into "true 3D" by the use of a projection matrix. There exist in computer graphics two main projection types: perspective and orthographic. The former is what emulates our real-world form of vision, and the latter is used for 2D, 2.5D, blueprints, engineering, that sort of thing. Orthographic projection is simpler, and so I will leave that as an exercise.

mat4 p;
glm_perspective((float) 640 / 480, glm_rad(90), 0.001f, 1000.f, p);

glMatrixMode(GL_PROJECTION);
glLoadMatrixf((float*) p);

The creation of a perspective projection matrix (P) requires the aspect ratio of our window. This makes sense; as you might remember, clip space stretches to the window size to keep itself internally a cube. This counteracts that. The next argument is the field of view given in radians. The last arguments adjust the near and far planes to values other than -1 and 1. It is necessary for the plane depths to be positive, else you get nonsense. It is also important not to choose too great a scale between the near and far planes, else you begin to notice artifacts from the limited precision of the depth buffer. To the right you will see a visualization of the move from camera space to clip space done by a perspective projection matrix.

With this in addition to the modelview matrix we can say we have finally reached true 3D. But, if you try running the code as-is, you might not notice a big difference. Indeed, with a lone triangle it's not very noticable. Try increasing the depth of the triangle (perhaps by changing the Z translation in the model matrix.)

But even better would be to animate the scene for a nice showcase. GLFW includes a function to get a relative current time, and your software library should feature something similar. Taking advantage of our rendering loop, we can choose a camera transformation depending on the current time. A simple example would be to rotate the camera around the origin point, making it orbit in effect.

mat4 m; //Model matrix.
glm_mat4_identity(m);

mat4 c; //Camera matrix.
glm_mat4_identity(c);
glm_rotate_y(c, glfwGetTime(), c);
glm_translate(c, (vec3) {0, 0, 1});

To reiterate:

  • The model matrix moves vertices from model space to world space
  • The inverse camera matrix (view matrix) moves vertices from world space to camera space
  • The projection matrix moves vertices from camera space to clip space

While all of this may seem like boilerplate when laid out like so, a real-world program will use many model matrices, a few view matrices, very few projection matrices, and so the program as a whole will end up being generic enough that all of these abstractions will prove themselves useful.

What is a model matrix?

Denoted M, a model matrix moves from model space to world space.

What is a view matrix?

Denoted V, a view matrix is the inverse of a camera matrix, and it moves from world space to camera space.

What is a projection matrix?

Denoted P, a projection matrix moves from camera space to clip space.

What is a space?

A coordinate system with a human-friendly convention, such as a standard origin or orientation.

What is necessary to construct a perspective projection matrix?

An aspect ratio, field of view and plane depths, which must be positive.