Как рассчитать луч из положения мыши для сцен, которые не используют перспективную проекцию / камеры? - PullRequest
0 голосов
/ 11 марта 2020

Моя цель - рассчитать луч, который указывает на сцену, для проверки щелчков мыши и прочего. Я не использую обычную перспективную проекцию / камеру, вместо этого я просто использую матрицу наклонной проекции (например, наклонную ортогональную c proj) для моей сцены без камеры (без матрицы просмотра). Все методы, которые я нашел в Интернете, являются своего рода c для перспективной проекции и камер и используют положение камеры непосредственно в качестве источника луча, а затем вычисляют направление луча из положения мыши и матриц проекции / вида. Однако в моем случае (если рассматривать проекцию в контексте реального мира), мое происхождение луча должно быть рассчитано по положению мыши, а направление луча должно быть одинаковым для всех лучей и иметь возможность прямого вычисления из матрицы проекции, но я просто не не знаю, как ..

Это моя матрица наклонной проекции, если это уместно:

fn oblique_projection(cam) -> Mat4 {

    let w = cam.screen_width;
    let h = cam.screen_height;
    let near = cam.near;
    let far = cam.far;

    // normal orthographic projection matrix:
    let (left, right, bottom, top) = (-w / 2.0, w / 2.0, -h / 2.0, h / 2.0);
    let tx = -(right + left) / (right - left);
    let ty = -(top + bottom) / (top - bottom);
    let tz = -(far + near) / (far - near);

    let m1 = mat4![
        2.0 / (right - left), 0.0, 0.0, 0.0,
        0.0, 2.0 / (top - bottom), 0.0, 0.0,
        0.0, 0.0, -2.0 / (far - near), 0.0,
        tx, ty, tz, 1.0,
    ];

    // apply a skew matrix on top of the orthographic matrix to get an oblique projection matrix
    let a = -self.z_scale * f32::cos(self.angle);
    let b = -self.z_scale * f32::sin(self.angle);

    let m2 = mat4![
        1.0, 0.0, 0.0, 0.0,
        0.0, 1.0, 0.0, 0.0,
        a, b, 1.0, 0.0,
        0.0, 0.0, 0.0, 1.0,
    ];

    return m1 * m2;

}

(в основном искаженная ортографическая проекция c, в результате получается вид, подобный изометрии c). )

РЕДАКТИРОВАТЬ:

Я нашел очень точное решение c для моей установки (моя наклонная проекция)

let a = -cam.z_scale * cos(cam.angle);
let b = -cam.z_scale * sin(cam.angle);

let skewed = mat4[
    1.0, 0.0, 0.0, 0.0,
    0.0, 1.0, 0.0, 0.0,
    a, b, 1.0, 0.0,
    0.0, 0.0, 0.0, 1.0,
]; // the only the skew part from the projection matrix construction above

let ray_dir = (skewed * vec3!(0, 0, -1)).normalized(); // apply the skew to a unit forward vector
let mouse_pos_clip_space = screen_to_clip(mouse_pos);
let clip_coord = vec4(mouse_pos_clip_space, -1, 1);
let ray_orig = projection_matrix.inverse() * clip_coord; // unproject the oblique projection matrix calculated in the previous code block


return Ray {
    origin: ray_orig,
    dir: ray_dir,
};

Так что Идея состоит в том, чтобы сначала выяснить, как построить луч для обычной ортографической проекции c, а затем применить перекос

. Это заставляет меня думать, что нет общего алгоритма fn get_ray(proj: Mat4, view: Mat4) -> (ray_origin: Vec3, ray_dir: Vec3), потому что, как я строю Луч значительно отличается от обычного проекционного изображения + камера.

Ответы [ 2 ]

1 голос
/ 11 марта 2020

Я собираюсь ответить на это в самом общем виде, и вы можете решить, будет ли это полезно.

В OpenGL (и в vulkan, и, возможно, в других графических API). Там нет «камеры».

