Math 实现光线拾取

Math 实现光线拾取,math,opengl,geometry,directx,picking,Math,Opengl,Geometry,Directx,Picking,我有一个使用directx和openGL的渲染器,还有一个3d场景。视口和窗口的尺寸相同 如何以独立于平台的方式拾取给定的鼠标坐标x和y?如果可以,在CPU上进行拾取,方法是通过鼠标指针计算眼睛的光线,并将其与模型相交 如果这不是一个选项,我会使用某种类型的ID渲染。为要拾取的每个对象指定唯一的颜色,使用这些颜色渲染对象,最后在鼠标指针下从帧缓冲区读取颜色 编辑:如果问题是如何从鼠标坐标构建光线,则需要以下内容:投影矩阵p和摄影机变换C。如果鼠标指针的坐标为(x,y),并且视口的大小为(宽度,高

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


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

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

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

编辑:如果问题是如何从鼠标坐标构建光线,则需要以下内容:投影矩阵p和摄影机变换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

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

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

2) 使用这两个值计算从Min Z到Max Z的向量

3) 使用向量和一个点计算从Min Z到MaxZ的光线


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

我对DirectX的经验不多,但我确信它与OpenGL类似。你要的是GlunProject呼叫

假设您有一个有效的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,您也可以实现gluUnProject,您也可以自己实现它,它的功能相对简单,如所述

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

  • 将窗口坐标(0..640,0..480)重新缩放为[-1,1],左下角为(-1,-1),右上角为(1,1)
  • 通过将缩放坐标乘以我所称的“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
    

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

    这就是我让它工作的方式,而不必计算投影矩阵的逆:

    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;
    }
    
    void应用程序::leftButtonPress(u32 x,u32 y){
    GL::Viewport vp=GL::getViewport();//只需调用glGet GL_Viewport
    vec3f p=vec3f::from(
    ((浮动)(vp.width-x)/(浮动)vp.width),
    ((浮动)y/(浮动)vp.高度),
    1.);
    //或者,vec3f p=vec3f::from(
    //((浮动)x/(浮动)vp.宽度),
    //((浮动)(vp.height-y)/(浮动)vp.height),
    //      1.);
    p*=vec3f::from(平截头体宽度,平截头体高度,1.);
    p+=vec3f::from(应用程序左截头体,应用程序下截头体,0);
    //现在p元素在(-1,1)中
    vec3f near=p*vec3f::from(APP_trustum_near);
    vec3f-far=p*vec3f::from(APP_-rustum_-far);
    //世界坐标中的光线
    光线={u-camera->getPos(),-(\u-camera->getBasis()*(远-近).normalize())};
    _光线->设置(ray.origin,ray.dir,10000.);//这是一个调试顶点数组,用于在屏幕上查看光线
    节点*节点=_场景->碰撞(光线,变换());
    
    cout我的情况与普通光线拾取相同,但有点不对劲。我以正确的方式执行了非投影操作,但它就是不起作用。我想,我犯了一些错误,但不知道在哪里。我的matix乘法、逆乘法和vector by matix乘法都可以正常工作,我已经对它们进行了测试。 在我的代码中,我对WM_LBUTTONDOWN做出了反应。因此lParam返回[Y][X]坐标为dword中的两个字。我提取它们,然后转换为规范化空间,我检查了这部分是否也可以正常工作。当我单击左下角时,我得到的值接近-1-1,其他三个角的值都很好。然后我使用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;
    

    @Tom,这个问题并不完全清楚。不管怎样,我已经编辑了我的答案,希望能对你有所帮助。值得注意的是,如果你使用类似DirectX的矩阵,那么乘法顺序是相反的。谢谢:)我一开始应该更清楚,但你编辑的答案正是我想要的!我不确定错误是在你的工作中还是在我的工作中,但我可以只有当我将近剪裁平面的负值用作鼠标剪裁的z坐标时,该算法才能工作。即:
    mouse\u clip=[float(x)*2/float(width)-1,1-float(y)*2/float(height),-1*近剪裁平面,1]
    只是为了省去其他人的麻烦:这种方法只有在鼠标剪辑中的3.坐标不是0而是-接近深度时才有效。此外,对于正交矩阵,p的计算方式必须不同。@Tom如我所说,如果你不想使用glu函数,你可以自己实现它的功能,那么你只需要得到modelv查看每个的视图和投影矩阵,并获得每个的z窗口位置