Opengl - Как Отменить Проецирование Курсора С Помощью Ортогональной Проекции

  • Автор темы Paiquague
  • Обновлено
  • 23, Oct 2024
  • #1

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

 
         Vector4 cScreen = Vector4(cursorNormX, cursorNormY, 0, 0);

Vector4 cView = Inverse(projection)*cScreen;

cView = Vector4(cView.x, cView.y, 0, 0);

Vector4 cWorld = Inverse(View) * cView;

cWorld = Vector4(cWorld.x, cWorld.y, 0, 0);

Vector3 cursorPos = cWorld.xyz();
 

Однако теперь при ортогональной проекции дальняя и ближняя плоскости имеют одинаковый размер, поэтому мы не можем вычислить направление курсора таким образом. Направление будет равно оси Z мира. (Я мало спал, поэтому надеюсь, что это имеет смысл).

Поэтому вместо этого я вычислил положение курсора, отменив проецирование курсора с обнуленным значением z. Мы можем вычислить координаты x и y и позже установить координату z по своему усмотрению.

Vector4 cScreen0 = Vector4(cursorNormX, cursorNormY, -1, 1); Vector4 cView0 = Inverse(projection)*cScreen0; cView0 = cView0*(1/cView0.w); Vector4 cWorld0 = Inverse(view) * cView0; Vector4 cScreen1 = Vector4(cursorNormX, cursorNormY, 1, 1); Vector4 cView1 = Inverse(projection)*cScreen1; cView1 = cView1*(1/cView1.w); Vector4 cWorld1 = Inverse(view) * cView1; Vector3 cursorDir = normalize(cWorld1.xyz()-cWorld0.xyz());

Однако я не получаю правильных результатов от проекции. Что мне не хватает?

Целью этого является возможность отбрасывать лучи в направлении курсора.

#opengl #c++ #проекции

Paiquague


Рег
22 Mar, 2011

Тем
73

Постов
186

Баллов
571
  • 26, Oct 2024
  • #2

Я до сих пор не уверен на 100%, понял ли я ваш вопрос из-за этого предложения:

Однако теперь при ортогональной проекции дальняя и ближняя плоскости имеют одинаковый размер, поэтому мы не можем вычислить направление курсора таким образом. Направление будет равно оси Z мира. (Я мало спал, поэтому надеюсь, что это имеет смысл).

Если я вас неправильно понял, дайте мне знать в комментариях, и я исправлю или удалю свой ответ.

Однако, если я правильно понял ваше намерение и вы хотите направить луч через усеченную пирамиду (например, чтобы подобрать объекты), то ваше утверждение неверно. Направление будет равно просматривать пространства' отрицательное направление z, а не мировые пространства». Итак, все, что вам нужно сделать, это преобразовать вектор направления или точки ближней и дальней плоскости в мировое пространство. Чтобы доказать, что это работает, я реализовал все в скрипте Python, который вы найдете в конце этого ответа. Если у вас есть интерпретатор Python с установленными MatPlotLib и NumPy, вы можете изменить параметры настройки и немного поэкспериментировать самостоятельно.

Итак, давайте посмотрим на соответствующую реализацию. Сначала мы вычисляем положение мыши в пространстве отсечения и соответствующие 2 точки на ближней и дальней плоскости.

 
 
 
 
 
 
 
 import numpy as np
from mpl_toolkits.mplot3d import Axes3D  # noqa: F401 unused import
import matplotlib.pyplot as plt

# setup --------------------------------------------------------------------------------

screen_height = 1080
screen_width = 1980

mouse_pos_x_screen = 500
mouse_pos_y_screen = 300

camera_position = [3, 0, 1]
camera_yaw = 20
camera_pitch = 30

# ----------------
# projection setup
# ----------------
perspective = False # set 'False' for orthogonal and 'True' for perspective projection

z_near_plane = 0.5
z_far_plane = 3

# only orthogonal
frustum_width = 3
frustum_height = 2

# only perspective
field_of_view = 70
aspect_ratio = screen_width / screen_height

# functions ----------------------------------------------------------------------------

def render_frustum(points, camera_pos, ax, right_handed=True):

line_indices = [

[0, 1],

[0, 2],

[0, 4],

[1, 3],

[1, 5],

[2, 3],

[2, 6],

[3, 7],

[4, 5],

[4, 6],

[5, 7],

[6, 7],

]

for idx_pair in line_indices:

