Сайт о телевидении

Сайт о телевидении

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

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

В компьютерной графике определенны понятия различных матриц. Это мировая матрица (World Matrix), матрица вида (View Matrix) и матрица проекции (Projection Matrix). С помощью данных матриц в исходном коде программы производятся матричные преобразования над моделями. Матричные преобразования подразумевают под собой умножение каждой вершины объекта на одну из матриц, а точнее последовательное умножение всех вершин объекта на каждую из трех матриц. Такой подход позволяет корректно представить модель в трехмерном пространстве вашего двухмерного монитора. Техника прохода модели через три перечисленные матрицы представляет суть механизма работы с графическими данными в трехмерной плоскости монитора.

Мировая матрица

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

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

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


Мировая матрица

где cos - угол вращения в радианах.

Вращение вершины вокруг оси Y выглядит так:


Вращение вершины вокруг оси Y

А вращение вокруг оси Z происходит по следующей формуле:


вращение вокруг оси Z

Трансляция вершины позволяет переместить эту саму вершину с координатами x, y, z в новую точку с новыми координатами x1, y1, z1. В математической записи это выглядит так:

X1 = x + Tx y1 = y + Ty z1 = z + Tz

Трансляция вершины в матричной записи выглядит следующим образом:


Трансляция вершины в матричной записи

где Tx, Ty и Tz - значения смещения по осям X, Y и Z.

Масштабировать вершину в пространстве (удалять или приближать) с координатами x, y, z в новую точку с новыми значениями x1, y1, z1, можно посредством следующей записи:

X1 = x * S y1 = y * S z1 = z * S

В матричной записи это выражается следующим образом:


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

где Sx, Sy, Sz - значения коэффициентов растяжения или сжатия по осям X, Y, Z.

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

Матрица вида

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

Фактически эта матрица позволяет определять жанр игры. Например, игра DOOM от первого лица – это можно сказать первые ряды портера в театре, тогда как игра Warcraft – это галерка на балконе. Матрица вида предназначена для определения положения камеры в пространстве, и вы можете смещать позицию камеры влево, вправо, вверх, вниз, удалять, приближать ее и так далее.

Матрица проекции

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


Матрица проекции

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

На рисунке мы видим координатное пространство камеры. Направление ("взгляд") камеры всегда совпадает с положительным направлением оси z, а сама камера расположена в начале координат.

Внутреннее пространство пирамиды изображённой на рисунке - это та часть виртуального мира, которую увидит пользователь.

Обратите внимание на три плоскости. Первая расположена на расстоянии 1 по оси z. Это ближняя плоскость. То что находится до неё игрок никогда не увидит. В данном случае значение z равно единице, но вообще говоря, оно может быть любым. Именно с ближней плоскостью связан один дефект отображения графики. Этот дефект проявляется прежде всего в шутерах (из-за большой свободы камеры). Когда ты слишком близко подходишь к объекту, то можно оказаться "внутри". Из последних игр этот дефект особенно сильно проявлялся в Left 4 dead: когда на игрока наваливалась толпа зомби, то очень часто можно было заглянуть внутрь других персонажей.

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

Шесть плоскостей ограничивающих пространство, которое увидит пользователь, называются отсекающими (clipping planes): левая правая верхняя нижняя ближняя и дальняя.

Плоскость расположенная между ближней и дальней - проекционная. В дальнейшем, эту плоскость мы будем располагать в z=1, т.е. она будет совпадать с ближней. Здесь я отделил ближнюю и проекционную плоскости, чтобы показать, что это всё-таки не одно и то же. Проекционная плоскость предназначена для последнего преобразования координат: преобразование из трёхмерного пространства камеры - в двухмерное пространство.

Именно благодаря проекционной плоскости пользователь увидит виртуальный мир. Собственно, эта плоскость и есть то, что увидит пользователь. Проекционная плоскость напрямую связана с такими понятиями как основной/фоновый буферы, окно программы и экран пользователя. Все эти понятия можно рассматривать как прямоугольную картинку, которая в памяти компьютера представлена массивом цифр.

Преобразование координат из трёхмерного мира в проекционную плоскость - самое сложное из тех, которые на данный момент были нами изучены.

Поле зрения/зона обзора (field of view)

