Как я могу преобразовать щелчок мыши в луч?

18

У меня есть перспективная проекция. Когда пользователь нажимает на экран, я хочу вычислить луч между ближней и дальней плоскостями, которые проецируются из точки мыши, чтобы я мог сделать некоторый код пересечения лучей с моим миром.

Я использую свои собственные классы матриц, векторов и лучей, и все они работают как положено.

Однако, когда я пытаюсь преобразовать луч в мировые координаты, мой дальний всегда заканчивается как 0,0,0, и поэтому мой луч проходит от щелчка мыши к центру пространства объекта, а не через него. (Координаты x и y ближнего и дальнего координат идентичны, они отличаются только координатами z, где они отрицательны друг от друга)

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

Что я не так понял? (Как вы снимаете проекцию мышки?)

Будет
источник
Просто из любопытства, есть ли причина, по которой вы не используете встроенный режим GL_SELECTION? (информация здесь)
Ricket
@Ricket Разве это не устарело? Не знаю, но вполне уверен.
Notabene
Х и у в пикселях? это было бы проблемой ...
Notabene
И вы умножаете vec3 на matrix4. Это не хорошо ... какое число входит в 4-ю позицию, когда вы делаете vec_t (1-й, 2-й, 3-й, 4-й)?
Notabene
@notebene Ух ты, наверное, я понятия не имел. Эта дискуссия хороша и предоставляет альтернативу, которую мне придется рассмотреть, и теперь я ДЕЙСТВИТЕЛЬНО надеюсь на хороший ответ на этот вопрос.
Ricket

Ответы:

14

Я недавно должен был решить это сам для приложения WebGL. Я приложил полный исходный код, но если он не работает сразу, вот несколько советов по отладке:

  1. Не отлаживайте свой непроектный метод в своей игре. Если возможно, попробуйте написать тесты стиля модульных тестов, чтобы было легче определить, что происходит не так.
  2. Обязательно распечатайте выходные лучи для ближней и дальней плоскостей отсечения.
  3. Помните, что матричная математика НЕ ​​коммутативна. A x C! = C x A. Дважды проверьте свою математику.

Кроме того, чтобы ответить на некоторые комментарии выше, вы почти никогда не хотите использовать API выбора OpenGL. Это помогает вам выбирать существующие элементы, как, например, если вы создавали меню, однако не может выполнять большинство реальных сценариев, таких как редактирование 3D-моделей. Где вам нужно добавить геометрию в результате клика.

Вот моя реализация. Здесь нет ничего волшебного. Просто JavaScript и библиотека Google Closure.

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

использование

  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 + ')');
  };
};
Крис Смит
источник
В каком случае, если (resultArr [3] == 0.0) имеет значение true?
Халсафар
3

Я не вижу никаких проблем с вашим кодом. Так что у меня есть только несколько предложений:

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;

И пытается изменить матрицу умножения на (p*mv).inverse();. Просто потому, что glMatrices полностью перенесены в память. (это один большой, может быть, извините)

Еще одна
лучевая передача Unprojecting - это не только способ получить луч от пикселя. Это мой код для 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);

Вы можете получить cameraRight, Up и Direction из матрицы просмотра (что наиболее полезно в шейдерах). Просмотр матрицы в opengl

Выбор цвета Выбор
цвета - это метод, при котором каждый объект, на который можно щелкнуть, отображается другим (сплошным) цветом, а затем считывается значение цвета в точке нажатия. GL_SELECTION это работало в opengl. Но это устарело сейчас. Но выбор цвета легко закодировать как 1 дополнительный проход рендеринга.

Используйте буфер кадров и визуализируйте в текстуру с тем же разрешением, что и разрешение экрана, используйте простой шейдер, который просто возвращает цвет во фрагментном шейдере. После «прохода цвета» прочитайте значение из адресации текстуры с помощью clickedPoint x и y. Найдите объект с цветом, который вы прочитали. Легко и довольно быстро.

NotaBene
источник