Установка матриц трансформаций и камеры

Автор: Евгений Рыжков Дата публикации: 26.03.2013

В этом уроке мы продолжим изучать OpenGL. В этом уроке вы узнаете: какие переменные существуют для установки матриц камеры; какие есть средства для установки координат и параметров камеры; для чего существуют константы шейдера и как их устанавливать; а также для чего нужна матрица трансформаций и перспективная матрица.

Типы данных для хранения параметров камеры

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

Существует два вида представления камеры: ненаправленная,

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


class CameraDirectional
{
        glm::vec3 pos;
        glm::vec3 direction;
        float FOV;
};

Такая камера будет называться ненаправленной, так как не существует определенной точки в которую она смотрит. В качестве направления вы можете определить любой нормализованный вектор. Умножив вектор directionна одну или несколько матриц поворота вы сможете поворачивать камеру. Преимущество ненаправленной камеры в том, что для неё не обязательно иметь точку, в которую она смотрит – а постоянно следить за тем, какую конкретно точку направить камеру и к чему эту точку привязать – весьма проблематично.

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


class CameraTarget
{
        glm::vec3 pos;
        glm::vec3 target;
        float FOV;
}

Типы данных OpenGL

В предыдущем уроке мы рассмотрели различные математические операции и представления чисел. Давайте немного отвлечемся от установки камеры и рассмотрим, какие типы данных понадобятся в OpenGL 4.x:

  • Для хранения скалярных значений — чисел с плавающей точкой используется тип данных float
  • Для хранения трехкомпонентных векторов используется тип данных glm::vec3
  • Для хранения информации о цвете используется glm::vec4
  • Матрицы в OpenGLописываются типом данных glm::mat4

Также существуют и другие типы данных OpenGL, однако они гораздо менее популярны.

Типы данных для хранения параметров камеры

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


struct Vertex
{
        glm::vec3 coord;
} Vertices[];

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


glm::mat4 mView;
glm::mat4 mProjection;

И затем умножить координату каждой из вершин на обе матрицы:


FromViewVertices[].coord=Vertices[].coord*mView*mProjection;

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

class CameraTarget
{
        glm::vec3 pos;
        glm::vec3 target;
        float FOV;
}

Но, перед использованием в вашем приложении, необходимо преобразовать данные камеры из этого формата в матрицы.

Установка матрицы камеры и проективной матрицы в OpenGL

Итак, продолжим. Перед тем как установить камеру типа CameraTargetвам потребуется преобразовать её координаты в матрицы OpenGL, для этого в OpenGL 4.xсуществует следующая функция:

glm::mat4 glm::lookAt(glm::vec3 Eye,glm::vec3 At,glm::vec3 Up);

Выходящими данными функции будет являться матрица. Входящие параметры определяют положение и направление камеры, что подходит для нашего объекта типа CameraTarget.

Таким образом, для того, чтобы преобразовать объект из CameraTargetв нужные нам матрицы нужно сделать следующее. Сначала заполним CameraTargetкакими-либо данными:

CameraTarget           MyCamera;
MyCamera.pos=          glm::vec3 (0,1.0f,-2.0f);
MyCamera.target=       glm::vec3 (0,1.0f,0);
MyCamera.FOV=          glm::90.0f;
glm::vec3 MyUpVector=  glm::vec3 (0,1.0f,0);

Обратите внимание, что вместе с заданием координат камеры мы также задали параметр FOV. Это fieldofview, то есть угол обзора. Просто камера может узкоуголной и широкоугольной – по вашему выбору. Оказывается, в OpenGLтоже можно менять линзы! Зададим в виде глобальных переменных матрицы

glm::mat4                  mWorld;
glm::mat4                  mView;
glm::mat4                  mProjection;

Теперь зададим нужные матрицы камеры и проекций:

mWorld=        glm::translation(1.0f,1.0f,1.0f);
mView=         glm::lookAt(MyCamera.pos,MyCamera.target,MyUpVector);
mProjection =  glm::perspective(MyCamera.FOV,1.6f.0f,0.1f,100.0f);

Обратите внимание, что хотя нам нужны были две матрицы мы установили все три требуемые матрицы OpenGL. Это сделано для того, чтобы просто чем-то заполнить необходимую матрицу mWorld. Мы заполнили mWorld единичной матрицей, что означает отсутствие предварительных преобразований, таких как масштабирование и перемещение.

Установка матриц камеры, трансформаций и проективной матрицы в качестве констант шейдера

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

Для установки любых значений констант шейдера в OpenGL существует несколько функций. Все эти функции начинаются с токенаglUniform. Далее в названии функции идет тип данных, так как можно устанавливать значения для скаляра, для вектора, также можно устанавливать константы целой матрицы. Перед тем как установить значение константы нужно получить идентификатор ID данной константы. Таким образом, сначала получим идентификаторы для констант матриц шейдера:

int iWorld =        glGetUniformLocation(spMain.pID(), "mWorld"); 
int iView =         glGetUniformLocation(spMain.pID(), "mView"); 
int iProjection =   glGetUniformLocation(spMain.pID(), "mProjection");

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

glUniformMatrix4fv(iWorld, 1,      GL_FALSE, glm::value_ptr(mWorld));
glUniformMatrix4fv(iView, 1,       GL_FALSE, glm::value_ptr(mView));
glUniformMatrix4fv(iProjection, 1, GL_FALSE, glm::value_ptr(mProjection));

После установки матриц можно сразу же запускать рендер наших примитив, то есть содержимого обобщенного буфера вершин и индексов с помощью знакомого нам кода.

Полезные мелочи: матрица трансформаций

Итак, мы установили матрицы для камеры, однако невыясненным остался вопрос, зачем нужна переменная mWorld, заполнявшееся единичной матрицей. Это матрица трансформаций. На самом деле этой переменной можно присваивать значение, отличное от единицы. И это полезная матрица, так как перед тем как отобразить объект мы его можем правильно расположить в пространстве.

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

При помощи матриц трансформаций mWorld мы можем переместить объекты куда угодно перед тем как сделать их Render. При этом предварительно мы можем повернуть объект вокруг своей оси на нужный угол а также увеличить или уменьшить объект. Зачастую объекты «приходят» из 3d редактора в таком виде, что их надо подгонять по размеру. В редких случаях в редакторе можно точно угадать и подобрать нужную величину создаваемого объекта, так как системы измерений всегда могут отличаться.

Пример приложения

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