На рисунке выше у проекционной плоскости (а значит и у изображения, которое увидит пользователь) ширина больше высоты. Ширина и высота проекционной плоскости задаются с помощью углов. Встречаются разные названия этих углов: поля зрения или зоны обзора. В английском - fields of view.

Зоны обзора задаются двумя углами. Назовём их: fovx - зона обзора по горизонтали, fovy - зона обзора по вертикали. Подробно о зонах обзора: ниже.

Z-буфер / w-буфер / буфер глубины (z-buffer / w-buffer / depth buffer)

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

Как вы возможно догадываетесь, изображение нужно рисовать начиная с самых дальных элементов и заканчивая самыми ближними. Очевидное решение: вычислить расстояние от начала координат (от камеры) до каждого объекта, а затем сравнить. В компьютерной графике используется немного более усовершенствованный механизм. У этого механизма несколько названий: z-буфер, w-буфер, буфер глубины. Размер z-буфера по количеству элементов совпадает с размером фонового и основного буферов. В z-буфер заносится z-компонента самого ближнего к камере объекта. В данном примере, там где синий треугольник перекрывает зелёный, в буфер глубины будут занесены z-координаты синего. Мы ещё поговорим о z-буферах более подробно в отдельном уроке.

Ортографическая / параллельная проекция (orthographic / parallel projection)

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

Для вычисления параллельной проекции достаточно отбросить лишнюю координату. Если у нас есть точка в пространстве [ 3 3 3 ], то при параллельной проекции на плоскость z=1, она спроецируется в точку .

Перспективная проекция (perspective projection) на проекционную плоскость

В данном виде проекции все линии сходятся в одной точке. Именно так устроено наше зрение. И именно с помощью перспективной проекции моделируется "взгляд" во всех играх.


Сравните этот рисунок с рисунком показывающим однородные координаты из предыдущего урока. Чтобы из трёхмерного пространства перейти в двухмерное, нужно первые две компоненты векторов разделить на третью: [ x/z y/z z/z ] = [ x/z y/z 1 ].

Как я уже писал выше, проекционная плоскость может располагаться где угодно между ближней и дальней. Мы будем всегда размещать проекционную плоскость в z=1, но в этом уроке мы рассмотрим и другие варианты. Посмотрим на картинку:


Расстояние до проекционной плоскости от начала координат обозначим как d. Мы рассмотрим два случая: d=1 и d=5. Важный момент: третья компонента всех векторов после проекции должна быть равна d - все точки расположены в одной плоскости z=d. Этого можно добиться умножив все компоненты вектора на d: [ xd/z yd/z zd/z ]. При d=1, мы получим: [ x/z y/z 1 ], именно эта формула использовалась для преобразования однородных координат.

Теперь, если мы отодвинем проекционную плоскость в точку z=5 (соотвтественно d=5), мы получим: [ xd/z yd/z zd/z ] = [ 5x/z 5y/z 5 ]. Последняя формула проецирует все векторы пространства в одну плоскость, где d=5.
У нас здесь небольшая проблемка. Предыдущая формула работает с трёхмерными векторами. Но мы договорились использовать четырёхмерные векторы. Четвёртую компоненту в данном случае можно просто отбросить. Но мы не будем этого делать, так как её использование даёт некоторые специфические возможности, которые мы ещё обсудим.

Нужно найти общий делитель третьей и четвёртой компонент, при делении на который в третьей компоненте остаётся значение d, а в четвёртой единица. Делитель этот - d/z. Теперь из обычного вектора [ x y z 1 ] нам нужно получить вектор готовый к проекции (делению) [ x y z z/d ]. Делается это с помощью матрицы преобразования (проверьте результат умножив любой вектор на данную матрицу):


Последнее преобразование - это ещё не проекция. Здесь мы просто приводим все векторы к нужной нам форме. Напоминаю, что мы будем размещать проекционную плоскость в d=1, а значит векторы будут выглядеть вот так: [ x y z z ].

Матрица перспективного преобразования

Мы рассмотрим матрицу перспективного преобразования использующуюся в DirectX:

Теперь мы знаем для чего предназначен элемент _34. Мы также знаем, что элементы _11 и _22 масштабируют изображение по горизонтали и вертикали. Давайте посмотрим, что конкретно скрывается за именами xScale и yScale.

