glLookAt moves camera position when lookAt is moved - role of projection matrix? - opengl

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:
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?

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.

Related

OpenGL ray tracing using inverse transformations

I have a pipeline that uses model, view and projection matrices to render a triangle mesh.
I am trying to implement a ray tracer that will pick out the object I'm clicking on by projecting the ray origin and direction by the inverse of the transformations.
When I just had a model (no view or projection) in the vertex shader I had
Vector4f ray_origin = model.inverse() * Vector4f(xworld, yworld, 0, 1);
Vector4f ray_direction = model.inverse() * Vector4f(0, 0, -1, 0);
and everything worked perfectly. However, I added a view and projection matrix and then changed the code to be
Vector4f ray_origin = model.inverse() * view.inverse() * projection.inverse() * Vector4f(xworld, yworld, 0, 1);
Vector4f ray_direction = model.inverse() * view.inverse() * projection.inverse() * Vector4f(0, 0, -1, 0);
and nothing is working anymore. What am I doing wrong?
If you use perspective projection, then I recommend to define the ray by a point on the near plane and another one on the far plane, in normalized device space. The z coordinate of the near plane is -1 and the z coordinate of the far plane 1. The x and y coordinate have to be the "click" position on the screen in the range [-1, 1] The coordinate of the bottom left is (-1, -1) and the coordinate of the top right is (1, 1). The window or mouse coordinates can be mapped linear to the NDCs x and y coordinates:
float x_ndc = 2.0 * mouse_x/window_width - 1.0;
flaot y_ndc = 1.0 - 2.0 * mouse_y/window_height; // flipped
Vector4f p_near_ndc = Vector4f(x_ndc, y_ndc, -1, 1); // z near = -1
Vector4f p_far_ndc = Vector4f(x_ndc, y_ndc, 1, 1); // z far = 1
A point in normalized device space can be transformed to model space by the inverse projection matrix, then the inverse view matrix and finally the inverse model matrix:
Vector4f p_near_h = model.inverse() * view.inverse() * projection.inverse() * p_near_ndc;
Vector4f p_far_h = model.inverse() * view.inverse() * projection.inverse() * p_far_ndc;
After this the point is a Homogeneous coordinates, which can be transformed by a Perspective divide to a Cartesian coordinate:
Vector3f p0 = p_near_h.head<3>() / p_near_h.w();
Vector3f p1 = p_far_h.head<3>() / p_far_h.w();
The "ray" in model space, defined by point r and a normalized direction d finally is:
Vector3f r = p0;
Vector3f d = (p1 - p0).normalized()

How to rotate an object using glm::lookAt()?

I'm working on a scenario that involves some cone meshes that are to be used as spot lights in a deferred renderer. I need to scale, rotate and translate these cone meshes so that they point in the correct direction. According to one of my lecturers I can rotate the cones to align with a direction vector and move them to the correct position by multiplying its model matrix with the matrix returned by this,
glm::inverse(glm::lookAt(spot_light_direction, spot_light_position, up));
however this doesn't seem to work, doing this causes all of the cones to be placed on the world origin. If I then translate the cones manually using another matrix it seems that the cones aren't even facing the right direction.
Is there a better way to rotate objects so that they face a specific direction?
Here is my current code that gets executed for each cone,
//Move the cone to the correct place
glm::mat4 model = glm::mat4(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
spot_light_position.x, spot_light_position.y, spot_light_position.z, 1);
// Calculate rotation matrix
model *= glm::inverse(glm::lookAt(spot_light_direction, spot_light_position, up));
float missing_angle = 180 - (spot_light_angle / 2 + 90);
float scale = (spot_light_range * sin(missing_angle)) / sin(spot_light_angle / 2);
// Scale the cone to the correct dimensions
model *= glm::mat4(scale, 0, 0, 0,
0, scale, 0, 0,
0, 0, spot_light_range, 0,
0, 0, 0, 1);
// The origin of the cones is at the flat end, offset their position so that they rotate around the point.
model *= glm::mat4(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, -1, 1);
I've noted this in the comments but I'll mention again that the cones origin is at center of the flat end of the cone, I don't know if this makes a difference or not, I just thought I'd bring it up.
Your order of the matrices seems correct, but the lookAt function expects:
glm::mat4 lookAt ( glm::vec3 eye, glm::vec3 center, glm::vec3 up )
Here eye is the location of the camera, center is the location of the object you are looking at (in your case if you dont have that location, you can use
spot_light_direction + spot_light_position ).
so just change
glm::lookAt(spot_light_direction, spot_light_position, up)
to
glm::lookAt(spot_light_position, spot_light_direction + spot_light_position, up)

