安卓OpenGL 3D拾取

6

我正在使用Android OpenGL-ES 2.0,但由于其带来的所有限制,我无法弄清如何将2D屏幕触摸转换为3D点。我无法获得正确的结果。

我尝试实现向点云发射光线,然后比较我的点与光线之间的距离,找到最近的点。

public class OpenGLRenderer extends Activity implements GLSurfaceView.Renderer {
    public PointCloud ptCloud;
    MatrixGrabber mg = new MatrixGrabber();
...
    public void onDrawFrame(GL10 gl) {

        gl.glDisable(GL10.GL_COLOR_MATERIAL);
        gl.glDisable(GL10.GL_BLEND);
        gl.glDisable(GL10.GL_LIGHTING);

        //Background drawing
        if(customBackground)
            gl.glClearColor(backgroundRed, backgroundGreen, backgroundBlue, 1.0f);
        else
            gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

        if (PointCloud.doneParsing == true) {
            if (envDone == false)
                setupEnvironment();

            // Clears the screen and depth buffer.
            gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

            gl.glMatrixMode(GL10.GL_PROJECTION);
            gl.glLoadIdentity();
            GLU.gluPerspective(gl, 55.0f, (float) screenWidth / (float) screenHeight, 10.0f ,10000.0f);

            gl.glMatrixMode(GL10.GL_MODELVIEW);

            gl.glLoadIdentity();
            GLU.gluLookAt(gl, eyeX, eyeY, eyeZ, 
                              centerX, centerY, centerZ, 
                              upX, upY, upZ);

            if(pickPointTrigger)
                pickPoint(gl);


            gl.glPushMatrix();

            gl.glTranslatef(_xTranslate, _yTranslate, _zTranslate);
            gl.glTranslatef(centerX, centerY, centerZ);
            gl.glRotatef(_xAngle, 1f, 0f, 0f);
            gl.glRotatef(_yAngle, 0f, 1f, 0f);
            gl.glRotatef(_zAngle, 0f, 0f, 1f);
            gl.glTranslatef(-centerX, -centerY, -centerZ);

            ptCloud.draw(gl);

            gl.glPopMatrix();
        }
    }
}

这是我的选择函数。我已经将位置设置为屏幕中央,仅用于调试目的:

public void pickPoint(GL10 gl){

        mg.getCurrentState(gl);

        double mvmatrix[] = new double[16];
        double projmatrix[] = new double[16];
        int viewport[] = {0,0,screenWidth, screenHeight};

        for(int i=0 ; i<16; i++){
            mvmatrix[i] = mg.mModelView[i];
            projmatrix[i] = mg.mProjection[i];
        }

        mg.getCurrentState(gl);

        float realY = ((float) (screenHeight) - pickY);
        float nearCoords[] = { 0.0f, 0.0f, 0.0f, 0.0f };
        float farCoords[] = { 0.0f, 0.0f, 0.0f, 0.0f };


        GLU.gluUnProject(screenWidth/2, screenHeight/2, 0.0f, mg.mModelView, 0, mg.mProjection, 0,
                    viewport, 0, nearCoords, 0);
        GLU.gluUnProject(screenWidth/2, screenHeight/2, 1.0f, mg.mModelView, 0, mg.mProjection, 0,
                    viewport, 0, farCoords, 0);

        System.out.println("Near: " + nearCoords[0] + "," + nearCoords[1] + "," + nearCoords[2]);
        System.out.println("Far:  " + farCoords[0] + "," + farCoords[1] + "," + farCoords[2]);



      //Plot the points in the scene
        nearMarker.set(nearCoords);
        farMarker.set(farCoords);
        markerOn = true;

        double diffX = nearCoords[0] - farCoords[0];
        double diffY = nearCoords[1] - farCoords[1];
        double diffZ = nearCoords[2] - farCoords[2];

        double rayLength = Math.sqrt(Math.pow(diffX, 2) + Math.pow(diffY, 2) + Math.pow(diffZ, 2));
        System.out.println("rayLength: " + rayLength);

        pickPointTrigger = false;   
    }

