Реализация выбора лучей

У меня есть средство визуализации, использующее directx и openGL, и трехмерную сцену. Окно просмотра и окно имеют одинаковые размеры.

Как реализовать выбор данных координат мыши x и y независимо от платформы?


person Tom J Nowell    schedule 19.01.2010    source источник
comment
Взгляните на это руководство, оно может быть полезным   -  person user3405291    schedule 26.06.2018


Ответы (6)


Если можете, выберите процессор, рассчитав луч, идущий от глаза через указатель мыши, и пересеките его с вашими моделями.

Если это не вариант, я бы выбрал какой-нибудь тип визуализации идентификатора. Назначьте каждому объекту, которому вы хотите выбрать уникальный цвет, визуализируйте объекты с этими цветами и, наконец, считайте цвет из фреймбуфера под указателем мыши.

РЕДАКТИРОВАТЬ: Если вопрос в том, как построить луч по координатам мыши, вам понадобится следующее: матрица проекции P и преобразование камеры C. . Если координаты указателя мыши - (x, y), а размер области просмотра - (ширина, высота), одна позиция в пространстве отсечения вдоль луча:

mouse_clip = [
  float(x) * 2 / float(width) - 1,
  1 - float(y) * 2 / float(height),
  0,
  1]

(Обратите внимание, что я перевернул ось Y, поскольку часто начало координат мыши находится в верхнем левом углу)

Верно и следующее:

mouse_clip = P * C * mouse_worldspace

Который дает:

mouse_worldspace = inverse(C) * inverse(P) * mouse_clip

Теперь у нас есть:

p = C.position(); //origin of camera in worldspace
n = normalize(mouse_worldspace - p); //unit vector from p through mouse pos in worldspace
person Andreas Brinck    schedule 19.01.2010
comment
@Tom Это не совсем ясно из вопроса. В любом случае, я отредактировал свой ответ, надеюсь, это поможет. - person Andreas Brinck; 19.01.2010
comment
Стоит отметить, что если вы используете матрицы, похожие на DirectX, то порядок умножения меняется на обратный. - person Goz; 20.01.2010
comment
спасибо :) Сначала я должен был быть яснее, но ваш отредактированный ответ - это то, что я хотел! - person Tom J Nowell; 21.01.2010
comment
Я не уверен, связана ли ошибка с вашей работой или моей, но я смог заставить этот алгоритм работать только тогда, когда я использовал отрицательное значение ближней плоскости отсечения для координаты z mouse_clip. IE: mouse_clip = [float(x) * 2 / float(width) - 1, 1 - float(y) * 2 / float(height), -1 * near_clipping_plane, 1] - person Alex W; 22.04.2012
comment
Просто чтобы избавить других от неприятностей: этот метод работает, только если 3. координата в mouse_clip не 0, а -near_depth. Кроме того, для ортогональной матрицы p необходимо вычислять иначе. - person Danvil; 08.12.2013
comment
Следует ли mouseWorldSpace делить по его собственной w координате, поскольку к ней применено преобразование перспективы? - person bwroga; 18.03.2018

Вот усеченная пирамида:

просмотр усеченной пирамиды

Для начала нужно определить, где на ближней плоскости произошел щелчок мышью:

  1. измените масштаб координат окна (0..640,0..480) на [-1,1], с (-1, -1) в нижнем левом углу и (1,1) в верхнем правом углу.
  2. «отменить» проекцию, умножив масштабированные координаты на то, что я называю матрицей «unview»: unview = (P * M).inverse() = M.inverse() * P.inverse(), где M - матрица ModelView, а P - матрица проекции.

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

Камера находится в M.inverse().col(4), то есть в последнем столбце обратной матрицы ModelView.

Конечный псевдокод:

normalised_x = 2 * mouse_x / win_width - 1
normalised_y = 1 - 2 * mouse_y / win_height
// note the y pos is inverted, so +y is at the top of the screen

unviewMat = (projectionMat * modelViewMat).inverse()

near_point = unviewMat * Vec(normalised_x, normalised_y, 0, 1)
camera_pos = ray_origin = modelViewMat.inverse().col(4)
ray_dir = near_point - camera_pos
person nornagon    schedule 07.06.2011
comment
что это за матрица modelView, о которой вы говорите? Это комбинация матрицы modelToWorld модели, которую мы пытаемся поразить, и viewMatrix камеры? - person Jubei; 06.11.2012
comment
Прошло много времени с тех пор, как я написал это, но я думаю, что это матрица, которая преобразует мировые координаты в координаты камеры. Если в вашем вершинном шейдере есть строка типа gl_Position = projection * modelView * vertexPos;, это бит посередине, где матрица projection - это перевод координат камеры в координаты окна просмотра. HTH: / - person nornagon; 06.11.2012

Что ж, довольно просто, теория, лежащая в основе этого, всегда одна и та же.

1) Отмените проецирование двумерных координат на трехмерное пространство. (у каждого API есть своя функция, но вы можете реализовать свою, если хотите). Один на Min Z, один на Max Z.

2) Используя эти два значения, вычислите вектор, который идет от Min Z и указывает на Max Z.

3) Используя вектор и точку, вычислите луч, который идет от Min Z до MaxZ.

4) Теперь у вас есть луч, с его помощью вы можете пересечь луч-треугольник / луч-плоскость / луч-что-то и получить результат ...

