3

I'm familiar with the "look-at" matrix and the goal of being able to rotate a Camera at a specific location towards a specified target.

When I tell the Camera to look at some target, there're no issues. However, when some other non-Camera object needs to "look-at" another object, such as a hypothetical Dog looking at some Food, so that the Dog "points" in its direction, it doesn't work as expected. In this case, when the look-at matrix used for the Camera is used (without modification) for the Dog, the Dog ends up looking in the opposite direction I intended it to face.

What follows is the code I've been using to build my look-at matrix:

public static Matrix4 createLookAtMatrix(
    Vector4 eyePosition,
    Vector4 targetPosition,
    Vector4 upDirection)
{
    // forward, side, up
    // 
    // f = normalize(targetPosition - eyePosition);
    // s = normalize(f × normalize(upDirection));
    // u = s × f;
    Vector4 f = targetPosition.sub(eyePosition).normalize();
    Vector4 s = f.cross(upDirection.normalize()).normalize();
    Vector4 u = s.cross(f);

    float[][] values = new float[][] {
        { s.x(),  s.y(),  s.z(), -s.dot(eyePosition) }, // row 0
        { u.x(),  u.y(),  u.z(), -u.dot(eyePosition) }, // row 1
        {-f.x(), -f.y(), -f.z(), -f.dot(eyePosition) }, // row 2
        { 0f   ,  0f   ,  0f   ,  1f                 }  // row 3
    };

    return createFrom(values);
}

What am I missing? Is there a way to build such a "look-at" matrix so that it works for both regardless of who/what the viewer is (i.e. Camera or Dog)?

I'd like to avoid depending on "special corner cases" to detect what kind of object is trying to "look-at" something else, and so on.

My system can have multiple SceneObjects at any given moment connected to the same SceneNode (SceneNodes are the objects that actually contain the transforms) and I really need a way to build a "look-at" matrix that will work regardless of which kind of object happens to get attached to the SceneNode.


This is a screenshot of the reference book I was reading/using when writing my code above:

The Look-At Matrix

code_dredd
  • 166
  • 1
  • 7
  • oIts the exactly same computation. – joojaa Feb 06 '17 at 14:10
  • @joojaa "olts"? Not sure I understood your comment. – code_dredd Feb 07 '17 at 03:58
  • Typo, it should read 'its'. The source you use uses a transpose trick just clear the matrix. Usually you shouldnt do this many steps beween calls as it confuses other programmers . Anyway the code is the same minus the extra matrix manipulation. I wont be writing code because your code does not tell me what all your conventions are. – joojaa Feb 07 '17 at 04:49
  • @joojaa When you say "minus the extra matrix manipulation", do you mean that the matrix from the reference above should simply be transposed so that it ends up in a column-major order or were you referring to something else? My intention is to use column-major matrices, but the one above was arranged in row-major based on how it was shown in the reference (which could've been a consistency issue fixed later in the ref) I've been going over my stuff and re-checking my understanding and other things, which is why it's taking a bit longer than usual for me to reply to comments. Thanks. – code_dredd Feb 07 '17 at 11:16
  • @rayits From the look of it its inverted after initial placement. See the ivnerse of a orthogonal matrix (a pure rotation) is its transpose. And ofcourse the inverse of a move is just negative move. Thats why it looks like its wrog order. Its right, just that many operations are stacked in one line of code. – joojaa Feb 07 '17 at 13:09

2 Answers2

1

You don't seem to get the concept right:

A look-at camera matrix is created by defining two points, the "from" point (the dog eyes for example assuming you take the position of the eyes of the dog and average them to get the point in between) and a "to" point which is where the dog looks at. So if your dog is at (3,3,3) and that you want it to look at the some food which is at (-10, 100, 20), then you need to call the lookAt() method with these 2 points and it will create a matrix that will actually orient and position the camera properly.

Now you are getting confused (probably) because most cameras in most 3D packages are positioned at the origin (by default) and looking down the z-axis (by default). You can create such camera with a lookAt() function btw that would look like that:

lookAt(Vec3f(0), Vec3f(0,0,-1));

For example. Now check what the matrix this call will produce with the matrix of the default camera in Maya. You will notice they are different. The second is an identity matrix, the first one is not.

Now for example if you create a default camera in Maya it will be position at (0,0,0) and looking down the negative z-axis. If you look at the 4x4 matrix of this camera you will get identity. And maybe what you are trying to do is to create a 4x4matrix using the lookAt function and multiply this matrix by the Maya's default camera matrix in which case, your camera will point in the wrong direction (it will be flipped along the camera's local coordinate z-axis). That's because the matrix returns by lookAt properly orient the camera (as suggested in the first paragraph) but since the default camera in Maya is already oriented along the negative-z axis, your Maya camera will look the wrong way.