Данные переменные зависят от зон обзора, о которых мы говорили выше. Увеличивая или уменьшая эти углы, можно масштавбировать (scale или zoom) изображение - менять размер и соотношение сторон проекционной плоскости. Механизм масштабирования отдалённо напомниает масштабирование в фотоаппаратах/камерах - принцип очень похожий. Рассмотрим рисунок:


Разделим угол fov на две части и рассмотрим только одну половинку. Что мы тут видим: увеличивая угол fov/2 (а соответсвенно и угол fov), мы увеличиваем sin угла и уменьшаем cos. Это приводит к увеличению проекционной плоскости и соответственно к уменьшеню спроецированных объектов. Идеальным для нас углом будет fov/2 = P/4. Напоминаю, что угол в P/4 радиан равен 45 градусам. При этом fov будет равен 90 градусам. Чем для нас хорош угол в 45 градусов? В данном случае не происходит масштабирования, а cos(P/4)/sin(P/4)=1.

Теперь мы можем легко масштабировать картинку по вертикали (горизонтали), используя синус и косинус половины зоны обзора (функция котангенса в C++ называется cot):

yScale = cos(fovY/2)/sin(fovY/2) = cot(fovY/2)
В DirectX используется только вертикальная зона обзора (fovY), а масштабирование по горизонатли зависит от вертикальной зоны обзора и соотношения сторон.

Напоминаю, что окно в наших программах размером в 500x500. Соотношение сторон: 1 к 1. Поэтому переменные будут равны: xScale=1, yScale=1.

Соотношение сторон стандартного монитора/телевизора: 4:3. Этому соотношению соответствуют разрешения экрана: 640x480, 800x600, 1600x1200. Мы пока не будем касаться полноэкранного режима, но можем изменить размер окна программы. Вы можете поменять размер окна (в present parameters), например, на 640X480. Но чтобы все предметы не растянулись (квадраты будут выглядеть как прямоугольники), не забудьте поменять соответствующие переменные в проекционной матрице.

Чуть не забыл, форумула для xScale в DirectX:

xScale = yScale / соотношение сторон
Соотношения сторон задаются просто: 1/1, 4/3, 16/9 - это из стандартных.

Осталось выяснить назначение элементов _33, _34 матрицы перспективного преобразования. zf - z-координата дальней плоскости (от far - далеко), а zn - z-координата ближней (от near - близко). Обратите внимание, что элемент _43 = _33 * -zn.

Легче всего понять, что именно делают эти формулы, можно на примерах. Умножим стандартный вектор [ x y z w ] на матрицу представленную выше. Рекомендую вам сделать это, взяв лист бумаги и карандаш (надеюсь вы помните как перемножать две матрицы). Компоненты вектора примут следующий вид.

1-ая = x*xScale
2-ая = y*yScale
3-я = z*(zf/(zf-zn)) + w*(-(zn*zf)/(zf-zn)) = (zf/(zf-zn))*(z - w*zn)
4-ая = (w*z)/d
Совершим проекционное преобразование (разделим все элементы на 4-ую компоненту, при этом допустим, что d=1 и w=1):

1-ая = (d*x*xScale)/(w*z) = (x*xScale)/z
2-ая = (d*y*yScale)/(w*z) = (y*xScale)/z
3-я = (zf/(zf-zn))*(z - w*zn)*(w*d/z) = (zf/(zf-zn))*(1 - zn/z)
4-ая = 1
В результате мы получили вектор вида:

[ x/(z*xScale) y/(z*yScale) (zf/(zf-zn))*(1-zn/z) 1 ]
Теперь, если вы зададите конкретные значения zf и zn, то обнаружите следующее (для положительных значений): если вектор расположен до ближней плоскости, то z-компонента после преобразования будет меньше нуля, если вектор расположен за дальней плоскостью, то z-компонента будет больше единицы.

Нет никакой разници где именно расположены ближняя и дальняя плоскости: zn=1, zf=10 или zn=10, а zf=100 (или любые другие значения) - после преобразования видимая область будет располагаться в отрезке от нуля до единицы, включительно.

Именно для этого и предназначены формулы в элементах _33, _34 проекционной матрицы - спроецировать расстояние от ближней до дальней плоскости в отрезок . Проверьте это, вычислив значения нескольких векторов для конкретных значений zn,zf (да-да, на листке бумаги!!!).

