glTranslatef 和鼠标点击 - gluUnProject

glTranslatef and mouse clicks - gluUnProject

请查看问题底部以了解我所寻求的当前解决方案,感谢 Finlaybob、elect、gouessej

对 OpenGL 长老的呼吁....我在检测鼠标在我的纹理平面上单击的相对位置时遇到了很大的问题。

我正在制作一个游戏,我在其中绘制一个大正方形并使用生成的大地图纹理对其进行纹理化。视图始终自上而下,您当前只能移动该正方形的 X Y 和 Z 坐标。

Screenshot of the map

OpenGL 初始化

screenRatio = (float)screenW / (float)screenH;
System.out.println("init");
glu = new GLU();
GL2 gl2 = drawable.getGL().getGL2();
gl2.glShadeModel( GL2.GL_SMOOTH );
gl2.glHint( GL2.GL_PERSPECTIVE_CORRECTION_HINT, GL2.GL_NICEST );
gl2.glClearColor( 0f, 0f, 0f, 1f );
gl2.glDepthMask(false);
gl2.glEnable(GL2.GL_DEPTH_TEST);

设置相机位置

gl2.glViewport(0, 0, 1024, 768);
gl2.glMatrixMode( GL2.GL_PROJECTION );
gl2.glLoadIdentity();
glu.gluPerspective( 45, screenRatio, 1, 100 );
glu.gluLookAt( 0, 0, 3, 0, 0, 0, 0, 1, 0 );
gl2.glMatrixMode(GL2.GL_MODELVIEW);
gl2.glLoadIdentity();

移动位置开始绘制地图

// typical camera coord example:
// CENTRE: 0.0f, 0.0f, 10f
// FULL ZOOM OUT AND TOP LEFT: -25f, 25f, 40f

// move position
gl2.glTranslatef( -cameraX, -cameraY, -cameraZ );

我怀疑 glTranslatef z 坐标可能有问题。当我绘制正方形 40f(例如)远离原点时

地图顶点信息

// here are the coordinates/dimensions of my textured square ( my map )
float[] vertexArray = {
    -25f,  25f,
     25f,  25f,
     25f, -25f,
     25f, -25f,
};

鼠标点击位置计算

"Borrowed" 来自 java-tips 1628-how-to-use-gluunproject-in-jogl.html

int x = mouse.getX(), y = mouse.getY();
int viewport[] = new int[4];
double mvmatrix[] = new double[16];
double projmatrix[] = new double[16];
int realy = 0;
double wcoord[] = new double[4];

gl2.glGetIntegerv(GL2.GL_VIEWPORT, viewport, 0);
gl2.glGetDoublev(GL2.GL_MODELVIEW_MATRIX, mvmatrix, 0);
gl2.glGetDoublev(GL2.GL_PROJECTION_MATRIX, projmatrix, 0);

realy = viewport[3] - (int) y - 1;

glu.gluUnProject(
    (double) x,
    (double) realy,
    0.0, // I have experimented with having this as 1.0 also
    mvmatrix, 0,
    projmatrix, 0,
    viewport, 0,
    wcoord, 0
);

试验 near/far 位(gluUnProject 的第 3 个参数)似乎产生了更好的效果,但似乎没有最佳点(我发现的最佳值是 0.945)

我非常希望 mCX、mCY 相对于渲染的地图坐标 (-25f - 25f) 而不管 Z 位置如何

mCX = (float)wcoord[0];
mCY = (float)wcoord[1];

在转换后的坐标处绘制一个矩形

gl2.glColor3f(1.f, 0.f, 0.f);
gl2.glBegin(GL2.GL_QUADS);
gl2.glVertex2f( mCX-0.1f, mCY+0.1f );
gl2.glVertex2f( mCX+0.1f, mCY+0.1f );
gl2.glVertex2f( mCX+0.1f, mCY-0.1f );
gl2.glVertex2f( mCX-0.1f, mCY-0.1f );
gl2.glEnd();

目前,坐标在 x 和 y 平移方面运行良好,如果我单击屏幕的正中央,无论我的 glTranslatef 移动如何,它都会在大致正确的位置绘制一个框。如果我点击远离屏幕中心的地方,我会看到一个指数偏移量。

指数偏移演示

当我单击屏幕的死点时,它会在鼠标点周围精确地绘制这个紫红色正方形,但只要移动最小,就会产生以下效果:

Fully zoomed in, click a couple of pixels right of centre

更新和工作...现在