line = np.transpose([points[idx_pair[0]], points[idx_pair[1]]])

ax.plot(line[2], line[0], line[1], "g")

if right_handed:

ax.set_xlim([-5, 5])

else:

ax.set_xlim([5, -5])

ax.set_ylim([-5, 5])

ax.set_zlim([-5, 5])

ax.set_xlabel("z")

ax.set_ylabel("x")

ax.set_zlabel("y")

ax.plot([-5, 5], [0, 0], [0, 0], "k")

ax.plot([0, 0], [-5, 5], [0, 0], "k")

ax.plot([0, 0], [0, 0], [-5, 5], "k")

if camera_pos is not None:

ax.scatter(

camera_pos[2], camera_pos[0], camera_pos[1], marker="o", color="b", s=30

)

def render_ray(p0,p1,ax):

ax.plot([p0[2], p1[2]], [p0[0], p1[0]], [p0[1], p1[1]], color="r")

ax.scatter(p0[2], p0[0], p0[1], marker="o", color="r")

def get_perspective_mat(fov_deg, z_near, z_far, aspect_ratio):

fov_rad = fov_deg * np.pi / 180

f = 1 / np.tan(fov_rad / 2)

return np.array(

[

[f / aspect_ratio, 0, 0, 0],

[0, f, 0, 0],

[

0,

0,

(z_far + z_near) / (z_near - z_far),

2 * z_far * z_near / (z_near - z_far),

],

[0, 0, -1, 0],

]

)

def get_orthogonal_mat(width, height, z_near, z_far):

r = width / 2

t = height / 2

return np.array(

[

[1 / r, 0, 0, 0],

[0, 1 / t, 0, 0],

[

0,

0,

-2 / (z_far - z_near),

-(z_far + z_near) / (z_far - z_near),

],

[0, 0, 0, 1],

]

)

def get_rotation_mat_x(angle_rad):

s = np.sin(angle_rad)

c = np.cos(angle_rad)

return np.array(

[[1, 0, 0, 0], [0, c, -s, 0], [0, s, c, 0], [0, 0, 0, 1]], dtype=float

)

def get_rotation_mat_y(angle_rad):

s = np.sin(angle_rad)

c = np.cos(angle_rad)

return np.array(

[[c, 0, s, 0], [0, 1, 0, 0], [-s, 0, c, 0], [0, 0, 0, 1]], dtype=float

)

def get_translation_mat(position):

return np.array(

[

[1, 0, 0, position[0]],

[0, 1, 0, position[1]],

[0, 0, 1, position[2]],

[0, 0, 0, 1],

],

dtype=float,

)

def get_world_to_view_matrix(pitch_deg, yaw_deg, position):

pitch_rad = np.pi / 180 * pitch_deg

yaw_rad = np.pi / 180 * yaw_deg

orientation_mat = np.matmul(

get_rotation_mat_x(-pitch_rad), get_rotation_mat_y(-yaw_rad)

)

translation_mat = get_translation_mat(-1 * np.array(position, dtype=float))

return np.matmul(orientation_mat, translation_mat)

# script -------------------------------------------------------------------------------

mouse_pos_x_clip = mouse_pos_x_screen / screen_width * 2 - 1
mouse_pos_y_clip = mouse_pos_y_screen / screen_height * 2 - 1

mouse_pos_near_clip = np.array([mouse_pos_x_clip, mouse_pos_y_clip, -1, 1], dtype=float)
mouse_pos_far_clip = np.array([mouse_pos_x_clip, mouse_pos_y_clip, 1, 1], dtype=float)

M_wv = get_world_to_view_matrix(camera_pitch, camera_yaw, camera_position)
if perspective:

M_vc = get_perspective_mat(field_of_view, z_near_plane, z_far_plane, aspect_ratio)
else:

M_vc = get_orthogonal_mat(frustum_width, frustum_height, z_near_plane, z_far_plane)

M_vw = np.linalg.inv(M_wv)
M_cv = np.linalg.inv(M_vc)

mouse_pos_near_view = np.matmul(M_cv,mouse_pos_near_clip)
mouse_pos_far_view = np.matmul(M_cv,mouse_pos_far_clip)

if perspective:

mouse_pos_near_view= mouse_pos_near_view / mouse_pos_near_view[3]

mouse_pos_far_view= mouse_pos_far_view / mouse_pos_far_view[3]

