Магия матрицы LookAt

Я нахожу математику иногда сложной, иногда забавной, иногда волшебной, а иногда сложной забавной и волшебной.Линейная алгебра¹ — это математика, стоящая за многими забавными технологиями, такими как как VR, AR, графика, машинное обучение, модные слова науки о данных и т. д.

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

Я быстро понял, что камеры в 3D-графике не существуетЭто все дым и зеркала. Мы создаем иллюзию его существования с помощью магии линейной алгебры. Давайте посмотрим, что я имею в виду, говоря взглянув (ха!) на матрицу LookAt.

Матрица LookAt — отличное упражнение по линейной алгебре. Он включает использование точечного произведения и перекрестного произведения. Он включает векторы. Он включает в себя матрицы.

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

Немного теории

Матрица LookAt – это матрица, преобразующая что-либо в точку в пространстве. Давайте ограничим наше обсуждение применением матрицы LookAt к камерам.

А именно, мы можем использовать матрицу LookAt для преобразования положения объектов в 3D-сцене, чтобы создать иллюзию того, что они просматриваются через объектив камеры.

Возьмем в качестве примера 3D-сцену, содержащую камеру и 3D-мяч, и применим к камере матрицу LookAt, которая преобразовывает ее для просмотра красного шара с определенной позиции в 3D, как мы могли бы ожидать. чтобы увидеть что-то вроде этого:

Кроме того, мы можем вместо этогозахотеть посмотреть на мирс объектива нашей камеры(что является гораздо более распространенным приложением)… поэтому вместо этого мы трансформируем шар/плоскость, чтобы создать иллюзию того, что мы смотрим на него с точки зрения камеры:

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

Некоторый код

Пуф! Вот магический трюк в полном объеме (я создал его прототип для приложения WebGPU, поэтому он написан на Javascript).

⚠️ Обратите внимание, что на самом деле я вычисляю матрицу LookAt, которую мы будем использовать для перемещения объектов сцены относительно камеры (пояснение в конце статьи).

Некоторые расчеты

Решение этой матрицы на самом деле является попыткой смоделировать эту камеру посредством тщательного расчета ее системы координат по отношению к мировому пространству. Или проще говоря, нам нужно найти векторы forwardVector , upVector и rightVector камеры относительно системы координат мяча.

Это немного похоже на колдовство. Мы можем взять оченьминимум информации и, в конце концов, получить целую матрицу, представляющую ортонормированную систему координат, как показано на рисунке:

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

Шаг первый: расчет направления оси вперед

Это на самом деле очень выполнимо. Учитывая положение нашей камеры и положение красного шара, мы можем вычислить направление forwardVector путем вычитания вектора:

forwardVector = normalize(cameraPosition — redBallPosition)

⚠️Не забудьте нормализовать результат вычитания вектора, поскольку нам нужен вектор направления, который является единичным вектором.

Шаг первый, готово. Давайте прыгнем на наших метлах ко второму шагу.

Шаг второй: расчет направления правой оси

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

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

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

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

Для двух линейно независимых векторов a и b перекрестное произведение a × b (читается как «a cross b ”), является вектором, который перпендикулярен как a, так и b.

Итак, мы знаем прямой вектор (поскольку мы нашли его на первом шаге).

НО, нам все еще не хватает восходящего вектора. Мы обречены? Нет… нам просто нужна магия.

Вот в чем хитрость: нам просто нужен любой старый вектор, который попадает в ту же плоскость, образованную вектором вперед и вверх… и не обязательно фактический вектор вверх самой камеры . В этом случае принято использовать (0, 1, 0) в качестве восходящего вектора.

Наконец, мы можем найти rightVector, выполнив следующие вычисления:

tempUpVector = (0, 1, 0)
rightVector = normalize(cross(tempUpVector, forwardVector))

Шаг третий: расчет направления оси вверх

Отсюда все гладко летит. Теперь мы знаем rightVector и forwardVector… поэтому, учитывая наши прежние предположения (эти три вектора ортонормированы), мы знаем, что для нахождения upVectorнам нужно найти перекрестное произведение из forwardVector и rightVector!

upVector = normalize(cross(forwardVector, rightVector))

Вот и все. Мы успешно рассчитали наши 3 вектора направления.

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

Шаг четвертый: вычислить вектор перемещения камеры

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

Почему бы просто не перевести его по положению камеры, которое мы использовали для расчета forwardVector?

Это отличный вопрос. Давайте посмотрим на пример.

Предположим, что наша камера изначально находилась в 3D-позиции в мировых координатах(0,4,4), а наш красный шар находится в исходной точке(0,0,0).

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

Сначала мы поворачиваем камеру, а затем переводим.

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

По сути, нам нужно подумать о том, как перевести камеру после ее поворота.

Настоящим вектором перемещения камеры будет (0, 0, 5,65). поскольку мы меняем базис координат (при сохранении его ортонормированных свойств).

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

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

translationX = точка(позициякамеры,правый вектор);
translationY = точка(позициякамеры,вектор вверх);
translationZ = точка(позициякамеры,вектор вперед);

⚠️Не забудьте определить rightVector , upVector и forwardVector как единичные векторы! От этого зависит правильность этих расчетов.

💡Мысленный эксперимент.Начнем с объекта в точке (0,0,0),можете ли вы представить, что получится, если сначала сдвинуть, а затем повернуть (вокруг исходной точки)? Можете ли вы представить себе вращение (вокруг исходной точки), а затем перемещение?

Шаг пятый: постройте матрицу!

Теперь просто заполните данные в эту матрицу:

Вот и все, мы закончили… но если вы ищете, как вы можете использовать это для 3D-приложения, которое вы разрабатываете… продолжайте читать!

Напомним, что я говорил в самом начале, что мы хотели бы смотреть на этот мир через объектив камеры. Типичное название матрицы преобразования, выполняющей эту операцию, — ViewMatrix.

Главное, что нужно понять, это то, что камера остается в исходной точке и смотрит вниз по отрицательной оси Z. Он не двигается. Чтобы имитировать движение камеры, вместо этого нам нужно перемещать объекты в сцене.

Поэтому нам нужно применить обратную матрицу LookAt к объектам в сцене, а не к камере.

В коде мы вычисляем именно это… перевод инвертируется (становится отрицательным), как и компонент вращения. Однако обратите внимание на очень важный момент, который заставил меня биться головой на работе, пытаясь решить проблему с вращением… обратное вращение — это транспонирование.

Итак, наша окончательная обратная матрица LookAt… или матрица представления будет выглядеть так:

Вы волшебник, читатель, гордитесь собой за то, что прошли через весь этот материал. До скорого!

¹ для непосвященных — это математика для массивов и матриц. Для посвященных — не распинайте меня за это грубое опошление.

Ресурсы