О трёхмерной графике в GMS2. Часть 2 из 2

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

e09db6b22657dfaba4a9c75ef60b9480.jpeg

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

Если нужно вращать куб вокруг другой точки, например вершины 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 создаёт комнату с разрешением 1366×768, поэтому я указываю соответствующие значения для соотношения сторон. Параметры ближнего и дальнего отсечения задают диапазон глубины, в пределах которого пиксели будут отображаться на экране. В строках 7 и 8 полученные матрицы устанавливаются для текущей камеры, а в девятой строке я активирую эти значения для текущего шага, иначе они вступят в силу только в следующем.

-6

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

-7

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

-8

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

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

-9

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

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

-10

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

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

-11

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

-12

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

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

-13

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

-14

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

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

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

© Habrahabr.ru