使用NinevehGL或OpenGL i-phone进行光线追踪的三维选择

3

我找不到用光线追踪方法在3D中正确且易懂的拾取表达方式。是否有人已经在任何编程语言中实现了这个算法?请分享可直接工作的代码,因为伪代码无法编译,通常会缺少一些部分。

2个回答

11

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

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

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

enter image description here

在以下示例中,我有一个对象列表,每个对象都具有位置和边界半径。交集当然永远不完全匹配,但现在还可以使用它。这不是伪代码,但它使用了我的自己的向量/矩阵库。您必须在某些地方进行替换。

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]

可以使用包围盒或其他隐式几何图形,或相交网格的三角形(这可能需要为性能原因构建kd树),而不是使用intersectSphere

[编辑]
这里是基于上面链接的一条线段/球体相交实现。它假设球体在原点,所以将from.xyz()作为p传递,给出 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;
}

这是一个很好的解释,谢谢您详细说明。我会尝试这种方法。请您能否也分享一下intersectSphere()函数的实现? - undefined
mat44 toWorld = (camera.projection * camera.transform).inverse();我不明白camera.projection和camera.transform矩阵是什么。你能解释一下或者在NinevehGL框架中写出等效的矩阵吗? - undefined
我找到了问题,Objective C 中指针的使用有点奇怪。我已经编辑了我的代码。但是 to 向量的 z 参数太高,而且 w 参数总是接近于1。所以在这种情况下,除以 w 不是一个解决办法。我的更新后的代码 这里是日志 - undefined
这里是投影矩阵视图矩阵的内容。 - undefined
当我将结果更新为0-4-8-12时,从变量到变量的值与触摸点相对稳定。(Z和W每次都相同,X和Y根据触摸坐标而变化) - undefined
显示剩余7条评论

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

我假设'from'是鼠标光标的位置,那么为什么它的z值是负一呢?如果我们假设使用的是OpenGL坐标系。
另外,按照这种方式,我们是否假设此时的深度范围是-1到+1呢?而不是我们视锥体的深度。

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