Bagaimana saya bisa mengonversi klik mouse menjadi sinar?

18

Saya memiliki proyeksi perspektif. Ketika pengguna mengklik pada layar, saya ingin menghitung sinar antara pesawat dekat dan jauh yang diproyeksikan dari titik mouse, sehingga saya dapat melakukan beberapa kode persimpangan sinar dengan dunia saya.

Saya menggunakan matriks dan vektor dan kelas ray saya sendiri dan mereka semua bekerja seperti yang diharapkan.

Namun, ketika saya mencoba dan mengkonversi sinar ke koordinat dunia saya jauh selalu berakhir sebagai 0,0,0 dan sinar saya pergi dari klik mouse ke pusat ruang objek, daripada melewatinya. (Koordinat x dan y dari dekat dan jauh identik, mereka hanya berbeda dalam koordinat z di mana mereka negatif satu sama lain)

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

Apa yang salah? (Bagaimana Anda tidak memproyeksikan titik mouse?)

Akan
sumber
Karena penasaran, apakah ada alasan Anda tidak menggunakan mode GL_SELECTION bawaan? (info di sini)
Ricket
@Ricket Bukankah sudah usang? Tidak tahu, tapi cukup yakin.
Notabene
Apakah x dan y dalam piksel? itu akan menjadi masalah ...
Notabene
Dan Anda mengalikan vec3 dengan matrix4. Itu tidak baik ... nomor berapa yang masuk ke posisi 4 ketika Anda melakukan vec_t (1, 2, 3, 4)?
Notabene
@ notebene Wow, kurasa begitu, aku tidak tahu. Diskusi ini bagus dan memberikan alternatif yang harus saya perhatikan, dan sekarang saya BENAR-BENAR mengharapkan jawaban yang baik untuk pertanyaan ini.
Ricket

Jawaban:

14

Saya baru-baru ini harus menyelesaikan ini sendiri untuk aplikasi WebGL. Saya telah melampirkan kode sumber lengkap, tetapi memetikannya tidak berfungsi langsung untuk Anda di sini adalah beberapa kiat debug:

  1. Jangan men-debug metode unproject Anda di game Anda. Jika memungkinkan, cobalah menulis tes gaya unit-test untuk membuatnya lebih mudah untuk mengisolasi apa yang salah.
  2. Pastikan untuk mencetak sinar keluaran untuk pesawat kliping dekat dan jauh.
  3. Ingat bahwa matematika matriks BUKAN komutatif. A x C! = C x A. Periksa matematika Anda.

Juga, untuk membalas beberapa komentar di atas, Anda hampir tidak pernah ingin menggunakan API pilihan OpenGL. Itu membantu Anda memilih item yang ada, seperti jika Anda membuat menu, namun gagal untuk melakukan sebagian besar skenario dunia nyata seperti mengedit model 3D. Di mana Anda perlu menambahkan geometri sebagai hasil dari klik.

Ini implementasi saya. Tidak ada keajaiban yang terjadi di sini. Hanya JavaScript dan pustaka Penutupan 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;
};

Pemakaian

  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
sumber
Dalam kasus apa jika (resultArr [3] == 0,0) bernilai true?
Halsafar
3

Saya tidak melihat masalah dengan kode Anda. Jadi saya hanya punya beberapa saran:

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;

Dan berusaha mengubah multiplacation matriks menjadi (p*mv).inverse();. Hanya karena glMatrices adalah nutaraly dalam memori. (ini salah satu besar mungkin, maaf)

Raycasting lain Memproyeksikan
bukan hanya cara bagaimana mendapatkan sinar dari piksel. Ini adalah kode saya untuk 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);

Anda bisa mendapatkan cameraRight, Up dan Direction dari view matrix (yang sebagian besar berguna dalam shader) Lihat matrix di opengl

Color Picking
Color picking adalah teknik di mana Anda membuat setiap objek yang dapat diklik dengan warna lain (solid) dan kemudian membaca nilai warna pada titik yang diklik. Ini berfungsi sebagai GL_SELECTION di opengl. Tetapi sekarang sudah ditinggalkan. Tetapi pemilihan warna mudah dikodekan sebagai 1 render pass tambahan.

Gunakan penyangga bingkai dan render ke tekstur dengan resolusi yang sama dengan resolusi layar, gunakan shader sederhana yang hanya mengembalikan warna dalam fragmen shader. Setelah "color pass", baca nilai dari pengalamatan tekstur dengan clickedPoint x dan y. Temukan objek dengan warna yang Anda baca. Mudah dan cukup cepat.

Notabene
sumber