Основы геймдева - графика

В перерывах между программированием хочется отдохнуть — решил продолжить писать про геймдев.
Подумал — как помочь тем, кто хочет начать делать игры, но в теории — полный ноль? Чтобы начинающим не пришлось лазить и читать кучи статей про теорию в перемешку с практикой.
Решил написать все, что нужно знать, прежде чем сесть за саму игру. Так сказать, построить фундамент в голове. При этом написать максимально кратко, но обо всем, не привязываясь к конкретным движкам и программам.
Начал с графики. Дальше планирую написать по управление, музыку и звук. Потом — конкретно про внутренее устройство игры (уровни, объекты, физика, оптимизация).

Приветствуются ваши замечания и поправки. Но не погружаясь в детали и подробности, чтобы сохранить краткость статьи.
Warning! Много текста.

ИЗ ЧЕГО СОСТОИТ ИГРА

Игра — это интерактивное приложение, где управление, выполняемое игроком, приводит к изменениям в приложении, картинке и звуке, что приносит игроку удовольствие.
При этом все действия игрока оказывают влияние только на выдуманный виртуальный мир внутри приложения, не приводя к изменениям на самом компьютере или сети (создание документов, изменение системных параметров).
Саму игру можно представить так:

ИГРА = Управление + КОД + Уровни + Графика + Звук

ncpiKW5

Код опрашивает управление, управляет игровым временем и случайными событиями и, при изменении всего этого вносит изменения в логику работы игры, графику и звук (например, перемещает картинку игрока, воспроизводит звук падения и т. д.)


ТИП ИГРЫ ПО СИСТЕМЕ КООРДИНАТ

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


2D-измерение

В двухмерных весь мир существует только в двух направления (X, Y).
Камера может показывать мир и его объекты:
— сверху (top-down)
— сбоку (sideview)
— или в изометрии (isometric), чуть сбоку, чуть сверху
Направление взгляда камеры фиксировано (игрок не может ее поворачивать), но может перемещать в двух направлениях.


3D-измерение

В трехмерных играх мир существует в трех направлениях (X, Y, Z).
Камера может показывать мир с любых сторон, обычно давая игроку возможность не только перемещать, но и вращать камеру в разные стороны, меняя ракурс.
В зависимости от игрового движка или программы для создания 3D-графики, направление этих осей может быть разное, например:
* X — вправо, Z — вперед, Y — вверх
* X — вправо, Y — вперед, Z — вверх
При переносе 3D-графики в игру из 3D-редактора нужно учитывать разное направление осей.


ГРАФИКА

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

2D-графика

Бывает двух видов — векторная и растровая.
Растровая состоит из пикселей, описывающих цвет каждой точки изображения на экране.
Векторная — описывает способ рисования картинки из примитивов (круг, прямоугольник, линия, кривая). Перед отображением такой графики движок выполняет растеризацию — преобразование векторной графики в растровую.
Плюсы векторной графики — отсутствие искажений картинки при разном масштабе. Минусы — затраты производительности на растеризацию.
Сейчас векторная графика в играх почти не используется, а если и есть, то чаще всего это уже растровая графика в большом разрешении.

ТЕКСТ

Текст (text) — это разновидность двухмерной графики, где каждой картинке соответствует заранее определенное число.
Шрифт описывает как нарисовать каждый символ. Шрифты бывают растровые и векторные.
Растровый шрифт всегда предназначен для отрисовки букв конкретного размера в пикселях. Увеличивать размер такого шрифта без искажений можно только в целое количество раз.
Для векторного шрифта символы могут быть любого размера, но перед изменением размера нужно заново проводить растеризацию — преобразование векторного шрифта в растровый.


Кодировки

