Уроки по OpenGL с сайта OGLDev


Урок 14 - Управление камерой - часть 1

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

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

Для использования клавиатуры нам потребуется другой API GLUT'a: glutSpecialFunc(). Эта функция записывает нашу для вызова, если будет нажата "специальная" клавиша. В группу специальных клавиш входят: F{1..12}, стрелки и PAGE-UP / PAGE-DOWN / HOME / END / INSERT. Если вам нужны обычные кнопки (символы и цифры) используйте glutKeyboardFunc().

Прямиком к коду!

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

Camera.h

class Camera
{
public:
    Camera();
    Camera(const Vector3f& Pos, const Vector3f& Target, const Vector3f& Up);
    bool OnKeyboard(int Key);
    const Vector3f& GetPos() const
    const Vector3f& GetTarget() const
    const Vector3f& GetUp() const

private:
    Vector3f m_pos;
    Vector3f m_target;
    Vector3f m_up;
};

Это объявление класса камеры. Он хранит 3 свойства, которые характеризуют камеру - позиция, направление и верхний вектор. Так же добавлены 2 конструктора. По умолчанию просто располагает камеру в начале координат, направляет ее в сторону уменьшения Z, а верхний вектор устремляет в "небо" (0,1,0). Но есть возможность создать камеру с указанием значений атрибутов. OnKeyboard() доставляет события клавиатуры в класс. Он возвращает значение типа bool, который указывает, воспринято ли камерой событие. Если клавиша подходит (одна из указывающих направление), возвращаемое значение - true. Если нет - false. Таким способом можно создать цепь клиентов, которые будут получать события и останавливаться, когда один из них воспользуется этим событием.

Camera.cpp:42

bool Camera::OnKeyboard(int Key)
{
    bool Ret = false;
    switch (Key) {
        case GLUT_KEY_UP:{
            m_pos += (m_target * StepSize);
            Ret = true;
            } break;
        case GLUT_KEY_DOWN:{
            m_pos -= (m_target * StepSize);
            Ret = true;
            } break;
        case GLUT_KEY_LEFT:{
            Vector3f Left = m_target.Cross(m_up);
            Left.Normalize();
            Left *= StepSize;
            m_pos += Left;
            Ret = true;
            } break;
        case GLUT_KEY_RIGHT:{
            Vector3f Right = m_up.Cross(m_target);
            Right.Normalize();
            Right *= StepSize;
            m_pos += Right;
            Ret = true;
            } break;
    }
    return Ret;
}

Эта функция двигает камеру согласно событиям клавиатуры. GLUT определил несколько макросов, обозначающих клавиши направления и этот оператор выбора основывается на них. К сожалению, тип этих макросов просто 'int', а не enum.

Движение вперед и назад проще. Так как это движение вдоль вектора направления, нам нужно только прибавить или вычесть вектор направления из вектора позиции. Вектор направления остается без изменений. Заметим, что прежде чем прибавлять / вычитать вектор направления, мы умножаем его на константу названую 'StepSize' (размер шага). Мы делаем это для всех направлений. Размер шага позволяет удобно изменять скорость (мы можем внести этот параметр в класс). Для удобства мы можем представить размер шага в виде единицы длины векторов (т.к. мы должны быть уверены, что вектор направления и верхний вектор являются векторами единичной длины).

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

Заметим, что в этой функции были использованы несколько новых операторов, такие как '+=' и '-=', которые были добавлены в класс Vector3f.

main.cpp:96

static void SpecialKeyboardCB(int Key, int x, int y){
    GameCamera.OnKeyboard(Key);
}
static void InitializeGlutCallbacks(){
    glutDisplayFunc(RenderSceneCB);
    glutIdleFunc(RenderSceneCB);
    glutSpecialFunc(SpecialKeyboardCB);
}

Здесь мы регистрируем новую функцию обратного вызова для получения специальных событий клавиатуры. Функция отправляет клавишу и позицию курсора в момент нажатия кнопки. Мы игнорируем координаты курсора и отправляем событие в нашу камеру, которая уже объявлена как глобальная переменная.

main.cpp:78

p.SetCamera(GameCamera.GetPos(), GameCamera.GetTarget(), GameCamera.GetUp());

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

powered byDisqus