Что такое вершинный буфер? Как создать трёхмерный объект и отрисовать его на экран? Для чего нужен формат вершин и как с ним работает вертексный шейдер? Как работает буфер глубины и что такое борьба за глубину? Как это влияет на полупрозрачность и почему важен порядок отрисовки объектов на экран? Как посчитать координаты камеры и задать перспективу? Для чего нужны матрицы и как ими пользоваться? Что такое отсечение и зачем оно нужно?

В первой части я показал как отрисовать куб и начать его вращать.

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

-2

В строке, обозначенной решёткой, начинается событие Draw. GMEdit показывает все события объекта в одном листинге и разделяет их таким образом. В строках 49-50 видно, что я включаю работу с буфером глубины. В четвёртой строке я создаю матрицу смещения, чтобы вершина G оказалась в начале координат. В шестой строке перемножаю матрицу смещения `ma` и матрицу вращения `mt`.

-3

Теперь куб вращается вокруг вершины G. Порядок перемножения матриц имеет значение. Например, у меня есть матрица перемещения T и матрица вращения R . Если я перемножу их как T x R , то сначала куб переместится, а потом будет вращаться вокруг начала координат. Если же перемножу как R x T , то сначала куб будет вращаться, а затем переместится.

Или другой пример. Допустим, у меня есть матрица куба M и матрица R для вращения вокруг оси Y. Если перемножить их как R x M , куб будет вращаться относительно своей локальной оси Y, а если как M x R , он будет вращаться относительно глобальной оси Y, как показано на гифке ниже, где ось Y обозначена зелёной линией, а ось X — красной.

-4

Вращение камеры и перспектива

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

-5

В третьей строке я получаю текущую камеру. В четвёртой строке строю матрицу вида. Функция matrix_build_lookat принимает девять параметров: позицию камеры, позицию цели камеры и направление оси, указывающей вверх. Наш куб находится в начале координат, поэтому в качестве цели я указываю начало координат. В пятой строке создаётся матрица перспективы. Функция matrix_build_projection_perspective_fov принимает 4 параметра: угол обзора, соотношение сторон, расстояние до ближнего и дальнего сечений. По умолчанию GMS2 создаёт комнату с разрешением 1366x768, поэтому я указываю соответствующие значения для соотношения сторон. Параметры ближнего и дальнего отсечения задают диапазон глубины, в пределах которого пиксели будут отображаться на экране. В строках 7 и 8 полученные матрицы устанавливаются для текущей камеры, а в девятой строке я активирую эти значения для текущего шага, иначе они вступят в силу только в следующем.

-6

Теперь куб отрисован в перспективе.

-7

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

-8

В строках 3-5 при нажатии кнопки мыши я изменяю значения широты и долготы, используя смещение мыши за один шаг. В строках 7-10 рассчитываю координаты камеры, а в строке 13 устанавливаю их.

Вот результат:

-9

Тут я хотел закончить гайд, но решил что нужно подробнее рассказать об отсечение? полупрозрачности и упомянуть про борьбу за глубину.

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

-10

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

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

-11

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

-12

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

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

-13

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

-14

Иногда разработчики игр вместо полупрозрачности используют дизеринг. Благодаря этому можно не беспокоится о порядке отрисовки объектов на экран.

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

Если вам понравилась эта статья, то:
Можно ознакомиться с первой частью
Статьёй о шейдерах

Комментарии (6)


  1. Dynasaur
    11.09.2024 19:39
    +2

    Почему в 3Д-графике для поворота и переноса используются матричные операции, а в системах навигации кватернионы? Ведь они описывают одно и то же? Если мы моделируем движение самолёта в 3Д-сцене матаппарат должен быть тем же, что и в системе навигации?


    1. Mike_666
      11.09.2024 19:39
      +3

      Кватернионы описывают только повороты (хотя и делают это более эффективно).

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

      И конечно же задачи моделирования и отображения - разные, и их можно решать с помощью разных инструментов.


      1. Dynasaur
        11.09.2024 19:39

        Да, верно. Матрицы ещё масштабировать позволяют, а самолёту в полёте масштабироваться не надо :-) Мог бы и сам догадаться :-) Спасибо


    1. alsekond Автор
      11.09.2024 19:39

      В системах навигации не разбираюсь. Про 3д графику могу сказать что ограничений на использование матриц нет. Движение самолёта можно смоделировать как матрицами так и кватернионами. Шейдерные языки заточены на использование матриц, но можно дописать функции работы с кватернионами. Например, файлы md5 используют кватернионы в описании костей модели.


    1. alsekond Автор
      11.09.2024 19:39

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


    1. alsekond Автор
      11.09.2024 19:39

      Вспомнил ещё, что кватернионы не страдают от Gimbal Lock и SLERP'ом можно плавно интерполировать их значения.