After years of taking beginner courses in OpenGL and linear algebra courses, I recently finally understood the point of the Model, View and Projection Matrices. Basically the Model Matrix converts the vertex coordinates of an 3D model into vertex coordinates in a 3D world (translating, rotating and scaling the model relative to the origin of the 3D world). The View Matrix converts the vertex coordinates of the 3D world into vertex coordinates relative to a camera (usually only translation and rotation of the world relative to the camera) and the Projection Matrix is used to compute/convert the vertex coordinate in a Camera view into a projection on a 2D plane (usually the screen).
I'm trying to create a camera system in a 3D Projection on a 2D Plane without OpenGL but by using JOML which is a Java Math (Mostly Linear Algebra math) Library for OpenGL often used with the LightWeight Java Game Library 3. I am able to create a camera system in OpenGL, which is quite easy with the 3 aforementioned matrices. But when I use the same exact matrices (and some extra code so that the projection appears on the screen) I can only do the Projection on a 2D Plane. The Model Matrix and View Matrix don't seem to have any effect on the way the model is projected on the screen.
Here is the code I'm using to project a cube on the screen:
private float theta = 0;
@Override
public void render(Graphics g) {
Vector3f cube3f[][] = {
// SOUTH
{ new Vector3f(-0.5f, -0.5f, -0.5f), new Vector3f(-0.5f, 0.5f, -0.5f), new Vector3f( 0.5f, 0.5f, -0.5f) },
{ new Vector3f(-0.5f, -0.5f, -0.5f), new Vector3f( 0.5f, 0.5f, -0.5f), new Vector3f( 0.5f, -0.5f, -0.5f) },
// EAST
{ new Vector3f( 0.5f, -0.5f, -0.5f), new Vector3f( 0.5f, 0.5f, -0.5f), new Vector3f( 0.5f, 0.5f, 0.5f) },
{ new Vector3f( 0.5f, -0.5f, -0.5f), new Vector3f( 0.5f, 0.5f, 0.5f), new Vector3f( 0.5f, -0.5f, 0.5f) },
// NORTH
{ new Vector3f( 0.5f, -0.5f, 0.5f), new Vector3f( 0.5f, 0.5f, 0.5f), new Vector3f(-0.5f, 0.5f, 0.5f) },
{ new Vector3f( 0.5f, -0.5f, 0.5f), new Vector3f(-0.5f, 0.5f, 0.5f), new Vector3f(-0.5f, -0.5f, 0.5f) },
// WEST
{ new Vector3f(-0.5f, -0.5f, 0.5f), new Vector3f(-0.5f, 0.5f, 0.5f), new Vector3f(-0.5f, 0.5f, -0.5f) },
{ new Vector3f(-0.5f, -0.5f, 0.5f), new Vector3f(-0.5f, 0.5f, -0.5f), new Vector3f(-0.5f, -0.5f, -0.5f) },
// TOP
{ new Vector3f(-0.5f, 0.5f, -0.5f), new Vector3f(-0.5f, 0.5f, 0.5f), new Vector3f( 0.5f, 0.5f, 0.5f) },
{ new Vector3f(-0.5f, 0.5f, -0.5f), new Vector3f( 0.5f, 0.5f, 0.5f), new Vector3f( 0.5f, 0.5f, -0.5f) },
// BOTTOM
{ new Vector3f( 0.5f, -0.5f, 0.5f), new Vector3f(-0.5f, -0.5f, 0.5f), new Vector3f(-0.5f, -0.5f, -0.5f) },
{ new Vector3f( 0.5f, -0.5f, 0.5f), new Vector3f(-0.5f, -0.5f, -0.5f), new Vector3f( 0.5f, -0.5f, -0.5f) },
};
Vector4f cube4f[][] = new Vector4f[cube3f.length][cube3f[0].length];
for(int i = 0; i < cube3f.length; i++) {
for(int j = 0; j < cube3f[i].length; j++) {
Matrix4f modelMatrix = new Matrix4f()
.rotate((float)Math.toRadians(theta), new Vector3f(0.0f, 1.0f, 0))
.rotate((float)Math.toRadians(theta), new Vector3f(1.0f, 0, 0))
.translate(new Vector3f(0, 5, 5)); // this is supposed to move the cube up 5 units and away 5 units
Vector4f tempvec = new Vector4f(cube3f[i][j], 0.0f).mul(modelMatrix);
Matrix4f viewMatrix = new Matrix4f().translate(new Vector3f(theta, 0, -20)); //this is supposed to translate the camera back 20 units
tempvec = tempvec.mul(viewMatrix);
Matrix4f projectionMatrix = new Matrix4f().identity().setPerspective((float)Math.toRadians(70.0f), 1280.0f/720.0f, 0.1f, 1000.0f);
cube4f[i][j] = tempvec.mul(projectionMatrix);
//following code makes the projection appear inside the screen's borders
cube4f[i][j].x += 1.0f;
cube4f[i][j].y += 1.0f;
cube4f[i][j].x *= 0.5f * 1280.0f;
cube4f[i][j].y *= 0.5f * 720.0f;
}
}
Graphics2D g2d = (Graphics2D)g;
g2d.setBackground(new Color(32, 32, 32, 255));
g2d.clearRect(0, 0, 1280, 720);
g2d.setColor(Color.WHITE);
for(int i = 0; i < cube4f.length; i++) {
g2d.drawLine((int)cube4f[i][0].x, (int)cube4f[i][0].y, (int)cube4f[i][1].x, (int)cube4f[i][1].y);
g2d.drawLine((int)cube4f[i][1].x, (int)cube4f[i][1].y, (int)cube4f[i][2].x, (int)cube4f[i][2].y);
g2d.drawLine((int)cube4f[i][2].x, (int)cube4f[i][2].y, (int)cube4f[i][0].x, (int)cube4f[i][0].y);
}
}
@Override
public void update() {
theta++;
}
In the above code, the cube is supposed to be 25 units away from the camera (because the cube is 5 unit away from the origin of the world and the camera 20 units away from the world on opposite direction) and 5 units to the right of the world. but that is not the case as we can see on the following picture:
As we can see in the picture; the cube is clearly centered and seen up-close.
I'm trying to find a solution that would allow me to keep the same "OpenGL" base code (more accurately JOML base code) on both my LWJGL3 application and my 3D Projection application. That is using the same Model, View and Projection Matrices to produce the same projection on both applications.
You missed the Perspective divide. The clip space coordinate is a Homogeneous coordinates. You have to transform the Homogeneous clip space coordinate to a Cartesian normalized device coordinate (all components are in range [-1, 1]) by dividing the x
, y
and z
component by the w
cpmponent:
tempvec = tempvec.mul(projectionMatrix);
cube4f[i][j] = new Vector4f(
tempvec.x / tempvec.w,
tempvec.y / tempvec.w,
tempvec.z / tempvec.w,
1.0f);
Since the vertices are points and not vectors, the 4th component of the vertex coordinate has to be 1 rather than 0:
Vector4f tempvec = new Vector4f(cube3f[i][j], 0.0f).mul(modelMatrix);
Vector4f tempvec = new Vector4f(cube3f[i][j], 1.0f).mul(modelMatrix);