¿Cómo puedo convertir un clic del mouse en un rayo?

18

Tengo una proyección en perspectiva. Cuando el usuario hace clic en la pantalla, quiero calcular el rayo entre los planos cercano y lejano que se proyecta desde el punto del mouse, para poder hacer un código de intersección de rayos con mi mundo.

Estoy usando mi propia matriz y clases de vectores y rayos y todos funcionan como se esperaba.

Sin embargo, cuando trato de convertir el rayo en coordenadas mundiales, mi distancia siempre termina como 0,0,0 y, por lo tanto, mi rayo va desde el clic del mouse al centro del espacio del objeto, en lugar de a través de él. (Las coordenadas x e y de cerca y de lejos son idénticas, solo difieren en las coordenadas z donde son negativas entre sí)

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é me he equivocado? (¿Cómo se desinstala el punto del mouse?)

Será
fuente
Solo por curiosidad, ¿hay alguna razón por la que no estás usando el modo GL_SELECTION incorporado? (información aquí)
Ricket
@Ricket ¿No está en desuso? No lo sé, pero estoy seguro.
Notabene
¿Están las x e y en píxeles? ese sería el problema ...
Notabene
Y estás multiplicando vec3 por matrix4. Eso no es bueno ... ¿qué número entra en la 4ta posición cuando haces vec_t (1ra, 2da, 3ra, 4ta)?
Notabene
@notebene Wow, supongo que sí, no tenía idea. Esta discusión es buena y proporciona una alternativa que tendré que analizar, y ahora REALMENTE espero una buena respuesta a esta pregunta.
Ricket

Respuestas:

14

Recientemente tuve que resolver esto yo mismo para una aplicación WebGL. He adjuntado el código fuente completo, pero en caso de que no funcione de inmediato, aquí hay algunos consejos de depuración:

  1. No depures tu método de no proyecto en tu juego. Si es posible, intente escribir pruebas de estilo de prueba unitaria para que sea más fácil aislar lo que va mal.
  2. Asegúrese de imprimir los rayos de salida para los planos de recorte cercanos y lejanos.
  3. Recuerde que las matemáticas matriciales NO son conmutativas. A x C! = C x A. Revisa tus matemáticas.

Además, para responder a algunos de los comentarios anteriores, casi nunca desea utilizar las API de selección de OpenGL. Eso lo ayuda a elegir elementos existentes, como si estuviera creando un menú, sin embargo, no puede realizar la mayoría de los escenarios del mundo real, como la edición de modelos 3D. Donde necesita agregar geometría como resultado del clic.

Aquí está mi implementación. No hay nada mágico pasando aquí. Solo JavaScript y la biblioteca de cierre 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;
};

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 + ')');
  };
};
Chris Smith
fuente
¿En qué caso if (resultArr [3] == 0.0) se evalúa como verdadero?
Halsafar
3

No veo ningún problema con tu código. Entonces solo tengo algunas sugerencias:

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;

Y tratando de cambiar la multiplacación matricial a (p*mv).inverse();. Solo porque glMatrices se transponen nutaraly en la memoria. (este es uno grande tal vez, lo siento)

Otro
Raycasting Unprojecting no es solo la forma de obtener rayos del píxel. Este es mi código para 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);

Puede obtener cameraRight, Up y Direction desde view matrix (que es principalmente útil en shader) View matrix en opengl

Selección de
color La selección de color es una técnica en la que renderiza cada objeto en el que se puede hacer clic con otro color (sólido) y luego lee el valor del color en el punto en el que se hizo clic. Funcionó como GL_SELECTION en opengl. Pero ahora está en desuso. Pero la selección de color es fácil de codificar como 1 pase de renderizado adicional.

Use el búfer de marco y renderice para texturizar con la misma resolución que la resolución de pantalla, use un sombreador simple que solo devuelve el color en el sombreador de fragmentos. Después de "color pass", lea el valor de una dirección de textura con clickedPoint x e y. Encuentra el objeto con el color que lees. Fácil y bastante rápido.

Notabene
fuente