So in summary:

  1. If you use the lookAt method in a renderer, it will work as you expect. There's no z problem.
  2. if you try to use it on a camera that is already oriented along the negative z-axis and yet has identity matrix yes indeed you will be looking the wrong way. This can be fixed easily by flipping the camera back one more time along the z-axis. Simply create another matrix that scales the object/camera by (1,1,-1).

EDIT

The OP says (see comment):

I did spot what looks like a typo: it says "(mind the direction of this vector: it is To − From not From − To)", but then you go on to write the line of code Vec3f forward = Normalize(from - to);, which contradicts what you were trying to clarify.

But a note in the reference says:

The camera is aligned along the negative z-axis. Does it mean I need to rotate the camera by 180 degrees along the y-axis or scale it up by -1 along the z-axis? Not at all. Transforming a camera is no different from transforming any other object in a scene. Keep in mind that in ray-tracing, we build the primary rays as if the camera was located in its default position. This is explained in the lesson Ray Tracing: Generating Camera Rays. This is when we actually reverse the direction of the rays. In other words, the z-coordinates of the rays' directions at this point are always negative: the camera in its default position, looks down along the negative z-axis. Those primary rays are then transformed by the camera-to-world matrix. Therefore, there is no need to account for the default orientation of the camera when the 4x4 camera-to-world matrix is built.

So if you look at the reference they use by default a right-hand coordinate system, which means x-axis points to the right, y is up and z goes out of the screen (outward, going your direction when x-axis points to the right). So they build a matrix that "follows this convention" which makes sense. Then they say in the note "yes but wait, normally the camera points down the negative z-axis, so you should be doing to - from and not from - to, and they explain that in the case of ray-tracing the fact that the camera looks down the z-axis is performed when the rays are cast (by inverting their z-coordinate).

Vec3f rayDirectionInCamSpace{ x, y, -1 }; // invert here
normalize(rayDirectionInCamSpace);
Vec3f rayDirInWorldSpace = transform(rayDirectionInCamSpace, camToWorldMat);
pixelColor = trace(rayOrigWorldSpace, rayDirInWorldSpace);

So no it's not a typo. Now the reference you are pointing out maybe assume this matrix needs to be used in some OpenGL render in which there is no possibility to invert they ray direction when they cast. So they indeed use to - from instead which is the same as multiplying the cam-to-world matrix you would get from the reference by a matrix that flips the coordinates z-axis (multiply by (1,1,1-)).

So no typo, things are quite logical in both cases. The result you get is always constrained to a lot of small details, such as the handiness of the coordinate system and how is the matrix later used down your graphics pipeline (OpenGL pipeline, ray-tracer, renderer, animation package, etc. which may follow its own conventions, etc.).

EDIT 2

Note that in the reference your provide (screenshot of book) they do target - eye but then they use the negative of that in the matrix construction. Not sure why. Then also no idea why the multiply the eye by the vectors for the translation part of the matrix. It seems to be using row-order matrices for the axis of the coordinate system but then the translation part should be at mat[3][0], mat[3][1] and mat[3][2] and not mat[0][3], mat[1][3] and mat[2][3]. Not sure what book this is but seems to be terribly wrong and confusing.

EDIT 3

Code (that seems to be the only solution):

