Урок №9. Камера в OpenGL

  Дмитрий Бушуев  | 

  Обновл. 7 Сен 2021  | 

 27795

 ǀ   17 

На предыдущем уроке мы обсуждали матрицу вида и то, как её можно использовать для перемещения по сцене. OpenGL сам по себе не знаком с концепцией камеры, но мы можем попытаться смоделировать её, перемещая все объекты на сцене в обратном направлении, создавая иллюзию нашего движения вперед.

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

Создание камеры в OpenGL

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

   её положение в мировом пространстве;

   направление, в котором смотрит камера;

   вектор, указывающий вправо от направления, в котором смотрит камера;

   вектор, указывающий вверх от камеры.

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

Пункт №1: Местоположение камеры

Получить позицию камеры очень просто — это вектор в мировом пространстве, который указывает положение камеры. Мы установим камеру в том же месте, что и на предыдущем уроке:

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

Пункт №2: Направление камеры

Следующий необходимый вектор — это вектор направления камеры (т.е. в какую точку она сфокусирована). Предположим, что камера указывает на начало нашей сцены: точку с координатами (0,0,0). Помните, что если мы вычтем два вектора друг из друга, то получим вектор, который является разницей этих двух векторов? Таким образом, вычитание вектора положения камеры из исходного вектора сцены приводит нас к искомому вектору направления. Мы хотим, чтобы ось z системы координат, определяемой матрицей вида, была положительной, и поскольку по соглашению в OpenGL камера указывает вдоль отрицательной оси z, то необходимо получить вектор, противоположный вектору направления. Для этого достаточно поменять местами порядок вычитания векторов, и тогда получится вектор, направленный вдоль положительной оси z камеры:

Примечание: Вы могли заметить, что «вектор направления» — это не самое лучшее название для такого объекта, так как вектор фактически указывает в обратном направлении от заданной точки фокуса.

Пункт №3: Вектор вправо

Еще один вектор, который нам нужен — это вектор-вправо (вектор, направленный вправо от направления, в котором смотрит камера), представляющий положительную ось x пространства камеры. Чтобы получить вектор-вправо, мы прибегнем к небольшому трюку, сначала задав вектор-вправо, указывающий вверх (в мировом пространстве). Затем мы выполним векторное умножение вектора-вправо с вектором направления из пункта №2. Поскольку результатом векторного произведения является вектор, перпендикулярный заданным векторам, то мы получим вектор, который указывает в направлении положительной оси x (если бы мы поменяли порядок векторного произведения, то получили бы вектор, указывающий вдоль отрицательной оси x):

Пункт №4: Вектор вверх

Теперь, когда у нас есть как вектор оси x, так и вектор оси z, получить вектор, указывающий вдоль положительной y-оси камеры — вектор-вверх, относительно легко: нужно воспользоваться векторным произведением вектора-вправо и вектора направления:

С помощью векторного произведения и нескольких небольших трюков нам удалось создать все необходимые векторы, формирующие пространство вида/камеры. В линейной алгебре данный процесс носит название «процесс Грама-Шмидта». Используя полученные векторы камеры, мы теперь можем определить LookAt-матрицу, которая является очень полезной для создания камеры.

LookAt-матрица


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

где R — это вектор-вправо, U — вектор-вверх, D — вектор направления, а P — вектор положения камеры. Обратите внимание, что поскольку мы хотим вращать и перемещать мир в направлении, противоположном направлению перемещения камеры, то элементы матрицы вращения (левая матрица) и матрицы трансляции (правая матрица) инвертированы (т.е. транспонированы и взяты с противоположным знаком). Использование LookAt-матрицы в качестве нашей матрицы вида преобразует все мировые координаты в координаты пространства окна просмотра, которое мы только что определили. Таким образом LookAt-матрица выполняет именно то, из-за чего и получила свое название: она создает матрицу вида, которая смотрит на заданную цель.