person feal87    schedule 19.01.2010

У меня мало опыта работы с DirectX, но я уверен, что он похож на OpenGL. Вам нужен вызов gluUnproject.

Предполагая, что у вас есть действующий Z-буфер, вы можете запросить содержимое Z-буфера в позиции мыши с помощью:

// obtain the viewport, modelview matrix and projection matrix
// you may keep the viewport and projection matrices throughout the program if you don't change them
GLint viewport[4];
GLdouble modelview[16];
GLdouble projection[16];
glGetIntegerv(GL_VIEWPORT, viewport);
glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
glGetDoublev(GL_PROJECTION_MATRIX, projection);

// obtain the Z position (not world coordinates but in range 0 - 1)
GLfloat z_cursor;
glReadPixels(x_cursor, y_cursor, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &z_cursor);

// obtain the world coordinates
GLdouble x, y, z;
gluUnProject(x_cursor, y_cursor, z_cursor, modelview, projection, viewport, &x, &y, &z);

если вы не хотите использовать glu, вы также можете реализовать gluUnProject, вы также можете реализовать его самостоятельно, его функциональность относительно проста и описана на opengl.org

person wich    schedule 20.01.2010
comment
@Tom, как я уже сказал, если вы не хотите использовать функцию glu, вы можете просто реализовать ее функции самостоятельно, все, что вам тогда понадобится, это получить матрицы вида и проекции для каждого и получить положение окна z для каждого. - person wich; 22.01.2010
comment
что потребовало бы от меня определения того же xyz, я мог бы опубликовать этот xyz и отметить его как ответ вместо этого, вы понимаете мои рассуждения? Кто-то еще опубликовал математику - person Tom J Nowell; 22.01.2010

Хорошо, эта тема старая, но это было лучшее, что я нашел по этой теме, и это немного помогло мне, поэтому я опубликую здесь для тех, кто подписан ;-)

Вот как я заставил его работать без необходимости вычислять обратную матрицу проекции:

void Application::leftButtonPress(u32 x, u32 y){
    GL::Viewport vp = GL::getViewport(); // just a call to glGet GL_VIEWPORT
vec3f p = vec3f::from(                        
        ((float)(vp.width - x) / (float)vp.width),
        ((float)y / (float)vp.height),
            1.);
    // alternatively vec3f p = vec3f::from(                        
    //      ((float)x / (float)vp.width),
    //      ((float)(vp.height - y) / (float)vp.height),
    //      1.);

    p *= vec3f::from(APP_FRUSTUM_WIDTH, APP_FRUSTUM_HEIGHT, 1.);
    p += vec3f::from(APP_FRUSTUM_LEFT, APP_FRUSTUM_BOTTOM, 0.);

    // now p elements are in (-1, 1)
    vec3f near = p * vec3f::from(APP_FRUSTUM_NEAR);
    vec3f far = p * vec3f::from(APP_FRUSTUM_FAR);

    // ray in world coordinates
    Ray ray = { _camera->getPos(), -(_camera->getBasis() * (far - near).normalize()) };

    _ray->set(ray.origin, ray.dir, 10000.); // this is a debugging vertex array to see the Ray on screen

    Node* node = _scene->collide(ray, Transform());
   cout << "node is : " << node << endl;
}

Это предполагает перспективную проекцию, но никогда не возникает вопроса об орфографической проекции.

person nulleight    schedule 29.04.2012

У меня такая же ситуация с обычным выбором лучей, но что-то не так. Я выполнил операцию непроекта должным образом, но она не работает. Я думаю, что ошибся, но не могу понять где. Мое умножение matix, обратное и векторное умножение matix - все работают нормально, я их проверил. В моем коде я реагирую на WM_LBUTTONDOWN. Таким образом, lParam возвращает координаты [Y] [X] как 2 слова в двойном слове. Я извлекаю их, затем конвертирую в нормализованное пространство, я проверил, что эта часть также работает нормально. Когда я нажимаю на нижний левый угол - я получаю значения, близкие к -1 -1, и хорошие значения для всех трех других углов. Затем я использую массив linepoins.vtx для отладки, и это даже не близко к реальности.

unsigned int x_coord=lParam&0x0000ffff; //X RAW COORD
unsigned int y_coord=client_area.bottom-(lParam>>16); //Y RAW COORD

double xn=((double)x_coord/client_area.right)*2-1; //X [-1 +1]
double yn=1-((double)y_coord/client_area.bottom)*2;//Y [-1 +1]

_declspec(align(16))gl_vec4 pt_eye(xn,yn,0.0,1.0); 
gl_mat4 view_matrix_inversed;
gl_mat4 projection_matrix_inversed;
cam.matrixProjection.inverse(&projection_matrix_inversed);
cam.matrixView.inverse(&view_matrix_inversed);

gl_mat4::vec4_multiply_by_matrix4(&pt_eye,&projection_matrix_inversed);
gl_mat4::vec4_multiply_by_matrix4(&pt_eye,&view_matrix_inversed);

line_points.vtx[line_points.count*4]=pt_eye.x-cam.pos.x;
line_points.vtx[line_points.count*4+1]=pt_eye.y-cam.pos.y;
line_points.vtx[line_points.count*4+2]=pt_eye.z-cam.pos.z;
line_points.vtx[line_points.count*4+3]=1.0;
person Antiusninja    schedule 20.11.2014