Используемые термины
- Компонент - объект, содержащий информацию об одном аспекте игрового объекта (таких как текстура, меш, положение и т.п.).
Игровой объект - объект в игровом мире, содержащий информацию в виде набора компонентов.
- Игровой мир - набор игровых объектов, разбитый на иерархическую структуру из ячеек, каждая из которых содержит несколько игровых объектов; назначение - оптимизация подгрузки и выгрузки игровых объектов (сразу ячейками).
- Игровая сцена - набор игровых объектов, непосредственно участвующих в игровом процессе; объекты подгружаются (по мере необходимости) из игрового мира и это именно те объекты, которые должны обновляться в процессе игры.
- Рендер - объект, предназначенный для отображения игровых объектов в 3D сцене, создается на основе одного или нескольких компонентов.
- Объект 3D сцены - объект (набор рендеров), из которых состоит 3D сцена.
- 3D сцена - набор объектов 3D сцены, которые отображаются в окне программы; является 'зеркалом' той части объектов игровой сцены, которые попадают в поле зрения камеры.
Исходные требования
- Не должно производиться никаких лишних действий (вызовов пустых функций), для каждого объекта (игрового и 3D сцены) должен выполнятся набор только ему необходимых действий.
- Отсутствие дублирования информации (все объекты, отрисовываемые при помощи одного меша, должны содержать ссылку на единую область памяти с информацией об этом меше).
Компоненты
Фреймворк предоставляет класс компонента Component, а также механизм создания рендеров для этих компонентов с учетом активного графического Api (рендеры создаются через объект класса Renders, который может быть получен при помощи функции GetRenders() класса Window).
Класс компонента:
- Содержит и предоставляет доступ к параметрам, которые используются для создания рендеров.
- Параметры (кроме обязательных) могут отсутствовать, для таких параметров предусмотрено значение по умолчанию.
- Обязательный параметр id - идентификатор компонента. Для компонентов с одним и тем же идентификатором будет использоваться один и тот же рендер, поэтому объекты, содержащие один и тот же меш, могут содержать компоненты, у которых задан только (один и тот же) идентификатор, а развернутое описание этого компонента можно сделать в другом месте и создать его рендер заранее.
- Обязательный параметр type - тип компонента, который определяет способ отрисовки этого компонента.
- Параметр kind - дочерний подтип компонента; у некоторых типов не используется.
- Первоначальная информация может загружаться в виде строк, в дальнейшем (для ускорения рендеринга) при обновлении игровых объектов значения параметров следует устанавливать того же типа, какой используют рендеры (подробнее см. в описании параметорв компонентов).
- Заметки
- Предполагается, что исходная информация о компоненте будет хранится в узлах xml файла, из которых параметры будут загружаться как атрибуты, у которых имя атрибута будет названием параметра, а значение атрибута - значением параметра.
Типы компонентов
Data
Вспомогательный компонент, предназначенный для передачи информации рендерам других типов.
- Заметки
- Вспомогательные комопненты следует передавать основному компоненту как параметр service в виде набора std::vector<std::shared_ptr<Component>> (какие данные нужны конкретным рендерам см. в описании соответствующих компонентов).
- Идентификатор этого компонента не используется.
kind | Параметры | Тип параметра (значение по умолчанию) | Описание параметра |
Position (1) | x, y, z | float (0.0f) | Положение (координаты) в пространстве |
Rotation (1) | x, y, z | float (0.0f) | Ориентация (углы поворота вокруг соответствующей оси в радианах) в пространстве |
Scale (1) | x, y, z | float (1.0f) | Масштабирующие коэффициенты по соответствующим осям |
Rect (1) | left, top, right, bottom | int | Границы прямоугольника |
Texture | content (4) | covellite::api::Buffer_t<uint8_t> | Бинарные данные текстуры в формате R8G8B8A8 |
width | int | Ширина изображения в пикселях |
height | int | Высота изображения в пикселях |
name | String_t | Имя текстуры в шейдере GLSL |
index | int | Индекс текстуры tX в шейдере HLSL |
destination | String_t (albedo) | Назначение текстуры (albedo, metalness, roughness, normal, occlusion, depth) |
mipmapping | bool (false) | Генерировать mip'ы для текстуры; только для текстур, не используемых как внеэкранные поверхности |
mapper | std::function<bool(const void *)> | Функция обратного вызова для чтения данных текстуры (каждый пиксель - это число uint32_t в формате ABGR) |
capacity | int (8) | Количество бит на каждый канал цвета (8, 16, 32) |
Shader | content (4) | covellite::api::Buffer_t<uint8_t> | Содержимое текстового файла шейдера в бинарном виде |
entry | String_t | Имя функции точки входа шейдера |
instance | String_t | Описатель (вида 'f4f4i4') структуры инстанс-буфера |
Buffer | content (4) | covellite::api::Buffer_t<Vertex> | Данные вертексного буфера для отрисовки плоских и объемных объектов (2) |
covellite::api::Buffer_t<int> | Данные индексного буфера |
count | size_t | Количество элементов в инстанс-буфере |
mapper | std::function<bool(void *)> | Функция обратного вызова для заполнения инстанс-буфера |
std::function<bool(void *, size_t &)> |
size | size_t | Размер инстанс-буфера в байтах |
Компонент Data.Texture может использоваться для следующих целей:
- Цель рендеринга во внеэкранную поверхность (см. компонент BkSurface).
- Источник данных для рендеринга текстурированных объектов (см. компонент Texture).
В рамках одного прохода (одна и та же камера или BkSurface) Data.Texture может использоваться только в одном качестве, т.е. в первом проходе ее можно передать компоненту BkSurface и она будет использоваться как цель рендеринга (и ее нельзя будет использовать в качестве текстуры объекта), а во втором проходе ее же можно передать компоненту Texture и использовать для рендеринга объектов.
- Заметки
- Параметр capacity текстур предназначен исключительно для увеличения точности значений при передаче их между разными проходами рендеринга (мировых координат для расчета теней, например), использовать его для текстур, загруженных из файла не имеет смысла. Текстуры глубины всегда создаются как одноканальные, у которых 24 бита на пиксель.
Camera
Компонент виртуальной камеры.
kind | Параметры | Тип параметра (значение по умолчанию) | Описание параметра |
Orthographic | service | Data.Position | |
Perspective | service | Data.Position, Data.Rotation | |
distance | float (0.0f) | Расстояние от позиции, заданной компонентом Data.Position до камеры |
fov | float (90.0f) | Угол поля зрения по вертикали в градусах |
znear | float (0.01f) | Ближняя плоскость отсечения |
zfar | float (200.0f) | Дальняя плоскость отсечения |
- | scale | float (1.0f) | Масштабирующий коэффициент размеров внеэкранной поверхности относительно размеров окна программы |
width | int (ширина окна программы) | Ширина внеэкранной поверхности в пикселях |
height | int (высота окна программы) | Высота внеэкранной поверхности в пикселях |
- Заметки
- Камера определяет способ отрисовки сцены и ее рендер должен быть первым в списке 3D сцены.
- Компонент формирует матрицы вида/проекции и передает их шейдеру (константный буфер шейдера CameraData).
- Сформированные матрицы вида/проекции после вызова рендера будут записаны исходному объекту компонента камеры как параметры view и projection в виде объекта класса glm::mat4 (матрицы записываются в том же виде, что и при передаче шейдеру, т.е. транспонированными).
-
- Параметры scale, width и height влияют на то, какого размера будет создана внеэкранная поверхность (компонент BkSurface), указанная после компонента камеры:
- Если не указаны параметры scale, width и height, будет создана внеэкранная поверхность с размерами, совпадающими с размерами окна программы; при изменении размеров окна программы размеры внеэкранной поверхности также автоматически изменятся.
- Если задан параметр scale, будет создана внеэкранная поверхность с размерами, равными размерам окна программы, домноженными на указанное значение (при этом значения параметров width и height будут проигнорированы); при изменении размеров окна программы размеры внеэкранной поверхности также автоматически изменятся с учетом указанного коэффициента.
- Если не указан параметр scale и указаны параметры width и height, будет создана внеэкранная поверхность указанных размеров; при изменении размеров окна программы размеры внеэкранной поверхности меняться не будут.
Ортографическа камера использует левостороннюю систему координат, в которой:
- Начало координат находится в левом верхнем углу экрана (viewport'ом в Windows является вся клиентская часть окна, в Android - все за исключением заголовка окна).
- Координаты вдоль оси X увеличиваются вправо.
- Координаты вдоль оси Y увеличиваются вниз.
- Камера смотрит на плоскость Oxy со стороны положительных значений оси Z.
- Одному пикселю окна программы соответствует изменение координаты на 1.0f.
- Необходимо сделать:
- Добавить картинку с осями координат для Windows и Android.
Перспективная камера использует правостороннюю систему координат:
- Координаты вдоль оси X увеличиваются с запада на восток.
- Координаты вдоль оси Y увеличиваются с юга на север.
- Координаты вдоль оси Z (высота над плоскостью Oxy) увеливаются снизу вверх.
- Камера смотрит в точку, заданную компонентом Data.Position с расстояния distance (т.е. реализован вид 'от третьего лица', если нужен вид 'от первого лица', установить distance в ноль).
- Data.Rotation - это углы поворота самой камеры относительно точки Data.Position (т.е. направление, обратное направлению взгляда).
- Отсутствие компонента Data.Rotation (нулевые значения углов ориентации) соответствует направлению взгляда из точки, смещенной от Data.Position на distance вдоль оси X в положительном направлении.
BkSurface
Компонент внеэкранной поверхности, предназначен для рендеринга в текстуру(ы).
kind | Параметры | Тип параметра | Описание параметра |
- | service | Data.Texture | Набор текстур внеэкранной поверхности |
width | int | Ширина внеэкранной поверхности в пикселях (только чтение) |
height | int | Высота внеэкранной поверхности в пикселях (только чтение) |
Подробнее о использовании компонента см. Рендеринг во внеэкранную поверхность.
State
Компонент изменения состояния конвеера рендеринга.
kind | Параметры | Тип параметра | Описание параметра |
Clear | color | uint32_t | Цвет заливки буфера кадра |
Depth | enabled | bool (false) | Включение/отключение использования буфера глубины (используется инвертированный буфер глубины) |
clear | bool (false) | Включение/отключение очистки буфера глубины |
overwrite | bool (true) | Включение/отключение перезаписи буфера глубины |
Blend | | | Включение использования прозрачности |
Sampler | | | Включение использования сглаживания текстур |
Scissor | service | Data.Rect | |
enabled | bool | В случае false Data.Rect не нужен |
AlphaTest | discard | float | Отбрасывание значений цвета, у которых значение Alpha-канала меньше или равно указанному |
- Заметки
- Каждая камера при рендеринге отключает:
- Использование внеэкранной поверхности (устаналивает вывод нап экран).
- Использование буфера глубины.
- Блендинг.
Shader
Компонент шейдера.
kind | Параметры | Тип параметра |
- | service | Data.Shader (3) |
- Заметки
- Тип создаваемого шейдера (вертексный или пиксельный) выводится автоматически из входного типа данных указанной функции точки входа.
- Всем шейдерам в качестве заголовочных файлов передаются:
- Файл, содержащий описания структур для передачи данных шейдеру (Fx).
- Файл, содержащий описания форматов входных данных шейдеров
- Если не указаны бинарные данные шейдера (параметры data и count), вместо них будут использоваться шейдеры по умолчанию (те, что использует фреймворк для рендеринга Gui).
- В том случае, если в тексте шейдера содержится ошибка, рендер не будет создан и попытка отрендерить объект без установленного шейдера может привести к падению видеодрайвера. Поэтому хорошей практикой является установка объекту камеры компонентов шейдеров по умолчанию (без передачи данных шейдера content), которые в этом случае позволят отрендерить объект 'абы как'.
Подробнее о том, как формируется окончательный текст шейдера, передаваемый компилятору, см. Шейдеры.
Texture
Компонент текстуры.
kind | Параметры | Тип параметра |
- | service | Data.Texture (3) |
В шейдерных реализациях для рендеринга требуется специальный пиксельный шейдер, т.к. текстуры просто передаются шейдеру в слоты в соотвествии со следующей логикой:
- Если заданы параметры name как TexEnvironment и index как 4, то текстура будет передана слоту, который для унификации шейдера под HLSL и GLSL следует объявлять и использовать как
COVELLITE_DECLARE_TEX2D(TexEnvironment, 4);
float3 Color = COVELLITE_TEX2D_COLOR(TexEnvironment, TexCoord).rgb;
- Если параметры name и index не указаны, слот текстуры в шейдере определяется по значению параметра destination
COVELLITE_DECLARE_TEX2D(TexAlbedo, 0);
COVELLITE_DECLARE_TEX2D(TexMetalness, 1);
COVELLITE_DECLARE_TEX2D(TexRoughness, 2);
COVELLITE_DECLARE_TEX2D(TexNormal, 3);
COVELLITE_DECLARE_TEX2D(TexOcclusion, 4);
COVELLITE_DECLARE_TEX2D(TexDepth, 5);
- Если не указаны параметры name, index и destination, слот текстуры в шейдере будет установлен как
COVELLITE_DECLARE_TEX2D(TexDiffuse, 0);
- Заметки
- Параметр mapper используется для установки функции обратного вызова, которая вызывается при активации рендера и предназначена для чтения содержимого буфера текстуры (только для текстур, используемых в качестве внеэкранных поверхностей). Нужно понимать, что при этом данные текстуры копируются из видеопамяти в системную память, что является тяжелой операцией и использование ее при каждой активации текстуры (возврат true при каждом вызове функции обратного вызова со значением nullptr) очень сильно ударит по производительности. Чтение данных из текстуры имеет смысл использовать в качестве особой, разовой операции; например, при обработке клика мышью.
Buffer
Компонент буферов геометрии меша и констант шейдера.
kind | Параметры | Тип параметра | Описание параметра |
Vertex | service | Data.Buffer (3) | |
mapper | std::function<bool(const Vertex *)> | Функция обратного вызова для изменения буфера |
Constant | service | Data.Buffer (3) | |
mapper | std::function<bool(const void *)> | Функция обратного вызова для заполнения пользовательского буфера |
size | size_t | Размер константного буфера в байтах (когда в качестве mapper используется std::function<bool(void *)>) |
name | String_t | Имя переменной пользовательского буфера в шейдере ('cbUserData' по умолчанию) |
- Необходимо сделать:
- Требуется обобщение способа передачи параметров константного и инстанс-буфера, т.к. они совпадают, но в первом случае они передаются самому компоненту, а во втором - компоненту Data.Buffer.
- Заметки
- Параметр kind можно не указывать, тип буфера выводится автоматически из типа переданных ему данных.
- Порядок обхода вершин вертексного буфера - против часовой стрелки.
- Если передан параметр mapper, будет создан буфер, который можно изменять (только содержимое, причем можно изменять только часть значений; размер буфера, равный значению параметра count изменить нельзя) прямо во время работы программы. Переданный функциональный объект будет вызываться два раза:
- Со значением параметра nullptr - вернуть true или false в зависимости от того, следует ли обновлять буфер или нет.
- С указателем на начало памяти буфера, если обновление требуется (возращаемое значение не используется).
Логика работы с источниками света через константный буфер с ограниченным количеством слотов для точечных источников света построена исходя из оптимизации расчета освещения каждого объекта (как передача данных, так и расчет освещения для большого количества источников света шейдером очень времязатратная операция, fps катастрофически падает уже при паре десятков источников света в сцене). Сцена может содержать сотни точечных источников света, но здравый смысл подсказывает, что они будут распределены по сцене более-менее равномерно и каждый конкретный объект будет освещен небольшим (единицы) количеством источников света. Поэтому строить набор компонентов объектов сцены имеет смысл следующим образом:
- При загрузке сцены все объекты, являющиеся точечными источниками света, добавляют свои параметры в общий список источников света сцены.
- Камере добавляется компонент Buffer.Constant, функция обратного вызова которого (mapper) формирует значения фонового и направленного источников света.
- Каждому объекту сцены добавляется компонент Buffer.Constant пользовательского константного буфера, функция обратного вызова которого перебирает список всех точечных источников света и добавляет в список источников света объекта те, которые освещают его сильнее всего (например, самые близкие).
- Расчет радиуса действия для точечного источника света можно посмотреть здесь.
- Для статичных объектов набор статичных же источников света можно формировать один раз.
Transform
Компонент установки положения/ориентации/масштабирования объекта.
kind | Параметры | Тип параметра | Описание параметра |
- | service | Data.Position/Rotation/Scale (3) | Объект, положение/ориентация/масштаб которого могут изменятся во время работы программы |
Static | Объект, положение/ориентация/масштаб которого не будут изменятся во время работы программы |
Billboard | Data.Position (3) | Объект, всегда развернутый 'лицом' к камере |
- Заметки
- Компонент формирует матрицу трансформации объекта и передает ее шейдеру (константный буфер шейдера ObjectData.World).
- У статического объекта мировая матрица будет вычислена один раз при его создании и в дальнейшем изменить его положение/ориентацию/масштаб будет невозможно; динамический объект захватывает переданные ему компоненты Data, поэтому его положение/ориентацию/масштаб можно менять в каждом кадре изменяя значения параметров этих компонентов.
- 'Лицом' billboard'a считается плоскость Oxy при взгляде со стороны положительных значений вдоль оси z.
Present
Компонент отправки информации в конвеер рендеринга.
kind | Параметры | Тип параметра |
Index | service | Data.Buffer (3) |
Instance | Data.Buffer |
- Заметки
- Компонент Present должен завершать набор компонентов каждого объекта 3D сцены.
- Указание значения kind не обязательно; если задан только Buffer.Index, будет создан компонент Present.Index, если заданы и Buffer.Index и Buffer.Instance - Present.Instance.
Updater
Компонент обновления объекта. Позволяет установить функциональный объект (функцию обратного вызова), связанную с конкретным объектом.
Параметры | Тип параметра | Описание параметра |
function | std::function<void(const float)> | Функциональный объект, который будет вызываться при активации рендера компонента |
- Заметки
- Переданному функциональному объекту при вызове будет передаваться время рендеринга текущего кадра (отсчет ведется с момента запуска программы) в секундах.
- Гарантируется, что в рамках рендеринга одного кадра все updater'ы всех объектов получат одно и то же значение времени.
- Компоненту можно установить новый функциональный объект в любой момент времени (это может делать даже сам вызванный функциональный объект во время работы, безо всяких ограничений).
Если необходимо, чтобы updater объекта вызывался только тогда, когда рендерится сам объект (например, когда он находится в поле зрения камеры), то компонент updater'a можно добавить в список компонентов самого объекта и забыть о нем. Если же updater нужно вызывать независимо от того, рендерится объект в текущем кадре или нет, то необходимо компонент его updater'a поместить в отдельный игровой объект и вызывать его рендер индивидуально.
Сноски
- Параметры этих компонентов могут изменяться во время работы программы (т.е. для изменения положения объекта в пространстве не нужно его пересоздавать, достаточно установить новые координаты комопоненту его положения), для изменения остальных (например замены 3D модели объекта) необходимо создать новый набор компонентов, удалить из 3D сцены старый и создать новый объект.
- Параметры можно указывать непосредственно основному компоненту, без использования компонента Data.
- Может быть несколько компонентов, что можно использовать для привязки (совместного движения) объектов друг к другу (в этом случае необходимо гарантировать, чтобы в качестве общего компонента использовался ОДИН И ТОТ ЖЕ объект, совпадения идентификаторов компонентов недостаточно!).
- Содержимое параметра content будет удалено из компонента во время создания рендера.
Общая концепция использования компонентов
Основная идея работы с 3D объектами предполагает использование архитектуры программы на основе паттерна MVC.
Модель
- Загружает игровой мир как набор ячеек, каждая из которых ссылается на место хранения информации о содержащихся в ячейке объектах.
- Содержит базу данных игровых объектов и функции, которые позволяют получать набор компонентов (для обновления и рендеринга) игрового объекта по его идентификатору.
- Содержит игровую сцену(ы), которая включает идентификаторы камеры, а также источников света и объектов, находящихся рядом с камерой.
- При перемещениях игрока добавляются приблизившиеся к камере и удаляются удалившиеся объекты (группами в ячейках).
- На стадии обновления обновляются объекты игровой сцены (можно обновлять объекты постепенно удаляясь от камеры пока не выйдет заданное на обновление время).
Представление
- Класс окна, которое создается между окнами Api и Gui.
- Содержит, создает и удаляет объекты 3D сцены на основе информации, полученной от модели.
- При построении/обновлении 3D сцены первой должна идти камера.
- Изменение геометрии/материала производится удалением/добавлением игрового объекта.
Взаимодействие частей кода игры
- Панель GUI с кнопками действий игрока (View) подписывается на события нажатия кнопок, при получении которых генерирует события действий игрока.
- Игровой мир (Model) подписывается на события действий игрока:
- Обновляет состояние игрока и объектов игровой сцены.
- В очередь создания объектов добавляются объекты, которые теперь попадают в зону действия камеры.
- Из игровой сцены удаляются объекты, расположенные слишком далеко от камеры.
- 3D сцена (View):
- По событию создания объекта запрашивает у модели набор компонентов объекта и создает его рендер.
- По событию удаления объекта удаляет его рендер.
- По событию отрисовки окна запрашивает у модели актуальынй набор игровых объектов и активирует их рендеры.
Примеры использования компонентов
Отрисовка 2D объектов
Отрисовка 3D объектов