2

我找不到使用光线追踪方法在 3D 中拾取的正确且可理解的表达方式。有没有人用任何语言实现过这个算法?直接分享工作代码,因为伪代码不能编译,所以一般都是缺部分写的。

4

2 回答 2

10

您所拥有的是屏幕上的 2D 位置。首先要做的是将该点从像素转换为标准化的设备坐标——-1 到 1。然后您需要在 3D 空间中找到该点所代表的线。为此,您需要 3D 应用程序用来创建投影和相机的转换矩阵/ces。

通常你有 3 个矩阵:投影、视图和模型。当您为对象指定顶点时,它们位于“对象空间”中。乘以模型矩阵得到“世界空间”中的顶点。再次乘以视图矩阵得到“眼睛/相机空间”。再次乘以投影给出“剪辑空间”。剪辑空间具有非线性深度。将 Z 组件添加到鼠标坐标中会将它们放置在剪辑空间中。您可以在任何线性空间中执行线/对象相交测试,因此您必须至少将鼠标坐标移动到眼睛空间,但在世界空间(或对象空间,具体取决于您的场景图)中执行相交测试更方便。

要将鼠标坐标从剪辑空间移动到世界空间,请添加 Z 分量并乘以逆投影矩阵,然后乘以逆相机/视图矩阵。要创建一条线,将计算沿 Z 的两个点 -fromto

在此处输入图像描述

在下面的示例中,我有一个对象列表,每个对象都有一个位置和边界半径。当然,交叉点永远不会完美匹配,但现在它已经足够好了。这不是伪代码,但它使用我自己的向量/矩阵库。您必须在某些地方替换您自己的。

vec2f mouse = (vec2f(mousePosition) / vec2f(windowSize)) * 2.0f - 1.0f;
mouse.y = -mouse.y; //origin is top-left and +y mouse is down

mat44 toWorld = (camera.projection * camera.transform).inverse();
//equivalent to camera.transform.inverse() * camera.projection.inverse() but faster

vec4f from = toWorld * vec4f(mouse, -1.0f, 1.0f);
vec4f to = toWorld * vec4f(mouse, 1.0f, 1.0f);

from /= from.w; //perspective divide ("normalize" homogeneous coordinates)
to /= to.w;

int clickedObject = -1;
float minDist = 99999.0f;

for (size_t i = 0; i < objects.size(); ++i)
{
    float t1, t2;
    vec3f direction = to.xyz() - from.xyz();
    if (intersectSphere(from.xyz(), direction, objects[i].position, objects[i].radius, t1, t2))
    {
        //object i has been clicked. probably best to find the minimum t1 (front-most object)
        if (t1 < minDist)
        {
            minDist = t1;
            clickedObject = (int)i;
        }
    }
}

//clicked object is objects[clickedObject]

代替intersectSphere,您可以使用边界框或其他隐式几何体,或与网格的三角形相交(出于性能原因,这可能需要构建 kd-tree)。

[编辑]
这是线/球相交的实现(基于上面的链接)。它假设球体位于原点,因此不是传递from.xyz()as p,而是 give from.xyz() - objects[i].position

//ray at position p with direction d intersects sphere at (0,0,0) with radius r. returns intersection times along ray t1 and t2
bool intersectSphere(const vec3f& p, const vec3f& d, float r, float& t1, float& t2)
{
    //http://wiki.cgsociety.org/index.php/Ray_Sphere_Intersection
    float A = d.dot(d);
    float B = 2.0f * d.dot(p);
    float C = p.dot(p) - r * r;

    float dis = B * B - 4.0f * A * C;

    if (dis < 0.0f)
        return false;

    float S = sqrt(dis);    

    t1 = (-B - S) / (2.0f * A);
    t2 = (-B + S) / (2.0f * A);
    return true;
}
于 2013-11-22T11:35:42.880 回答
0
vec4f from = toWorld * vec4f(mouse, -1.0f, 1.0f);
vec4f to = toWorld * vec4f(mouse, 1.0f, 1.0f);

我假设'from'是鼠标光标的位置?如果是这样,那么为什么它的 z 是负数,如果我们假设 openGL 坐标。

同样以这种方式我们假设此时的深度是-1到+1对吗?而不是我们沮丧的深度。

于 2021-03-10T11:34:33.523 回答