void lookAt(const Vec3f& from, const Vec3f& to, Matrix44f &V)
{
    static const Vec3f tmp(0, 1, 0);
    Vec3f forward = normalize(from - to);
    Vec3f right = crossProduct(normalize(tmp), forward); 
    Vec3f up = crossProduct(forward, right); 

    V[0][0] = right.x;
    V[0][1] = right.y;
    V[0][2] = right.z;
    V[1][0] = up.x;
    V[1][1] = up.y;
    V[1][2] = up.z;
    V[2][0] = forward.x;
    V[2][1] = forward.y;
    V[2][2] = forward.z;

    V[3][0] = from.x;
    V[3][1] = from.y;
    V[3][2] = from.z;
}


int main(int argc, char **argv)
{
    ...
    lookAt(from, to, camToWorldMatrix);
    worldToCamMatrix = camera.camToWorldMatrix.inverse();

    Matrix44f projectionMatrix;

    Matrix44f openGLWorldToCamMatrix = worldToCamMatrix;
    Matrix44f openGLProjectionMatrix = projectionMatrix;

    while (!glfwWindowShouldClose(window)) {        
        glViewport(0, 0, width, height);

        glUniformMatrix4fv(projectionLocation, 1, GL_FALSE, (const GLfloat *)&openGLProjectionMatrix[0][0]);
        glUniformMatrix4fv(viewLocation, 1, GL_FALSE, (const GLfloat *)&openGLWorldToCamMatrix[0][0]);

        ...            
        wwapBuffers(window);

    }

    return 0;
}
user18490
  • 656
  • 4
  • 7
  • I'm not familiar with Maya, so I wouldn't know what it looks like there. If it's relevant to you, I wrote the code to load Wavefront models exported from Blender; they load/render fine, so there's no issue there. I did mention that I have a working look-at matrix applied to the camera (which needs 3 parameters, not 2: eyePosition, targetPosition, and upDirection vectors). The transformation is actually contained in a SceneNode that the Camera, or Dog, is attached to, hence the question for a way to make it work regardless of which one needs to 'look' at some specified direction. – code_dredd Feb 05 '17 at 12:50
  • I already replied to your question. Take the dog eye as the eyePosition of your lookout method. If your camera is looking the other way around then apply a negative scale along the camera z-axis. There is no reason one or the other solution should not work. Again if you understand what the lookAt method works, you wouldn't ask the question;-) So do you understand what it does? – user18490 Feb 05 '17 at 12:55
  • I am using the dog's eye as eyePosition when trying to turn it towards the target position. I'm also using camera position as eyePosition when creating a separate look-at for the camera. However, the logic that allows the camera to look in the correct direction (i.e. face the target) leaves the dog facing in the opposite direction; the dog is not the camera position. The camera is looking at the dog which is trying to look at something else. If I scale the matrix, then the camera looks in the opposite direction I intend it to. – code_dredd Feb 05 '17 at 13:03
  • I've made several updates to the question, which I think now does a better job at explaining the issue, removing ambiguity by showing the code I've been using, and showing the reference on which it was based, etc. I did go over your reference, but it explains the same concepts. I did spot what looks like a typo: it says "(mind the direction of this vector: it is To − From not From − To)", but then you go on to write the line of code Vec3f forward = Normalize(from - to);, which contradicts what you were trying to clarify. Let me know if you still think my question can be improved. – code_dredd Feb 06 '17 at 12:07
  • I replied in the edit. – user18490 Feb 06 '17 at 12:24
  • Thanks for replying quickly. I'm wondering if my updated post clarified anything? Your edit seems to only be about what I initially thought could be was a potential typo. My look-at matrix is meant to be used with OpenGL. Does my look-at matrix look incorrect to you? From what I understand in what you insisted so much that I read (which is not the first article/reference I had consulted), it should work just fine regardless of whether the eyePosition is that of the Dog or the Camera, which is what I always understood to be the case, until I ran into the issue I'm trying to solve. – code_dredd Feb 06 '17 at 12:32
  • Just saw your other edit; the book reference is explicitly states that it's using column-major vectors and matrices, not row-major. – code_dredd Feb 06 '17 at 12:32
  • @Ray. Then if they use column-major ordering the way the matrix is written down is just plain wrong. This is not a good book. Can you point the reference please? – user18490 Feb 06 '17 at 12:34
  • Well, I would've expected the side vector to be the left-most column only, not the top row (i.e. the transpose of that is what I would've expected), but I don't see the reference stating that it was transposing the pictured matrix. It could've been a typo or something in its errata. – code_dredd Feb 06 '17 at 12:37
  • @Ray: why are the vector for the camera coordinate system in row form and the translation part in column form? If it's column-major matrix vectors should be written as columns. I gave you source code. That's how it's done in OpenGL. Many things can go wrong in your code, so follow this example and you will get it right ... code uses row-major matrices. The matrix from your ref is ALL wrong. It writes the vectors of the coordinate system in row form and the translation in column form. It's a mix of both!!! WRONG WRONG you won't go anywhere with that. You need to understand how matrices work – user18490 Feb 06 '17 at 12:42
  • I had been sent a draft copy of the reference, so any "title" I provide would be meaningless, and would probably be a tangential discussion. – code_dredd Feb 06 '17 at 12:42
  • Just saw your last edit. Will go over it. – code_dredd Feb 06 '17 at 12:44
  • I understand how matrices work; please don't confuse a problem specific to the look-at matrix with not knowing anything about matrices. It comes across as condescending, even if that's not your intention :/ – code_dredd Feb 06 '17 at 12:46
  • @Ray: you mind this useful as well: http://stackoverflow.com/questions/4124041/is-opengl-coordinate-system-left-handed-or-right-handed. I am just trying to help you. You assume you know but you don't if you can't see that the matrix you publish on this forum is not a row-major or column-major order I am sorry but I believe yes you should first really understand this and has someone who has 20 years of CGI programming behind if you don't want to learn then that's up to you, but I am just trying to mentor you here... and if I come as condescending maybe being humble is good too;-) – user18490 Feb 06 '17 at 13:50
  • The reason the rows and colums are weirdly flipped is that they have done a 90 degree turn/flip in the matrix. While it works, it is a bit magick and should be avoided IMHO. Which is also why op is unable to do what he wants. – joojaa Feb 06 '17 at 14:20
  • @user18490 I understand and appreciate that you're trying to help, and I've been re-checking my stuff under the working hypothesis that I really understand absolutely nothing (even other refs), trying to discover which part(s) I misunderstood. I never intended to come across as arrogant, if that's your impression (really, I never would've posted a public question if that were the case), but it's a bit over the top to suggest that I know absolutely nothing about it, which is also too generic/vague to help me figure out which particular detail(s) I may have misunderstood. Hope that makes sense. – code_dredd Feb 07 '17 at 11:26
  • @user18490 Well, I've been going over my other references (including OpenGL Super Bible 6 on matrices, model-view transform, and look-at matrix topics), but I'm still unable to figure out what part I've misunderstood to such a spectacular degree. (The code in edit-3 did not work for me.) I had noticed SB6 referring to the 'look-at' matrix as the 'view matrix' (p.77, 79) and thought that both were the same thing until I got burned (look-at and view matrices get built differently), so now I think of them as different matrices. I mention this in case it's comes across as related to my issue. – code_dredd Feb 08 '17 at 09:23
  • @user18490 BTW, I'm also familiar with this other article on the topic, which I had read a while back, but I'm re-reading yet again for good measure. – code_dredd Feb 08 '17 at 09:24
1

Both the question and its answer is confusing.

First of all the matrix you have

The Look-At Matrix

is a column "Look at" view matrix rather than a model matrix. A correct column "Look At" model matrix would be

$$\begin{bmatrix} side_x&side_y&side_z&0\\ up_x&up_y&up_z&0\\ Forward_x&Forward_y&Forward_z&0\\ eye_x&eye_y&eye_z&1 \end{bmatrix}^T$$, which is what the other answer is saying. This means that matrix in the book is the inverse of the matrix given by me and the other answer.

First matrix will be used to transform the objects in the world space to the view space of the camera. Second matrix will be used to transform the camera from its local space to the world space such that camera is pointing at whatever the "target" vector is.

There is nothing special about the second matrix, you can use it on a dog to point to food, provided its mouth is along the +z direction in its local space.

So in summary, first matrix is a view matrix is used transform objects from world space to view and the second matrix is a regular model matrix which can be applied to any object in the scene to transform it from its local space to the world space with "eye" as its world position and +z direction looking at "target".

There is nothing wrong with either matrices just that they are used for accomplishing total different tasking.