How does gluLookAt work?

2019-04-07 23:54发布

问题:

From my understanding,

gluLookAt(
        eye_x, eye_y, eye_z,
        center_x, center_y, center_z,   
        up_x, up_y, up_z
    );

is equivalent to:

glRotatef(B, 0.0, 0.0, 1.0);
glRotatef(A, wx, wy, wz);
glTranslatef(-eye_x, -eye_y, -eye_z);

But when I print out the ModelView matrix, the call to glTranslatef() doesn't seem to work properly. Here is the code snippet:

#include <stdlib.h>
#include <stdio.h>
#include <GL/glut.h>

#include <iomanip>
#include <iostream>
#include <string>

using namespace std;

static const int Rx = 0;
static const int Ry = 1;
static const int Rz = 2;

static const int Ux = 4;
static const int Uy = 5;
static const int Uz = 6;

static const int Ax = 8;
static const int Ay = 9;
static const int Az = 10;

static const int Tx = 12;
static const int Ty = 13;
static const int Tz = 14;

void init() {
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glEnable(GL_DEPTH_TEST);
    glShadeModel(GL_SMOOTH);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    GLfloat lmodel_ambient[] = { 0.8, 0.0, 0.0, 0.0 };
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
}

void displayModelviewMatrix(float MV[16]) {
    int SPACING = 12;
    cout << left;
    cout << "\tMODELVIEW MATRIX\n";
    cout << "--------------------------------------------------" << endl;
    cout << setw(SPACING) << "R" << setw(SPACING) << "U" << setw(SPACING) << "A" << setw(SPACING) << "T" << endl;   
    cout << "--------------------------------------------------" << endl;
    cout << setw(SPACING) << MV[Rx] << setw(SPACING) << MV[Ux] << setw(SPACING) << MV[Ax]  << setw(SPACING) << MV[Tx] << endl;
    cout << setw(SPACING) << MV[Ry] << setw(SPACING) << MV[Uy] << setw(SPACING) << MV[Ay]  << setw(SPACING) << MV[Ty] << endl;
    cout << setw(SPACING) << MV[Rz] << setw(SPACING) << MV[Uz] << setw(SPACING) << MV[Az] << setw(SPACING)  << MV[Tz] << endl;
    cout << setw(SPACING) << MV[3] << setw(SPACING) << MV[7] << setw(SPACING) << MV[11] << setw(SPACING) << MV[15] << endl;
    cout << "--------------------------------------------------" << endl;
    cout << endl;
}

void reshape(int w, int h) {
    float ratio = static_cast<float>(w)/h;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0, ratio, 1.0, 425.0);
}

void draw() {
    float m[16];
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glGetFloatv(GL_MODELVIEW_MATRIX, m);
    gluLookAt(
        300.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 0.0f,
        0.0f, 1.0f, 0.0f
    );
    glColor3f(1.0, 0.0, 0.0);
    glutSolidCube(100.0);
    glGetFloatv(GL_MODELVIEW_MATRIX, m);
    displayModelviewMatrix(m);
    glutSwapBuffers();
}


int main(int argc, char** argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(400, 400);
    glutInitWindowPosition(100, 100);
    glutCreateWindow("Demo");
    glutReshapeFunc(reshape);
    glutDisplayFunc(draw);
    init();
    glutMainLoop();
    return 0;
} 

No matter what value I use for the eye vector:
300, 0, 0 or
0, 300, 0 or
0, 0, 300
the translation vector is the same, which doesn't make any sense because the order of code is in backward order so glTranslatef should run first, then the 2 rotations. Plus, the rotation matrix, is completely independent of the translation column (in the ModelView matrix), then what would cause this weird behavior? Here is the output with the eye vector is (0.0f, 300.0f, 0.0f)

        MODELVIEW MATRIX
--------------------------------------------------
R           U           A           T
--------------------------------------------------
0           0           0           0
0           0           0           0
0           1           0           -300
0           0           0           1
--------------------------------------------------

I would expect the T column to be (0, -300, 0)! So could anyone help me explain this?

The implementation of gluLookAt from http://www.mesa3d.org

