我有一个使用DirectX和OpenGL的渲染器,以及一个3D场景。视口和窗口具有相同的尺寸。
如何以平台无关的方式实现拾取(picking),并给定鼠标坐标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
mouse_clip = [float(x) * 2 / float(width) - 1, 1 - float(y) * 2 / float(height), -1 * near_clipping_plane, 1]
- Alex WmouseWorldSpace
应用了透视变换,因此它的w
坐标应该被除以自身的值吗? - bwroga以下是视椎体:
首先,您需要确定鼠标单击事件在近平面的哪个位置发生:
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
gl_Position = projection * modelView * vertexPos;
这样的行,那么中间的部分就是projection
矩阵,它是从相机到视口坐标的转换。希望对你有所帮助 :/ - nornagon很简单,其背后的理论总是相同的。
1)将您的2D坐标两次映射到3D空间中(每个API都有自己的函数,但如果您想要的话,可以自己实现)。在Min Z和Max Z处分别进行。
2)使用这两个值计算从Min Z点到Max Z点的向量。
3)使用向量和一个点计算从Min Z到MaxZ的光线。
4)现在你有了一条光线,使用它可以进行射线三角形/射线平面/射线某物交叉并获得结果...
我对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上有详细描述。
好的,这个主题很旧,但它是我在这个主题上找到的最好的,而且它帮助了我一点,所以我会在这里发布给那些正在关注它的人们 ;-)
这是我让它工作的方式,而不必计算投影矩阵的逆:
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;
}
这假设了一个透视投影,但问题从一开始就不会出现在正交投影中。
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;