代码之家  ›  专栏  ›  技术社区  ›  Tom J Nowell

实现光线拾取

  •  31
  • Tom J Nowell  · 技术社区  · 15 年前

    我有一个使用directx和opengl的渲染器,还有一个3d场景。视窗和视窗的尺寸相同。

    如何以独立于平台的方式选择给定的鼠标坐标x和y?

    6 回复  |  直到 8 年前
        1
  •  29
  •   Peter O. Manuel Pinto    10 年前

    如果可以的话,可以通过鼠标指针计算眼睛的光线并将其与模型相交,从而在CPU上进行拾取。

    如果这不是一个选项,我会去一些类型的ID渲染。为每个要拾取的对象指定一种唯一颜色,用这些颜色渲染对象,最后从鼠标指针下的帧缓冲区中读取颜色。

    编辑: 如果问题是如何从鼠标坐标构造光线,则需要以下内容:投影矩阵 以及摄像机变换 C . 如果鼠标指针的坐标是 (x,y) 视窗的大小是 (宽、高) 剪辑空间中沿光线的一个位置是:

    mouse_clip = [
      float(x) * 2 / float(width) - 1,
      1 - float(y) * 2 / float(height),
      0,
      1]
    

    (注意,我翻转了y轴,因为鼠标坐标的原点通常位于左上角)

    以下也是正确的:

    mouse_clip = P * C * mouse_worldspace
    

    它给出:

    mouse_worldspace = inverse(C) * inverse(P) * mouse_clip
    

    我们现在有:

    p = C.position(); //origin of camera in worldspace
    n = normalize(mouse_worldspace - p); //unit vector from p through mouse pos in worldspace
    
        2
  •  23
  •   nornagon    13 年前

    这是观景台:

    viewing frustum

    首先,您需要确定鼠标单击在近平面上的位置:

    1. 将窗口坐标(0..640,0..480)重新缩放到[-1,1],左下角为(-1,-1),右上角为(1,1)。
    2. 通过将缩放坐标乘以我所称的“unview”矩阵来“撤消”投影: unview = (P * M).inverse() = M.inverse() * P.inverse() ,其中 M 是模型视图矩阵和 P 是投影矩阵。

    然后确定相机在世界空间中的位置,并绘制一条光线,从相机开始,穿过在近平面上找到的点。

    摄像机在 M.inverse().col(4) ,即逆模型视图矩阵的最后一列。

    最终伪码:

    normalised_x = 2 * mouse_x / win_width - 1
    normalised_y = 1 - 2 * mouse_y / win_height
    // note the y pos is inverted, so +y is at the top of the screen
    
    unviewMat = (projectionMat * modelViewMat).inverse()
    
    near_point = unviewMat * Vec(normalised_x, normalised_y, 0, 1)
    camera_pos = ray_origin = modelViewMat.inverse().col(4)
    ray_dir = near_point - camera_pos
    
        3
  •  1
  •   feal87    15 年前

    很简单,这背后的理论总是一样的

    1)将二维坐标的两倍取消投影到三维空间上。(每个api都有自己的函数,但如果需要,可以实现自己的函数)。一个在最小z,一个在最大z。

    2)利用这两个值计算从最小z到最大z的向量。

    3)用矢量和一个点计算从最小z到最大z的光线

    4)现在你有一条射线,用它你可以做一个射线三角形/射线平面/射线某物的交点,得到你的结果…

        4
  •  1
  •   wich    15 年前

    我很少有directx的经验,但我确信它与opengl类似。你想要的是一个令人沮丧的电话。

    假设您有一个有效的z缓冲区,您可以使用以下命令在鼠标位置查询z缓冲区的内容:

    // obtain the viewport, modelview matrix and projection matrix
    // you may keep the viewport and projection matrices throughout the program if you don't change them
    GLint viewport[4];
    GLdouble modelview[16];
    GLdouble projection[16];
    glGetIntegerv(GL_VIEWPORT, viewport);
    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
    glGetDoublev(GL_PROJECTION_MATRIX, projection);
    
    // obtain the Z position (not world coordinates but in range 0 - 1)
    GLfloat z_cursor;
    glReadPixels(x_cursor, y_cursor, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &z_cursor);
    
    // obtain the world coordinates
    GLdouble x, y, z;
    gluUnProject(x_cursor, y_cursor, z_cursor, modelview, projection, viewport, &x, &y, &z);
    

    如果不想使用glu,还可以实现glunproject,也可以自己实现,它的功能相对简单,在 opengl.org

        5
  •  0
  •   Tom J Nowell    13 年前

    好吧,这个话题已经过时了,但它是我在这个话题上找到的最好的一个,它对我有点帮助,所以我将在这里为下面的人发帖;-)

    这就是我不需要计算投影矩阵逆的方法:

    void Application::leftButtonPress(u32 x, u32 y){
        GL::Viewport vp = GL::getViewport(); // just a call to glGet GL_VIEWPORT
    vec3f p = vec3f::from(                        
            ((float)(vp.width - x) / (float)vp.width),
            ((float)y / (float)vp.height),
                1.);
        // alternatively vec3f p = vec3f::from(                        
        //      ((float)x / (float)vp.width),
        //      ((float)(vp.height - y) / (float)vp.height),
        //      1.);
    
        p *= vec3f::from(APP_FRUSTUM_WIDTH, APP_FRUSTUM_HEIGHT, 1.);
        p += vec3f::from(APP_FRUSTUM_LEFT, APP_FRUSTUM_BOTTOM, 0.);
    
        // now p elements are in (-1, 1)
        vec3f near = p * vec3f::from(APP_FRUSTUM_NEAR);
        vec3f far = p * vec3f::from(APP_FRUSTUM_FAR);
    
        // ray in world coordinates
        Ray ray = { _camera->getPos(), -(_camera->getBasis() * (far - near).normalize()) };
    
        _ray->set(ray.origin, ray.dir, 10000.); // this is a debugging vertex array to see the Ray on screen
    
        Node* node = _scene->collide(ray, Transform());
       cout << "node is : " << node << endl;
    }
    

    这假设是透视投影,但问题从一开始就不会出现在正交投影上。

        6
  •  0
  •   Sergey    8 年前

    我和普通的射线采摘也有同样的情况,但是出了点问题。我已经用正确的方法做了这项不受欢迎的手术,但它就是不起作用。我想,我犯了一些错误,但不知道在哪里。我的矩阵乘法,逆矩阵和向量的矩阵乘法都很好,我已经测试过了。 在我的代码中,我对wm lbuttondown做出了反应。因此lparam在一个双字中返回[y][x]坐标。我提取它们,然后转换为规范化空间,我已经检查过这个部分也可以正常工作。当我点击左下角时-我得到接近-1-1的值,其他3个角的值都很好。然后我使用linepoins.vtx数组进行调试,它甚至不接近实际。

    unsigned int x_coord=lParam&0x0000ffff; //X RAW COORD
    unsigned int y_coord=client_area.bottom-(lParam>>16); //Y RAW COORD
    
    double xn=((double)x_coord/client_area.right)*2-1; //X [-1 +1]
    double yn=1-((double)y_coord/client_area.bottom)*2;//Y [-1 +1]
    
    _declspec(align(16))gl_vec4 pt_eye(xn,yn,0.0,1.0); 
    gl_mat4 view_matrix_inversed;
    gl_mat4 projection_matrix_inversed;
    cam.matrixProjection.inverse(&projection_matrix_inversed);
    cam.matrixView.inverse(&view_matrix_inversed);
    
    gl_mat4::vec4_multiply_by_matrix4(&pt_eye,&projection_matrix_inversed);
    gl_mat4::vec4_multiply_by_matrix4(&pt_eye,&view_matrix_inversed);
    
    line_points.vtx[line_points.count*4]=pt_eye.x-cam.pos.x;
    line_points.vtx[line_points.count*4+1]=pt_eye.y-cam.pos.y;
    line_points.vtx[line_points.count*4+2]=pt_eye.z-cam.pos.z;
    line_points.vtx[line_points.count*4+3]=1.0;