Это таблица, по которой компьютер определяет какому числу в файле или переменной какой символ шрифта соответствует.
Если в выбранном шрифте нет такого символа, то на экран либо ничего выведется, либо появится пустой квадрат.
Важная особенность — если нет каких-то пометок, то кодировку текста нельзя определить, ее можно только угадать (например, по частоте встречаемых символов) или знать заранее.
В кодировках необходимо разбираться если вы планируется выводить текст на экран более чем на одном языке. Делая такую игру, нужно выбрать шрифт, в котором есть символы всех языков, используемых в ваше игре, или менять шрифт в зависимости от выбранного языка.

* ansi
Это не кодировка, а стандарт кодирования символов, где каждый символ занимает 1 байт.
Первые 128 символов (от 0 до 127) всегда одинаковые и предназначены для латинского алфавита, цифр и знаков пунктуации.
То, какие символы занимают остальные позиции (от 128 до 255) — зависит от выбранной кодировки.
Примеры кодировок:
cp866 — кириллические (русские) символы для MS-DOS
cp1251 — кириллические (русские) символы для Windows
koi8-r — кириллические (русские) символы для Unix-систем
Также есть кодировки для специфических символов, встречаемых, например, в немецком и французском языках.
В современных играх такие кодировки не используются.

* utf-8
Кодировка, предназначенная для символов любых языков.
Недостаток этой кодировки — символы имеют разный размер в байтах, от 1 до 4 (1 байт — для самых часто используемых символов, латинских, 4 байта — для самых редких). Это сделано для экономии памяти.
Поэтому эта кодировка плохо подходит для манипуляции со строками, когда вам нужно оперировать отдельными символами строки.
В начале некоторых текстовых файлов есть BOM-сигнатура (EF BB BF в 16-тиричной системе счисления) чтобы текстовым редакторам было понятно что кодировка файла — utf8.
Есть также кодировка utf-16.

* unicode
По 2 байта на символ. Поэтому, в отличие от utf-8, в этой кодировке удобно выполнять операции со строками.
Один байт содержит номер кодовой страницы (набор символов), другой — номер символа в этом наборе.
Например, страница 00 соответствует латинским символам, а 04 — кириллическим.
Порядок этих двух байт может быть прямой («Big Endian») — сначала номер страницы, потом номер символа или обратный («Little Endian») — сначала номер символа, потом — номер страницы.
Unicode используется для текста в большинстве современных игр.


Форматы для хранения шрифтов

Для хранения растровых шрифтов можно использовать любой формат изображений без потерь (bmp, png), а расположение символов определяется движком или самим программистом игры.
Для векторных шрифтов:

fon — формат шрифтов, используемый в Windows. В играх применяется редко.
ttf — формат шрифтов, чаще всего используемый в играх.

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

Растровая 2D-графика

Картинки, представляющие собой предметы, персонажей, детали окружения — называются спрайтами.
Обычно создается одна большая картинка (атлас, sheet), с размерами, 256×256, 512×512, 1024×1024 пикселей, в которой находится много спрайтов.

VRoUxet

Изображение состоит из пикселей, каждый пиксель в видеокарте занимает 4 байта, по одному байту на компоненту:
R — красный
G — зеленый
B — синий
A — прозрачность
Комбинацией этих компонент получается разный цвет.
При хранении картинки на жестком диске (и в графическом редакторе) пиксели располагаются по целым координатам X, Y.
Каждая компонента, в случае 1 байта, — в виде целого числа от 0 до 255.
Картинка размером 1024×1024 пикселей займет в видеокарте 4 * 1024 * 1024 = 4 Мб памяти.
Вне зависимости от того сколько это картинка занимает места на жестком диске.
В игровых движках есть возможности уменьшить или увеличить размер пикселя в байтах внутри памяти видеокарты.
Это приведет к уменьшению или увеличению количества цветов в изображении.


Порядок отрисовки в 2D