Перспективное проецирование

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

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

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

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

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

В этой матрице элементы a , d , е отвечают за масштабирование, m , n , L - за смещение, p , q , r - за проецирование, s - за комплексное масштабирование, х - за вращение.

Одноточечное проецирование на плоскость z = 0

Суть этого проецирования такова: чем глубже находится предмет, тем больше становится значение z-координаты и знаменателя rz + 1, и, следовательно, тем мельче выглядит предмет на плоскости проекции. Выполним несложные выкладки и поясним их графически:
уравнение x"/F = x/(F + z пр) равносильно: x" = xF/(F + z пр) = x/(1 + z пр /F) = x/(1 + rz пр), где r = 1/F, F - фокус.

Для того, чтобы точки, лежащие на линии, параллельной оси z , не терялись друг за другом, используется одноточечное проецирование на линию (см. матрицу преобразования и рис. 4.2 ); исчезла z-координата, но, поскольку дальние предметы стали более мелкими, чем такие же близкие, у зрителя появляется ощущение глубины. Запомните: это первый способ передачи глубины на плоскости!

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

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

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

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

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

Матрица общего перспективного преобразования

В этой матрице элементы a , d , е отвечают за масштабирование, m , n , L — за смещение, p , q , r — за проецирование, s — за комплексное масштабирование, х — за вращение.

Двигатель не двигает корабль.
Корабль остается на месте, а
двигатели двигают вселенную
вокруг него.

Футурама

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

Гомогенные координаты

В предыдущих уроках мы предполагали, что вершина расположена по координатам (x, y, z). Давайте-ка добавим еще одну координату – w. Отныне вершины у нас будут по координатам (x, y, z, w)

Вскоре вы поймете, что к чему, но пока примите это как данность:

  • Если w==1, тогда вектор (x,y,z,1) – это позиция в пространстве
  • Если w==0, тогда вектор (x,y,z,0) – это направление.

Запомните это как аксиому без доказательств!!!

И что это нам дает? Ну, для вращения ничего. Если вы вращаете точку или направление, то получите один и тот же результат. Но если вы вращаете перемещение(когда вы двигаете точку в определенном направлении), то все кардинально меняется. А что значит «переместить направление»? Ничего особенного.

Гомогенные координаты позволяют нам оперировать единым матаппаратом для обоих случаев.

Матрицы Трансформаций

Введение в матрицы

Если по простому, то матрица, это просто массив чисел с фиксированным количеством строк и столбцов.

Например, матрица 2 на 3 будет выглядеть так:

В 3д графике мы пользуемся почти всегда матрицами 4х4. Это позволяет нам трансформировать наши (x,y,z,w) вершины. Это очень просто – мы умножаем вектор позиции на матрицу трансформации.

Матрица*Вершину = трансформированная вершина

Все не так страшно как выглядит. Укажите пальцем левой руки на a, а пальцем правой руки на x. Это будет ax. Переместите левый палец на следующее число b, а правый палец вниз на следующее число – y. У нас получилось by. Еще раз – cz. И еще раз – dw. Теперь суммируем все получившиеся числа – ax+by +cz +dw . Мы получили наш новый x. Повторите то же самое для каждой строки и вы получите новый вектор (x,y,z,w).

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

В С++ при помощи библиотеки GLM:

glm::mat4 myMatrix;


glm::vec4 myVector;



glm :: vec 4 transformedVector = myMatrix * myVector ; // Не забываем про порядок!!! Это архиважно!!!

В GLSL:

mat4 myMatrix;


vec4 myVector;


// заполняем матрицу и вектор нашими значениями… это мы пропускаем


vec 4 transformedVector = myMatrix * myVector ; // точно так же как и в GLM

(Чего-то мне кажется, что вы не скопировали этот кусок кода к себе в проект и не попробовали…ну же, попробуйте, это интересно!)

Матрица перемещений

Матрица перемещения, это, наверное, самая простая матрица из всех. Вот она:


Тут X, Y, Z – это значения, которые мы хотим добавить к нашей позиции вершины.

Итак, если нам нужно переместить вектор (10,10,10,1) на 10 пунктов, по позиции Х, то:
(Попробуйте это сами, ну пожаааалуйста!)

