如何将屏幕上的鼠标坐标转换为3D坐标

3

我正在使用C++中的GLUT创建一个3D应用程序。

现在,我想要实现一个类似于这样的方法:

Vector3* MyClass::get3DObjectfromMouse(int mouseX, int mouseY);

我该如何实现这个方法?


1
如何从屏幕坐标中投射一条光线,并选择该光线与之相交的第一个对象? - vadimvolk
2
您无法这样做,需要一个第三个坐标。通常,人们使用近面作为点1,远面作为点2,然后投射穿过它们两个的光线。该光线代表了投影到窗口《x,y》的无限多个点。或者,如果您想要在《x,y》处找到屏幕上对象的位置,则可以读取深度缓冲区,该缓冲区将提供用于获取未投影位置所需的Window《z》。 - Andon M. Coleman
1个回答

15

正如Andon M. Coleman所评论的那样,您可以通过使用未投影的屏幕坐标进行射线/物体相交测试来实现这一点。这种技术通常被称为picking

picking的伪C++代码:

假设我们有一个三维对象类型/类:

class Object3D { ... };

一个3D拾取函数将返回与从给定2D点到远平面相同点的线相交的所有对象列表。

struct LineSegment 
{
    Vector3 start;
    Vector3 end;
};

Object3D[] Pick(float x, float y)
{
    LineSegment lineSeg;
    Object3D[] intersectedObjs;

    // Do both un-projections for z-near (0) and z-far (1).
    // This produces a line segment going from z-near to far.
    UnProject(x, y, /* z = */ 0.0, modelViewMatrix, projectionMatrix, viewport, lineSeg.start);
    UnProject(x, y, /* z = */ 1.0, modelViewMatrix, projectionMatrix, viewport, lineSeg.end);

    // Iterate all object in the scene or in the current view:
    for (Object3D obj : scene)
    {
        if (TestLineIntersection(obj, lineSeg))
        {
            // This object is crossed by the picking line.
            intersectedObjs.Add(obj);
        }
    }

    // Optionally you might want sort them from distance 
    // to the camera/viewer before returning the intersections.
    return intersectedObjs;
}

UnProject() 函数将如下所示:

bool UnProject(float winX, float winY, float winZ,
               const Matrix4 & modelView, const Matrix4 & projection,
               const ScreenRect viewport, Vector3 & worldCoordinates)
{
    // Compute (projection x modelView) ^ -1:
    const Matrix4 m = inverse(projection * modelView);

    // Need to invert Y since screen Y-origin point down,
    // while 3D Y-origin points up (this is an OpenGL only requirement):
    winY = viewport.Height() - winY;

    // Transformation of normalized coordinates between -1 and 1:
    Vector4 in;
    in[0] = (winX - viewport.X()) / viewport.Width()  * 2.0 - 1.0;
    in[1] = (winY - viewport.Y()) / viewport.Height() * 2.0 - 1.0;
    in[2] = 2.0 * winZ - 1.0;
    in[3] = 1.0;

    // To world coordinates:
    Vector4 out(m * in);
    if (out[3] == 0.0) // Avoid a division by zero
    {
        worldCoordinates = Vector3Zero;
        return false;
    }

    out[3] = 1.0 / out[3];
    worldCoordinates[0] = out[0] * out[3];
    worldCoordinates[1] = out[1] * out[3];
    worldCoordinates[2] = out[2] * out[3];
    return true;
}

需要澄清的是,TestLineIntersection() 执行的是一条线与 AABB 的相交测试。边界框应该被转换到世界空间,因为它通常表示为本地模型空间中的一组点。

bool TestLineIntersection(const Object3D & obj, const LineSegment & lineSeg)
{
    AABB aabb = obj.GetAABB();
    aabb.TransformBy(obj.modelMatrix);
    return aabb.LineIntersection(lineSeg.start, lineSeg.end);
}

// AABB.cpp:
bool AABB::LineIntersection(const Vector3 & start, const Vector3 & end) const
{
    const Vector3 center     = (mins + maxs) * 0.5;
    const Vector3 extents    = maxs - center;
    const Vector3 lineDir    = 0.5 * (end - start);
    const Vector3 lineCenter = start + lineDir;
    const Vector3 dir        = lineCenter - center;

    const float ld0 = Mathf::Abs(lineDir[0]);
    if (Mathf::Abs(dir[0]) > (extents[0] + ld0))
    {
        return false;
    }

    const float ld1 = Mathf::Abs(lineDir[1]);
    if (Mathf::Abs(dir[1]) > (extents[1] + ld1))
    {
        return false;
    }

    const float ld2 = Mathf::Abs(lineDir[2]);
    if (Mathf::Abs(dir[2]) > (extents[2] + ld2))
    {
        return false;
    }

    const Vector3 vCross = cross(lineDir, dir);
    if (Mathf::Abs(vCross[0]) > (extents[1] * ld2 + extents[2] * ld1))
    {
        return false;
    }
    if (Mathf::Abs(vCross[1]) > (extents[0] * ld2 + extents[2] * ld0))
    {
        return false;
    }
    if (Mathf::Abs(vCross[2]) > (extents[0] * ld1 + extents[1] * ld0))
    {
        return false;
    }

    return true;
}

-1 这个问题不值得如此好的回答。 ;) - Qix - MONICA WAS MISTREATED
2
顺便提一下,我几天前刚刚实现了这个功能,所以还记忆犹新 :) - glampert
@glampert。你能分享一下TestLineIntersection的代码片段吗?谢谢! - AlvinfromDiaspar
顺便问一下,我只是想澄清Unproject方法的作用。我的理解是它返回世界坐标中的顶点。如果这是正确的,那么相机的世界坐标如何影响它?我之所以问这个问题,是因为我的起始和结束向量似乎是相对于世界原点(而不是我的相机)。 - AlvinfromDiaspar
很难说。我认为最好你在这里或游戏开发板块提出一个问题,并提供更多的背景信息。 - glampert
显示剩余10条评论

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