Comment puis-je convertir un clic de souris en rayon?

18

J'ai une projection en perspective. Lorsque l'utilisateur clique sur l'écran, je veux calculer le rayon entre les plans proche et lointain qui projette à partir du point de la souris, afin que je puisse faire du code d'intersection de rayons avec mon monde.

J'utilise mes propres classes de matrice et de vecteur et de rayon et elles fonctionnent toutes comme prévu.

Cependant, lorsque j'essaie de convertir le rayon en coordonnées universelles, mon extrême se termine toujours par 0,0,0 et mon rayon va donc du clic de la souris au centre de l'espace objet, plutôt qu'à travers lui. (Les coordonnées x et y de près et de loin sont identiques, elles ne diffèrent que par les coordonnées z où elles sont négatives l'une de l'autre)

GLint vp[4];
glGetIntegerv(GL_VIEWPORT,vp);
matrix_t mv, p;
glGetFloatv(GL_MODELVIEW_MATRIX,mv.f);
glGetFloatv(GL_PROJECTION_MATRIX,p.f);
const matrix_t inv = (mv*p).inverse();
const float
    unit_x = (2.0f*((float)(x-vp[0])/(vp[2]-vp[0])))-1.0f,
    unit_y = 1.0f-(2.0f*((float)(y-vp[1])/(vp[3]-vp[1])));
const vec_t near(vec_t(unit_x,unit_y,-1)*inv);
const vec_t far(vec_t(unit_x,unit_y,1)*inv);
ray = ray_t(near,far-near);

Qu'est-ce que je me suis trompé? (Comment pouvez-vous défaire le point de la souris?)

Volonté
la source
Par simple curiosité, y a-t-il une raison pour laquelle vous n'utilisez pas le mode GL_SELECTION intégré? (info ici)
Ricket
@Ricket n'est-il pas obsolète? Je ne sais pas, mais bien sûr.
Notabene
Les x et y sont-ils en pixels? ce serait le problème ...
Notabene
Et vous multipliez vec3 par matrix4. Ce n'est pas bon ... quel numéro passe en 4ème position quand on fait vec_t (1er, 2ème, 3ème, 4ème)?
Notabene
@notebene Wow, je suppose que oui, je n'avais aucune idée. Cette discussion est bonne et fournit une alternative que je devrai examiner, et maintenant j'espère VRAIMENT une bonne réponse à cette question.
Ricket

Réponses:

14

J'ai récemment dû résoudre ce problème moi-même pour une application WebGL. J'ai joint le code source complet, mais au cas où cela ne fonctionnerait pas immédiatement pour vous, voici quelques conseils de débogage:

  1. Ne déboguez pas votre méthode non projetée dans votre jeu. Si possible, essayez d'écrire des tests de style de test unitaire pour faciliter l'isolement de ce qui ne va pas.
  2. Assurez-vous d'imprimer les rayons de sortie pour les plans de détourage proche et éloigné.
  3. N'oubliez pas que les mathématiques matricielles ne sont PAS commutatives. A x C! = C x A. Revérifiez vos calculs.

De plus, pour répondre à certains commentaires ci-dessus, vous ne voulez presque jamais utiliser les API de sélection d'OpenGL. Cela vous aide à choisir des éléments existants, comme si vous créiez un menu, mais cela ne permet pas d'exécuter la plupart des scénarios du monde réel comme l'édition de modèles 3D. Où vous devez ajouter une géométrie à la suite du clic.

Voici ma mise en œuvre. Il n'y a rien de magique ici. Juste JavaScript et la bibliothèque de fermeture de Google.

gluUnProject

/**
 * Port of gluUnProject. Unprojects a 2D screen coordinate into the model-view
 * coordinates.
 * @param {Number} winX The window point for the x value.
 * @param {Number} winY The window point for the y value.
 * @param {Number} winZ The window point for the z value. This should range
 *    between 0 and 1. 0 meaning the near clipping plane and 1 for the far.
 * @param {goog.math.Matrix} modelViewMatrix The model-view matrix.
 * @param {goog.math.Matrix} projectMatrix The projection matrix.
 * @param {Array.<Number>} view the viewport coordinate array.
 * @param {Array.<Number>} objPos the model point result.
 * @return {Boolean} Whether or not the unprojection was successful.
 */
