0

Short version: I'm using a glLookAt view matrix with a perspective projection matrix, and I'm wondering why a change in the camLookAt vector (the point where I'm looking at) leads to a change in the camera's position.

Long version: I'm using the code described here for my own implementation of the well-known glLookAt function:

        var z = glm.normalize(camPosition - camLookAt);
        var y = up;
        var x = glm.normalize(glm.cross(y, z));
        y = glm.cross(z, x);
        var result = new mat4(1.0f);
        result[0, 0] = x.x;
        result[1, 0] = x.y;
        result[2, 0] = x.z;
        result[3, 0] = -Dot(x, camPosition);
        result[0, 1] = y.x;
        result[1, 1] = y.y;
        result[2, 1] = y.z;
        result[3, 1] = -Dot(y, camPosition);
        result[0, 2] = z.x;
        result[1, 2] = z.y;
        result[2, 2] = z.z;
        result[3, 2] = -Dot(z, camPosition);
        result[0, 3] = 0;
        result[1, 3] = 0;
        result[2, 3] = 0;
        result[3, 3] = 1.0f;
        return result;

This seems to work nicely, except for one strange thing: When I leave the cam's position fixed, but change the camLookAt variable, it seems that the camera moves not just its angle, but also its position.

To demonstrate it, I have made an explicit calculation:

  • Take the world points (0, 0, 0) and (1, 0, 0).
  • Look at them from camPosition = (-3, 0, 0) and camLookAt = (0, 0, 0) with up = (0, 0, 1). So we're looking at the origin, in the direction of the positive x axis. This is the matrix I get:
     0, -1,  0,  0
     0,  0,  1,  0
    -1,  0,  0, -3
     0,  0,  0,  1
  • Since both points lie on the x axis, they should appear at the same spot on our camera. One dot should be exactly behind the other one. This works great, the first point gets mapped to (0, 0, -3) and the second one to (0, 0, -4).
  • Now change camLookAt to (0, 3, 0), i.e. rotate the camera by 45 degrees to the left. This is the matrix I get:
     0.71, -0.71,  0.00,  2.12
     0.00,  0.00,  1.00,  0.00
    -0.71, -0.71,  0.00, -2.12
     0.00,  0.00,  0.00,  1.00
  • Since we don't move the camera's position, the points should still be behind each other. However, this isn't the case, the two points get transformed to (2.1213, 0, -2.1213) and (2.8284, 0, -2.8284) respectively. So the (X,Y) components of the two transformed points do not match.

Now, up to this point we have used Euclidean transformations only, so there is no perspective yet. However, I'm using a perspective projection matrix like this

const float radians = (60.0f / 360.0f) * (float)Math.PI * 2.0f;
projectionMatrix = glm.perspective(radians, width / height, 0.01f, 1000f);

Even after this projection matrix is applied, the two points don't land on the same (X, Y) coordinates, i.e. on the same pixel on screen.

What am I missing? Is that the intended behavior of glLookAt, or am I using / implementing it wrong?

EDIT: The projection matrix is applied in the vertex shader, simply like this:

void main(void) {
    gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(in_Position, 1.0);
}

EIDT 2: Here's a recording of my current scene, just as clarification. The only thing that's being changed with time is the camLookAt vector. The camera should stay at the same position, but somehow it seems to move:

enter image description here

EDIT 3: As recommended by @NicoSchertler, I just did all the calculations on the CPU. The viewMatrix multiplication is done above, and it results in (2.1213, 0, -2.1213) and (2.8284, 0, -2.8284) respectively. Now I apply the projection matrix (see above for how I'm getting the projection matrix):

    1.30,  .00,   .00,  .00
     .00, 1.73,   .00,  .00
     .00,  .00, -1.00, -.02
     .00,  .00, -1.00, 1.00

and I get the following vectors

(2.75, 0.00, 2.10, 3.12)
(3.67, 0.00, 2.81, 3.83)

Then I divide them by their w value which yields

(0.88, 0.00, 0.67, 1.00)
(0.96, 0.00, 0.73, 1.00)    

So they clearly are not behind each other.

Is there a bug in the projection matrix?

Jonas Sourlier
  • 13,684
  • 16
  • 77
  • 148

1 Answers1

3

However, this isn't the case, (...) the (X,Y) components of the two transformed points do not match.

The two points having different x/y coordinates does not mean that they are not behind each other. Obviously, they have a different depth. The camera is located at the origin in this coordinate system. And if you calculate the direction from camera to the points (i.e. normalize(p - 0)), we get:

direction1 = {0.707107, 0, -0.707107}
direction2 = {0.707107, 0, -0.707107}

So, the direction from the camera to the two points is the same, hence they are behind each other.

You haven't shown how you do the projection. But the points must project onto the same location. There is a little caveat with perspective projections, though. The projected point will have a w-component that is not 1. You need to divide the point by this w-component (perspective divide) to find the actual projected point. And then, the x/y coordinates should actually match.

Nico Schertler
  • 32,049
  • 4
  • 39
  • 70
  • Thanks, so it really is a matter of projection. How I'm doing the projection is described at the bottom of my post. Not sure but I don't think it is necessary to divide by `w` in the shader. I think OpenGL does that automatically after the shader is finished, right? – Jonas Sourlier Dec 08 '17 at 15:42
  • 1
    Yes, that's right. OpenGL takes care of the perspective divide. Are you uploading the matrix properly (i.e. in column-major order)? – Nico Schertler Dec 08 '17 at 15:47
  • Well, I'm passing it as `matrix.to_array()` in SharpGL. This is how it's done in the examples. I think if I passed it in the wrong order, the scene would not look like a 3D scene. But the scene looks OK, I can move around with the camera, and the objects are rendered as expected. The only thing is, when I move `camLookAt` while leaving `camPosition` fixed, the position of the camera seems to change slightly. – Jonas Sourlier Dec 08 '17 at 15:52
  • It looks like the camera is positioned a little bit "behind" `camPosition`. If I move `camLookAt` to the left, then the camera seems to be moved a little bit to the right. – Jonas Sourlier Dec 08 '17 at 15:53
  • Can you do your example calculations in code until the projection step (including perspective divide)? I can't see anything problematic. If it works on CPU side, then you know that it must be a problem with the data transfer. – Nico Schertler Dec 08 '17 at 15:57
  • 2
    Thank you Nico, I found it. GLMNet's `perspective` returns a matrix whose `(3, 3)` entry is `1.0`. However, other perspective implementations create a matrix with a `0.0` entry at `(3, 3)`. I changed that, and now everything works fine. Seems to be a GLMNet bug. – Jonas Sourlier Dec 08 '17 at 19:28