2

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:

up-close centered rotating cube

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.

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
Paiku Han
  • 581
  • 2
  • 16
  • 38

1 Answers1

2

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);
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • There is one issue remaining: When I setup an FPS camera (WASD keys on keyboard) and press the W key to get closer to the object everything looks fine. But when I Keep pressing it after I reached the cube, the cube goes back to its position (away from the camera) as if I was using the absolute value of the Z axis. What is the fix please? In the OpenGL version the camera is behaving normally. – Paiku Han Sep 22 '20 at 18:22
  • 2
    @PaikuHan Yes of course, because you have no clip planes. You draw the cube even if it is befind the camera (z > 0). Clipping has to be done if `tempvec.z < -tempvec.w` or `tempvec.z > tempvec.w`. Note, in OpenGL clipping is done at rasterization per fragment. You cannot clip per vertex, but you can clip if for all the vertices of the cube `tempvec.z < -tempvec.w`. Note if you don't clip, then the cube is projected even if it is behind the camera. If you take a closer look you'll notice, that the cube is mirrored on the x and y axis. – Rabbid76 Sep 22 '20 at 18:27
  • you are right. I was wondering why some extra lines were appearing when I was inside the cube. Now I know it's the other side of the cube being rendered! – Paiku Han Sep 22 '20 at 18:37