К счастью для нас, мы можем переложить всю эту работу на библиотеку GLM. Нам необходимо только указать положение камеры, целевую позицию и вектор, который представляет собой вектор-вверх в мировом пространстве (вектор-вверх, который мы использовали для вычисления вектора-вправо). Затем GLM создаст LookAt-матрицу, которую мы сможем использовать в качестве матрицы вида:

Аргументами функции glm::LookAt() являются: позиция камеры, позиция цели и вектор-вверх. В примере, приведенном выше, создается матрица вида, аналогичная той, которую мы создавали на предыдущем уроке.

Прежде чем углубляться в пользовательский ввод, давайте сначала немного повеселимся, вращая камеру вокруг нашей сцены. Фокус направим на точку (0,0,0). Необходимо будет задействовать некоторые наши знания тригонометрии, чтобы создать для каждого кадра координаты x и z, представляющие точку на окружности, и далее будем использовать их для задания положения нашей камеры. Пересчитывая с течением времени координаты x и y, мы проходим все точки на окружности, и, таким образом, получается, что камера вращается вокруг сцены. Ширину окружности зададим с помощью предопределенной переменной radius. И в каждом кадре с помощью функции glfwGetTime() из библиотеки GLFW будем создавать новую матрицу вида:

Если вы запустите данный код, то получите что-то вроде следующего:

Теперь с помощью вышеописанного маленького фрагмента кода камера вращается с течением времени вокруг сцены. Не стесняйтесь экспериментировать с параметрами радиуса и положения/направления, чтобы получить представление о том, как работает LookAt-матрица.

  GitHub / Урок №9. Камера в OpenGL — Исходный код №1

Двигаемся дальше

Размахивать камерой вокруг сцены — это, конечно, весело, но куда веселее делать все движения самостоятельно! Для этого сначала нам нужно настроить камеру, определив некоторые переменные камеры в верхней части нашей программы:

LookAt-функция изменится на:

Первое, что мы делаем — это устанавливаем положение камеры с помощью заранее определенной переменной cameraPos. Направление — это текущее положение + вектор направления, который мы только что определили. В результате мы получим гарантию того, что как бы мы ни двигались, камера продолжит смотреть в направлении цели. Давайте немного поиграем с этими переменными, обновляя вектор cameraPos при нажатии некоторых клавиш.

Мы уже определили функцию processInput() для управления вводом с клавиатуры, поэтому давайте добавим несколько дополнительных ключевых команд:

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

Примечание: Обратите внимание, что мы нормализуем результирующий вектор-вправо. Если бы мы не нормализовали этот вектор, то результирующее векторное произведение могло бы вернуть векторы разного размера, основываясь на переменной cameraFront. Если бы мы не нормализовали вектор, то двигались бы медленно или быстро, в зависимости от ориентации камеры, а не от составляющей скорости движения.

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

Скорость перемещения камеры


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

Графические приложения и игры обычно отслеживают переменную deltaTime, которая хранит время, затраченное для визуализации последнего кадра. Затем мы умножаем все переменные скорости на значение deltaTime. В результате получается, что, когда в кадре мы имеем большое значение deltaTime (а это означает, что последний кадр занял больше среднего времени), скорость для этого кадра также будет немного выше, чтобы это всё сбалансировать. При использовании данного подхода не имеет значения, насколько мощный у вас компьютер, скорость камеры будет сбалансирована соответствующим образом так, что у каждого пользователя наша программа будет работать одинаково.

Для расчета значения deltaTime необходимо отслеживать 2 глобальные переменные:

Затем в каждом кадре мы вычисляем новое значение deltaTime для последующего использования:

Теперь, когда у нас есть deltaTime, мы можем использовать её значение при расчете скоростей:

Поскольку теперь мы используем переменную deltaTime, камера будет двигаться с постоянной скоростью в 2.5 единицы в секунду. Вместе с кодом и его описанием, указанными в предыдущем разделе данного урока, теперь для перемещения по сцене у нас должна получиться гораздо более плавная и устойчивая камера:

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

  GitHub / Урок №9. Камера в OpenGL — Исходный код №2

