Наконец-то мы добрались до 6 урока!
float FoV = initialFoV - 5 * glfwGetMouseWheel();
// Матрица проекции : 45° Угол обзора, 4:3 соотношение сторон, дальность видимости от 0.1 до 100
ProjectionMatrix = glm::perspective(FoV,4.0f / 3.0f, 0.1f, 100.0f );
// Матрица камеры
ViewMatrix= glm::lookAt(
position, // Положение камеры
position+direction, // А вот сюда мы смотрим
up
);
В этом уроке мы будем учиться использовать клавиатуру у
мышку, чтобы двигать камеру как в игре шутере.
Интерфейс
Так как этот код будет использоваться везде в последующих уроках, давайте поместим код в отдельный файл common/controls.cpp, а сами функции объявим в файле common/controls.h поэтому в следующих уроках нам не придется писать этот код заново.
В этом уроке не будет таких прям уж больших изменений по
сравнению с нашими предыдущими наработками. Самое большое отличие — мы будем
вычислять МВП матрицу не один раз, при старте приложения, а каждый кадр. Для
этого давайте сначала перенесем этот код в главный цикл:
do{
// ...
// Вычисляем
МВП матрицу в зависимости от нажатий клавиш и двигания мышкой
computeMatricesFromInputs();
glm::mat4
ProjectionMatrix = getProjectionMatrix();
glm::mat4
ViewMatrix = getViewMatrix();
glm::mat4
ModelMatrix = glm::mat4(1.0);
glm::mat4 MVP =
ProjectionMatrix * ViewMatrix * ModelMatrix;
// ...
// ...
}
В этом коде у нас есть 3 новых функции:
·
computeMatricesFromInputs() читаем состояние
клавиатуры и мыши и вычисляем матрицы Проекции и Вида. В этой функции и будет
происходить вся основная магия.
·
getProjectionMatrix() возвращает вычисленную
матрицу Проекции.
·
getViewMatrix() возвращает вычисленную матрицу
Вида.
Это, конечно, лишь один из способов создания подобной
функциональности. Если вам он не нравится, переделайте как хотите.
Код выполняющий всю магию
Нам нужно несколько переменных:
// позиция
glm::vec3 position = glm::vec3( 0, 0, 5 );
// горизонтальный угол : по -Z
float horizontalAngle = 3.14f ;
// вертикальный угол : 0, смотрим на горизонт
float verticalAngle = 0.0f ;
// Угол обзора
float initialFoV = 45.0f ;
float speed =3.0f ; // 3
в секунду
float speed =
float mouseSpeed = 0.005f ;
О FoV можно думать как об уровне увеличения. 80 — очень
широкий угол, все уменьшено и сильно искажено. 60-45 — нормальный вид. 20 —
подзорная труба.
Нам нужно будет пересчитывать позицию, горизонтальный угол и
вертикальный угол и угол обзора в зависимости от действий пользователя.
Ориентация
Читать ввод с мышки очень просто:
// получаем координаты курсора
int xpos, ypos;
glfwGetMousePos(&xpos, &ypos);
int xpos, ypos;
glfwGetMousePos(&xpos, &ypos);
но нам нужно не забывать о том, что необходимо передвигать
курсор назад в центр экрана, иначе он вскоре выйдет за пределы окна, или
упрется в край монитора и больше не будет двигаться.
// Двигаем мышь в центр экрана
glfwSetMousePos(1024/2, 768/2);
glfwSetMousePos(1024/2, 768/2);
Заметьте, код предполагает, что окно у нас размера 1024*768,
что, конечно, не всегда верно. Если хотите сделать красивее, можно получить
размеры окна функцией glfwGetWindowSize.
Теперь вычислим необходимые углы:
horizontalAngle += mouseSpeed * deltaTime * float(1024/2
- xpos );
verticalAngle += mouseSpeed * deltaTime * float( 768/2 - ypos );
verticalAngle += mouseSpeed * deltaTime * float( 768/2 - ypos );
Краткое описание того, что делает этот код:
- 1024/2 — положение по Х. Чем дальше мы от центра, тем на больший угол нужно повернуться.
- float(...) преобразовываем всем в дробные числа
- mouseSpeed — коефициент, чтобы ускорить или замедлить вращение. Чтобы правильно настроить его, нужно просто попробовать разные значения.
Теперь мы можем посчитать вектор, который будет представлять
направление в Мировом Пространстве в которое смотрит камера.
// направление : Преобразовываем сферические координаты в
декартовы
glm::vec3 direction(
glm::vec3 direction(
cos(verticalAngle)
* sin(horizontalAngle),
sin(verticalAngle),
cos(verticalAngle)
* cos(horizontalAngle)
);
Это стандартный способ перехода от сферических координат в
декартовы. Если вы ничего не знаете о синусах и косинусах — вот краткое
описание:
Формула выше — это то же самое, но в 3д пространстве.
Формула выше — это то же самое, но в 3д пространстве.
Теперь нам нужно правильно посчитать “up” вектор. Заметьте, «вверх», это
совсем не обязательно в сторону +Y. Если вы опустите голову вниз, то ваша макушка
будет направлена горизонтально.
Единственная для нас константа за которую можно привязаться
— вектор который выходит справа от камеры всегда горизонтальный.
Это можно проверить, если вы выпрямите свою правую руку вбок
и начнете крутиться вокруг своей оси, или смотреть вверх и вниз, рука будет
горизонтальной.
Давайте объявим вектор «вправо»: его координата Y будет 0,
так как он горизонтальный, а координаты X и Z будут вычисляться так:
// Вектор «вправо»
glm::vec3 right = glm::vec3(
sin(horizontalAngle - 3.14f/2.0f),
0,
cos(horizontalAngle - 3.14f/2.0f)
);
glm::vec3 right = glm::vec3(
sin(horizontalAngle - 3.14f/2.0f),
0,
cos(horizontalAngle - 3.14f/2.0f)
);
Теперь у нас есть вектор «вправо» и вектор «направление».
Вектор «вверх» это вектор который перпендикулярный этим двум. Для нахождения
перпендикулярных векторов издавна есть прекрасный метод — векторное
произведение(cross product)
// Вектор «вверх»: перпендикуляр к направлению и к
«вправо»
glm::vec3 up = glm::cross( right, direction );
glm::vec3 up = glm::cross( right, direction );
Чтобы запомнить как работает векторное произведение, просто
вспомните правило правой руки из урока 3. Первый вектор, это большой палец,
второй вектор — указательный, а средний палец — это и есть векторное
произведение. Все очень просто и удобно.
Позиция
Код для вычисления позиции достаточно прост:
// Двигаемся вперед
if (glfwGetKey( GLFW_KEY_UP ) == GLFW_PRESS){
position += direction * deltaTime * speed;
}
// Двигаемся назад
if (glfwGetKey( GLFW_KEY_DOWN ) == GLFW_PRESS){
position -= direction * deltaTime * speed;
}
// Шаг вправо
if (glfwGetKey( GLFW_KEY_RIGHT ) == GLFW_PRESS){
position += right * deltaTime * speed;
}
// Шаг влево
if (glfwGetKey( GLFW_KEY_LEFT ) == GLFW_PRESS){
position -= right * deltaTime * speed;
}
if (glfwGetKey( GLFW_KEY_UP ) == GLFW_PRESS){
position += direction * deltaTime * speed;
}
// Двигаемся назад
if (glfwGetKey( GLFW_KEY_DOWN ) == GLFW_PRESS){
position -= direction * deltaTime * speed;
}
// Шаг вправо
if (glfwGetKey( GLFW_KEY_RIGHT ) == GLFW_PRESS){
position += right * deltaTime * speed;
}
// Шаг влево
if (glfwGetKey( GLFW_KEY_LEFT ) == GLFW_PRESS){
position -= right * deltaTime * speed;
}
Единственная вещь на которой я хотел бы заострить внимание —
deltaTime. Мы ввели её из-за того, что вы вряд ли бы хотели смещать камеру на 1
каждый кадр, так как:
- Если у вас быстрый компьютер, и сцена рендерится, например, со скоростью в 100fps, то камера будет перемещаться со скоростью 100 единиц в секунду.
- Если у вас медленный компьютер, и сцена рендерится, например, со скоростью в 20fps, то камера будет перемещаться со скоростью 20 единиц в секунду.
Чтобы избежать этих проблем с разной скоростью перемещения
на разных компьютерах обычно пользуются такой штукой как deltaTime — или время
которое прошло с последнего кадра.
- Если у вас быстрый компьютер, и у вас 100 fps, то вы двигаетесь со скоростью 1/100*скорость за один кадр. Итого 1*скорость в секунду.
- Если у вас медленны компьютер, и у вас 20 fps, то вы двигаетесь со скоростью 1/20*скорость за один кадр. Итого 1*скорость в секунду.
Это уже гораздо лучше. Сам же deltaTime вычисляется очень
просто:
double currentTime = glfwGetTime();
float deltaTime = float(currentTime — lastTime);
double currentTime = glfwGetTime();
float deltaTime = float(currentTime — lastTime);
Угол обзора
Ради любопытства давайте установим угол обзора зависимым от колесика мышки:float FoV = initialFoV - 5 * glfwGetMouseWheel();
Вычисление матриц
Вычисление самих матриц очень простое, используем те же функции, что и раньше, только подставляем вычисленные нами параметры:// Матрица проекции : 45° Угол обзора, 4:3 соотношение сторон, дальность видимости от 0.1 до 100
ProjectionMatrix = glm::perspective(FoV,
// Матрица камеры
ViewMatrix= glm::lookAt(
position, // Положение камеры
position+direction, // А вот сюда мы смотрим
up
);
Результат
Отсечение невидимых граней
Теперь, когда вы можете свободно двигаться по пространству, можно заметить, что если мы зайдем внутрь куба, то внутренние грани тоже будут видимы. И хотя это может казаться правильным, у нас есть поле для оптимизации. В обычных приложениях мы обычно не входим внутрь объектов.
Идея такова, что мы проверяем положение камеры относительно
треугольника. Если камера спереди, то показываем треугольник, если сзади — нет.
Так как обычно модели замкнуты, и мы находимся не внутри, то мы и не должны
видеть полигоны с обратной стороны. И если не рисовать эти не видимые полигоны,
то это может ускорить рендеринг раза в 2.
Самое интересное, что эту проверку очень просто включить.
GPU автоматически вычисляет нормаль каждого треугольника(помните векторное
произведение?) и проверяет его
направление по отношению к камере.
Один недостаток — ориентация треугольника задана положением
его вершин. Это означает, что если вы переставите две вершины в вашем
мешбуфере, то у вас будет дырка в кубе. Но обычно никто не создает 3д модели в
коде, а делает их в 3д редакторах, а там эта проблема устраняется командой
«инвертировать нормали»(на самом деле, как я надеюсь вы уже поняли,
инвертируются сами вершины, что в результате и приводит к инвертированию
нормалей).
Вот как включить отсечение обратных граней:
// Не показываем треугольники которые смотрят не на камеру
glEnable(GL_CULL_FACE);
// Не показываем треугольники которые смотрят не на камеру
glEnable(GL_CULL_FACE);
Упражнения
- Ограничьте вертикальный угол, чтобы пользователь не мог двигаться верх и вниз.
- Создайте камеру которая будет вращаться вокруг объекта(позиция=центрОбъекта+(радиус*cos(время), высота, радиус*sin(время)))
Здравствуйте, Админ))
ОтветитьУдалитьНе могли бы вы сбросить сюда исходник? (disqrl@yandex.ru)
уроки классные, но действительно нужны исходники..
ОтветитьУдалитьИсходники можно найти вот тут: http://code.google.com/p/opengl-tutorial-org/source/browse/
ОтветитьУдалитьХороши уроки!Жаль Админ не автор!
ОтветитьУдалитьЭтот комментарий был удален автором.
ОтветитьУдалитьЭтот комментарий был удален автором.
ОтветитьУдалитьДобрый день, в исходном tutorial 'е и тут увидел переменную deltaTime, но в исходном коде controls.cpp её нет, можете объяснить её необходимость? или же это очепятка?
ОтветитьУдалитьТеперь вычислим необходимые углы:
horizontalAngle += mouseSpeed * deltaTime * float(1024/2 - xpos );
verticalAngle += mouseSpeed * deltaTime * float( 768/2 - ypos );
переменная очень замедляет скорость обзора мышкой
Удалить