3
\$\begingroup\$

I have a little LWJGL application and I have been trying to figure out how to successfully use the GLU.gluUnProject function in order to transform a given point on the screen (mouse position) to world coordinates. I found several examples on Google and really tried hard to get it to work. I do manage to get a result vector but the values it gives me make no sense.

I initialize my scene using GL11.glFrustum(...) as follows:

public void initialize()
{
GL11.glMatrixMode(GL11.GL_PROJECTION);
GL11.glLoadIdentity();
GL11.glFrustum(-0.5, 0.5, -0.5, 0.5, 1.5, 6.5);
// left, right, bottom, top, zNear, zFar

GL11.glMatrixMode(GL11.GL_MODELVIEW);
GL11.glLoadIdentity();

GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_REPLACE);
}

The window has a size of 800 x 600. Here is a screenshot:

screen capture

Now to find a corresponding point in the game world for a given mouse position on the screen I use the following method:

public void calcWorldCoordinates(float mouseX, float mouseY) 
// mouseX is in the range of [0..800] and mouseY in [0..600]
{
FloatBuffer model = BufferUtils.createFloatBuffer(16);
FloatBuffer projection = BufferUtils.createFloatBuffer(16);
IntBuffer viewport = BufferUtils.createIntBuffer(16);

GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, model);
GL11.glGetFloat(GL11.GL_PROJECTION_MATRIX, projection);
GL11.glGetInteger(GL11.GL_VIEWPORT, viewport);

float viewportHeight = viewport.get(3); // = 600.0f
float y = viewportHeight - mouseY;

FloatBuffer posNear = BufferUtils.createFloatBuffer(16);
FloatBuffer posFar = BufferUtils.createFloatBuffer(16);

GLU.gluUnProject(mouseX, y, -1.5f, model, projection, viewport, posNear);
GLU.gluUnProject(mouseX, y, -6.5f, model, projection, viewport, posFar);

// ...
}

For mouseX = 300, mouseY = 400 I receive:

posNear = [x=-0.0580, y=0.0774, z=-0.6964]
posFar = [x=-0.0208, y=0.0278, z=-0.6964]

None of the three resulting coordinates makes sense to me. As far as I can tell x should be somewhere around 240 (each tile in the scene is of size 120 x 80 pixels). It should definately be positive though and not -0.0580 or -0.0208.

I also looked at the model and projection matrix as well as the viewport size. Those are:

model:
1.0, 0.0, 0.0, 0.0
0.0, 1.0, 0.0, 0.0
0.0, 0.0, 1.0, 0.0
0.0, 0.0, 0.0, 1.0

projection:
3.0, 0.0, 0.0, 0.0
0.0, 3.0, 0.0, 0.0
0.0, 0.0, -1.6, -1.0
0.0, 0.0, -3.9, 0.0

viewport: 0, 0, 800, 600

I rewrote the code several times using different implementations of other people but could not get it to work. Maybe somebody could give me a hint. What I am doing wrong?

\$\endgroup\$
  • 1
    \$\begingroup\$ I would recommend a title that summarizes your problem succinctly. This will allow people knowledgeable about topic to quickly realize they are able to help. \$\endgroup\$ – ClassicThunder Mar 6 '14 at 21:17
  • \$\begingroup\$ You are right. I hope it is better now. \$\endgroup\$ – xoric Mar 6 '14 at 21:43
6
\$\begingroup\$
gluUnProject(float winx, float winy, float winz, FloatBuffer modelMatrix,
    FloatBuffer projMatrix, IntBuffer viewport, FloatBuffer obj_pos)

winx ,winy and winz are the screen coordinates which you want to transform back to world coordinates.

winx and winy are in this case the x and y coordinates of the mouse as returned from Mouse.getX() and Mouse.getY(). In your example you have these lines:

float viewportHeight = viewport.get(3);
float y = viewportHeight - mouseY;

The reason you may see this in tutorials is that in Windows the origin used for mouse coordinates is the top left corner of the display while openGL uses the bottom left corner, so these lines are needed to transform the Windows mouse coordinates to openGL coordinates. However LWJGL already does this for us as the origin used for Mouse.getY() and Mouse.getX() is the bottom left corner, so this is not needed.

winz is the depth into the screen of the point that you want to transform back to world coordinates. The near clip plane is at z=0 and the far clip plane is at z=1. If you want the depth of the object that you clicked on, (in order to find its world coordinates), you can use glReadPixels like this:

FloatBuffer z = BufferUtils.createFloatBuffer(1);
glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, z);

which reads the depth of the pixel at x, y (Mouse.getX(), Mouse.getY() in this case) from the depth buffer and stores it in z. Note that the time at which you call this does make a difference, for example if you clear your buffer then check for input and call this function and then render it will not work because your buffer is empty. This can actually be useful because it allows you to render the clickable objects in your world first, then check for input, followed by rendering things you would like to be able to click through, such as a semi-transparent chat interface.

Another option which may be useful is to call gluUnProject twice with different z values to retrieve the vector from your mouse into the screen by subtracting the two points obtained.

modelMatrix, projMatrix, and viewport are the modelview matrix, projection matrix, and viewport of your world. The way you are retrieving them is correct, and may work for you for now, however if you change any of these after drawing your world, for example to render a gui over part of the display, then it may fail. To prevent this you can move your calls to glGetFloat/Integer to directly after you setup your world matrices and viewport.

obj_pos, the last parameter, is where the coordinates of the transformed point are stored if gluUnProject does not fail (which is indicated by a return value of true). In your code you create this float buffer with a capacity of 16, although it only actually needs to have a capacity of 3.

\$\endgroup\$
  • \$\begingroup\$ Thank you so much for your detailed explanation! I thought I had understood most of this already but reading your remarks actually made it much clearer to me. I am going to choose the solution using two separate calls of gluUnProject with zNear=0.0f and zFar=1.0f. Replacing my faulty z-values (-1.5f and -6.5f taken from the glFrustum parameters) with those now results in a vector that I can comprehend. The resulting x- and y-values are in the range of -0.5f and +0.5f, the resulting z-values are between -1.5f and -6.5f which fit exactly the parameters I passed to glFrustum. \$\endgroup\$ – xoric Mar 7 '14 at 13:27
  • 1
    \$\begingroup\$ Note: I was also able to get the other solution working for which you first read the screen depth (using glReadPixels) under the mouse pointer. To be able to do this it is important to initially enable depth testing: GL11.glEnable(GL11.GL_DEPTH_TEST); The code will then look like this: FloatBuffer screenDepth = BufferUtils.createFloatBuffer(1); GL11.glReadPixels(mouseX, mouseY, 1, 1, GL11.GL_DEPTH_COMPONENT, GL11.GL_FLOAT, screenDepth); boolean b = GLU.gluUnProject(x, y, screenDepth.get(0), modelview, projection, viewport, objPos); Hope this helps. \$\endgroup\$ – xoric Mar 7 '14 at 13:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.