So i've got a little program that draws a few spheres, then i attempt to right click on them. Upon right clicking, it draws a line between the near and far planes, under the mouse when i click. However, its giving strange results, such as the line being out by quite a long way. The direction of the line is right however, just the line is maybe about 10 to the left along X, or 5 to right right along Y (Those are random examples).
Here's my code :
Positioning the camera
gluLookAt(mouse.getScrollY(), 0.0f, 0.0f,
0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f);
glRotated(mouse.getAngleV(), 0.0f, -1.0f, 0.0f);
glRotated(mouse.getAngleH(), 0.0f, 0.0f, -1.0f);
mouse.getScrollY is simply a value based on how far the camera is scrolled back form the origin.
Obtaining the coordinates
void Mouse::GetGLPos(int x, int y)
{
//init vars:
GLint viewport[4];
GLdouble modelview[16];
GLdouble projection[16];
GLfloat winX, winY;
GLdouble posX, posY, posZ;
GLdouble FposX, FposY, FposZ;
//get gl specs
glGetDoublev( GL_MODELVIEW_MATRIX, modelview ); //get Modelmatrix
glGetDoublev( GL_PROJECTION_MATRIX, projection ); //get projection matrix
glGetIntegerv( GL_VIEWPORT, viewport ); //get viewport values
//calculate the gl mouseposition
winX = (float)x;
winY = (float)viewport[3] - (float)y;
std::cout << "X "<< winX << " Y " << winY << endl;
gluUnProject( winX, winY, 0.0, modelview, projection, viewport, &posX, &posY, &posZ);
gluUnProject( winX, winY, 1.0, modelview, projection, viewport, &FposX, &FposY, &FposZ);
std::cout << "Near positions:" << posX << " | " << posY << " | " << posZ << endl;
std::cout << " Far positions:" << FposX << " | " << FposY << " | " << FposZ << endl << endl;
for (int i = 0; i <= 15; i++)
{
cout << modelview[i] << endl;
}
if (counter == 5)
{
counter = 0;
}
LinestoreNear[counter + 1] = Vec3(posX, posY, posZ);
LinestoreFar[counter + 1] = Vec3(FposX, FposY, FposZ);
counter ++;
mouseOgl[0] = posX;
mouseOgl[1] = posY;
mouseOgl[2] = posZ;
}
x and y that are passed to it are simply the mouse's x and y coordinates on the screen. ((328,657) for example)
And finally, just in case, here is the drawing the line
glDisable(GL_LIGHTING);
for (int i = 0; i <= 4 - 1 ; i++)
{
glColor3f(1.0f, 1.0f, 1.0f);
glBegin(GL_LINES);
glVertex3f(mouse.LinestoreNear[i].a, mouse.LinestoreNear[i].b, mouse.LinestoreNear[i].c);
glVertex3f(mouse.LinestoreFar[i].a, mouse.LinestoreFar[i].b, mouse.LinestoreFar[i].c);
glEnd();
}
glEnable(GL_LIGHTING);
heres a very brief overview of the process you need to use
to get world coordinates that are correct you need to have a 4x4 model matrix for each object in your scene. The model matrix contains all the transformations convert manipulate model coordinates to/from world coordinates.
ie every time you call glTranslate, you have to transform the model matrix as follows:
T = [ 1 0 0 x ]
[ 0 1 0 y ]
[ 0 0 1 z ]
[ 0 0 0 1 ]
M' = M * T
You can then use the transformed model matrix to obtain world space coordinates
Homogenise the coords first however (or check they are already) ie. just set w = 1, if they are not already homogenous coordinates.
If you do not convert the coordinates between object space and world space then you will get the behaviour you are experiencing, and it is easy to do it at the wrong stage as well, so plan out the required steps and you should be fine:)
Hope this helps.
Finally got time to verify your results. It works exactly as it should.
With LookAt you specify (GLU will calculate these matrices for you) camera position and rotation matrices (indirectly, via 'up' and 'view point' vectors, but still it will result into these transformations). Just right after you specify two additional rotation matrices - which means, you rotating a world around a zero point (which is already shifted by LookAt). For you, this kind of rotation will look like camera rotating around certain point at space, maintaining constant distance. Lines keeping their place, with some distortion introduced by perspective transformation (if you have it), and being clipped by far clipping plane (this will look like lines becoming shorter and longer during rotation).
Related
I want to control my camera so that it can rotate around the model.
The theoretical code should be:
// `camera_rotation_angle_x_` and `camera_rotation_angle_y_` are initialized to 0, and can be modified by the user.
glm::mat4 CreateViewMatrix(glm::vec3 eye_pos, glm::vec3 scene_center, glm::vec3 up_vec) {
auto eye_transform = glm::translate(glm::mat4(1.0f), -scene_center); // recenter
eye_transform = glm::rotate(eye_transform, camera_rotation_angle_x_, glm::vec3(1.0f, 0.0f, 0.0f));
eye_transform = glm::rotate(eye_transform, camera_rotation_angle_y_, glm::vec3(0.0f, 1.0f, 0.0f));
eye_transform = glm::translate(eye_transform, scene_center); // move back
eye_pos = eye_transform * glm::vec4(eye_pos, 1.0f);
up_vec = eye_transform * glm::vec4(up_vec, 0.0f);
return glm::lookAt(eye_pos, scene_center, up_vec);
}
But the two lines "recenter" and "move back" must be written as follows to rotate correctly, otherwise the distance from the camera to the center will vary when the rotation parameters change:
auto eye_transform = glm::translate(glm::mat4(1.0f), scene_center); // recenter *The sign has changed*
...
eye_transform = glm::translate(eye_transform, -scene_center); // move back *The sign has changed*
...
// Correctness test, only when the distance remains constant, the rotation logic is correct.
cout << "eye_pos: " << eye_pos[0] << ", " << eye_pos[1] << ", " << eye_pos[2] << endl;
cout << "distance: " << (sqrt(
pow(eye_pos[0] - scene_center[0], 2)
+ pow(eye_pos[1] - scene_center[1], 2)
+ pow(eye_pos[2] - scene_center[2], 2)
)) << endl;
It is the correct logic to subtract the central value first and then add it back. It does not make any sense to add and then subtract.
So what's going wrong that I have to write code with logic errors in order for it to work properly?
The caller's code is below, maybe the bug is here?
// `kEyePos`, `kSceneCenter`, `kUpVec`, `kFovY`, `kAspect`, `kDistanceEyeToBack` and `kLightPos` are constants throughout the lifetime of the program
UniformBufferObject ubo{};
ubo.model = glm::mat4(1.0f);
ubo.view = CreateViewMatrix(kEyePos, kSceneCenter, kUpVec);
ubo.proj = glm::perspective(kFovY, kAspect, 0.1f, kDistanceEyeToBack);
// GLM was originally designed for OpenGL, where the Y coordinate of the clip coordinates is inverted.
// The easiest way to compensate for that is to flip the sign on the scaling factor of the Y axis in the projection matrix.
// Because of the Y-flip we did in the projection matrix, the vertices are now being drawn in counter-clockwise order instead of clockwise order.
// This causes backface culling to kick in and prevents any geometry from being drawn.
// You should modify the frontFace in `VkPipelineRasterizationStateCreateInfo` to `VK_FRONT_FACE_COUNTER_CLOCKWISE` to correct this.
ubo.proj[1][1] *= -1;
ubo.light_pos = glm::vec4(kLightPos, 1.0f); // The w component of point is 1
memcpy(vk_buffer_->GetUniformBufferMapped(frame_index), &ubo, sizeof(ubo));
I found that the problem was with the order of matrix construction, not with the glm::lookAt method.
m = glm::rotate(m, angle, up_vec)
is equivalent to
m = m * glm::rotate(glm::mat4(1), angle, up_vec)
, not
m = glm::rotate(glm::mat4(1), angle, up_vec) * m
as I thought.
This easily explains why swapping "recenter" and "move back" works properly.
The correct code is as follows:
glm::mat4 VulkanRendering::CreateViewMatrix(glm::vec3 eye_pos, glm::vec3 scene_center, glm::vec3 up_vec) const {
// Create transform matrix in reverse order.
// First rotate around the X axis, and then around the Y axis, otherwise it does not match the practice of most games.
auto view_transform = glm::translate(glm::mat4(1.0f), scene_center); // last: move back
view_transform = glm::rotate(view_transform, camera_rotation_angle_y_, glm::vec3(0.0f, 1.0f, 0.0f));
view_transform = glm::rotate(view_transform, camera_rotation_angle_x_, glm::vec3(1.0f, 0.0f, 0.0f));
view_transform = glm::translate(view_transform, -scene_center); // first: recenter
eye_pos = view_transform * glm::vec4(eye_pos, 1.0f); // The w component of point is 1
up_vec = view_transform * glm::vec4(up_vec, 0.0f); // The w component of vector is 0
return glm::lookAt(eye_pos, scene_center, up_vec);
}
I am using QGLWidget and QtOpenGL to display my point clouds and glReadPixels and gluUnProject to pick a point from a poiint cloud. The problem is that the glReadPixels does not seem to pick pixels of my points.
I've tried to use different point sizes as well as different block sizes in glReadPixels but the "ray" seems to go through the points. Im wondering if I need to calculate the closes point to the ray since its almost impossible to click right on the point.
The points are drawn with (just and example of a point in origon )
`
GLuint list = glGenLists(1);
glNewList(list, GL_COMPILE);
glPointSize(10.0f);
glBegin(GL_POINTS);
glColor3f(0.0f, 255.0f, 0.0f);
glVertex3f(0.0f, 0.0f, 0.0f);
glEnd();
glEndList();
updateScene();`
The point picking is done by the getObejctCoords function below.
`
void pclView::getObjectCoords(QMouseEvent *event)
GLdouble projection[16];
GLdouble modelView[16];
GLint viewPort[4];
GLdouble obj_coords0, obj_coords1, obj_coords2;
GLdouble pt_coords0, pt_coords1, pt_coords2;
glGetDoublev(GL_PROJECTION_MATRIX, projection);
glGetDoublev(GL_MODELVIEW_MATRIX, modelView);
glGetIntegerv(GL_VIEWPORT, viewPort);
// Window parameters
winX = event->pos().x();
winY = viewPort[3] - event->pos().y();
// get Window Z
glReadPixels( event->pos().x(), int(winY), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ);
// Unproject 2D click to 3D location
gluUnProject( winX, winY, winZ, modelView, projection, viewPort, &obj_coords0, &obj_coords1, &obj_coords2);
std::cout << "x: " << obj_coords0;
std::cout << " y: " << obj_coords1;
std::cout << " z: " << obj_coords2 << std::endl;
`
At camera position (0,0,-50) rotation: (0, 0) (By clicking at the point at almost at origon (but on the point ) the function produces the following output
ยด x: 0 y: -0.578724 z: -950 `
And the actual result should (as I've understood it) should be something like
x: 0 y: -0.578724 z: -0
I have a question about how the standard perspective projection matrix works, the one you get from gluPerspective() or glm::perspective(). Specifically, I'm wondering about the near/far clipping planes.
I was under the impression that after transforming a point with the perspective matrix, objects on the near clip plane mapped to a Z value of -1, and objects near the far clip plane mapped to a Z value of 1.
For instance, lets say you have a camera located at the origin, looking in the direction of the positive Z axis, with near clip plane at 1.0 and far clip plane set to 2.0. I would then expect that a things having "world" Z coordinates between 1.0 and 2.0 would be transformed as having projected coordinates between -1.0 and 1.0.
In other words, i would expect this C++ code:
#include <iostream>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
using namespace glm;
void print_vec(vec4 pnt) {
std::cout << "(" << pnt.x << ", " << pnt.y << ", " << pnt.z << ")" << "\n";
}
int main() {
mat4x4 view = lookAt(
vec3(0.0f, 0.0f, 0.0f), // eye at origin
vec3(0.0f, 0.0f, 1.0f), // looking towards +Z
vec3(0.0f, 1.0f, 0.0f)); // up is +Y
mat4x4 projection = perspective(
radians(90.0f), // field of view
1.0f, // aspect ratio
1.0f, // near clipping plane
2.0f); // far clipping plane
// transformation matrix for world coordinates
mat4x4 vp = projection * view;
// points (0,0,1), (0,0,1.5) and (0,0,2) in homogeneous coordinates
print_vec(vp * vec4(0.0f, 0.0f, 1.0f, 1.0f));
print_vec(vp * vec4(0.0f, 0.0f, 1.5f, 1.0f));
print_vec(vp * vec4(0.0f, 0.0f, 2.0f, 1.0f));
}
to print out this
(0, 0, -1)
(0, 0, 0)
(0, 0, 1)
But it doesn't. It prints out this:
(0, 0, -1)
(0, 0, 0.5)
(0, 0, 2)
And I don't understand why. The near clip plane projects like I would expect, but a point exactly midway between the planes are closer to the back when projected, and a point on the far clip plane is outside the [-1,1] range entirely.
I figure there's some basic math thing i'm just missing, so that's why I'm asking you fine fellows.
(incidentally, the thing I'm working on is actually written in Rust, not C++, but I switched to C++ for this test, just to be sure there wasn't a bug in the Rust implementation or something.)
But it doesn't. It prints out this:
(0, 0, -1)
(0, 0, 0.5)
(0, 0, 2)
That is wrong, because the result is not of type vec3, it is a Homogeneous coordinate of type vec4:
(0, 0, -1, 1)
(0, 0, 0.5, 1.5)
(0, 0, 2, 2)
You have to do a Perspective divide then you' ll get the expected result:
(0/1, 0/1, -1/1)
(0/1.5, 0/1.5, 0.5/1.5)
(0/2, 0/2, 2/2)
I'm currently working on a project, and I need to be able to move objects with my mouse, and to do some mouse picking. I generate a 1024*768 window, and my code with gluUnproject is :
glm::vec3 GetOGLPos(int x, int y)
{
GLint viewport[4];
GLdouble modelview[16];
GLdouble projection[16];
GLfloat winX, winY, winZ;
GLdouble posX, posY, posZ; // posX2, posY2, posZ2;
glGetDoublev( GL_MODELVIEW_MATRIX, modelview );
glGetDoublev( GL_PROJECTION_MATRIX, projection );
glGetIntegerv( GL_VIEWPORT, viewport );
winX = (float)x;
winY = (float)viewport[3] - (float)y;
glReadPixels( x, int(winY), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ );
gluUnProject( winX, winY, winZ, modelview, projection, viewport, &posX, &posY, &posZ);
std::cout << "PosX = " << posX << " - PosY = " << posY << " - Pos Z = " << posZ << std::endl;
return glm::vec3(posX, posY, posZ);
}
And I use it this way :
glm::ivec2 pos = _input.getMousePosition(); //0, 0 is high left corner
glm::vec3 posIn = GetOGLPos(pos.x, pos.y);
The position in pixels are good, but the position I get are, I think, false.
Here is some output I have if I click in the center of the screen (My object origin is in (0, 0, 0)) :
Xpixel = 512 Ypixel = 364 XOGL = 0 YOGL = -0.0111111 ZOGL = 1
And the output I have when i click somewhere else :
Xpixel = 237 Ypixel = 207 XOGL = -0.537109 YOGL = 0.425 ZOGL = 1
So I have two problems here :
The Z value doesn't change (so maybe it is a vector and not coordonates)
The object moves toward my mouse (since I translate my object to the new coordonates, so the new origin point should be XOGL, YOGL, ZOGL) but only on few pixels. So the values are either wrong or misused.
Can someone help me?
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);
}
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.
" 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.
#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.