…И у нас получится (20,10,10,1) в гомогенном векторе. Как вы, я надеюсь, помните, 1 значит, что вектор представляет собой позицию, а не направление.

А теперь давайте попробуем таким же образом трансформировать направление (0,0,-1,0):

И в итоге у нас получился тот же вектор (0,0,-1,0).
Как я и говорил, двигать направление не имеет смысла.

Как же нам закодить это?

В С++ при помощи GLM:

#include // после


glm::mat4 myMatrix = glm::translate(10.0f, 0.0f, 0.0f );


glm::vec4 myVector(10.0f, 10.0f, 10.0f, 0.0f );


glm :: vec 4 transformedVector = myMatrix * myVector ; // и какой у нас получится результат?


А в GLSL: В GLSL так редко кто делает. Чаще всего с помощью функции glm::translate(). Сначала создают матрицу в С++, а затем отправляют её в GLSL, и уже там делают лишь одно умножение:

vec4 transformedVector = myMatrix * myVector;

Единичная матрица(Identity Matrix)

Это специальная матрица. Она не делает ничего. Но я упоминаю её, так как важно знать, что умножение A на 1.0 в результате дает А:

glm::mat4 myIdentityMatrix = glm::mat4(1.0f);

Матрица Масштабирования

Матрица масштабирования так же достаточно проста:

Поэтому если вам хочется увеличить вектор(позицию или направление, не важно) в два раза по всем направлениям:

А координата w не поменялась. Если вы спросите: «А что такое масштабирование направления?». Полезно не часто, но иногда полезно.

(заметьте, что масштабирование единичной матрицы с (x,y,z) = (1,1,1))

С++:

// Используйте #include и #include


glm::mat4 myScalingMatrix = glm::scale(2.0f, 2.0f ,2.0f);

Матрица Вращения

А вот эта матрица достаточно сложная. Поэтому я не буду останавливаться на подробностях её внутренней реализации. Если сильно хочется, лучше почитайте (Matrices and Quaternions FAQ)

В С ++:

// Используйте #include и #include


glm::vec3 myRotationAxis(??, ??, ??);


glm::rotate(angle_in_degrees, myRotationAxis);

Совмещенные Трансформации

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

TransformedVector = TranslationMatrix * RotationMatrix * ScaleMatrix * OriginalVector;

И снова порядок!!! Сначала нужно изменить размер, потом прокрутить и лишь потом сдвинуть.

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

  • Сделайте шаг вперед(не свалите компьютер со стола) и повернитесь влево
  • Повернитесь влево и сделайте один шаг вперед.

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

Не правильный способ:

  • Перемещаем корабль на (10,0,0). Его центр теперь на 10 по Х от центра.
  • Увеличиваем размер нашего корабля в 2 раза. Каждая координата умножается на 2 относительно центра который далеко… И в итоге у нас получается корабль необходимого размера но по позиции 2*10=20. Что не совсем то, чего мы хотели.

Правильный способ:

  • Увеличиваем размер корабля в 2 раза. Теперь у нас есть большой корабль расположенный по центру.
  • Перемещаем корабль. Размер корабля не изменился и он расположен в нужном месте.
Умножение матрицы на матрицу выполняется почти так же, как и умножение матрицы на вектор. Не будем вдаваться в подробности, а любопытствующие пусть почитают это из специализированных источников. Мы же просто будем полагаться на библиотеку GLM.
ВС++:
glm::mat4 myModelMatrix = myTranslationMatrix * myRotationMatrix * myScaleMatrix;
glm::vec4 myTransformedVector = myModelMatrix * myOriginalVector;
В GLSL:
mat4 transform = mat2 * mat1;
vec4 out_vec = transform * in_vec;

Матрицы Модели, Вида и Проекции

Для иллюстраций предполагаем, что мы уже умеем рисовать в OpenGL любимую 3д модель программы Blender – голову обезьяны Сюзанны.

Матрицы Модели, Вида и Проекции очень удобный метод разделения трансформаций. Если сильно хочется, вы можете не использовать их(мы же не использовали их в уроках 1 и 2). Но я настойчиво рекомендую вам пользоваться ими. Просто почти все 3д библиотеки, игры итд используют их для разделения трансформаций.