Скорее, у вас есть прямоугольник angular пробел, который идет (в случае opengl) от -1 до 1 в направлениях x и y и от 0 до 1 в z direction.

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

Почему это имеет значение? Любая «камера» - это не что иное, как преобразование, которое берет произвольную точку X и отображает ее в новую точку X '. Другими словами, матрица classi c MVP просто берет точки модели и устанавливает их особым образом в призме OpenGL.

Таким образом, в общем случае камера - это просто функция, которая отображает мировая точка указывает на точку камеры или C (X) = X '.

Это означает, что для любой камеры (включая нелинейные камеры) непроекция эквивалентна обратной функции C ^ {- 1}, которая удовлетворяет C ^ {- 1} (X ') = X.

Просто ответьте на вопрос уже!

Положение камеры в нормализованной призме равно (0,0). Это просто причуда, что все ваши лучи параллельны, а не сходятся к одной точке. Таким образом, в пространстве камеры луч для данного пикселя (x, y) просто (x, y, -1) (или +1), если вы хотите, чтобы этот луч был в мировых координатах, то просто умножьте его на инверсию вашей вершины. матрица преобразования.

Надеюсь, это поможет.

0 голосов
/ 12 марта 2020

Если ваша проекционная матрица не является единственной матрицей (и не является), то то, что вы хотите, может быть легко достигнуто с помощью обратной матрицы (и все непроектированные функции classi c делают это внутренне).

Ключевым моментом здесь является то, что GPU всегда использует идеальную ортографическую c проекцию: растеризатор просто берет пространство окна x и y, пространство окна z координата в этот момент только некоторые дополнительные данные, связанные с вершинами (и интерполированные на фрагмент), аналогичные атрибутам цвета или texcoord.

Матрица проекции (в сочетании с делением перспективы) используется для преобразования пространство, такое, что объем вашего вида (будь то усеченный элемент для перспективной проекции, прямоугольник с осями для стандартного орто или какой-нибудь наклонный параллелепипед, как в вашем случае) отображается в нормализованном диапазоне координат устройства [-1,1]^3. Затем он далее преобразуется в пространство окна с использованием настроек области просмотра и диапазона глубины. Таким образом, матрица проекций не выполняет проекцию в строгом математическом смысле (идемпотентное отображение, которое в основном теряет информацию).

Один пиксель (ну, строго говоря, каждая точка на плоскости , вы бы обычно использовали центр пикселя как репрезентативный для всего пикселя) в пространстве окна определяет луч вида, и этот луч вида всегда имеет ортогональное направление (0,0, + / - 1) (положительное или отрицательное z зависит от некоторых настройки, но это совершенно не имеет значения здесь) в пространстве окна. Другими словами, вы можете в основном взять только две точки пространства окна с одинаковыми позициями 2D x и y и просто варьировать z, чтобы вычислить луч взгляда там.

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

Представляется наиболее целесообразным просто выбрать первую точку на ближней плоскости. (z_win=0) и другой на дальней плоскости (z_win=1).

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

То, что я написал, относится к общему случаю произвольной матрицы проекции. В перспективной проекции направление луча будет отличаться в каждой точке. Но с любым видом аффинно-проекционных матриц (которые вы используете) вы просто можете отменить проекцию самого вектора direction в однородных координатах: (0,0,1,0). Это сводится к inverse(proj) * vec(0,0,1,0), или просто к третьему столбцу матрицы обратной проекции, и даст пространственный вектор глаза с компонентом w, все еще равным 0, поэтому он остается направленным во время преобразования (в отличие от перспективы случай, когда вы будете в конечном итоге с реальной точкой). Вы также можете напрямую трансформироваться в мировое пространство, трансформировавшись с помощью inverse(proj*view). Другие части типичной функции unproject здесь не нужны, потому что преобразование области просмотра не изменит направления просмотра, и единственное, что может сделать диапазон глубины, - это перевернуть знак для z, который можно легко компенсировать вручную, если вы используете некоторые обратные карты там.

...