Осматриваемся вокруг

Не так уж и интересно передвигаться только с помощью клавиш клавиатуры, тем более, что мы не можем повернуться, и, как следствие этого, наше перемещение в пространстве является довольно ограниченным. Вот тут-то в дело и вступает мышка!

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

Углы Эйлера


Углы Эйлера — это три значения, которые могут представлять любое вращение в 3D-пространстве, определенные Леонардом Эйлером где-то в 1700-х годах. Есть три угла Эйлера:

   Тангаж (англ. «pitch») — это угловое движение объекта относительно главной поперечной оси инерции (величина того, насколько мы смотрим вверх или вниз) — изображен в первой части картинки.

   Рыскание (англ. «yaw») — это угловые движения объекта относительно вертикальной оси, а также небольшие изменения курса вправо или влево — изображено во второй части картинки.

   Крен (англ. «roll») — это поворот объекта вокруг его продольной оси (он используется в камерах космического полета) — изображен в третьей части картинки.

Следующее изображение даст нам наглядное представление углов:

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

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

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

Если мы определим длину гипотенузы равной 1, то, используя знания тригонометрии (косинус угла — это отношение прилежащего к углу катета к гипотенузе, а синус угла — отношение противолежащего углу катета к гипотенузе), получаем, что длина основания x треугольника равна cos(θ)/h=cos(θ)/1=cos(θ), а длина катета y треугольника равна sin(θ)/h=sin(θ)/1=sin(θ).

В результате мы имеем формулы для получения длин сторон (x и y) в прямоугольных треугольниках, в зависимости от заданного угла. Давайте воспользуемся ими для вычисления составляющих вектора направления.

Представим себе тот же самый треугольник, но теперь посмотрим на него сверху, причем прилегающая и противолежащая стороны параллельны осям x и z сцены (как если бы мы смотрели вниз вдоль оси y):

Если мы представим себе угол рыскания как угол, отсчитываемый против часовой стрелки относительно стороны x, то можно увидеть, что длина стороны x равна cos(yaw). И точно так же длина стороны z равна sin(yaw).

Вооружившись данными знаниями и полученным значением рыскания, мы можем создать вектор направления камеры:

Это является решением того, как можно получить 3D-вектор направления, используя значение рыскания, но это еще не всё: ведь нам еще нужно использовать значение тангажа, чтобы найти y-компоненту вектора направления. Давайте посмотрим на сторону оси y, как будто мы сидим на плоскости xz:

Аналогично предыдущему примеру в данном треугольнике мы можем видеть, что y-компонента направления равна sin(pitch):

Однако, мы можем заметить, что сторона xz находится под влиянием cos(pitch), поэтому нам необходимо добавить cos(pitch) как одну из составных частей вектора направления. С учетом этого мы получаем конечный вектор направления:

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

Мы настроили мир сцены таким образом, чтобы всё было расположено в направлении отрицательной оси z. Однако, если мы посмотрим на треугольник рыскания (со сторонами x и z), то увидим, что при угле θ, равным 0 градусов, мы получим вектор направления камеры, указывающий в сторону положительной оси x. Чтобы убедиться, что камера по умолчанию указывает направление отрицательной оси z, мы можем установить по умолчанию значение рыскания, равным 90 градусам (по часовой стрелке). Положительное значение угла поворота определяет вращение против часовой стрелки, поэтому мы по умолчанию устанавливаем значение рыскания, равным:

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

Мышка как устройство ввода

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

Сначала мы сообщаем GLFW, что он должен скрыть курсор и захватить его. Захват курсора означает, что после того, как приложение получило фокус, курсор мыши остается в центре окна (если только приложение не потеряет фокус или не завершит свою работу). Мы можем сделать это с помощью одного простого вызова функции:

После этого вызова куда бы мы не переместили мышь, она не будет видна и не покинет окно. Это идеально подходит для камеры в стиле FPS.

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

