math - Implementando Ray Picking
opengl geometry (6)
Tengo un renderizador usando directx y openGL, y una escena 3d. La ventana gráfica y la ventana tienen las mismas dimensiones.
¿Cómo implemento la elección de las coordenadas del mouse xey en una plataforma independiente?
Aquí está el frustum de visualización:
Primero debe determinar en qué parte del plano cercano se produjo el clic del mouse:
- cambie la escala de las coordenadas de la ventana (0..640,0..480) a [-1,1], con (-1, -1) en la esquina inferior izquierda y (1,1) en la esquina superior derecha.
- ''deshacer'' la proyección multiplicando las coordenadas escaladas por lo que yo llamo matriz ''no vista'': vista
unview = (P * M).inverse() = M.inverse() * P.inverse()
, dondeM
es la matriz ModelView yP
es la matriz de proyección.
Luego, determine dónde está la cámara en el espacio mundial y dibuje un rayo que comience en la cámara y pase por el punto que encontró en el plano cercano.
La cámara está en M.inverse().col(4)
, es decir, la columna final de la matriz inversa ModelView.
Pseudocódigo final:
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
Bueno, bastante simple, la teoría detrás de esto es siempre la misma
1) Desproyecto dos veces su coordenada 2D en el espacio 3D. (cada API tiene su propia función, pero puede implementar la suya si lo desea). Uno en Min Z, uno en Max Z.
2) Con estos dos valores, calcule el vector que va desde Min Z y apunte a Max Z.
3) Con el vector y un punto, calcule el rayo que va de Min Z a MaxZ
4) Ahora tienes un rayo, con esto puedes hacer una intersección triángulo-rayo / plano-rayo / rayo-algo y obtener tu resultado ...
Ok, este tema es antiguo, pero fue lo mejor que encontré sobre el tema, y me ayudó un poco, así que publicaré aquí para aquellos que están siguiendo ;-)
Esta es la forma en que lo hice funcionar sin tener que calcular el inverso de la matriz de proyección:
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;
}
Esto supone una proyección de perspectiva, pero la pregunta nunca surge para el ortográfico en primer lugar.
Si puede, haga la selección en la CPU calculando un rayo desde el ojo a través del puntero del mouse e interseccione con sus modelos.
Si esto no es una opción, iría con algún tipo de representación de ID. Asigne a cada objeto que desea elegir un color único, renderice los objetos con estos colores y finalmente lea el color del framebuffer debajo del puntero del mouse.
EDITAR: Si la pregunta es cómo construir el rayo a partir de las coordenadas del ratón, necesita lo siguiente: una matriz de proyección P y la cámara transformada C. Si las coordenadas del puntero del mouse son (x, y) y el tamaño de la ventana es (ancho, alto) una posición en el espacio del clip a lo largo del rayo es:
mouse_clip = [
float(x) * 2 / float(width) - 1,
1 - float(y) * 2 / float(height),
0,
1]
(Tenga en cuenta que volteé el eje Y ya que a menudo el origen de las coordenadas del mouse está en la esquina superior izquierda)
Lo siguiente también es cierto:
mouse_clip = P * C * mouse_worldspace
Lo que da:
mouse_worldspace = inverse(C) * inverse(P) * mouse_clip
Ahora tenemos:
p = C.position(); //origin of camera in worldspace
n = normalize(mouse_worldspace - p); //unit vector from p through mouse pos in worldspace
Tengo la misma situación con el ray picking ordinario, pero algo está mal. Realicé la operación sin proyecto de la manera correcta, pero simplemente no funciona. Creo que cometí un error, pero no sé dónde. Mi multiplicación matix, inversa y vectorial por multiplicaciones matix todo funciona bien, los he probado. En mi código, estoy reaccionando en WM_LBUTTONDOWN. Entonces lParam devuelve las coordenadas [Y] [X] como 2 palabras en un dword. Los extraigo, luego los convierto en espacio normalizado, he comprobado que esta parte también funciona bien. Cuando hago clic en la esquina inferior izquierda, obtengo valores cercanos a -1 -1 y buenos valores para las otras 3 esquinas. Estoy usando linepoins.vtx array para depuración y ni siquiera está cerca de la realidad.
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;
Tengo poca experiencia con DirectX, pero estoy seguro de que es similar a OpenGL. Lo que quieres es la llamada al proyecto gluUn.
Suponiendo que tiene un búfer Z válido, puede consultar el contenido del búfer Z en la posición del mouse con:
// 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);
si no quieres usar glu, también puedes implementar el gluUnProject, también puedes implementarlo tú mismo, su funcionalidad es relativamente simple y se describe en opengl.org