Rotate object to "look at" another object in 3 dimensions?

I want to create a formula that rotates my object (o1) to always point into the direction of another object (o2), regardless of o1's position.
Kind of like the camera in the following image:
http://puu.sh/bLUWw/aaa653accf.png
I got the following code so far, but the yaw-axis seems to be inverted:
Vector3 lookat = { lookAtPosition.x, lookAtPosition.y, lookAtPosition.z };
Vector3 pos = { position.x, position.y, position.z };
Vector3 objectUpVector = { 0.0f, 1.0f, 0.0f };
Vector3 zaxis = Vector3::normalize(lookat - pos);
Vector3 xaxis = Vector3::normalize(Vector3::cross(objectUpVector, zaxis));
Vector3 yaxis = Vector3::cross(zaxis, xaxis);
Matrix16 pm = {
xaxis.x, yaxis.x, zaxis.x, 0,
xaxis.y, yaxis.y, zaxis.y, 0,
xaxis.z, yaxis.z, zaxis.z, 0,
0, 0, 0, 1
};
See the following image:
http://puu.sh/bLUSG/5228bb2176.jpg
I'm sure it's just a few variables that have to be swapped, but I couldn't find them...
PS: The position of the object matrix is multiplied at a later stage, for testing purposes...
I found the answer to my issue, it turns out that I just had to change the order of the values inside the matrix like so:
Matrix16 pm = {
xaxis.x, xaxis.y, xaxis.z, 0,
yaxis.x, yaxis.y, yaxis.z, 0,
zaxis.x, zaxis.y, zaxis.z, 0,
0, 0, 0, 1
};
camera matrix is inverse of transform matrix representing its coordinate system
look here: Transform matrix anatomy
origin = o1.position
Z axis = o1.position-o2.position
and make it unit length of coarse
mine frustrum/Zbuffer are configured to view in -Z axis direction
now just compute X,Y axises as perpendicular to eachother and Z also via crossproduct
and make them unit length of coarse
so take some vector (non parallel to Z)
ideally something best to align to like Up vector (0,1,0);
multiply it by Z axis and store result to X(or Y)
and then multiply it again by Z axis and store to Y(or X).
Now you have the transform matrix M1 representing O1 coordinate system
if you want just to render the object then thi is it
if you want to have camera on object o1
then just compute:
ViewMatrix=inverse(M1)*ProjectionMatrix;
here inverse matrix computation for mine OpenGL matrices

Setting up an asymmetric frustum