Здесь xpos и ypos представляют собой текущие позиции мыши. Как только мы зарегистрируем в GLFW нашу callback-функцию, то при каждом движении мыши будет вызываться mouse_callback:

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

   Шаг №1: Вычислить смещение мышки с момента последнего кадра.

   Шаг №2: Добавить значения смещений к значениям рыскания и тангажа камеры.

   Шаг №3: Добавить некоторые ограничения к минимальным/максимальным значениям тангажа.

   Шаг №4: Вычислить вектор направления.

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

Затем в callback-функции мышки мы вычисляем смещение движения между последним и текущим кадрами:

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

Затем мы добавляем значения смещений к глобально объявленным значениям тангажа и рыскания:

На третьем этапе нам нужно добавить некоторые ограничения к камере, чтобы пользователи не могли совершать странные движения (включая так называемый LookAt-переворот, когда вектор направления параллелен вектору-вверх глобального мира). Тангаж должен быть ограничен таким образом, чтобы пользователи не могли смотреть выше 89 градусов (при 90 градусах мы получаем LookAt-переворот), а также не ниже -89 градусов. Это гарантирует, что пользователь сможет смотреть вверх на небо или вниз на свои ноги, но не дальше. Ограничения работают путем замены значения угла Эйлера его значением ограничения всякий раз, когда оно выходит за указанные пределы:

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

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

Данный вычисленный вектор направления содержит все вращения, вычисленные на основе движения мыши. Поскольку вектор cameraFront уже содержится в GLM-функции lookAt(), то мы готовы к работе.

Если бы мы сейчас запустили код, то заметили бы, что камера делает большой резкий скачок всякий раз, когда окно впервые получает фокус от нашего курсора мыши. Причина этого внезапного скачка заключается в том, что, как только наш курсор входит в окно, вызывается callback-функция мыши с позицией xpos и ypos, равной местоположению, из которого наша мышь попала на экран. Часто данная позиция находится на значительном удалении от центра экрана, что приводит к большим смещениям и, следовательно, к большому скачку движения. Мы можем обойти эту проблему, определив глобальную переменную типа bool, чтобы проверить, является ли это первый раз, когда мы получаем ввод мыши. Если это происходит в первый раз, то мы обновляем начальные позиции мыши до новых значений xpos и ypos. Затем результирующие движения мыши будут использовать вновь введенные координаты положения для расчета смещений:

В итоге код превращается в:

Ну вот и всё! Давайте запустим пример и понаблюдаем за тем, как теперь мы можем свободно перемещаться по нашей 3D-сцене!

Масштабирование

В качестве небольшого дополнения к камере мы также реализуем интерфейс масштабирования. На предыдущем уроке мы говорили, что поле зрения (или fov) в значительной степени определяет, насколько мы можем видеть сцену. Когда поле зрения становится меньше, то проецируемое пространство сцены также становится меньше. Это уменьшенное пространство проецируется на те же NDC, создавая иллюзию увеличения масштаба. Для увеличения масштаба мы будем использовать колесо прокрутки мыши. Подобно движению мыши и вводу с клавиатуры у нас есть callback-функция для прокрутки мыши:

При вертикальной прокрутке значение yoffset сообщает нам величину, которую мы прокрутили. Вызывая функцию scroll_callback(), мы изменяем содержимое глобально объявленной переменной fov. Поскольку 45.0 является fov-значением по умолчанию, то мы хотим ограничить уровень масштабирования между 1.0 и 45.0.

Теперь мы должны в каждом кадре отправлять на GPU матрицу перспективной проекции, но на этот раз с переменной fov в качестве поля зрения:

И, наконец, не забудьте зарегистрировать callback-функцию прокрутки:

И вот оно! Мы внедрили простую камеру, которая позволяет свободно перемещаться в 3D-окружении:

Посмотреть результат

  GitHub / Урок №9. Камера в OpenGL — Исходный код №3

Пользовательский класс Camera

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

