Come posso convertire un clic del mouse in un raggio?


18

Ho una proiezione prospettica. Quando l'utente fa clic sullo schermo, voglio calcolare il raggio tra i piani vicino e lontano che sporge dal punto del mouse, in modo da poter fare un po 'di codice di intersezione raggio con il mio mondo.

Sto usando le mie classi di matrici, vettori e raggi e funzionano tutti come previsto.

Tuttavia, quando provo a convertire il raggio in coordinate del mondo, il mio lontano finisce sempre come 0,0,0 e quindi il mio raggio va dal clic del mouse al centro dello spazio dell'oggetto, piuttosto che attraverso di esso. (Le coordinate xey del vicino e del lontano sono identiche, differiscono solo nelle coordinate z dove sono negative l'una dall'altra)

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

Cosa ho sbagliato? (Come si fa a non proiettare il punto del mouse?)


Solo per curiosità, c'è un motivo per cui non stai utilizzando la modalità GL_SELECTION integrata? (informazioni qui)
Ricket,

@Ricket Non è deprecato? Non lo so, ma abbastanza sicuro.
Notabene,

La xey in pixel? quello sarebbe il problema ...
Notabene,

E stai moltiplicando vec3 per matrix4. Non va bene ... quale numero va in 4a posizione quando fai vec_t (1a, 2a, 3a, 4a)?
Notabene,

@notebene Wow, immagino di si, non ne avevo idea. Questa discussione è buona e fornisce un'alternativa che dovrò esaminare, e ora sto DAVVERO sperando in una buona risposta a questa domanda.
Ricket,

Risposte:


14

Di recente ho dovuto risolverlo da solo per un'applicazione WebGL. Ho allegato il codice sorgente completo, ma nel caso in cui non funzioni subito, ecco alcuni suggerimenti per il debug:

  1. Non eseguire il debug del metodo non proiettato nel gioco. Se possibile, prova a scrivere test di stile unit test per facilitare l'isolamento di ciò che non va.
  2. Assicurati di stampare i raggi di output sia per i piani di ritaglio vicini che lontani.
  3. Ricorda che la matematica matriciale NON è commutativa. A x C! = C x A. Controlla la tua matematica.

Inoltre, per rispondere ad alcuni commenti sopra, non vuoi quasi mai usare le API di selezione di OpenGL. Questo ti aiuta a scegliere gli elementi esistenti, come se stessi creando un menu, tuttavia non riesce a eseguire la maggior parte degli scenari del mondo reale come la modifica dei modelli 3D. Dove è necessario aggiungere la geometria come risultato del clic.

Ecco la mia implementazione. Non c'è nulla di magico qui. Solo JavaScript e la libreria di chiusura di 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;
};

uso

  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 + ')');
  };
};

In che caso se (resultArr [3] == 0.0) viene valutato come vero?
Halsafar,

3

Non vedo alcun problema con il tuo codice. Quindi ho solo alcuni suggerimenti:

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;

E cercando di cambiare la multiplacation matrix in (p*mv).inverse();. Solo perché le glMatrici sono trasposte in memoria nutaraly. (questa è forse una grande, scusa)

Un altro raycasting
Unprojecting non è solo un modo per ottenere ray dal pixel. Questo è il mio codice per 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);

È possibile ottenere CameraRight, Up e Direction dalla matrice di visualizzazione (che è principalmente utile nello shader) Visualizza la matrice in modalità aperta

Selezione
colore La selezione colore è una tecnica in cui si esegue il rendering di ogni oggetto selezionabile con un altro colore (solido) e quindi si legge il valore del colore nel punto selezionato. Ha funzionato come GL_SELECTION in inglese. Ma ora è deprecato. Ma la selezione dei colori è facile da codificare come 1 passaggio di rendering aggiuntivo.

Usa il frame buffer e renderizza la trama con la stessa risoluzione dello schermo, usa un semplice shader che restituisce semplicemente il colore nel frammento di shader. Dopo "passaggio del colore", leggi il valore da un indirizzamento di trama con ClickedPoint x e y. Trova l'oggetto con il colore che leggi. Facile e abbastanza veloce.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.