改变透视眼界的 zNear 和 Far 值时没有达到预期的结果,一个 1.0-1000.0 的透视模型怎么可能将远点设定为 11 个单位之外呢?

GLU.gluPerspective(gl, 55.0f, (float) screenWidth / (float) screenHeight, 1.0f ,100.0f);
.....
07-18 11:23:50.430: INFO/System.out(31795): Near: 57.574852,-88.60514,37.272636
07-18 11:23:50.430: INFO/System.out(31795): Far:  0.57574844,0.098602295,0.2700405
07-18 11:23:50.430: INFO/System.out(31795): rayLength: 111.74275719790872

GLU.gluPerspective(gl, 55.0f, (float) width / (float) height, 10.0f , 1000.0f);
...
07-18 11:25:12.420: INFO/System.out(31847): Near: 5.7575016,-7.965394,3.6339219
07-18 11:25:12.420: INFO/System.out(31847): Far:  0.057574987,0.90500546,-0.06634784
07-18 11:25:12.420: INFO/System.out(31847): rayLength: 11.174307289026638

寻求任何建议或者你在我的代码中发现的错误。非常感谢。我会尽可能地进行奖励(这已经是一个问题了一段时间)。

1个回答

9
我也在处理这个问题,这是一个非常恼人的问题。我有两个潜在的线索:1. 一些结果z依赖于相机所在位置,而不是你期望的方式。当相机z为0时,无论winZ是什么,结果z都为-1。到目前为止,我主要关注结果z,所以我对其他坐标没有任何确切的数字,但我刚才研究了我的代码和你的代码,并发现报告的射线长度随着相机离(0,0,0)越远而增加。在(0,0,0)处,射线长度被报告为0。大约一个小时前,我收集了一堆点(cameraZ,winZ,resultZ),并将它们插入Mathematica中。结果似乎表明一种双曲线形式的东西;当其中一个变量固定时,另一个会导致结果z线性变化,变化速率取决于固定变量。

我的第二个线索来自http://www.gamedev.net/topic/420427-gluunproject-question/; swordfish引用了一个公式:

WinZ = (1.0f/fNear-1.0f/fDistance)/(1.0f/fNear-1.0f/fFar)

现在,这似乎与我收集的数据不符,但值得一看。我想我会尝试弄清楚这个东西的数学原理并找出问题所在。如果你有什么发现,请告诉我。哦,还有,这是适用于我收集的数据的公式:

-0.11072114015496763- 10.000231721597817 x - 0.0003149873867479971x^2 - 0.8633277851535017 y + 9.990256062051143x y + 8.767260632968973*^-9 y^2

Wolfram Alpha通过以下方式绘制它: http://www.wolframalpha.com/input/?i=Plot3D[-0.11072114015496763%60+-+10.000231721597817%60+x+-++++0.0003149873867479971%60+x^2+-+0.8633277851535017%60+y+%2B++++9.990256062051143%60+x+y+%2B+8.767260632968973%60*^-9+y^2+%2C+{x%2C+-15%2C++++15}%2C+{y%2C+0%2C+1}]


AHA!成功了!据我所知,gluUnProject功能是完全失效的。或者说,没有人真正理解如何使用它。无论如何,我制作了一个函数,可以正确地撤销gluProject函数,这似乎确实是他们用来在屏幕上绘制的方式!代码如下:

public float[] unproject(float rx, float ry, float rz) {//TODO Factor in projection matrix
    float[] modelInv = new float[16];
    if (!android.opengl.Matrix.invertM(modelInv, 0, mg.mModelView, 0))
        throw new IllegalArgumentException("ModelView is not invertible.");
    float[] projInv = new float[16];
    if (!android.opengl.Matrix.invertM(projInv, 0, mg.mProjection, 0))
        throw new IllegalArgumentException("Projection is not invertible.");
    float[] combo = new float[16];
    android.opengl.Matrix.multiplyMM(combo, 0, modelInv, 0, projInv, 0);
    float[] result = new float[4];
    float vx = viewport[0];
    float vy = viewport[1];
    float vw = viewport[2];
    float vh = viewport[3];
    float[] rhsVec = {((2*(rx-vx))/vw)-1,((2*(ry-vy))/vh)-1,2*rz-1,1};
    android.opengl.Matrix.multiplyMV(result, 0, combo, 0, rhsVec, 0);
    float d = 1 / result[3];
    float[] endResult = {result[0] * d, result[1] * d, result[2] * d};
    return endResult;
}

