Wie kann ich einen Mausklick in einen Strahl umwandeln?

18

Ich habe eine perspektivische Projektion. Wenn der Benutzer auf den Bildschirm klickt, möchte ich den Strahl zwischen der nahen und der fernen Ebene berechnen, der vom Mauspunkt aus projiziert wird, damit ich einen Strahlschnittcode mit meiner Welt erstellen kann.

Ich benutze meine eigenen Matrix-, Vektor- und Strahlenklassen und sie funktionieren alle wie erwartet.

Wenn ich jedoch versuche, den Strahl in Weltkoordinaten umzuwandeln, ergibt sich für meinen Abstand immer ein Wert von 0,0,0, und mein Strahl wandert vom Mausklick zur Mitte des Objektraums und nicht durch diesen. (Die x- und y-Koordinaten von nah und fern sind identisch, sie unterscheiden sich nur in den z-Koordinaten, in denen sie negativ sind.)

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

Was habe ich falsch gemacht? (Wie können Sie den Mauszeiger aus der Projektion entfernen?)

Wille
quelle
Gibt es einen Grund, warum Sie aus Neugier den integrierten GL_SELECTION-Modus nicht verwenden? (Infos hier)
Ricket
@Ricket Ist es nicht veraltet? Weiß nicht, aber ganz sicher.
Notabene
Sind die x und y in Pixel? Das wäre das Problem ...
Notabene
Und Sie multiplizieren vec3 mit matrix4. Das ist nicht gut ... welche Zahl geht in die 4. Position, wenn Sie vec_t (1., 2., 3., 4.)?
Notabene
@notebene Wow, ich denke es ist, ich hatte keine Ahnung. Diese Diskussion ist gut und bietet eine Alternative, die ich untersuchen muss, und jetzt hoffe ich WIRKLICH auf eine gute Antwort auf diese Frage.
Ricket

Antworten:

14

Ich musste das kürzlich selbst für eine WebGL-Anwendung lösen. Ich habe den vollständigen Quellcode angehängt, aber falls er nicht von Anfang an funktioniert, hier einige Tipps zum Debuggen:

  1. Debuggen Sie Ihre Unproject-Methode nicht in Ihrem Spiel. Wenn möglich, versuchen Sie, Komponententests zu schreiben, um das Erkennen von Fehlern zu vereinfachen.
  2. Stellen Sie sicher, dass die Ausgabestrahlen sowohl für die nahen als auch für die fernen Beschneidungsebenen gedruckt werden.
  3. Denken Sie daran, dass Matrixmathematik NICHT kommutativ ist. A x C! = C x A. Überprüfen Sie Ihre Mathematik.

Um auf einige der obigen Kommentare zu antworten, möchten Sie fast nie die Auswahl-APIs von OpenGL verwenden. Auf diese Weise können Sie vorhandene Elemente auswählen, z. B. wenn Sie ein Menü erstellt haben. In den meisten realen Szenarien wie der Bearbeitung von 3D-Modellen ist dies jedoch nicht möglich. Wo müssen Sie Geometrie als Ergebnis des Klickens hinzufügen.

Hier ist meine Implementierung. Hier ist nichts Magisches los. Nur JavaScript und Googles Closure-Bibliothek.

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

Verwendung

  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
quelle
In welchem ​​Fall ergibt if (resultArr [3] == 0.0) true?
Halsafar
3

Ich sehe keine Probleme mit Ihrem Code. Also ich habe nur ein paar Vorschläge:

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;

Und versuchen, Matrixmultiplazierung zu ändern (p*mv).inverse();. Nur weil glMatrices normalerweise im Speicher transponiert werden. (Dies ist eine große vielleicht, sorry)

Ein weiteres Raycasting Beim
Entprojektieren geht es nicht nur darum, wie Sie einen Strahl aus dem Pixel erhalten. Das ist mein Code für 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);

Sie können cameraRight, Up und Direction aus der View-Matrix (was vor allem im Shader nützlich ist) abrufen. View-Matrix in opengl

Kommissionierung Farbe
Farbe Kommissionierung ist eine Technik , wo Sie mit einer anderen (fest) Farbe jedes anklickbare Objekt machen und dann den Wert der Farbe bei klickten Punkt lesen. Es hat als GL_SELECTION in opengl funktioniert. Aber es ist jetzt veraltet. Die Farbauswahl ist jedoch einfach als 1 zusätzlicher Renderdurchgang zu codieren.

Verwenden Sie den Framebuffer und rendern Sie die Textur mit der gleichen Auflösung wie die Bildschirmauflösung. Verwenden Sie einen einfachen Shader, der nur die Farbe im Fragment-Shader zurückgibt. Lesen Sie nach "Color Pass" den Wert aus einer Textur-Adressierung mit clickedPoint x und y. Finden Sie ein Objekt mit der Farbe, die Sie lesen. Einfach und ziemlich schnell.

Notabene
quelle