I'm trying to create a ray from my mouse location out into 3D space, and apparently in order to do that I need to "UnProject()" it.
Doing so will give me a value between 0 & 1 for each axis.
This can't be right for drawing a "Ray" or a line from the viewport, can it? All this is, is a percentage essentially of my mouse to viewport size.
If this is actually right, then I don't understand the following:
I draw triangles that have vertices that are not constrained from 0-1, rather they are coordinates like (0,100,0), (100,100,0), (100,0,0), And these draw perfectly fine
But also, drawing the vertices that are unprojected from my mouse coordinates as lines/points also draw perfectly fine.
How the heck would I then compare my mouse coordinates to the coordinates of my objects?
If this is actually wrong, then what can cause such an error?
I tried unprojecting my own object's vertices, and those aren't constrained from 0-1.
I don't know whether or not the way I handle my "projections" when rendering is even compatible with gluUnproject. I've been just doing it the way these tutorials here show it (near bottom): http://qt-project.org/wiki/Developer-Guides#28810c65dd0f273a567b83a48839d275
This is the way I try to get my mouse coordinates:
GLdouble modelViewMatrix[16];
GLdouble projectionMatrix[16];
GLint viewport[4];
GLfloat winX, winY, winZ;
glGetDoublev(GL_MODELVIEW_MATRIX, modelViewMatrix);
glGetDoublev(GL_PROJECTION_MATRIX, projectionMatrix);
glGetIntegerv(GL_VIEWPORT, viewport);
winX = (float)x;
winY = (float)viewport[3] - (float)y;
glReadPixels( winX, winY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ );
GLdouble nearPlaneLocation[3];
gluUnProject(winX, winY, 0, modelViewMatrix, projectionMatrix,
viewport, &nearPlaneLocation[0], &nearPlaneLocation[1],
&nearPlaneLocation[2]);
GLdouble farPlaneLocation[3];
gluUnProject(winX, winY, 1, modelViewMatrix, projectionMatrix,
viewport, &farPlaneLocation[0], &farPlaneLocation[1],
&farPlaneLocation[2]);
QVector3D nearP = QVector3D(nearPlaneLocation[0], nearPlaneLocation[1],
nearPlaneLocation[2]);
QVector3D farP = QVector3D(farPlaneLocation[0], farPlaneLocation[1],
farPlaneLocation[2]);
Perhaps my actual projections are off?
void oglWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
QMatrix4x4 mMatrix;
QMatrix4x4 vMatrix;
QMatrix4x4 cameraTransformation;
cameraTransformation.rotate(alpha, 0, 1, 0);
cameraTransformation.rotate(beta, 1, 0, 0);
QVector3D cameraPosition = cameraTransformation * QVector3D(camX, camY, distance);
QVector3D cameraUpDirection = cameraTransformation * QVector3D(0, 1, 0);
vMatrix.lookAt(cameraPosition, QVector3D(camX, camY, 0), cameraUpDirection);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(cameraPosition.x(), cameraPosition.y(), cameraPosition.z(), camX, camY, 0, cameraUpDirection.x(), cameraUpDirection.y(), cameraUpDirection.z());
shaderProgram.bind();
shaderProgram.setUniformValue("mvpMatrix", pMatrix * vMatrix * mMatrix);
shaderProgram.setUniformValue("texture", 0);
for (int x = 0; x < tileCount; x++)
{
shaderProgram.setAttributeArray("vertex", tiles[x]->vertices.constData());
shaderProgram.enableAttributeArray("vertex");
shaderProgram.setAttributeArray("textureCoordinate", textureCoordinates.constData());
shaderProgram.enableAttributeArray("textureCoordinate");
//Triangle Drawing
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, tiles[x]->image.width(), tiles[x]->image.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, tiles[x]->image.bits());
glDrawArrays(GL_TRIANGLES, 0, tiles[x]->vertices.size());
}
shaderProgram.release();
}
Where as, pMatrix is a 4x4 matrix, controlled during resize events like:
pMatrix.setToIdentity();
pMatrix.perspective(fov, (float) width / (float) height, 0.001, 10000);
glViewport(0, 0, width, height);
and my vertex shader is set up like this:
uniform mat4 mvpMatrix;
in vec4 vertex;
in vec2 textureCoordinate;
out vec2 varyingTextureCoordinate;
void main(void)
{
varyingTextureCoordinate = textureCoordinate;
gl_Position = mvpMatrix * vertex;
}
glReadPixels takes integers (x and y) and you don't seem to be using winZ for some reason in gluUnProject.
Try it like this:
gluUnProject(winX, winY, winZ, glView, glProjection, viewport, &posX, &posY, &posZ);
Also, if you want the ray to stop when it meets something in the depth buffer then don't clear the depth buffer after rendering. If you do a glClear(GL_DEPTH_BUFFER_BIT) then the ray should go as far as the far clip you set in your projection matrix.
I also have no idea why you need to call it more than once. The last three floats will be the target vector and you can just use your camera position as the source of the ray (depending on what you are doing).
Part of my problem here was poorly describing it. I accidentally left residual code from frantically testing, resulting in bits of "read Pixel" functions and related nonsense which wasn't useful for solving the problem.
The rest of my problem was due to inconsistent data types for the matrices, and trying to pull matrices from OpenGL when it never had them stored in the first place.
The problem was solved by:
Using GLM to hold all my matrices
performing the calculations myself (inverse view matrix * inverse model matrix * inverse projection matrix) * vector holding NDC converted screen space coordinates (range of -1 to 1: x or y divided by width or height, * 2 - 1), which also has a z of -1 or 1 for the far or near planes, and a w of 1.
Divide result by the fourth spot of the vector.
I still do not know why unprojecting doesn't work for me, as I got the wrong results with GLU as well as GLM's unproject function, but doing it manually worked for me.
Since my problem extended over quite a great length of time, and took up several questions, I owe credit to a few individuals who helped me along the way:
srobins of facepunch, in this thread
derhass from here, in this question, and this discussion
Related
I am drawing a texture with z=0 as visible in the following image:
My near plane and far plane settings are 0.001 and 120.0. I can move around and zoom in and out using my keyboard.
What I want is to identify the world position of a mouse cursor position (e.g. red in the image) while using gluunproject.
Problem: The results posX, posY and posZ are somewhat close but not correct. E.g. for the center of the visible view (blue) I get posX=8.90000 and posY=53.80000. However if I move the mouse to the left I get posX=8.89894 (should be 3.xx). If I move the cursor to the right I get 8.90103 instead of approx. 14-16.
Here is my code:
glMatrixMode (GL_PROJECTION);
glLoadIdentity();
perspectiveGL(65.0,(GLdouble)width()/(GLdouble)height(), 0.001, 120.0);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Go to current World-Position
glTranslated(-m_posWorld.x(), -m_posWorld.y(), -m_posWorld.z());
// Try to project Screen(Mouse)-Coordinates to World Coordinates
GLint viewport[4];
GLdouble modelview[16];
GLdouble projection[16];
GLfloat winX, winY, winZ;
glGetDoublev( GL_MODELVIEW_MATRIX, modelview );
glGetDoublev( GL_PROJECTION_MATRIX, projection );
glGetIntegerv( GL_VIEWPORT, viewport );
winX = (float)m_lastPos.x(); // m_lastPos is a Point storing the last Mouse Cursor Position within the OpenGL-Widget (without borders)
winY = (float)viewport[3]-(float)m_lastPos.y();
winZ = -m_posWorld.z();
GLdouble posX, posY, posZ;
int success = glhUnProjectf( winX, winY, -m_posWorld.z(), modelview, projection, viewport, &posX, &posY, &posZ);
What might be the problem here?
To "unproject" something, you need window-space x,y and z.
m_posWorld.z almost certainly is not in window-space.
Window-space z is what the depth buffer stores, it is a value between [0.0, 1.0] (assuming default depth range). Because the depth buffer gives you this value, you can easily figure it out by reading the depth buffer back at (winX, winY) (though performance will suffer).
You can transform your world-space coordinate into window-space:
vec4 posClip = projectionMatrix * viewMatrix * m_posWorld; // Project to clip-space
vec4 posNDC = posClip / posClip.w; // Perspective Divide
float winZ = (posNDC * vec4 (0.5) + vec4 (0.5)).z; // Adjust for Depth Range
The approach discussed above will work much quicker, but requires some matrix math. You are currently relying completely on OpenGL itself to do your matrix math for you, and if you want to keep doing that your only option will be to read the depth buffer back:
GLfloat winZ;
glReadPixels (winX, winY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ);
I have a got a simple OpenGL program which sets up the camera as follows :
void
SimRenderer::render() {
glDepthMask(true);
glClearColor(0.5f, 0.5f, 0.7f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glFrontFace(GL_CW);
glCullFace(GL_FRONT);
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
QMatrix4x4 mMatrix;
QMatrix4x4 vMatrix;
QMatrix4x4 cameraTransformation;
cameraTransformation.rotate(mAlpha, 0, 1, 0); // mAlpha = 25
cameraTransformation.rotate(mBeta, 1, 0, 0); // mBeta = 25
QVector3D cameraPosition = cameraTransformation * QVector3D(0, 0, mDistance);
QVector3D cameraUpDirection = cameraTransformation * QVector3D(0, 1, 0);
vMatrix.lookAt(cameraPosition, QVector3D(0, 0, 0), cameraUpDirection);
mProgram.bind();
mProgram.setUniformValue(mMatrixUniformLoc, mProjMatrix * vMatrix * mMatrix );
// render a grid....
}
But the result is an upside down camera !!
!1
When I change the view matrix to be set up as:
QVector3D cameraUpDirection = cameraTransformation * QVector3D(0, -1, 0);
It works ! But why should I need to set my up direction as negative Y when my real up direction is positive Y ?
Complete class here : https://code.google.com/p/rapid-concepts/source/browse/trunk/simviewer/simrenderer.cpp
Other info: I am rendering to a QQuickFramebufferObject which binds a FBO to a widgets surface before calling the rendering function. Dont think that would be an issue but anyway. And this is not a texturing issue at all, there arent any textures to be flipped etc. Seems the camera is interpreting the up direction in the opposite way !!
http://doc.qt.digia.com/qt-maemo/qmatrix4x4.html#lookAt
Update :
So since using lookat and cameraTransformations both together may not work I am trying :
QMatrix4x4 mMatrix;
QMatrix4x4 vMatrix;
QMatrix4x4 cameraTransformation;
cameraTransformation.rotate(mAlpha, 0, 1, 0); // 25
cameraTransformation.rotate(mBeta, 1, 0, 0); // 25
cameraTransformation.translate(0, 0, mDistance);
vMatrix = cameraTransformation.inverted();
That produces exactly the same result :)
I think the camera up axis needs to be accounted for in some way.
It is actually not the camera that upside down but the texture was rendered to QML surface upside down. That is really confusing because you do get the correct direction (Y up) if you are using widget based stacks (QOpenGLWidget) or simply QOpenGLWindow.
Basically the same as this question. some explanation can be found on the forum or in the bug tracker.
I think the best solution is the one in bug tracker which requires no additional transformation on either the QML item or in matrix: overriding updatePaintNode to setTextureCoordinatesTransform to vertically mirrored.
QSGNode *MyQQuickFramebufferObject::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *nodeData)
{
if (!node) {
node = QQuickFramebufferObject::updatePaintNode(node, nodeData);
QSGSimpleTextureNode *n = static_cast<QSGSimpleTextureNode *>(node);
if (n)
n->setTextureCoordinatesTransform(QSGSimpleTextureNode::MirrorVertically);
return node;
}
return QQuickFramebufferObject::updatePaintNode(node, nodeData);
}
Typically this effect is caused by one of several things.
Mixing up radians and degrees
Forgetting to set the modelview matrix to the inverse of the camera transform
Screwing up the inputs to lookat
I suspect the issue with this is the last.
QVector3D cameraUpDirection = cameraTransformation * QVector3D(0, 1, 0);
Why are you multiplying the up vector by this transformation? I can understand multiplying the distance, so that the camera position is transformed, rotating the up axis sent to a lookat function will result in weirdness I suspect.
Generally, doing transforms using a camera matrix AND using lookat is a little odd. If you already have a camera matrix with the proper rotation, you can just translate that matrix by the distance required, expressed as a Z vector of the appropriate length, probably QVector3D(0, 0, mDistance), and then setting the view matrix to the inverse of the camera matrix:
vMatrix = cameraTransformation.inverted();
I need to detect mouse motion and draw a ball at the mouse's position. I need the ball to be in world coordinate. So I'm trying to use glUnProject for this task and seems I'm not succeeding till now. This is my motionFunc:
void motionFunc( int x, int y)
{
GLint viewport[4];
GLdouble modelview[16];
GLdouble projection[16];
GLfloat winX, winY, winZ;
GLdouble posX, posY, posZ;
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); //printf("winz: %f posz: %f\n",winZ,posZ);
Ball.x=posX;
Ball.y=posY;
Ball.z=posZ;
//printf("%f %f %f\n",Ball.x,Ball.y,posZ);
glutPostRedisplay();
}
now, I added a breakpoint on glutPostRedisplay. Turns out when I click-drag mouse, the Ball's coordinates(Ball.x,Ball.y,Ball.z) are something like:
(Ball).x -727.175354
(Ball).y 407.310242
(Ball).z -865.000610
why is the z coordinate so far? My camera is at z=+135. And other objects in my model are like, at z= -3 to +3. I need the ball's z coordinate to be in the same range.
now, what exactly is winZ? Here, I checked it always turns out to be 1.00. I tried to hardcode winZ and I found at winZ=0.85, the ball seems like to be always under the mouse(I can drag the ball with my mouse and the ball is always under the pointer). But then the Ball's coordinates are like:
(Ball).x -4.67813921
(Ball).y 2.57806134
(Ball).z 128.370895
which is so close to the camera but x and y coordinates are not good for me. they always come out to be near the origin. which is not what I want. My other objects' x and y coordinates have a wider range.
Finally, my question is, what is the correct way to do glUnproject or something of the same sort?
A 2D mouse coordinate cannot unambiguously unmapped to a 3D world coordinate. The 2D coordinate corresponds with a line in 3D space. The winz influences which point on this line is returned. When you use 0.0 for winz you will get the point at the near clipping plane. When you use 1.0 you will get the point at the far clipping plane.
If you are using a depth buffer you could retrieve the value from the depth buffer using the glReadPixels function and use that as winz. Here a piece of code from a toy project of mine in Java
FloatBuffer depth = BufferUtils.createFloatBuffer(1);
glReadPixels(mouse_x, mouse_y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, depth);
depth.rewind();
FloatBuffer farPos = BufferUtils.createFloatBuffer(3);
GLU.gluUnProject(mouse_x, mouse_y, depth.get(),
mainContext.getModelviewMatrix(),
mainContext.getProjectionMatrix(), viewport, farPos);
Most is the same in C/C++ except for the weird NIO buffers.
I am trying to use gluUnProject to get my mouse coordinates into world coordinates, however it seems to not be working, or I am just misunderstanding the functionality of the glUnProject function, here is the code I am working with, my matrices all check out fine and as for the -300 on the mouse x coordinate, I am using a C++ Win32 Dialog and the ScreenToClient is giving me funky results.
int appWidth = CApplication::GetInstance()->GetWidth();
int appHeight = CApplication::GetInstance()->GetHeight();
float fAspect = (float)appWidth / (float)appHeight;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0f, fAspect, 0.1f, 100000.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(m_vecCamera.x, -m_vecCamera.y, m_vecCamera.z);
GLint viewport[4];
GLdouble modelview[16];
GLdouble projection[16];
GLfloat winX, winY, winZ;
GLdouble posX, posY, posZ;
glEnable(GL_DEPTH);
//Retrieve the Model/View, Projection, and Viewport Matrices
glGetDoublev( GL_MODELVIEW_MATRIX, modelview );
glGetDoublev( GL_PROJECTION_MATRIX, projection );
glGetIntegerv( GL_VIEWPORT, viewport );
//Retrieve the Mouse X and the flipped Mouse Y
winX = (float)pInput->msg.param1-300.0f;
winY = (float)viewport[3]-(float)pInput->msg.param2;
glReadPixels( int(winX), int(winY), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ );
gluUnProject(winX, winY, winZ, modelview, projection, viewport, &posX, &posY, &posZ);
This is however giving me coordinates relative to the center of my screen, and I am assuming is relative to my camera, I also tried implementing my own function
Vector2f MouseUnProject(int x, int y)
{
GLint viewport[4];
GLdouble modelview[16];
GLdouble projection[16];
GLfloat winX, winY, winZ;
GLdouble posX, posY, posZ;
glEnable(GL_DEPTH);
//Retrieve the Model/View, Projection, and Viewport Matrices
glGetDoublev( GL_MODELVIEW_MATRIX, modelview );
glGetDoublev( GL_PROJECTION_MATRIX, projection );
glGetIntegerv( GL_VIEWPORT, viewport );
//Retrieve the Mouse X and the flipped Mouse Y
winX = (float)x;
winY = (float)viewport[3]-y;
glReadPixels( int(winX), int(winY), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ );
double projectionX, projectionY;
double viewX, viewY;
double worldX, worldY;
//Convert from Screen Coords to Projection Coords
projectionX = (double)winX / ((double)viewport[2]/2.0) - 1.0;
projectionY = (double)winY / ((double)viewport[3]/2.0) + 1.0;
//Convert from projection Coords to View Coords
viewX = projectionX * modelview[14];
viewY = projectionY * modelview[14];
//Convert from View Coords to World Coords
worldX = viewX + modelview[12];
worldY = viewY - modelview[13];
return Vector2f(worldX, worldY);
}
It works to a certain mount, but when moving the camera, the numbers instantly go off a bit, the conversion from projection to view coords 'seems' to be ok, and the projection coords are definitely good.
I would really prefer to use glUnProject rather then my own function, but I can't get it to work for the life of me and all of the google searches I found don't seem to answer my question. What exactly does the GL documentation mean by 'object space' perhaps my understanding of that is wrong, and if so what do I additionally have to do to get my coordinates in the right space?
was posted a year ago,but anyways....so you are getting coordinates relative the the screen because you made a call to gluPerspective. this call internally calls glfrustum which will generate normalized coordinates in the range {-1, 1}. However if you called glfrustum directly with your near/far values you would have got the result from gluUnproject in that range.
To get back to your map editor coordinates, simply take the result from gluUnproject and manually range convert back to your editor coordinate system, ie{-1,1} => {0, max}
To get started you should test gluUnProject by inputting (0,0), (midX, midY), (maxX, maxY) and the result from gluUnProject should be (-1, -1, depth), (0, 0, depth) and (1, 1, depth). If you setup the projection matrix using glFrustum then the above result will be returned in the near/far range.
how to handle clik on specific 3d or 2d object in opengl, for example i have the following code
void Widget::drawCircle(float radius) {
glBegin(GL_TRIANGLE_FAN);
for (int i = 0; i < 360; i++) {
float degInRad = i*DEG2RAD;
glVertex2f(cos(degInRad) * radius, sin(degInRad) * radius);
}
glEnd();
}
So i need to handle click on this circle, is there any solutions for this problem?
When I need to detect clicks, I usually do my ordinary draw loop, but instead of drawing the objects with texturing, lighting and other effects enabled, I draw each of them with flat/no shading, each in a different color. I then check the color on the pixel the mouse is on, and map backwards from the color returned from the framebuffer to the object that I drew with that color.
Perhaps this technique is useful for you, too.
Take a look into this nehe tutorial item. It is very complex, but it shows how opengl picking works. In my opinion, if you need it, you are better with some game engine then with opengl.
Here is another (similar) way of selecting items in opengl.
opengl mouse raytracing will provide you with all details how to select items in opengl. This thread even provides the code how it is done :
Vector3 World::projectedMouse(float mx, float my){
GLdouble model_view[16];
GLint viewport[4];
GLdouble projection[16];
GLfloat winX, winY, winZ;
GLdouble dx, dy, dz;
glGetDoublev(GL_MODELVIEW_MATRIX, model_view);
glGetDoublev(GL_PROJECTION_MATRIX, projection);
glGetIntegerv(GL_VIEWPORT, viewport);
winX = (float)mx;
winY = (float)viewport[3] - (float)my;
glReadPixels ((int)mx, (int)winY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ);
gluUnProject(winX, winY, 0, model_view, projection, viewport, &bx, &by, &bz);
Vector3 pr2 = Vector3(bx, by, bz);
glColor3f(1,0,0);
glBegin (GL_LINE_LOOP);
glVertex3f(player->getPosition().x, player->getPosition().y + 100, player->getPosition().z); // 0
glVertex3f(pr.x,pr.y,pr.z); // 1
glVertex3f(player->getPosition().x, player->getPosition().y, player->getPosition().z); // 0
glEnd();
return pr;
}