public float distanceToDepth(float distance) {
    return ((1/fNear) - (1/distance))/((1/fNear) - (1/fFar));
}

它当前假定以下全局变量: mg - 一个带有当前矩阵的MatrixGrabber viewport - 一个包含视口 ({x, y, width, height}) 的float[4]

它所需要的变量相当于 gluUnProject 应该需要的变量。例如:

float[] xyz = {0, 0, 0};
xyz = unproject(mouseX, viewport[3] - mouseY, 1);

这将返回鼠标下方在远平面上的点。我还添加了一个函数,用于在指定相机距离和其0-1表示之间进行转换...类似于这样:

unproject(mouseX, viewport[3] - mouseY, distanceToDepth(5));

这将返回鼠标下方距离相机5个单位的点。 我使用问题中提供的方法进行了测试 - 我检查了近平面和远平面之间的距离。在fNear为0.1,fFar为100的情况下,距离应该是99.9。据我所知,不管相机的位置或方向如何,我始终得到约99.8977的结果。哈哈,很高兴解决了这个问题。如果您对此有任何问题或想让我重写以接受输入而不是使用全局变量,请告诉我。希望这能帮助一些人; 在认真尝试修复之前,我已经思考了几天这个问题。

嘿,我已经弄清楚它应该是什么样子的了,我已经发现他们在实现gluUnProject时漏掉了什么。他们忘记(或者是故意不想告诉任何人)将结果向量的第四个元素除以,这种操作可以使向量归一化或类似的东西。gluProject在应用矩阵之前将其设置为1,所以当你撤销它们时,它需要为1。长话短说,你实际上可以使用gluUnProject,但是你需要传递一个float [4],然后将所有结果坐标除以第四个坐标,就像这样:

float[] xyzw = {0, 0, 0, 0};
android.opengl.GLU.gluUnProject(rx, ry, rz, mg.mModelView, 0, mg.mProjection, 0, this.viewport, 0, xyzw, 0);
xyzw[0] /= xyzw[3];
xyzw[1] /= xyzw[3];
xyzw[2] /= xyzw[3];
//xyzw[3] /= xyzw[3];
xyzw[3] = 1;
return xyzw;

xyzw现在应该包含相关的空间坐标。这似乎与我拼凑出来的那个完全一样。它可能会快一点;我认为他们合并了其中一个步骤。


我已经有了一些进展。 我可以拿到结果的nearCoords,然后在farCoords方向上进行迭代(我只是根据Z_FAR的大小来进行迭代),光线与我实际选择的内容相当接近。 看看这对你有什么帮助。 - RedLeader
我还没有尝试沿着nearCoords的方向进行迭代,部分原因是因为在我第一次点击时,似乎nearCoords和farCoords都与我的点击位置没有太大关系。我注意到gluUnProject似乎不起作用(以一种新的方式)-我通过gluProject运行了一些坐标,然后立即将结果通过gluUnProject运行,得到了完全不同的结果。我目前正在尝试进行反向变换。我注意到您在拾取时使用lookAt,但在绘制时使用一系列变换。为什么会这样? - Erhannis
这只是我的设计,它有些工作,但不完全符合要求(如果我选择左边则接近,右边则不太接近),但它相当精确。 - RedLeader
我尝试按照你提到的方法考虑第四个坐标,但这可能会使点的选择更加偏离目标。你还做了其他的更改吗? - RedLeader
嗯。所以,你是将其他的除以了四分之一,对吧?你尝试过将pickPoint放在ptCloud.draw函数旁边,这样它们就可以使用相同的矩阵了吗?此外,近平面/远平面距离有任何影响吗? - Erhannis
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接