Порядок рисования спрайтов зависит от выбранной проекции графики.
При виде сбоку или сверху все спрайты разбиваются на группы и рисуются в следующем порядке:
— задний план [background] (земля, небо, строения или деревья вдалеке).
— средний план [middleground] (то, что является элементами уровня — препятствия, декорации).
— передний план [frontground] (все, что находится перед элементами уровня — персонажи, объекты, бонусы).
— ГУИ [GUI] (игровая информация — текст диалогов, полоска здоровья, количество очков, меню паузы).
Это связано с тем, что объекты на каждом плане находятся на фиксированном расстоянии от игровой камеры, и оно — не меняется.

При изометрическом виде, когда объекты могут перемещаться вперед-назад и, тем самым, менять расстояние до камеры (например, как в играх жанра Beat’em Up), на каждом из планов нужно вводить сортировку. В таком случае сначала рисуются объекты, находящиеся дальше всего от камеры, потом — более близкие.


Пиксельные шейдеры

Отрисовка картинки выполняется так:

пиксель в видеокарте => вычисления => пиксель на экране

У разработчика игры есть возможность указать свои вычисления с помощью пиксельного шейдера.
Пиксельный или фрагментный шейдер (pixel/fragment shader) — это программа, которая объясняет видеокарте как рисовать конкретную растровую картинку.
Например, вы можете написать шейдер, который будет выводить картинку черно-белой или менять расположение пикселей. Сама рисуемая картинка в видеокарте останется без изменений.

При выполнении шейдера ему передаются:
— цвет рисования
— ссылка на изображение в видеокарте
— ссылка на текстурные координаты (положение рисуемого пикселя в изображении)
— экранные координаты, куда рисуется этот пиксель

Что нужно помнить:
1) шейдер работает не со спрайтом, а со всем изображением, в котором находится рисуемый спрайт
2) в видеокарте координаты пикселей задаются не как целый номер пикселя по X и Y, а в виде вещественного числа от 0.0f до 1.0f (например, 0.234f).
Точке (0.0f, 0.0f) соответствует левый верхний угол изображения, а точке (1.0f, 1.0f) — правый нижний угол изображения.
Это сделано затем, чтобы при изменении размера изображения в видеокарте (например, выбор в настройках низкого или высокого качества текстур), для шейдера положение пикселей не изменилось.
3) Компоненты цветов пикселей также вместо целого от 0 до 255 заменяются на вещественное от 0.0f до 1.0f.
4) Картинку можно рисовать не только на экран, но и в канвас (canvas), то есть в другую картинку в видеокарте.

Для описания шейдеров чаще всего используется языки GLSL или HLSL, похожие на язык Си.
Как писать шейдер — нужно разбираться для конкретного выбранного игрового движка.

Примеры использования пиксельных шейдеров:
— Постэффекты изображения перед выводом на экран
— Замена цвета пикселей в одинаковых спрайтах разных персонажей (например, замена цвета перчаток и одежды в боксе или файтинге, без создания отдельных спрайтов)
— Обводка контура спрайтов


Анимация 2D-графики

Анимация — это плавное изменение картинки со временем.
В 2D-играх для анимации в основном используется замена спрайтов на похожие, с небольшими отличиями.
Быстрая замена таких спрайтов создает у человека ощущение движения.
Для анимации нужно хранить информацию о времени, которое осталось до след. кадра и сам кадр (на какой спрайт поменяется изображение объекта), а также:
— Скорость воспроизведения анимации — на какое число умножается время, если множитель отрицательный, то анимация воспроизводится в обратном порядке.
— Зациклена ли анимация (в этом случае после достижения последнего кадра анимация продолжится с первого)
— В какой момент времени или на каком кадре анимации должны происходить игровые события (например, удара, звук ходьбы)

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


Хранение на диске

В файлах изображения хранятся в форматах:

bmp — Все данные хранятся в сыром виде, почти как в видеокарте. Занимает много места на диске (4 байта на пиксель), не рекомендуется использовать.
Зато максимально прост в обработке при создании своего 2D-движка.