mouse_pos_near_world = np.matmul(M_vw, mouse_pos_near_view)
mouse_pos_far_world = np.matmul(M_vw, mouse_pos_far_view)

# calculate view frustum ---------------------------------------------------------------

points_clip = np.array(

[

[-1, -1, -1, 1],

[ 1, -1, -1, 1],

[-1,  1, -1, 1],

[ 1,  1, -1, 1],

[-1, -1,  1, 1],

[ 1, -1,  1, 1],

[-1,  1,  1, 1],

[ 1,  1,  1, 1],

],

dtype=float,
)

points_view = []
points_world = []
for i in range(8):

points_view.append(np.matmul(M_cv, points_clip[i]))

points_view[i] = points_view[i] / points_view[i][3]

points_world.append(np.matmul(M_vw, points_view[i]))

# plot everything ----------------------------------------------------------------------

plt.figure()
plt.plot(mouse_pos_x_screen,mouse_pos_y_screen, marker="o", color="r")
plt.xlim([0, screen_width])
plt.ylim([0, screen_height])
plt.xlabel("x")
plt.ylabel("y")
plt.title("screen space")

plt.figure()
ax_clip_space = plt.gca(projection="3d")
render_ray(mouse_pos_near_clip, mouse_pos_far_clip, ax_clip_space)
render_frustum(points=points_clip, camera_pos=None, ax=ax_clip_space, right_handed=False)
ax_clip_space.set_title("clip space")

plt.figure()
ax_view = plt.gca(projection="3d")
render_ray(mouse_pos_near_view, mouse_pos_far_view, ax_view)
render_frustum(points=points_view, camera_pos=[0, 0, 0], ax=ax_view)
ax_view.set_title("view space")

plt.figure()
ax_world = plt.gca(projection="3d")
render_ray(mouse_pos_near_world, mouse_pos_far_world, ax_world)
render_frustum(points=points_world, camera_pos=camera_position, ax=ax_world)
ax_world.set_title("world space")

plt.show()
 

Теперь мы получили задействованные матрицы. Мои обозначения здесь следующие: я использую два символа после point_view_near = [x_view, y_view, -z_near, 1] that are abbreviations of the involved spaces. The first character is the source and the second the target space. The characters are x_view = (x_screen / screen_width - 0.5) * frustum_width y_view = (y_screen / screen_height - 0.5) * frustum_height для клипового пространства, M_vw for view space, and [0, 0, -1, 0] для мирового пространства. Так cWorld = Vector4(cWorld.x, cWorld.y, 0, 0); is the view space to clip space transformation a.k.a the projection matrix.

cView = Vector4(cView.x, cView.y, 0, 0);

Теперь я просто использую правильные матрицы преобразования для преобразования клипа в мировое пространство. Обратите внимание, что перспективная проекция требует деления на Vector4 cScreen = Vector4(cursorNormX, cursorNormY, -1, 1); after the transformation to view space. This is not necessary for the orthographic projection, but performing it does not affect the result.

Vector4 cScreen = Vector4(cursorNormX, cursorNormY, 0, 0); Vector4 cView = Inverse(projection)*cScreen; cView = Vector4(cView.x, cView.y, 0, 0); Vector4 cWorld = Inverse(View) * cView; cWorld = Vector4(cWorld.x, cWorld.y, 0, 0); Vector3 cursorPos = cWorld.xyz();

Насколько я вижу, это идентично вашему первому разделу кода. Теперь давайте посмотрим на результат для перспективной и ортогональной проекции со следующими параметрами настройки:

screen_height = 1080 screen_width = 1980 mouse_pos_x_screen = 500 mouse_pos_y_screen = 300 camera_position = [3, 0, 1] camera_yaw = 20 camera_pitch = 30 z_near_plane = 0.5 z_far_plane = 3 # only orthogonal frustum_width = 3 frustum_height = 2 # only perspective field_of_view = 70 aspect_ratio = screen_width / screen_height

Значения пространства экрана и пространства отсечения идентичны для обеих проекций:

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

Теперь для перспективной проекции я получаю следующие графики:

Синяя точка — это положение камеры.

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

Как видите, подход, который вы использовали в первом разделе кода, работает независимо от выбранной проекции. Я не знаю, почему ты думал, что это не так. Я предполагаю, что вы могли допустить небольшую ошибку при реализации матрицы ортогональной проекции. Например, если вы случайно перевернули строки и столбцы (транспонировали) матрицы ортогональной проекции, вы получите такую ​​ерунду:

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

