О трёхмерной графике в GMS2. Часть 2 из 2
Что такое вершинный буфер? Как создать трёхмерный объект и отрисовать его на экран? Для чего нужен формат вершин и как с ним работает вертексный шейдер? Как работает буфер глубины и что такое борьба за глубину? Как это влияет на полупрозрачность и почему важен порядок отрисовки объектов на экран? Как посчитать координаты камеры и задать перспективу? Для чего нужны матрицы и как ими пользоваться? Что такое отсечение и зачем оно нужно?
В первой части я показал как отрисовать куб и начать его вращать.
Если нужно вращать куб вокруг другой точки, например вершины G, сначала смещаем его так, чтобы эта точка оказалась в начале координат, а затем выполняем вращение. Для этого создаю матрицу смещения и умножаю её на матрицу вращения.
В строке, обозначенной решёткой, начинается событие Draw. GMEdit показывает все события объекта в одном листинге и разделяет их таким образом. В строках 49-50 видно, что я включаю работу с буфером глубины. В четвёртой строке я создаю матрицу смещения, чтобы вершина G оказалась в начале координат. В шестой строке перемножаю матрицу смещения ma и матрицу вращения mt.
Теперь куб вращается вокруг вершины G. Порядок перемножения матриц имеет значение. Например, у меня есть матрица перемещения T и матрица вращения R . Если я перемножу их как T x R , то сначала куб переместится, а потом будет вращаться вокруг начала координат. Если же перемножу как R x T , то сначала куб будет вращаться, а затем переместится.
Или другой пример. Допустим, у меня есть матрица куба M и матрица R для вращения вокруг оси Y. Если перемножить их как R x M , куб будет вращаться относительно своей локальной оси Y, а если как M x R , он будет вращаться относительно глобальной оси Y, как показано на гифке ниже, где ось Y обозначена зелёной линией, а ось X — красной.
Вращение камеры и перспектива
Я остановил вращение куба и его смещение. Теперь он снова отрисовывается в начале координат. Далее я изменю параметры камеры, переместив её в мире и задав перспективную проекцию вместо ортогональной. Для этого необходимо создать соответствующие матрицы и применить их к текущей камере.
В третьей строке я получаю текущую камеру. В четвёртой строке строю матрицу вида. Функция matrix_build_lookat принимает девять параметров: позицию камеры, позицию цели камеры и направление оси, указывающей вверх. Наш куб находится в начале координат, поэтому в качестве цели я указываю начало координат. В пятой строке создаётся матрица перспективы. Функция matrix_build_projection_perspective_fov принимает 4 параметра: угол обзора, соотношение сторон, расстояние до ближнего и дальнего сечений. По умолчанию GMS2 создаёт комнату с разрешением 1366x768, поэтому я указываю соответствующие значения для соотношения сторон. Параметры ближнего и дальнего отсечения задают диапазон глубины, в пределах которого пиксели будут отображаться на экране. В строках 7 и 8 полученные матрицы устанавливаются для текущей камеры, а в девятой строке я активирую эти значения для текущего шага, иначе они вступят в силу только в следующем.
Теперь куб отрисован в перспективе.
По нажатию кнопки мыши я буду вращать камеру вокруг куба. Для этого потребуется знать радиус, широту и долготу.
В строках 3-5 при нажатии кнопки мыши я изменяю значения широты и долготы, используя смещение мыши за один шаг. В строках 7-10 рассчитываю координаты камеры, а в строке 13 устанавливаю их.
Вот результат:
Тут я хотел закончить гайд, но решил что нужно подробнее рассказать об отсечение? полупрозрачности и упомянуть про борьбу за глубину.
Когда 2 поверхности расположены близко друг к другу, то отрисовываясь на экран некоторые фрагменты задней поверхности могут отрисовываться спереди, как это видно на гифке внизу.
Это происходит потому что точности буфера глубины недостаточно чтобы правильно расположить пиксели по глубине и чем дальше такие поверхности будут от камеры, тем больше будет погрешность.
Чтобы показать как работает отсечение я уберу верхнюю грань у куба.
Видно нижнюю грань и заднюю. Это потому что сейчас отсечение выключено и по этому треугольники отрисовываются на экран независимо от порядка вершин по или против часовой стрелки. Теперь с помощью функции gpu_set_cullmode я включаю отсечение.
На этой гифке видно что теперь грани которые отвёрнуты от камеры не отрисовываются, благодаря включённому отсечению.
Теперь я сделаю одну из плоскотей полупрозрачной для теста. Через неё должно быть видно бэкграунд и заднюю тёмносерую плоскость.
На этой гифке видно, что через полупрозрачную плоскость не видно заднюю тёмносерую плоскость. Это происходит потому что полупрозрачная плоскость отрисовывается первой и буфер глубины забивается её значениями, по этому когда я отрисовываю заднюю плоскость, то её не видно, потому что из-за занятого в этом месте буфера глубины отрисовка останавливается. Теперь я поменяю порядок отрисовки так чтобы полупрозрачная плоскость отрисовывалась позже чем задняя тёмносерая и теперь на гифке внизу видно что проблема была исправлена.
Иногда разработчики игр вместо полупрозрачности используют дизеринг. Благодаря этому можно не беспокоится о порядке отрисовки объектов на экран.
Итак, в этом гайде я рассказал, как создать вершинный буфер и отрисовать его. Рассмотрел работу с матрицами, вращение куба со смещением и вращение вокруг разных осей. Также разобрал, как рассчитать положение камеры в пространстве и задать перспективу. Я объяснил, что такое буфер глубины, борьба за глубину и полупрозрачность. Упомянул про отсечение и немного рассказал о GMEdit.
Спасибо, что дочитали статью до конца. Надеюсь, она вам понравилась, и вы узнали что-то новое. Если это так, поставьте лайк — возможно, тогда о ней узнает больше людей.
Сейчас я стараюсь выпускать по одной статье в неделю. Подписывайтесь на меня, чтобы не пропускать новые публикации.
Если вам понравилась эта статья, также рекомендую ознакомиться с другими материалами о программировании в GameMaker:
Спасибо за ваше внимание! Если вы считаете моё творчество полезным, то я буду очень рад если вы поддержите меня оформив подписку. Это поможет чаще выпускать статьи и разрабатывать игры.