png — Использует сжатие цепочек повторяющихся пикселей, без потери качества, можно задействовать прозрачность. Используйте для пиксель-арта и графического интерфейса.
Можно уменьшать количество цветов в изображении, используя палитру.

jpg — Использует сжатие с потерями, хорошо подходит для фотографических изображений. При каждом сохранении качество картинки теряется, поэтому исходные изображения нужно хранить в png. Можно регулировать степень сжатия, уменьшая размер изображения и качество картинки.

Есть и другие, более редкие форматы (pcx, tiff, tga), поддерживаемые не всеми движками.
Из них отмечу:

psd — формат изображений для Фотошопа, есть прозрачность и многослойность.

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


3D-графика, свет и тени

В трехмерной графике есть понятие освещения.
Источники света могут быть:
* глобальными (ambient light)
* направленными (directed), все лучи параллельны, идут в одном направлении из бесконечно удаленной точки (пример — солнце)
* радиальными (radial, sphere, arealight), испускающие затухающий свет из одной точки (пример — лампа)
* узконаправленными (spotlight), аналогичны радиальным, но испускают свет не во все стороны, а в определенном направлении, пучком ограниченного угла (пример — фонарик, прожектор)

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

Тени (shadows) бывают:
* запеченые (baked) — изображение теней является просто фиксированным рисунком на неподвижных объектах, можно использовать когда источник света и объекты — не меняются.
* попиксельные (pixel) — тень является пиксельной картинкой, которая рисуется на поверхности. Чем мельче пиксели в тени, тем больше места тень занимает в памяти видеокарты.
* стенсильные (stencil) — является проекцией модели объекта на поверхность в виде черного силуэта.

Видеокарты имеют аппаратную поддержку источников света и теней.
Однако максимальное количество одновременно работающих источников света в игре ограничено небольшим количеством (от 4 до 16).


3D-Модели

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

Меш (mesh) — представляет форму объекта, состоящую из полигонов (polygon) — многоугольников, замкнутых фигур, образуемых грани объекта.
Чаще всего эти полигоны являются треугольниками (triangle), но иногда — четырехугольниками (square).
Грани объекта могут быть односторонними или двухсторонними.
Для односторонних от порядка перечисления вершин в полигоне (1,2,3 или 3,2,1) зависит с какой стороны находится грань полигона. Противоположная сторона грани останется полностью прозрачной. Для двусторонних граней обе стороны полигона будут выглядеть одинаково.

У каждого полигона есть нормаль-вектор, определяющий перпендикуляр плоскости грани.
Нормаль используется при расчете освещения грани от источников света — чем меньше угол между нормалью грани и направлением на источник света, тем лучше будет освещена эта грань.
У каждой вершины меша есть также текстурные координаты U и V (число от 0.0f до 1.0f). По текстурным координатам вершин одного полигона видеокарта определяет — какой участок текстуры будет наложен на этот полигон.

Материал (material) — содержит ссылки на текстуры (двухмерные пиксельные изображения), содержащие для каждой грани информацию о:
— цвете [diffuse] (что изображено на грани)
— отражающей способности [specular] (насколько блестит)
— неровностях, их глубине [bump, normal, paralax] (используется при освещении для симуляции шершавых поверхностей)
и другую информацию.
Все эти текстуры в целом описывают внешний вид модели.


Порядок отрисовки в 3D

В 3D все объекты могут располагаться на разном расстоянии до камеры и это расстояние может постоянно меняться, причем не только для объектов, но даже для отдельных полигонов двух объектов.
Поэтому в видеокартах есть аппаратная возможность отрисовки полигонов по расстоянию до камеры (z-buffer), от самого дальнего к самому ближнему.


Вершинные (vertex) шейдеры

Это шейдеры, которые объясняют видеокарте как отрисовывать 3D-модель.
Собственно, от них и зависит — как обработать освещение и содержимое текстур, прежде чем выдать картинку.
Эти шейдеры принимают:
— координаты вершин полигона
— его текстурные координаты (U,V)
— нормаль-вектор
и другую информацию.
Затем, после отработки вершинных шейдеров применяются фрагментные шейдеры, которые действуют как пиксельные шейдеры в 2D.