Матрица Модели

Данная модель, как и наш любимый треугольничек, задана набором вершин. X, Y, Z координаты заданы относительно центра объекта. Так вот, если вершина расположена по координатам (0,0,0), то она находится в центре всего объекта

Теперь мы имеем возможность двигать нашу модель. Например, потому, что пользователь управляет ей с помощью клавиатуры и мыши. Это сделать очень просто: масштабирование*вращение*перемещение и все. Вы применяете вашу матрицу ко всем вершинам в каждом кадре(в GLSL а не в C++) и все перемещается. Все что не перемещается – расположено в центре «мира».

Вершины находятся в мировом пространстве Черная стрелка на рисунке показывает, как мы переходим из пространства модели, в мировое пространство(Все вершины были заданы относительно центра модели, а стали заданы относительно центра мира)

Эту трансформацию можно отобразить следующей диаграммой:


Матрица Вида

Давайте еще раз прочитаем цитату из футурамы:

«Двигатель не двигает корабль. Корабль остается на месте, а двигатели двигают вселенную вокруг него.»


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

По умолчанию наша камера находится в центре Мировых Координат. Чтобы двигать наш мир нужно создать новую матрицу. К примеру нам нужно переместить нашу камеру на 3 единицы вправо(+Х). Это то же самое, что переместить весь мир на 3 единицы влево(-Х). И пока ваши мозги плавятся, давайте попробуем:


// Используйте #include и #include


glm::mat4 ViewMatrix = glm::translate(-3.0f, 0.0f ,0.0f);

Картинка ниже демонстрирует это: мы переходим от Мирового пространства(все вершины заданы относительно центра мира как мы это делали в предыдущей секции) к пространству камеры(все вершины заданы относительно камеры).

И прежде чем ваша голова совсем взорвется, посмотрите на прекрасную функцию из нашей старой доброй GLM:

glm::mat4 CameraMatrix = glm::LookAt(


cameraPosition , // Позиция камеры в мировых координатах


cameraTarget , // точка на которую мы хотим посмотреть в мировых координатах


upVector // скорее всего glm :: vec 3(0,1,0), а (0,-1,0) будет все показывать вверх ногами, что иногда тоже прикольно.


Вот иллюстрация к вышесказанному:


Но к нашей радости, это еще не все.

Матрица Проекции

Сейчас мы имеем координаты в пространстве камеры. Это значит, что после всех этих трансформаций, вершина которой посчастливилось оказаться в x==0 и y==0 будет отрендерена в центре экрана. Но мы же не можем пользоваться лишь координатами X,Y, чтобы понять куда рисовать вершину: дистанция к камере(Z) должна тоже учитываться! Если у нас есть две вершины, то одна из них будет более ближе к центру экрана чем другая, так как у неё больше координата Z.

Это называется перспективная проекция:


И к большому счастью для нас, матрица 4х4 может представлять собой и перспективные трансформации:

glm::mat4 projectionMatrix = glm::perspective(


FoV , // Горизонтальное Поле Вида в градусах. Или величина приближения . Как будто « линза » на камере . Обычно между 90(суперширокий, как рыбий глаз) и 30(как небольшая подзорная труба)


4.0 f / 3.0 f , // Соотношение сторон. Зависит от размера вашего окна. Например, 4/3 == 800/600 == 1280/960, знакомо, не правда ли?


0.1 f , // Ближнее поле отсечения. Его нужно задавать как можно большим, иначе будут проблемы с точностью.


100.0 f // Дальнее поле отсечения. Нужно держать как можно меньшим.


);

Повторим то что мы сейчас сделали:

Мы ушли от пространства камеры(все вершины заданы в координатах относительно камеры) в гомогенное пространство(все вершины в координатах маленького куба(-1,1). Все что находится в кубе – находится на экране.)

И финальная диаграмма:


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

После умножения на проекционную матрицу у нас выходит следующее:

На предыдущей картинке поле вида превратилось в идеальный куб(с координатами вершин от -1 до 1 по всем осям.), а все объекты деформированы в перспективе. Все голубые объекты которые близко к камере – стали большими, а которые дальше – маленькими. Так же как и в жизни!

Вот какой вид у нас открывается из «объектива»:

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