web-dev-qa-db-fra.com

Mise en œuvre de Ray Picking

J'ai un moteur de rendu utilisant DirectX et OpenGL, ainsi qu'une scène 3D. La fenêtre et la fenêtre ont les mêmes dimensions.

Comment implémenter la sélection avec les coordonnées de souris x et y de manière indépendante de la plate-forme?

31
Tom J Nowell

Si vous le pouvez, faites le choix sur le processeur en calculant un rayon de l'œil à l'aide du pointeur de la souris et en le croisant avec vos modèles.

Si ce n'est pas une option, je choisirais un type de rendu d'identifiant. Attribuez à chaque objet pour lequel vous souhaitez sélectionner une couleur unique, restituez les objets avec ces couleurs et lisez la couleur dans le framebuffer situé sous le pointeur de la souris.

EDIT: Si la question est de savoir comment construire le rayon à partir des coordonnées de la souris, vous avez besoin des éléments suivants: une matrice de projectionPet la transformation de caméraC. Si les coordonnées du pointeur de la souris sont (x, y) et que la taille de la fenêtre d'affichage est (largeur, hauteur) une position dans l'espace de clip le long du rayon est:

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

(Remarquez que j'ai inversé l'axe des y, car l'origine des coordonnées de la souris se trouve souvent dans le coin supérieur gauche)

Ce qui suit est également vrai:

mouse_clip = P * C * mouse_worldspace

Qui donne:

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

Nous avons maintenant:

p = C.position(); //Origin of camera in worldspace
n = normalize(mouse_worldspace - p); //unit vector from p through mouse pos in worldspace
29
Andreas Brinck

Voici le frustum de visualisation:

viewing frustum

Vous devez d’abord déterminer l’emplacement du clic de souris sur le plan rapproché:

  1. redimensionnez les coordonnées de la fenêtre (0..640,0..480) à [-1,1], avec (-1, -1) dans le coin inférieur gauche et (1,1) dans le coin supérieur droit.
  2. 'annulez' la projection en multipliant les coordonnées mises à l'échelle par ce que j'appelle la matrice 'unview': unview = (P * M).inverse() = M.inverse() * P.inverse(), où M est la matrice ModelView et P est la matrice de projection.

Déterminez ensuite où se trouve la caméra dans le monde et tracez un rayon partant de la caméra et passant par le point que vous avez trouvé dans le plan rapproché.

La caméra se trouve à M.inverse().col(4), c’est-à-dire la dernière colonne de la matrice ModelView inverse.

Pseudocode 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
23
nornagon

J'ai peu d'expérience de DirectX, mais je suis sûr que cela ressemble à OpenGL. Ce que vous voulez, c'est l'appel gluUnproject.

En supposant que vous ayez un tampon Z valide, vous pouvez interroger le contenu du tampon Z à une position de souris avec:

// 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 vous ne voulez pas utiliser glu, vous pouvez également implémenter gluUnProject. Vous pouvez également le faire vous-même. Ses fonctionnalités sont relativement simples et sont décrites à l'adresse suivante: opengl.org

1
wich

Eh bien, assez simple, la théorie derrière cela est toujours la même

1) Déplacez deux fois votre coordonnée 2D sur l’espace 3D. (chaque API a sa propre fonction, mais vous pouvez implémenter la vôtre si vous le souhaitez). Un à Min Z, un à Max Z.

2) Avec ces deux valeurs, calculez le vecteur qui va de Min Z et pointez sur Max Z.

3) Avec le vecteur et un point, calculer le rayon allant de Min Z à MaxZ

4) Maintenant vous avez un rayon, avec ceci vous pouvez faire une intersection rayon-plan/rayon-plan/quelque chose et obtenir votre résultat ...

1
feal87

Je suis dans la même situation avec la sélection de rayons ordinaire, mais quelque chose ne va pas. J'ai effectué l'opération sans projet de la bonne manière, mais cela ne fonctionne tout simplement pas. Je pense que j'ai commis une erreur, mais je ne peux pas savoir où. Mes multiplications matix, inverse et vecteur par matix fonctionnent toutes bien, je les ai testées. Dans mon code, je réagis sur WM_LBUTTONDOWN. Donc, lParam renvoie les coordonnées [Y] [X] sous la forme de 2 mots dans un mot. Je les extrait, puis convertis en espace normalisé, j'ai vérifié que cette partie fonctionnait également bien. Lorsque je clique sur le coin inférieur gauche, j'obtiens des valeurs proches de -1 -1 et de bonnes valeurs pour les 3 autres coins. J'utilise ensuite le tableau linepoins.vtx pour le débogage et ce n'est même pas proche de la réalité. 

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;
0
Antiusninja

Ok, ce sujet est vieux mais c’est le meilleur que j’ai trouvé sur le sujet, et cela m’a un peu aidé, alors je posterai ici pour ceux qui suivent ;-)

C'est ainsi que je l'ai fait fonctionner sans avoir à calculer l'inverse de la matrice de projection:

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

Cela suppose une projection en perspective, mais la question ne se pose jamais pour l'orthographe.

0
nulleight