I have a program in which I am tracking user's position and setting the frustum (setting the camera at user's position) to change the perspective of the scene as per the user's position. Until right now, I had all four corners of the display screen at the same z and I was able to set the asymmetric frustum and change the scene according to the user's perspective.
The current code looks something like the following:
UserCam::begin(){
saveGlobalMatrices();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glFrustum(_topLeftNear.x, _bottomRightNear.x, _bottomRightNear.y, _topLeftNear.y, _camZNear, _camZFar);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(_wcUserHead.x, _wcUserHead.y, _topLeftScreen.z, _wcUserHead.x, _wcUserHead.y, _topLeftScreen.z-1, 0, 1, 0);
}
UserCam::end(){
loadGlobalMatrices();
}
UserCam::setupCam(){
this->_topLeftScreen = _wcTopLeftScreen - _wcUserHead; //wcTopLeftScreen, wcBottomRightScreen and wcUserHead are in the same frame of reference
this->_bottomRightScreen = _wcBottomRightScreen - _wcUserHead;
this->_topLeftNear = (_topLeftScreen/ _topLeftScreen.z) * _camZNear;
this->_bottomRightNear = (_bottomRightScreen/_bottomRightScreen.z )) * _camZNear;
}
However, I want to be able to do the same with a display which is kept tilted to the user and/or does not have all its vertices at the same Z.
The above can be imagined as a sort of tilted window, the vertices of which would have the frustum defined from the user's position. How is such a frustum possible where the display does not have all the vertices at the same Z?
EDIT
There are three planes in the setup that I am considering. The middle one give the correct asymmetric frustum since all the vertices are at the same Z, whereas the left and right planes have two vertices each at different Z. The vertices of the same are as follows:
Plane1: TL : (-426.66, 0, 200), TR: (0, 0, 0), BL : (-426.66, 320.79, 200), BR : (0, 320.79, 0)
Plane2: TL : (0, 0, 0), TR: (426.66, 0, 0), BL : (0, 320.79, 0), BR: (426.66, 320.79, 0)
Plane3: TL: (426.66, 0, 0), TR: (853.32, 0, 200), BL : (426.66, 320.79, 0), BR : (853.32, 320.79, 200)
The idea in this setup is to transform it to a case where all corners have the same z-coordinate. Usually this is done with a view matrix and you get:
overall_transform = (projection) * (view * world)
or in the OpenGL wording
overall_transform = projection * modelview
If you don't want to tamper with the original modelview matrix, you should introduce another matrix in between:
overall_transform = (projection * adaption) * (view * world)
where adaption is a rotation matrix that maps the screen's corners to a plane with constant z-coordinate.
In order to find the correct parameters for projection you have to transform the screen with adaption.
Edit
We start with an arbitrary scene where the camera's position, direction and the screen is known. We consider that the model matrices are already there for each object:
We then need the view transformation V that aligns the camera with the origin. This matrix can easily calculated with gluLookAt. The overall matrix is then T = V * M:
Up to this step the matrices are the same for all three screens. So this part should be in the modelview matrix. What we add now goes into the projection matrix because it differs per screen.
We need to apply a rotation R that aligns the screen perpendicular to the z-axis. The position of the camera must not change at this step because it represents the projection center. The overall transformation is now T = R * V * M.
In order to calculate the angle, we can use e.g. atan2:
dx = right.x - left.x
dz = right.z - left.z
angle = atan2(dz, dx)
It might be necessary to adapt this calculation slightly to your actual needs.
Now is the time to apply the actual perspective transform, which can be done with glFrustum.
We need to find the local edges of the screen. You could transform the screen coordinates with the current transform (R * V).
TL' = R * V * TL
BL' = R * V * BL
BR' = R * V * BR
Now all three coordinates should have the same z-coordinate. We can use these as follows:
common_z = TL'.z = BL'.z = BR'.z
glFrustum(TL'.x / common_z * z_near,
BR'.x / common_z * z_near,
BL'.y / common_z * z_near,
TL'.y / common_z * z_near,
z_near, z_far)
So overall T = glFrustum * R * V * M:
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(...);
//any further model transforms
glMatrixMode(GL_PROJECTION);
glFrustum(...);
glRotate(...);

3D Projection Issue: Z Value doesn't lie in [1,-1] after Perspective Divide

I'm trying to do a simple perspective projection in the process of rasterizing a 3D point. Here are all the matrices and other info. All Matrices are row major. The coordinate system is Right Handed.
The Camera is at [0,0,-1] and the point is at [0,0,0] (w=1 for matrix operations)
Model View Matrix(Inverse of Cam Matrix i.e, tx = 0;ty = 0; tz = 1):
[1 0 0 tx]
[0 1 0 ty]
[0 0 1 tz]
[0 0 0 1 ]
Perspective Matrix:
[f/aspect,0,0,0]
0,f,0,0
0,0,-(near+far)/(near-far),2*far*near/(near-far)
0,0,1,0]
aspect is equal to 1 since the viewport is square. Far = 100 and Near = 0.1
f = 1/tan(fovDegress*M_PI/360);
The resultant Matrix is:
1.94445, 0, 0, 0
0, 1.944445, 0, 0
0, 0, 1.020202, -2.020202
0, 0, 1, 0
Now I apply the Model View Matrix and then the Projection Matrix to the point vector and then I get a new point Pv = {x,y,z,w}
Then I get the normalized co-ordinates x' = x/w ; y' = y/w; and z' = z/w;
x' and y' always lie in between [-1,1] as long as the point is in the frustum. But the same isn't the case for z'. As the point approaches near the camera, the z' values increases exponentially. When the point is at [0,0,0] z' value equals -1.
Now, I need to clip some lines, so I need z' value to be in between [1,-1]. I am wondering what's wrong with my procedure. Thank you.
What you experiencing is the nonlinearity of perspectivic depth mapping. With a frustum projection matrix this follows a 1/x law with distance from the viewpoint.
EDIT:
Just double checked your matrices: You got your frustum matrix wrong. The correct frustum matrix is
f/aspect, 0, 0, 0
0, f, 0, 0
0, 0, -(far+near)/(far-near), 2*far*near/(far-near)
0, 0, 1, 0
Still you'll run into a division by zero if you approach the origin. Just put the vector (0,0,0,1) through that thing, which results in (x=0,y=0,z=2*far*near/(far-near),w=0) in clip space. And then the homogenous division {x,y,z}/(w=0) ← blows up.