在为我的地图生成纹理时,我还生成了一个替代纹理,它将每个 "tile" 表示为不同的颜色。在我最初和当前的尝试中,这个图块的颜色是它的 X 和 Y 坐标的函数(一张地图由 100 个横向的图块和 100 个向下的图块组成,所以 x+y 坐标的范围是 0 - 99)

我最终得到了一个看起来像从绿色到红色渐变的纹理。下面的代码将在鼠标单击时快速渲染此纹理(用户无法察觉)并读取鼠标下的 rgb 值。然后我们将该 rgb 值转换为世界坐标和 BOOM...我的地图的相对坐标实现了。

float pX, pY;

// render a colourised version of the scene for the purposes of "picking"
// https://www.opengl.org/archives/resources/faq/technical/selection.htm
public void pick ( GL2 gl2 ) {

    // DRAW PICKING SCENE
    gl2.glClearBufferfv(GL2.GL_COLOR, 0, clearColor);
    gl2.glClearBufferfv(GL2.GL_DEPTH, 0, clearDepth);
    gl2.glTranslatef( -cameraX, -cameraY, -cameraZ );

    // draw my map but use the colour gradient texture
    for ( Entity e : this.entities ) {
        e.drawPick( gl2 );
    }

    // not sure what this does #cargo-cult
    gl2.glFlush();
    gl2.glFinish();
    gl2.glPixelStorei(GL2.GL_UNPACK_ALIGNMENT, 1);


    // After rendering ask OpenGL to read the colour of the screen at the given window coordinates!
    FloatBuffer buffer = FloatBuffer.allocate(4);

    int realy = 0; 
    int viewport[] = new int[4];
    gl2.glGetIntegerv(GL2.GL_VIEWPORT, viewport, 0);
    realy = viewport[3] - (int) mouse.getY() - 1;

    gl2.glReadPixels( mouse.getX(), realy, 1, 1, GL2.GL_RGBA, GL2.GL_FLOAT, buffer);
    float[] pixels = new float[3];
    pixels = buffer.array();

    // pixels holds rgb values respectively
    // convert the red + green values back into x + y values
    pX = (pixels[0] * 255) - 25f;
    pY = -((pixels[1] * 255) - 25f);

    // draw the proper texture
    for ( Entity e : this.entities ) {
        e.draw( gl2 );
    }
}

你快搞定了。不过,您需要在 unproject 函数中为 Z 取一个好的值。

您要做的是获取光标的位置并乘以矩阵以给出“3d space”中的一个点。您的矩阵可能是 4x4 或 4x3,因此您需要一个 4 分量向量。 (x,y,z,w)

绘制地图时,现有点会乘以 1 个或多个矩阵,包括投影矩阵。 (例如 -25.0f,25.0f,0.0f,1.0f - 实际上是一个 3d 点)。当它与所有矩阵相乘时,GPU 基本上会返回该顶点的归一化设备坐标 (NDC) 值(在所有轴上介于 -1 和 1 之间)。

要执行相反的操作并取消投影,您需要为 Z 设置一个 valid/good 值。原因是在 NDC 中,绘制的所有内容都在所有轴上的 -1,1 中,以获取所有内容在(更远的东西被压扁了一点)。例如,如果你有一个> 100000 zFar 的巨大距离,这就是你如何变得闪烁和怪异,它仍然必须适合 -1,1.

执行此操作的最佳方法是使用深度缓冲区,通过捕获深度值,它将为您提供 NDC 中 z 坐标的良好近似值,您可以将其传递给 unproject 调用。

最佳位置为 0.945 的原因可能取决于相机与地图的距离,反之亦然。通常情况下,深度缓冲区靠近近平面的细节比远平面多得多 - 它不是线性的。

http://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices/ 在页面底部附近有很好的视觉效果,是一般介绍矩阵的好资源:

您可以看到移动到 NDC 造成的失真。这是透视视角所需要的,但你在向后转换时也需要考虑到这一点。

前面提到的颜色选择也是可行的选择,但仍然需要一些工作。因为你只有一个对象,所以你必须用不同的颜色渲染图像的每个纹素,将其输出到单独的颜色缓冲区,检查缓冲区上的颜色并以某种方式将其与 space。虽然它可能会完成,但我会说颜色选择更适合多个对象。

根据我的阅读,深度缓冲区可能更适合你,因为它是一个对象,深度缓冲区将为你单击的每个点提供一个 Z 坐标。它可能仍然在你的远平面上,但它仍然会给你一个值。

或者,按照@elect 的建议使用正交投影。