Анимация 3D-графики

* Анимация заменой моделей
Этот способ аналогичен заменой спрайтов в 2D.
Для каждого положения объекта создаются отдельные меши, которые заменяются со временем.

* Скелетная анимация (skeletal animation)
Позволяет создать более плавное движение, описывая с помощью кривых плавное изменение координат каждой вершины меша во времени.
Перед созданием скелетной анимации нужно провести 2 операции:
* ригинг (riging) — создание скелета модели, кости которого будут двигаться во время анимации, для условного разделения модели на подвижные части тела. Скелет приобретает вид иерархического дерева, где у каждой кости есть родительская кость, к которой она прикреплена (в случае с гуманоидами корневой костью является таз).
* скининг (skining) — привязывание каждой вершины модели к кости, вместе с которой та будет перемещаться. Вершина может быть привязана с разным весом сразу к нескольким костям, если она должна двигаться при перемещении нескольких костей.

Перед выполнением этих операций модель должна находиться в T-позе (T-pose). Это исходное положение вершин модели, которое дает наилучший доступ ко всем вершинам модели и соответствует максимальному расслаблению конечностей персонажа.

Затем происходит создание анимации, заключающееся в создание ключевых кадров анимации на временной шкале и типов переходов между ними.
Во время воспроизведения анимации меняется поза модели (положение всех костей), что воспринимается игроком как движение.
При создании анимации может использоваться:
* прямая кинематика (forward kinematics) — вы меняете положение кости и вместе с ней меняется положение всех ее дочерних костей (от таза к конечности)
* обратная кинематика (inverse kinematics) — вы меняете положение кости и вместе с ней меняется положение ее родительских костей (от конечности к тазу) так, чтобы все кости остались соединенными.
При создании анимации следует следить чтобы поза (положение костей) в конце одной анимации совпадала с позой в начале следующей. Для этого также можно использовать функции игрового движка для плавного перемещения костей от одной позы к другой.

* Вершинная анимация или анимация формы (vertex animation, morph animation)
Вы меняете положение одной вершины меша и все остальные вершины движутся в след за ней, сохраняя расстояние и пропорции.
Чаще всего используется для лицевой анимации персонажей. Поддерживается не всеми движками.


Воксельная графика

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

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


Хранение на диске

Текстуры, как и спрайты, хранятся в файлах изображений (png или jpg).

Для создания моделей используются 3D-редакторы. У каждого разработчика редактора — свой формат моделей.
Иногда 3D-редакторы сохраняют модели в собственном формате — чтобы использовать модель в игре, нужно ее экспортировать (преобразовать) в формат, понятный игровому движку.
При экспорте важно следить за совпадением осей координат внутри игры с осями самой модели, иначе в игре модель будет перевернута. Также при экспорте могут возникнуть ошибки, нестыковки и модель в игре может выглядеть не так, как в 3D-редакторе.

obj — текстовой формат, описывающий меш. Содержит список координат всех вершин, текстурных координат, нормалей, материалов (mat) и т. д.
Максимально прост для обработки при написании своего 3D-движка.

mat — текстовой формат, содержащий ссылки на все текстуры меша. Идет вместе с obj-файлом.

3ds — Бинарный формат описания модели, старый формат 3DS Max. Может содержать в себе как всю информацию о модели, так и только информацию о меше.
К сожалению, поддерживает описание не всех параметров моделей, используемых современными движками (например, анимацию).
Зато поддерживается всеми 3D-движками, особенно старыми.

fbx — Формат программ фирмы Autodesk. Лучше всего подходит для сохранения анимации.

max — Современный формат 3DS Max.

dae — формат программ фирмы Collada.

blend — формат программы Blender. Обычно игровыми движками не поддерживается и требуется экспортировать в другой формат.