void GLAPIENTRY
gluLookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx,
      GLdouble centery, GLdouble centerz, GLdouble upx, GLdouble upy,
      GLdouble upz)
{
    float forward[3], side[3], up[3];
    GLfloat m[4][4];

    forward[0] = centerx - eyex;
    forward[1] = centery - eyey;
    forward[2] = centerz - eyez;

    up[0] = upx;
    up[1] = upy;
    up[2] = upz;

    normalize(forward);

    /* Side = forward x up */
    cross(forward, up, side);
    normalize(side);

    /* Recompute up as: up = side x forward */
    cross(side, forward, up);

    __gluMakeIdentityf(&m[0][0]);
    m[0][0] = side[0];
    m[1][0] = side[1];
    m[2][0] = side[2];

    m[0][1] = up[0];
    m[1][1] = up[1];
    m[2][1] = up[2];

    m[0][2] = -forward[0];
    m[1][2] = -forward[1];
    m[2][2] = -forward[2];

    glMultMatrixf(&m[0][0]);
    glTranslated(-eyex, -eyey, -eyez);
}

回答1:

If we let a rotation and translation matrix like your modelview matrix

Rxx Rxy Rxz Tx 
Ryx Ryy Ryz Ty 
Rzx Rzy Rzz Tz 
 0   0   0   1 

act on an arbitrary vector

x
y
z
1

we get

Rxx x + Rxy y + Rxz z  +  Tx 
Ryx x + Ryy y + Ryz z  +  Ty
Rzx x + Rzy y + Rzz z  +  Tz
1

(I'm writing things so vectors get multiplied by matrices on the left).

This shows that the translation components of the matrix give the translation to apply after doing the rotation. That's why they aren't the same as your (-eye_x, -eye_y, -eye_z) vector, because as you point out that translation is being done before the rotation.

The reason that the translation is always along the -z direction is because in the view frame the -z direction points towards the centre. Since you always have the centre 300 units from the eye, all of your eye positions put the centre at (0, 0, -300) in the view frame. Therefore, because the centre starts at the origin before we do any translating, the translation to give it the correct co-orindates must be (0, 0, -300).

Also, you might have noticed this, but the modelview matrix you show is pathological because you have the up vector pointing along the view direction (from eye to centre). That explains why it has two full rows of zeros.



回答2:

" I'm very confused about how the rotations are performed using the forward, up and side vectors in this code..." I think you should know something about "UVN camera".There is some theory about coordinates translates between two coordinate systems.In the above examle, the two coordinates are world coordinates and camera coordinates. And the result is: x

N - The vector from the target to camera. Also known as the 'look at' vector in some 3D literature. This vector corresponds to the -Z axe.

V - When standing upright this is the vector from your head to the sky. If you are writing a flight simulator and the plane is reversed that vector may very well point to the ground. This vector corresponds to the Y axe.

U - This vector points from the camera to its "right" side". It corresponds to the X axe.



回答3:

@Andon M. Coleman - how is the above diagram row major? Having a Row or Column major matrix is about the memory representation of 2D structures in 1D memory and has nothing to do with the above diagram of a 4x4 transformation matrix.

If vectors U,V,N were written as columns as you seem to suggest, then you would have a camera-space to world-space transformation.

However, the input to the matrix is world-space position and the output is camera-space position and so the matrix is a transformation world-space to camera-space.

The reason U,V,N are transposed is because this is the inverse of the matrix you suggest and by using the property of orthogonal matrices, where their inverse is also its transpose. That is, we write U,V,N vectors to the rows to get a world-space to camera-space transformation and U,V,N to the columns to get camera-space to world-space transform.

Also, the choice of multiplying with the world position on the right is because the diagram is using column vectors. We would left multiply if we were using row vectors. It has NOTHING to do with how we store the matrix in memory and has everything to do with how we multiply two matrices together, that is, do we convert our vector to a 1X4 or a 4X1 matrix before we multiply it with the 4x4 matrix.

In short, the above diagram is fine, it's just a transformation from one space to another, please don't confuse the matter with talk about memory layout which is a programming detail.