Problem converting screen coordinates to OpenGL world coordinates - c++

I'm using C++/Qt/OpenGL 4.3 to implement an OpenGL viewer and I'm stuck on converting mouse coordinates to world coordinates.
Update: After reading the comments and answer, I found this code to work correctly:
float depth;
double mouseX;
double mouseY;
_app->getCurPos(mouseX, mouseY);
glReadPixels(mouseX, _h - mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, (void *)&depth);
float x = (2.0f * mouseX) / _w - 1.0f;
float y = 1.0f - (2.0f * mouseY) / _h;
float z = depth * 2.0 - 1.0; // convert to NDC
QVector4D pos(x, y, z, 1.0);
QMatrix4x4 invVM(_app->camera()->ViewMatrix().inverted());
QMatrix4x4 invPM(_app->camera()->ProjectionMatrix().inverted());
QVector4D world = invVM * invPM * pos;
world /= world.w();
qDebug() << "world" << world;
(from the original question...)
Where does this code go wrong? Its output is included below.
Code:
// mouse coordinates to world coordinates
QVector3D GLCamera::transformScreen(float mouseX, float mouseY)
{
// our 3D view has Y as "UP"
float x = (2.0f * mouseX) / _w - 1.0f;
float y = 0.0f;
float z = 1.0f - (2.0f * mouseY) / _h;
// hard code NDC to upper-right of screen
x = 1.0;
y = 0.0;
z = 1.0;
QVector4D ndc = QVector4D(x, y, z, 1);
QVector4D point1 = ndc * mProjectionMatrix.inverted();
qDebug() << point1;
QVector3D point3D = QVector3D(point1) / point1.w();
qDebug() << point3D;
QVector3D point2 = point3D * mViewMatrix.inverted();
qDebug() << point2;
qDebug() << "mViewMatrix: " << mViewMatrix;
qDebug() << "inv : " << mViewMatrix.inverted();
qDebug() << "mProjMatrix: " << mProjectionMatrix;
qDebug() << "inv : " << mProjectionMatrix.inverted();
return point2;
}
Output:
qDebug: QVector4D(1, 0, 1, 1)
qDebug: QVector4D(0.742599, 0, -49.995, 49.005)
qDebug: QVector3D(0.0151535, 0, -1.0202)
# This is the returned value x seems so much smaller than z
qDebug: QVector3D(-0.000938916, -0.0418856, 0.0473429)
qDebug: mViewMatrix: QMatrix4x4(type:Translation,Rotation
1 0 0 0
0 0.748955 -0.662621 -0.626549
0 0.662621 0.748955 -22.9856
0 0 0 1
)
qDebug: inv : QMatrix4x4(type:Translation,Rotation
1 0 0 0
0 0.748955 0.662621 15.7
0 -0.662621 0.748955 16.8
0 0 0 1
)
qDebug: mProjMatrix: QMatrix4x4(type:General
1.34662 0 0 0
0 2.41421 0 0
0 0 -1.0002 -0.020002
0 0 -1 0
)
qDebug: inv : QMatrix4x4(type:General
0.742599 0 0 0
0 0.414214 0 0
0 0 0 -1
0 0 -49.995 50.005
)

The top right corner of the screen is (1, 1, depth) in NDC. You need a reasonable depth value to get the point that you want. Otherwise, you will only get a ray.
The matrices you show are supposed to be used for right-multiplication:
QVector4D point1 = mProjectionMatrix.inverted() * ndc;
QVector3D point2 = mViewMatrix.inverted() * point3D;
The perspective divide should happen at the very end and not in between. Up to this point, continue working with 4D vectors. Otherwise, passing the view-space position (point1) as a QVector3D will set a w-component of 0 and you will lose any translations. You can pass it as a QPoint3D, which would set w=1, but keeping the 4D vector is the safest choice.
QVector3D point3D = QVector3D(point1) / point1.w();
I would also suggest, naming the intermediate results reasonably (not just pointX). Name them pointClipSpace, pointViewSpace, pointWorldSpace or similar.
Again, deciding for an NDC depth is very important to get an interpretable result.

Related