Как и в случае шейдерного объекта, мы полностью определяем класс камеры в одном заголовочном файле с названием camera.h. Рекомендуется хотя бы один раз проверить класс в качестве примера того, как вы могли бы создать свою собственную камеру.

Примечание: Камера, которую мы только что разработали — это летающая камера, которая подходит для большинства целей и хорошо работает с углами Эйлера, но будьте осторожны при создании различных камер, таких как FPS-камера или камера моделирования полета. Каждая камера имеет свои собственные трюки и причуды, так что не забудьте о них сначала разузнать. Например, наша летающая камера не допускает значений тангажа выше или равных 90 градусам, а статический вектор-вверх (0,1,0) не работает, когда мы учитываем значения крена.

Обновленную версию исходного кода с использованием нового объекта камеры можно найти здесь:

  GitHub / Урок №9. Камера в OpenGL — Исходный код №4

Упражнения

Задание №1

Посмотрите, можно ли преобразовать класс камеры таким образом, чтобы он стал настоящей FPS-камерой, где вы не сможете летать; вы можете только смотреть вокруг, оставаясь на плоскости xz.

Ответ №1

Задание №2

Попробуйте придумать свою собственную LookAt-функцию, в которой вы вручную создадите матрицу вида, как описано в начале этого урока. Замените GLM-функцию LookAt() своей собственной реализацией и посмотрите, работает ли она так, как нужно.

Ответ №2

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (33 оценок, среднее: 5,00 из 5)
Загрузка...