источник

$$

\begin{bmatrix}

mouse_pos_near_view = np.matmul(M_cv, mouse_pos_near_clip) mouse_pos_far_view = np.matmul(M_cv, mouse_pos_far_clip) if perspective: mouse_pos_near_view= mouse_pos_near_view / mouse_pos_near_view[3] mouse_pos_far_view= mouse_pos_far_view / mouse_pos_far_view[3] mouse_pos_near_world = np.matmul(M_vw, mouse_pos_near_view) mouse_pos_far_world = np.matmul(M_vw, mouse_pos_far_view)

\frac{2}{w}&0&0&0\\ w as initial vector.

0&\frac{2}{h}&0&0\\ M_wv = get_world_to_view_matrix(camera_pitch, camera_yaw, camera_position) if perspective: M_vc = get_perspective_mat(field_of_view, z_near_plane, z_far_plane, aspect_ratio) else: M_vc = get_orthogonal_mat(frustum_width, frustum_height, z_near_plane, z_far_plane) M_vw = np.linalg.inv(M_wv) M_cv = np.linalg.inv(M_vc) is, that your z-component should be identical to your near plane value and not zero. You might get away with that since it would just shift your point a little bit in the camera viewing direction in world space, but more problematic is that you set w to 0. This makes it impossible to apply any translation to the vector by $4 \times 4$ matrix multiplication. So when you transform to world space, you will always end up with a point that treats the camera to be located at the coordinate system origin, regardless of its true position. So you need to set the w-component to 1. However, if the previous lines are correct, you should automatically get the correct z- and w-values which makes this line obsolete.

0&0&\frac{-2}{f-n}&-\frac{f+n}{f-n}\\ M_vc doesn't make much sense either to me. Your camera is somewhere in 3d world space. Why do you remove the z-component you previously calculated? With this, you move the point into the XY-plane for no reason. Just remove this line.

0&0&0&1 w with the view-to-world matrix ( v \end{bmatrix}

$$

Здесь $w$ — ширина усеченной пирамиды, $h$ — высота усеченной пирамиды, $f$ — значение z в дальней плоскости и $n$ — значение z в ближней плоскости. Это представление, если вы используете векторы-столбцы и матрицы, умноженные слева. Для векторов-строк и матриц, умноженных справа, вам необходимо их транспонировать.

c

Ваш второй подход:

M_

имеет множество проблем, и все они связаны с z- и w-компонентами ваших векторов. По сути, вам нужно выполнить те же преобразования, что и в первом подходе. Так что используйте

Одна проблема на линии

mouse_pos_x_clip = mouse_pos_x_screen / screen_width * 2 - 1 mouse_pos_y_clip = mouse_pos_y_screen / screen_height * 2 - 1 mouse_pos_near_clip = np.array([mouse_pos_x_clip, mouse_pos_y_clip, -1, 1], dtype=float) mouse_pos_far_clip = np.array([mouse_pos_x_clip, mouse_pos_y_clip, 1, 1], dtype=float)
 

Sususususu


Рег
27 Apr, 2014

Тем
53

Постов
197

Баллов
502
Похожие темы Дата
Похожие темы
Экструдер — Большой 3D-Принтер Размером 10 X 10 X 4 Дюйма — Нужен G-Код, Чтобы Сообщить, Когда Он Прекращает Выдавливание, И Остановить Машину.
Opengl — Работает Ли Плавное Освещение С Затенением Гуро На Отдельных Треугольниках?
Adobe Photoshop — Создание Спрайтов Для Игр
Двойной Экран Для Сравнения В Photoshop Cs6
Изменить Размер — Растянуть Все Изображение, Чтобы Внутренняя Часть Имела Определенный Размер
Цвет — Фон Текста Выделяется При Печати. То, Что Вы Видите На Экране, Отличается От Того, Что Вы Получаете На Принтере.
Искажение. Как «Растянуть» Часть Изображения, Чтобы Все Изображение Согнулось, Чтобы Компенсировать Растяжение?
Adobe Photoshop — Ищу Копию Комиксов Начала 90-Х (А Не Только С Использованием Полутонового Фильтра)
Вектор. Почему Мой Встроенный Eps Выглядит Пиксельным В Pdf-Файле Photoshop?
Adobe Illustrator – Какая Операция Обратна Операции «Развернуть»?
Тем
403,760
Комментарии
400,028
Опыт
2,418,908

Интересно