实施Ray Picking

时间:2010-01-19 11:34:18

标签: math opengl geometry directx picking

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

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

6 个答案:

答案 0 :(得分:29)

如果可以,可以通过鼠标指针计算眼睛的光线并与模型相交来对CPU进行拾取。

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

编辑:如果问题是如何从鼠标坐标构建光线,则需要以下内容:投影矩阵 P 和相机变换 C < / strong>即可。如果鼠标指针的坐标是(x,y),并且视口的大小是(宽度,高度)沿着光线的剪辑空间中的一个位置是:< / p>

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 :(得分:24)

以下是观看视锥体:

viewing frustum

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

  1. 将窗口坐标(0..640,0..480)重新缩放为[-1,1],左下角为(-1,-1),顶部为(1,1)右。
  2. 通过将缩放后的坐标乘以我称之为“unview”矩阵来“撤消”投影:unview = (P * M).inverse() = M.inverse() * P.inverse(),其中M是ModelView矩阵,P是投影矩阵。
  3. 然后确定相机在世界空间中的位置,并从相机开始绘制光线并穿过您在近平面上找到的点。

    相机位于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
    

答案 2 :(得分:1)

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

1)将2D坐标的两次取消投影到3D空间。 (每个API都有自己的功能,但如果需要,可以实现自己的功能)。一个在Min Z,一个在Max Z。

2)使用这两个值计算从Min Z开始并指向Max Z的矢量。

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

4)现在你有一条光线,你可以做一个光线三角形/光线平面/光线的交叉点来获得你的结果...

答案 3 :(得分:1)

我的DirectX经验很少,但我确信它与OpenGL类似。你想要的是gluUnproject调用。

假设您有一个有效的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,您也可以自己实现它,它的功能相对简单,并在opengl.org

中描述

答案 4 :(得分:0)

好的,这个主题很老但是我在这个主题上发现的最好,这对我有所帮助,所以我会在这里发帖给那些正在关注的人; - )

这是我在不必计算投影矩阵的逆的情况下使其工作的方式:

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;
}

这假定是透视投影,但首先不会出现正交投影的问题。

答案 5 :(得分:0)

普通射线拾取我的情况与此相同,但出了点问题。我以正确的方式执行了unproject操作,但它不起作用。我想,我犯了一些错误,但无法弄清楚在哪里。通过matix乘法我的matix乘法,逆和向量都可以正常工作,我已经测试了它们。 在我的代码中,我正在对WM_LBUTTONDOWN做出反应。所以lParam在[d]中将[Y] [X]坐标返回为2个单词。我提取它们,然后转换为规范化空间,我检查过这部分也工作正常。当我点击左下角时 - 我得到接近的值-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;