OpenGL Scale Triangle Mesh To Unit Cube

I'm trying to load in a triangle mesh from an .off file and show the triangle mesh centered at the origin and scaled to fit in the unit cube. But for some reason I'm off by a large factor and it looks like
The way I'm doing this is finding the extrema of the mesh, and using that to offset the surface by that amount.
float avgX = (maxX + minX) / 2;
float avgY = (maxY + minY) / 2;
float avgZ = (maxZ + minZ) / 2;
Vector3f center(avgX, avgY, avgZ);
Vector3f offset = Vector3f(0, 0, 0) - center;
Translation3f translation(offset);
cout << "offset is: " << endl << offset << endl;
double d_theta = (M_PI / 180);
AngleAxisf rotation(d_theta, Vector3f(0, 0, 1));
float scaleX = (float) 1 / (abs(maxX - minX));
float scaleY = (float) 1 / (abs(maxY - minY));
float scaleZ = (float) 1 / (abs(maxZ - minZ));
AlignedScaling3f scale = AlignedScaling3f(scaleX, scaleY, scaleZ);
I then put it into a vector of surfaces with
Vector3f translatedCenter = translation * rotation * scale * center;
VertexBufferObject VBO;
VBO.init();
VBO.update(Vertices);
program.bindVertexAttribArray("position", VBO);
VertexBufferObject VBO_N;
VBO_N.init();
VBO_N.update(FlatNormals);
program.bindVertexAttribArray("normals", VBO_N);
cout << "updated normals" << endl;
VertexBufferObject VBO_C;
VBO_C.init();
VBO_C.update(C);
program.bindVertexAttribArray("color",VBO_C);
cout << "updated color " << endl;
Surface* s = new Surface(VBO, Vertices, translation, rotation, scale, percentScale, translatedCenter, SmoothNormals, FlatNormals, C);
And I pass it to the Vertex Shader as "model"
Affine3f model = s->getTranslation() * s->getRotation() * s->getScale();
glUniformMatrix4fv(program.uniform("model"), 1, GL_FALSE, model.data());
This is all being done using the Eigen library (https://eigen.tuxfamily.org/dox/group__TutorialGeometry.html#TutorialGeoTransform)
No matter what I try I'm off by a little bit. What am I doing wrong?
Swap translation and rotation:
Affine3f model = s->getRotation() * s->getTranslation() * s->getScale();
Note, the translation moves the center of the object to the center of the view. After that the rotation matrix rotates around the this center.
If you don't have any projection matrix, then the view space is the normalized device space where each coordinate is in range [-1, 1]. This mean the length of a side is 2 = 1 - (-1). You have to respect this when you calculate the scale:
float scaleX = (float) 2 / (abs(maxX - minX));
float scaleY = (float) 2 / (abs(maxY - minY));
float scaleZ = (float) 2 / (abs(maxZ - minZ));

OpenGL move object and keep transformation

I've a object, which is transfomred (rotated at 45deg on the Y axis).
The target is to move (translate) the object on the x and y axis and keep the transformation effect as it is.
Its very hard to explain, so I made a picture:
I know the concept of the camera in opengl and i know i cant really move the camera but in fact everything is moving around the camera. Does someone actually know how to achieve this?
My code:
//set mvp
matrixProj = new PerspectiveProjectionMatrix(fovy, aspect, near, far);
matrixView = new ModelMatrix();
matrixView.LookAtTarget(new Vertex3f(0, 0, 2), new Vertex3f(0, 0, 0), new Vertex3f(0, 1, 0));
matrixModel = new ModelMatrix();
matrixModel.SetIdentity();
matrixModel.RotateY(45);
matrixModel.Translate(-2, -2, 0);
Matrix4x4 mvp = matrixProj * matrixView * matrixModel;
Gl.UniformMatrix4(Gl.GetUniformLocation(shaderProgram, "MVP"), 1, false, mvp.ToArray());
//draw quad
Gl.Begin(PrimitiveType.Quads);
Gl.Vertex3(-2, 2, 0);
Gl.Vertex3(2, 2, 0);
Gl.Vertex3(2, -2, 0);
Gl.Vertex3(-2, -2, 0);
Gl.End();
You have to change the order of the instructions. A rotation around the axis of the object is performed, by multiplying the translation matrix of the object by the rotation matrix.
This means you have to do the translation first and then the rotation.
matrixModel = new ModelMatrix();
matrixModel.SetIdentity();
matrixModel.Translate(-2, -2, 0);
matrixModel.RotateY(45);
Note, the translation matrix looks like this:
Matrix4x4 translate;
translate[0] : ( 1, 0, 0, 0 )
translate[1] : ( 0, 1, 0, 0 )
translate[2] : ( 0, 0, 1, 0 )
translate[3] : ( tx, ty, tz, 1 )
And the rotation matrix around Y-Axis looks like this:
Matrix4x4 rotate;
float angle;
rotate[0] : ( cos(angle), 0, sin(angle), 0 )
rotate[1] : ( 0, 1, 0, 0 )
rotate[2] : ( -sin(angle), 0, cos(angle), 0 )
rotate[3] : ( 0, 0, 0, 1 )
A matrix multiplication works like this:
Matrix4x4 A, B, C;
// C = A * B
for ( int k = 0; k < 4; ++ k )
for ( int l = 0; l < 4; ++ l )
C[k][l] = A[0][l] * B[k][0] + A[1][l] * B[k][1] + A[2][l] * B[k][2] + A[3][l] * B[k][3];
The result of translate * rotate is this:
model[0] : ( cos(angle), 0, sin(angle), 0 )
model[1] : ( 0, 1, 0, 0 )
model[2] : ( -sin(angle), 0, cos(angle), 0 )
model[3] : ( tx, ty, tz, 1 )
Note, the result of rotate * translate would be:
model[0] : ( cos(angle), 0, sin(angle), 0 )
model[1] : ( 0, 1, 0, 0 )
model[2] : ( -sin(angle), 0, cos(angle), 0 )
model[3] : ( cos(angle)*tx - sin(angle)*tx, ty, sin(angle)*tz + cos(angle)*tz, 1 )
Extension to the answer:
A perspective projection matrix looks like this:
r = right, l = left, b = bottom, t = top, n = near, f = far
2*n/(r-l) 0 0 0
0 2*n/(t-b) 0 0
(r+l)/(r-l) (t+b)/(t-b) -(f+n)/(f-n) -1
0 0 -2*f*n/(f-n) 0
where :
r = w / h
ta = tan( fov_y / 2 );
2*n / (r-l) = 1 / (ta*a) ---> 1/(r-l) = 1/(ta*a) * 1/(2*n)
2*n / (t-b) = 1 / ta ---> 1/(t-b) = 1/ta * 1/(2*n)
If you want to displace the filed of view by an offset (x, y), then you have to do it like this:
x_disp = 1/(ta*a) * x/(2*n)
y_disp = 1/ta * y/(2*n)
1/(ta*a) 0 0 0
0 1/t 0 0
x_disp y_disp -(f+n)/(f-n) -1
0 0 - 2*f*n/(f-n) 0
Set up the perspective projection matrix like this:
float x = ...;
float y = ...;
matrixProj = new PerspectiveProjectionMatrix(fovy, aspect, near, far);
matrixProj[2][0] = x * matrixProj[0][0] / (2.0 * near);
matrixProj[2][1] = y * matrixProj[1][1] / (2.0 * near);
To glFrustum, a pixel offset, can be applied like this:
float x_pixel = .....;
float y_pixel = .....;
float x_dipl = (right - left) * x_pixel / width_pixel;
float y_dipl = (top - bottom) * y_pixel / height_pixel;
glFrustum( left + x_dipl, right + x_dipl, top + y_dipl, bottom + y_dipl, near, far);

gluLookAt specification

I have some problems understanding the specification for gluLookAt.
For example the z-axis is defined as:
F = ( centerX - eyeX, centerY - eyeY, centerZ - eyeZ )
with center being the point the camera looks at and eye being the position the camera is at.
f = F / |F|
and the View-Matrix M is defined as:
( x[0] x[1] x[2] 0 )
( y[0] y[1] y[2] 0 )
(-f[0] -f[1] -f[2] 0 )
( 0 0 0 1 )
with x and y being the x,y-axis and f being the z-axis
If my camera is positioned at (0, 0, 5) and the camera looks at the center. Then f would look along the negative z-axis because of the first equation (center - eye) the f-vector would be: (0,0,0) - (0,0,5) = (0,0,-5)
So far everything makes sense to me, but then the f-vector is multiplied by -1 in the M-Matrix above.
That way the f-vector looks along the positive z-axis and away from the center.
I found that the perspective matrix gluPerspective will also multiply the z-axis of the camrea with -1 which turns the z-axis again and makes it look toward the world's negative z-axis.
So what is the point of multiplying it with -1?
Because gluLookAt is a View Matrix for a right-handed system. In this space, Z-coordinate increments as it goes out of screen, or behind the camera. So all objects that the camera can see have negative Z in view space.
EDIT
You should review your maths. The matrix you exposed lacks the translation to camera position.
Following this notation let's do:
Obtain f normalized, up normalized, s normalized, and u=sn x f. Notice that s must be normalized because f and up may be not be perpendicular and then their cross-product is not a vector of length=1. This is not mentioned in the link above.
Form the matrix and pre-multiply by the translation to camera position, L= M ยท T
The resulting lookAt matrix is:
s.x s.y s.z -dot(s, eye)
u.x u.y u.z -dot(u, eye)
-f.x -f.y -f.z dot(f, eye)
0 0 0 1
With your data: camera=(0,0,5), target=(0,0,0), and up=(0,1,0), the matrix is:
1 0 0 0
0 -1 0 0
0 0 1 -5
0 0 0 1
Let's apply this transformation a the point A=(0,0,4). We get A'=(0,0,-1).
Again for B=(0,0,20), B'=(0,0,15).
A' has a negative Z, so the camera sees it. B' has a positive value, the camera can not see it.
I know this isn't a direct answer to the question but it might help someone who is looking for an equivalent function without using GLU, for example, if they are porting old OpenGL2 code to modern OpenGL.
Here is an equivalent function to gluLookAt(...):
void gluLookAt(float eyeX, float eyeY, float eyeZ,
float centreX, float centreY, float centreZ,
float upX, float upY, float upZ) {
GLfloat mat[16];
float forwardX = centreX - eyeX;
float forwardY = centreY - eyeY;
float forwardZ = centreZ - eyeZ;
glm::vec3 forward = glm::normalize(glm::vec3(forwardX, forwardY, forwardZ));
glm::vec3 right = glm::cross(glm::vec3(forwardX, forwardY, forwardZ),
glm::vec3(upX, upY, upZ));
right = glm::normalize(right);
mat[0] = right.x;
mat[1] = right.y;
mat[2] = right.z;
mat[3] = 0.0f;
mat[4] = upX;
mat[5] = upY;
mat[6] = upZ;
mat[7] = 0.0f;
mat[8] = -forward.x;
mat[9] = -forward.y;
mat[10] = -forward.z;
mat[11] = 0.0f;
mat[12] = 0.0f;
mat[13] = 0.0f;
mat[14] = 0.0f;
mat[15] = 1.0f;
glMultMatrixf(mat);
glTranslatef (-eyeX, -eyeY, -eyeZ);
}

Getting 2D world coordinates from mouse in 3D scene?

I am trying to get the 2d world coordinates on a 2D plane (Z = 0) where I clicked with the mouse in a 3D scene. I figured out that ray-casting would probably be the best method.
This code is that I scavenged from the Internet:
glm::vec3 Drawer::MouseToWorldCoords(glm::vec2 coords)
{
//getting camera position
glm::mat3 rotMat(view);
glm::vec3 d(view[3]);
glm::vec3 retVec = -d * rotMat;
//std::cout << " x " << retVec.x << " y " << retVec.y << " z " << retVec.z << std::endl;
//getting mouse coords
float x = 2.0 * coords.x / WINDOW_WIDTH - 1;
float y = -2.0 * coords.y / WINDOW_HEIGHT + 1;
float z = -1.0f;
//raycasting
glm::vec4 ray(x, y, z,1.0f);
glm::vec4 ray_eye = inverse(proj) * ray;
ray_eye = glm::vec4(ray_eye.x,ray_eye.y, 1.0, 0.0);
glm::vec3 ray_world = glm::vec3((glm::inverse(view) * ray_eye));
ray_world = glm::normalize(ray_world);
//intersecting plane with ray
glm::vec3 ba = retVec - ray_world ;
float nDotA = glm::dot(glm::vec3(0.0f,0.0f,1.0f), ray_world);
float nDotBA = glm::dot(glm::vec3(0.0f,0.0f,1.0f), ba);
glm::vec3 intersect = (ray_world + (((0.0f - nDotA) / nDotBA) * ba)) ;
return glm::vec3( -intersect.x * 10.0f,-intersect.y * 10.0f,0.0f );
}
This snippet of code does not work the way it should though. As you can see in the image:
The program simply spawns cubes at the location returned by the function. To produce this result I clicked only on the edges of the screen (except for the 2 in the middle of course).

gluDisk rotation for mapping

I'm trying to create sub-cursor for terrain mapping.
Basic by code: (old image, but rotation is same)
image http://www.sdilej.eu/pics/274a90360f9c46e2eaf94e095e0b6223.png
This is when i testing change glRotate ax to my numbers:
image2 http://www.sdilej.eu/pics/146bda9dc51708da54b9249706f874fc.png
What i want:
image3 http://www.sdilej.eu/pics/69721aa237608b423b635945d430e561.png
My code:
void renderDisk(float x1, float y1, float z1, float x2, float y2, float z2, float radius, int subdivisions, GLUquadricObj* quadric)
{
float vx = x2 - x1;
float vy = y2 - y1;
float vz = z2 - z1;
//handle the degenerate case of z1 == z2 with an approximation
if( vz == 0.0f )
vz = .0001f;
float v = sqrt( vx*vx + vy*vy + vz*vz );
float ax = 57.2957795f * acos( vz/v );
if(vz < 0.0f)
ax = -ax;
float rx = -vy * vz;
float ry = vx * vz;
glPushMatrix();
glTranslatef(x1, y1, z1);
glRotatef(ax, rx, ry, 0.0);
gluQuadricOrientation(quadric, GLU_OUTSIDE);
gluDisk(quadric, radius - 0.25, radius + 5.0, subdivisions, 5);
glPopMatrix();
}
void renderDisk_convenient(float x, float y, float z, float radius, int subdivisions)
{
// Mouse opacity
glColor4f( 0.0f, 7.5f, 0.0f, 0.5f );
GLUquadricObj* quadric = gluNewQuadric();
gluQuadricDrawStyle(quadric, GLU_LINE);
gluQuadricNormals(quadric, GLU_SMOOTH);
gluQuadricTexture(quadric, GL_TRUE);
renderDisk(x, y, z, x, y, z, radius, subdivisions, quadric);
gluDeleteQuadric(quadric);
}
renderDisk_convenient(posX, posY, posZ, radius, 20);
This is a simple one. In your call to renderDisk() you supply bad arguments. Looks like you copied the function from some tutorial without understanding how it works. The first three parameters control the center position, and the other three parameters control rotation using a second position which the disk is always facing. If the two positions are equal (which is your case), this line is executed:
//handle the degenerate case of z1 == z2 with an approximation
if( vz == 0.0f )
vz = .0001f;
And setting z to nonzero makes the disc perpendicular to XZ plane, which is also the horizontal plane for your terrain. So ... to make it okay, you need to modify your function like this:
void renderDisk_convenient(float x, float y, float z, float radius, int subdivisions)
{
// Mouse opacity
glColor4f( 0.0f, 7.5f, 0.0f, 0.5f );
GLUquadricObj* quadric = gluNewQuadric();
gluQuadricDrawStyle(quadric, GLU_LINE);
gluQuadricNormals(quadric, GLU_SMOOTH);
gluQuadricTexture(quadric, GL_TRUE);
float upX = 0, upY = 1, upZ = 0; // up vector (does not need to be normalized)
renderDisk(x, y, z, x + upX, y + upY, z + upZ, radius, subdivisions, quadric);
gluDeleteQuadric(quadric);
}
This should turn the disc into the xz plane so it will be okay if the terrain is flat. But in other places, you actually need to modify the normal direction (the (upX, upY, upZ) vector). If your terrain is generated from a heightmap, then the normal can be calculated using code such as this:
const char *p_s_heightmap16 = "ps_height_1k.png";
const float f_terrain_height = 50; // terrain is 50 units high
const float f_terrain_scale = 1000; // the longer edge of terrain is 1000 units long
TBmp *p_heightmap;
if(!(p_heightmap = p_LoadHeightmap_HiLo(p_s_heightmap16))) {
fprintf(stderr, "error: failed to load heightmap (%s)\n", p_s_heightmap16);
return false;
}
// load heightmap
TBmp *p_normalmap = TBmp::p_Alloc(p_heightmap->n_width, p_heightmap->n_height);
// alloc normalmap
const float f_width_scale = f_terrain_scale / max(p_heightmap->n_width, p_heightmap->n_height);
// calculate the scaling factor
for(int y = 0, hl = p_normalmap->n_height, hh = p_heightmap->n_height; y < hl; ++ y) {
for(int x = 0, wl = p_normalmap->n_width, wh = p_heightmap->n_width; x < wl; ++ x) {
Vector3f v_normal(0, 0, 0);
{
Vector3f v_pos[9];
for(int yy = -1; yy < 2; ++ yy) {
for(int xx = -1; xx < 2; ++ xx) {
int sx = xx + x;
int sy = yy + y;
float f_height;
if(sx >= 0 && sy >= 0 && sx < wh && sy < hh)
f_height = ((const uint16_t*)p_heightmap->p_buffer)[sx + sy * wh] / 65535.0f * f_terrain_height;
else
f_height = 0;
v_pos[(xx + 1) + 3 * (yy + 1)] = Vector3f(xx * f_width_scale, f_height, yy * f_width_scale);
}
}
// read nine-neighbourhood
/*
0 1 2
+----------+----------+
|\ | /|
| \ | / |
| \ | / |
| \ | / |
3|_________\|/_________|5
| 4/|\ |
| / | \ |
| / | \ |
| / | \ |
|/ | \|
+----------+----------+
6 7 8
*/
const int p_indices[] = {
0, 1, //4,
1, 2, //4,
2, 5, //4,
5, 8, //4,
8, 7, //4,
7, 6, //4,
6, 3, //4,
3, 0 //, 4
};
for(int i = 0; i < 8; ++ i) {
Vector3f a = v_pos[p_indices[i * 2]];
Vector3f b = v_pos[p_indices[i * 2 + 1]];
Vector3f c = v_pos[4];
// triangle
Vector3f v_tri_normal = (a - c).v_Cross(b - c);
v_tri_normal.Normalize();
// calculate normals
v_normal += v_tri_normal;
}
v_normal.Normalize();
}
// calculate normal from the heightmap (by averaging the normals of eight triangles that share the current point)
uint32_t n_normalmap =
0xff000000U |
(max(0, min(255, int(v_normal.z * 127 + 128))) << 16) |
(max(0, min(255, int(v_normal.y * 127 + 128))) << 8) |
max(0, min(255, int(-v_normal.x * 127 + 128)));
// calculate normalmap color
p_normalmap->p_buffer[x + wl * y] = n_normalmap;
// use the lightmap bitmap to store the results
}
}
(note this contains some structures and functions that are not included here so you won't be able to use this code directly, but the basic concept is there)
Once you have the normals, you need to sample normal under location (x, z) and use that in your function. This will still make the disc intersect the terrain where there is a steep slope next to flat surface (where the second derivative is high). In order to cope with that, you can either lift the cursor up a bit (along the normal), or disable depth testing.
If your terrain is polygonal, you could use vertex normals just as well, just take triangle that is below (x, y, z) and interpolate it's vertices normals to get the normal for the disc.
I hope this helps, feel free to comment if you need further advice ...