Комментариев: 17

  1. Powarito:

    В первом задании вместо хард-кодового присваивания y-компоненте вектора Position камеры значения нуля, лучше использовать следующие подходы:

    1. Взять вектор направления Front, присвоить его y-компоненте ноль, и нормализировать полученый вектор, и для движения вперед и назад использовать этот вектор вместо Front.

    float velocity = MovementSpeed * deltaTime;

    glm::vec3 horizVec = Front;
    horizVec.y = 0.0f;
    horizVec = glm::normalize(horizVec);

    if (direction == Camera_Movement::FORWARD)
    Position += horizVec * velocity;
    if (direction == Camera_Movement::BACKWARD)
    Position -= horizVec * velocity;
    if (direction == Camera_Movement::LEFT)
    Position -= Right * velocity;
    if (direction == Camera_Movement::RIGHT)
    Position += Right * velocity;

    Это будет работать, когда у нас камера никогда не смотрит ровно в 90 грудусов вверх или в 0 градусов вниз (тут x и z компоненты будут тогда равны ноль). Но в коде мы этому не даем случиться, потому что камера переварачивалась бы.

    2. Как уже писал пользователь @Grave18, можно взять векторное произведение векторов верха мира и веткора-вправо камеры, и получим вектор, указывающий вперед в нужном направлении:

    glm::vec3 front = glm::cross(WorldUp, Right);

    Но тут, чтобы front был нормализованным, нужно либо его самого нормализировать, либо сделать так, чтобы WorldUp и Right были сами единичными и перпендикулярными. В пользовательском коде WorldUp может быть задан не как (0, 1, 0), а другими значениями. Я думаю, в конструкторе класса Camera нужно нормализировать WorldUp после его инициализации (ну или входной параметр, которым он инициализируется).
    Тут, по логике, даже если камера будет смотреть ровно вверх или вниз (90 или 0 градусов соответственно), то все равно камера будет двигаться.

  2. lngvar:

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

    А почему ? потому что в статье забыли указать то, что можно найти в исходнике

    И еще досадная ошибка, которая не позволяет скролить

    надо поставить fov<=45.0f в первом if  .  иначе не будет шанса изменить этот fov.

    Но , в целом, все очень неплохо. Спасибо за курс

  3. Сергей Федоров:

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

    Вынес это перед циклом — всё работает.

  4. Elan:

    Зачем в коде класса в фунции ProcessKeyboard() использовать множественные if, если лучше будет switch?

  5. Андрей:

    Здравствуйте, столкнулся с непониманиманием в разделе углы Эйлера. Там написано что "длина основания x треугольника равна cos(θ)/h". Но если cos(θ) = x/h, то при переносе h в другую сторону получается x = cos(θ) * h.

    1. Сергей Андреевич:

      В данном примере гипотенуза равна 1.

  6. splashgy:

    Делал первое упражнение, я конечно может быть я ошибаюсь, но подход в виде записи 0.0f как положение камеры по y по моему мнению немного непрактичен. Не имело бы больше смысла выделить дополнительную переменную под последнее положение по координате y и присваивать именно её как позицию по высоте? Просто если реализовывать прыжки, то тогда при любом передвижении в одну из сторон игрок будет примагничиваться к "земле". Т.е. не имело бы больше смысла реализовать этот callback следующим образом:

  7. Trigun:

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

  8. Akr1d0v:

    У меня появился вопрос по поводу камеры, а именно в первом задание мы делаем так, что камеры движется только по xz, но если полностью повернуть камеру на 89 градусов(или -89 градусов), то скорость движения камеры уменьшается, и если зайти сейчас в любую 3D игру, то при таких же условиях мы будем двигаться с такой же скоростью, как если бы смотрели прямо. Я понимаю, почему это происходит, но я не могу найти решение этой проблемы.

  9. Grave18:

    Как вариант, в первом упражнении, чтобы не хардкодить позицию камеры Y = 0, можно пересчитать отдельный вектор-вперед камеры специально для движения вперед и назад в функции ProcessKeyboard():

  10. Артём:

    Спасибо большое за замечательные переводы уроков! ))
    Подскажите пожалуйста, а как реализовать классический поворот камеры вокруг объекта при нажатии левой кнопки мыши, как это делается во всех 3D программах? Может быть есть уже какое-нибудь готовое решение?

  11. Георгий:

    Здравствуйте! Поймал непонятную ситуацию у себя. Чтоб резких движений не делать, решил уточнить. В mouse_callback получаю все время возрастающие значения xpos и ypos, в независимости от движения мыши. Причем если убираю glfwSetInputMode(glfw_cursor_disabled), то значения уже корректные, но мышка выходит из окна. Это с opengl проблема? Есть ли ещё способ мышь в экране оставить?
    Спасибо.

  12. Артём:

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

    Мои мысли на этот счет: у нас есть трехмерные координаты вершин фигур, которые после обработки шейдерами превращаются в двухмерные координаты нашего окна на экране. При клике мышкой мы можем получить координаты нахождения курсора.
    И собственно пути решения состоит в том чтобы:
    1) каким-то образом сохранять двухмерные координаты фигур и сравнивать их с двухмерными координатами мышки.
    2) ИЛИ превращать двухмерные координаты мышки в трехмерные координаты с помощью определенных математических операций и сравнивать их с трехмерными координатами наших фигур.

  13. Арбузик❤❤❤:

    Нашел незначительную ошибку: при перемещении мыши вверх камера поворачивается вниз, а не также вверх, и наоборот. В функции ProcessMouseMovement заменил
    pitch += yoffset;
    на
    pitch -= yoffset;
    Таким образом, камера смотрит туда же куда и мышь.

    1. Фото аватара Дмитрий Бушуев:

      Странно, только что проверил — у меня всё нормально.
      Можете указать на каком конкретно месте в статье (или в каком архиве с исходниками) у вас появилась данная проблема?

      1. Арбузик❤❤❤:

        Код я писал по мере прохождения урока, так что он отличается от исходников. Попробовал скачать последний архив и запустить проект из него у себя — всё, как вы и сказали, нормально. Думаю, у меня это произошло из-за того, что написал не так реализацию одного из методов камеры. 😀
        Извините пожалуйста за потраченное время!

        1. Фото аватара Дмитрий Бушуев:

          Хорошо, что всё разрешилось 🙂

Добавить комментарий для Арбузик❤❤❤ Отменить ответ

Ваш E-mail не будет опубликован. Обязательные поля помечены *