octorok.math.Matrix.gluUnProject = function(winX, winY, winZ,
                        modelViewMatrix, projectionMatrix,
                        viewPort, objPos) {
  // Compute the inverse of the perspective x model-view matrix.
  /** @type {goog.math.Matrix} */
  var transformMatrix =
    projectionMatrix.multiply(modelViewMatrix).getInverse();

  // Transformation of normalized coordinates (-1 to 1).
  /** @type {Array.<Number>} */
  var inVector = [
    (winX - viewPort[0]) / viewPort[2] * 2.0 - 1.0,
    (winY - viewPort[1]) / viewPort[3] * 2.0 - 1.0,
    2.0 * winZ - 1.0,
    1.0 ];

  // Now transform that vector into object coordinates.
  /** @type {goog.math.Matrix} */
  // Flip 1x4 to 4x1. (Alternately use different matrix ctor.
  var inMatrix = new goog.math.Matrix([ inVector ]).getTranspose();
  /** @type {goog.math.Matrix} */
  var resultMtx = transformMatrix.multiply(inMatrix);
  /** @type {Array.<Number>} */
  var resultArr = [
    resultMtx.getValueAt(0, 0),
    resultMtx.getValueAt(1, 0),
    resultMtx.getValueAt(2, 0),
    resultMtx.getValueAt(3, 0) ];

  if (resultArr[3] == 0.0) {
    return false;
  }

  // Invert to normalize x, y, and z values.
  resultArr[3] = 1.0 / resultArr[3];

  objPos[0] = resultArr[0] * resultArr[3];
  objPos[1] = resultArr[1] * resultArr[3];
  objPos[2] = resultArr[2] * resultArr[3];

  return true;
};

Usage

  this.sys.event_mouseClicked = function(event) {
    // Relative x and y are computed via magic by SystemModule.
    // Should range from 0 .. viewport width/height.
    var winX = event.relativeX;
    var winY = event.relativeY;
    window.console.log('Camera at [' + me.camera.position_ + ']');
    window.console.log('Clicked [' + winX + ', ' + winY + ']');

    // viewportOriginX, viewportOriginY, viewportWidth, viewportHeight
    var viewPort = [0, 0, event.viewPortWidth, event.viewPortHeight];
    var objPos = [];  // out parameter.

    // The camera's model-view matrix is the result of gluLookAt.
    var modelViewMatrix = me.camera.getCameraMatrix();
    // The perspective matrix is the result of gluPerspective.
    var perspectiveMatrix = pMatrix.get();

    // Ray start
    var result1 = octorok.math.Matrix.gluUnProject(
      winX, winY, 0.0,
      modelViewMatrix, perspectiveMatrix,
      viewPort, objPos);
    window.console.log('Seg start: ' + objPos + ' (result:' + result1 + ')');

    // Ray end
    var result2 = octorok.math.Matrix.gluUnProject(
      winX, winY, 1.0,
      modelViewMatrix, perspectiveMatrix,
      viewPort, objPos);
    window.console.log('Seg end: ' + objPos + ' (result:' + result2 + ')');
  };
};
Chris Smith
la source
Dans quel cas si (resultArr [3] == 0.0) est évalué à vrai?
Halsafar
3

Je ne vois aucun problème avec votre code. Je n'ai donc que quelques suggestions:

unit_x = (2.0f*((float)(x-vp[0])/(vp[2]-vp[0])))-1.0f,
unit_y = (2.0f*((float)(y-vp[1])/(vp[3]-vp[1])))-1.0f;

Et essayer de changer la multiplacation de la matrice en (p*mv).inverse();. Tout simplement parce que les glMatrices sont transposées en mémoire. (c'est un gros peut-être, désolé)

Un autre raycasting
Unprojecting n'est pas seulement un moyen d'obtenir un rayon à partir du pixel. Voici mon code pour raycaster:

//works for right handed coordinates. So it is opengl friendly.
float mx = (float)((pixel.x - screenResolution.x * 0.5) * (1.0 / screenResolution.x) * camera.fovX * 0.5);
float my = (float)((pixel.y - screenResolution.y * 0.5) * (1.0 / screenResolution.x) * camera.fovX * 0.5);
float4 dx = cameraRight * mx;
float4 dy = cameraUp * my;

float3 dir = normalize(cameraDir + (dx + dy).xyz * 2.0);

Vous pouvez obtenir cameraRight, Up et Direction à partir de la matrice de vue (ce qui est surtout utile dans le shader) Voir la matrice en opengl

Sélection de couleur La sélection de
couleur est une technique où vous effectuez le rendu de chaque objet cliquable avec une autre couleur (solide), puis lisez la valeur de la couleur au point cliqué. Cela a fonctionné comme GL_SELECTION en opengl. Mais il est désormais obsolète. Mais la sélection des couleurs est facile à coder en 1 passage de rendu supplémentaire.

Utilisez un tampon d'image et effectuez un rendu de texture avec la même résolution que la résolution d'écran, utilisez un simple shader qui renvoie simplement la couleur dans le fragment shader. Après "passage de couleur", lisez la valeur d'un adressage de texture avec clickedPoint x et y. Trouvez un objet avec une couleur que vous lisez. Facile et assez rapide.

Notabene
la source