Covellite++  Version: 2.3.0 Revision: 2580 Platform: x64 Build: 15:23 16.10.2020
Кроссплатформенный фреймворк для разработки приложений на С++
Отрисовка 2D объектов

Описание отрисовки в окне программы плоских объектов с использованием компонентной системы.

Используемые термины

  • Компонент - объект, содержащий информацию об одном аспекте игрового объекта (таких как текстура, меш, положение и т.п.).

Игровой объект - объект в игровом мире, содержащий информацию в виде набора компонентов.

  • Игровой мир - набор игровых объектов, разбитый на иерархическую структуру из ячеек, каждая из которых содержит несколько игровых объектов; назначение - оптимизация подгрузки и выгрузки игровых объектов (сразу ячейками).
  • Игровая сцена - набор игровых объектов, непосредственно участвующих в игровом процессе; объекты подгружаются (по мере необходимости) из игрового мира и это именно те объекты, которые должны обновляться в процессе игры.
  • Рендер - объект, предназначенный для отображения игровых объектов в 3D сцене, создается на основе одного или нескольких компонентов.
  • Объект 3D сцены - объект (набор рендеров), из которых состоит 3D сцена.
  • 3D сцена - набор объектов 3D сцены, которые отображаются в окне программы; является 'зеркалом' той части объектов игровой сцены, которые попадают в поле зрения камеры.
Заметки
  • В данном описании под объектом подразумевается 'объект 3D сцены', а под сценой - '3D сцена'.
  • Обозначение компонента как State.Scissor подразумевает объект компонента, у которого установлены параметры type == 'State' и kind == 'Scissor'.
  • Под объектом компонента подразумевается объект C++ класса компонента.
Подробнее о компонентах и их параметрах см. Компонентная система.

Общее описание

Хотя компонентная система предназначена - в первую очередь - для отрисовки трехмерных сцен, тем не менее она позволяет также рисовать в окне простые плоские объекты (именно этот режим используется для отрисовки GUI). Идея состоит в том, что при помощи специальной ортогональной камеры (которая 'смотрит' на плоскость Oxy со стороны положительных значений оси z) отображаются объекты, состоящие из треугольников, вершины которых лежат в плоскости Oxy.

Используемый формат вершин:

using Vertex_t = ::covellite::api::Vertex;

Отрисовка производится при помощи формирования сцены, содержащей рендеры:

  • Камеры - уникальный объект, определяющий способ отрисовки сцены.
  • Общих объектов (например, настройки конвеера рендеринга).
  • Объектов, которые непосредственно формируют требуемое изображение на экране.

Сцена определяется набором идентификаторов ее объектов, таких сцен может быть несколько, добавлять объекты разных сцен можно в очереди рендеринга разных проходов, при рендеринге нескольких сцен текущая сцена будет отрисоваваться поверх предыдущей; также можно сформировать несколько наборов идентификаторов объектов, каждый из которых отрисовывать в зависимости от неких условий.

Создание и удаление рендеров для компонентов, а также рендеринг объектов 3D сцены производится через объект класса covellite::expanse::Window, который должен быть создан при старте программы и передан окну формирования 3D сцены.

Предупреждения
Реализация OpenGL требует, чтобы рендеры создавались и активировались в том же потоке, в котором было создано окно графического Api.
Заметки
При использовании в разных объектах компонентов с одним и тем же id будет использоваться один и тот же объект рендера (это позволяет, например, один раз сформировать набор общих для всех объектов рендеров, а затем обращаться к ним через объекты компонентов, содержащих только идентификаторы), но при этом следует учитывать, что для некоторых типов компонентов рендеры не создаются (например, компоненты Data). Для таких компонентов параметр id не имеет смысла и при необходимости использования несколькими объектами одного и того же компонента (например, для совместного перемещения группы объектов), следует использовать один и тот же объект компонента.

Разные объекты могут использовать рендеры, выполняющие одно и то же действие, в этом случае для экономии ресурсов можно создать эти рендеры (один раз) заранее, а в дальнейшем использовать их через компоненты с соответствующими id.

Пример создания объекта, состоящего из компонентов, которые в дальнейшем будут использоваться для всех объектов:

  • Двух тестур.
  • Двух пиксельных шейдеров (для для отрисовки текстурированного и залитого цветом объектов).
  • Индексного буфера.
  • Updater'a, синхронизирующего fps = 60.
  • Главного updater'a сцены, который добавляет идентификаторы объектов, которые должны отрисовываться в текущем кадре.
const auto & Vfs = ::covellite::app::Vfs_t::GetInstance();
const auto CommonObjectId = CreateObject(
LoadTexture("simple2dgame.bricks.jpg", uT("Simple2DGame.Texture")) +
LoadTexture("simple2dgame.clock.png", uT("Simple2DGame.Texture.Clock")) +
GameObject_t
{
Component_t::Make(
{
{ uT("id"), uT("Simple2DGame.Shader.Pixel.Textured") },
{ uT("type"), uT("Shader") },
{ uT("entry"), uT("psTextured") },
{ uT("content"), Vfs.GetData("Data\\Shaders\\Textured.fx") },
}),
Component_t::Make(
{
{ uT("id"), uT("Simple2DGame.Shader.Pixel.Colored") },
{ uT("type"), uT("Shader") },
{ uT("entry"), uT("psColored") },
// { uT("content"), - }, // использовать шейдер по умолчанию
}),
Component_t::Make(
{
{ uT("id"), uT("Simple2DGame.Present.Rectangle") },
{ uT("type"), uT("Present") },
{ uT("content"), ::std::vector<int>{ 0, 1, 2, 2, 1, 3, } },
}),
pSynchronizer60fps,
pMainUpdater
});
Заметки
Далее в примерах в качестве объектов будут использоваться прямоугольники, состоящие из двух треугольников, но на практике объекты могут быть любой формы из любого количества треугольников (ограничением являются лишь возможности железа).

Камера и настройки конвеера рендеринга

Заметки
Здесь и далее все создаваемые игровые объекты создаются через функцию класса covellite::expanse::Window, а возвращаемые ей идентификаторы добавляются в очередь рендеринга главным updater'ом сцены.

В данном примере формируется объект, состоящий из компонентов:

  • Ортографической камеры (с компонентом смещения ее таким образом, чтобы центр поля зрения оказался в центре экрана).
  • Компонента, включающего использование прозрачности.
  • Компонента настроек вывода текстуры.
  • Компонента используемого вертексного шейдера:
const auto pCameraPosition = Component_t::Make(
{
{ uT("type"), uT("Data") },
{ uT("kind"), uT("Position") },
{ uT("x"), -_Xo },
{ uT("y"), -_Yo },
});
return CreateObject(
{
Component_t::Make(
{
{ uT("id"), uT("Simple2DGame.Camera.") + Id.GetStringId() },
{ uT("type"), uT("Camera") },
{ uT("kind"), uT("Orthographic") },
{ uT("service"), GameObject_t{ pCameraPosition } },
}),
Component_t::Make(
{
{ uT("id"), uT("Simple2DGame.State.Blend") },
{ uT("type"), uT("State") },
{ uT("kind"), uT("Blend") },
}),
Component_t::Make(
{
{ uT("id"), uT("Simple2DGame.State.Sampler") },
{ uT("type"), uT("State") },
{ uT("kind"), uT("Sampler") },
}),
Component_t::Make(
{
{ uT("id"), uT("Simple2DGame.Shader.Vertex.Rectangle") },
{ uT("type"), uT("Shader") },
{ uT("entry"), uT("vsFlat") },
}),
});

Если камера не использует буфер глубины, отрисовка объектов производится в порядке вызовов их рендеров (те, что вызваны позже будут отрисованы поверх тех, которые были вызваны ранее), при включенном буфере глубины видимость объектов будет определяться их расположением на оси z (подробнее про положение объектов см. в описании рендеринга объектов).

Заметки
При использовании буфера глубины следует учитывать, что отрисовываться будут только те объекты, которые находятся вдоль оси z в диапазоне координат [-1.0f...1.0f], с учетом установленного компонента масштабирования (если, например, параметр z у Data.Scale будет установлен в 0.0f, то независимо от положения на оси z все объекты будут отрисовываться в порядке вызовов их рендеров).

Рендеринг объектов

Объект, отображаемый на экране, представляет собой набор рендеров, который создается на основе набора компонентов и должен включать:

  • Вершинный шейдер.
  • Пиксельный шейдер.
  • Текстуру (только для текстурированных объектов).
  • Вертексный буфер.
  • Компонент трансформации (не обязателен).
  • Present с индексным буфером (именно он осуществляет отрисовку объекта, поэтому должен располагаться последним).

Вертексный буфер формируется в зависимости от того, какой объект (просто закрашенный цветом или текстурированный) нужно отрисовывать.

Компоненты Data.Position/Data.Rotation/Data.Scale используются для перемещения/вращения/изменения размеров объектов без их пересоздания (достаточно установить новые значения параметров при обновлении сцены), причем:

  • Компонентов одного типа может быть несколько (что позволяет, например, создавать группы объектов, которые должны быть расположены относительно друг друга определенным образом, но при этом двигаться/вращаться вместе; во втором случае необходимо использовать общий объект компонента, использование разных объектов с одинаковым id нужного эффекта не даст).
  • Важен порядок следования компонентов (поворот-перемещение даст сначала поворот вокруг точки [0, 0] с нулевым радиусом, а затем уже смещение повернутого объекта, а перемещение-поворот даст сначала смещение от точки [0, 0], а затем уже поворот вокруг нее с радиусом смещения).
Необходимо сделать:
  • Добавить картинку, объясняющую разницу между поворот-смещение и смещение-поворот.

Поскольку одному пикселю на экране соответствует смещение на 1.0f вдоль осей X и Y, существует два способа создания объектов требуемого размера:

  • Сформировать вертексный буфер, содержащий координаты вершин в пикселях.
  • Сформировать вертексный буфер, содержащий координаты вершин в условных единицах (например, считать единицей ширину окна программы) и каждому объекту добавить компонент Data.Scale, которому установлены параметры x и y, увеличивающие координаты до нужных значений в пикселях.

Простой объект

Пример формирования простого залитого заданным цветом объекта; по порядку:

  • Пиксельный шейдер для отрисовки объектов без текстуры (создан ранее, поэтому используется компонент, содержащий только id).
  • Вертексный буфер, содержащий уникальные для данного объекта данные вершин.
  • Уникальный компонент локальной транформации объекта, включающий:
    • Смещение объектов относительно начала координат.
    • Масштабирования до размеров экрана (за единицу принят размер центральной области игры)).
  • Компонент отрисовки объекта (создан ранее).
const VertexData_t VertexData =
{
{
_Polygon.Left, _Polygon.Top, 0.0f, 1.0f,
_TexCoord.Left, _TexCoord.Top,
_R, _G, _B, _A,
}, // 0
{
_Polygon.Left, _Polygon.Bottom, 0.0f, 1.0f,
_TexCoord.Left, _TexCoord.Bottom,
_R, _G, _B, _A,
}, // 1
{
_Polygon.Right, _Polygon.Top, 0.0f, 1.0f,
_TexCoord.Right, _TexCoord.Top,
_R, _G, _B, _A,
}, // 2
{
_Polygon.Right, _Polygon.Bottom, 0.0f, 1.0f,
_TexCoord.Right, _TexCoord.Bottom,
_R, _G, _B, _A,
}, // 3
};
Component_t::Make(
{
{ uT("id"), uT("Simple2DGame.Shader.Pixel.Colored") },
}),
Component_t::Make(
{
{ uT("id"), uT("Simple2DGame.Buffer.Vertex.") + Id.GetStringId() },
{ uT("type"), uT("Buffer") },
{ uT("content"), VertexData },
{ uT("dimension"), 2 },
}),
Component_t::Make(
{
{ uT("id"), uT("Simple2DGame.Transform.Rectangle.") + Id.GetStringId() },
{ uT("type"), uT("Transform") },
{ uT("service"), Trasform },
}),
Component_t::Make(
{
{ uT("id"), uT("Simple2DGame.Present.Rectangle") },
})
Заметки
  • Общий для всех объектов вертексный шейдер устанавливается вместе с камерой.
  • Цвета разных вершин могут отличаться, что позволяет использовать градиентную заливку цветом.
  • Текстурные координаты для такого объекта не имеют смысла и будут проигнорированы.

Текстурированный объект

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

const image::Universal_t<image::pixel::RGBA> Image
{
::covellite::app::Vfs_t::GetInstance().GetData(
PathToTextureDirectory / _RelativePathToSourceFile)
};
(*_pData)[uT("content")] = Image.GetData().Buffer;
(*_pData)[uT("width")] = static_cast<int>(Image.GetData().Width);
(*_pData)[uT("height")] = static_cast<int>(Image.GetData().Height);
return
{
Component_t::Make(
{
{ uT("id"), _Id },
{ uT("type"), uT("Texture") },
{ uT("service"), GameObject_t{ _pData } },
}),
};

Пример формирования текстурированного объекта; по порядку:

  • Пиксельный шейдер для отрисовки объектов c текстурой (создан ранее, поэтому используется компонент, содержащий только id).
  • Текстура (создана ранее).
  • Вертексный буфер, содержащий уникальные для данного объекта данные вершин.
  • Уникальный компонент локальной транформации объекта, включающий:
    • Смещение объектов относительно начала координат.
    • Масштабирования до размеров экрана (за единицу принят размер центральной области игры)).
  • Компонент отрисовки объекта (создан ранее).
const VertexData_t VertexData =
{
{
_Polygon.Left, _Polygon.Top, 0.0f, 1.0f,
_TexCoord.Left, _TexCoord.Top,
_R, _G, _B, _A,
}, // 0
{
_Polygon.Left, _Polygon.Bottom, 0.0f, 1.0f,
_TexCoord.Left, _TexCoord.Bottom,
_R, _G, _B, _A,
}, // 1
{
_Polygon.Right, _Polygon.Top, 0.0f, 1.0f,
_TexCoord.Right, _TexCoord.Top,
_R, _G, _B, _A,
}, // 2
{
_Polygon.Right, _Polygon.Bottom, 0.0f, 1.0f,
_TexCoord.Right, _TexCoord.Bottom,
_R, _G, _B, _A,
}, // 3
};
Component_t::Make(
{
{ uT("id"), uT("Simple2DGame.Shader.Pixel.Textured") },
}),
Component_t::Make(
{
{ uT("id"), _TextureId },
}),
Component_t::Make(
{
{ uT("id"), uT("Simple2DGame.Buffer.Vertex.") + Id.GetStringId() },
{ uT("type"), uT("Buffer") },
{ uT("content"), VertexData },
{ uT("dimension"), 2 },
}),
Component_t::Make(
{
{ uT("id"), uT("Simple2DGame.Transform.Rectangle.") + Id.GetStringId() },
{ uT("type"), uT("Transform") },
{ uT("service"), Trasform },
}),
Component_t::Make(
{
{ uT("id"), uT("Simple2DGame.Present.Rectangle") },
})
Заметки
  • Общий для всех объектов вертексный шейдер устанавливается вместе с камерой.
  • Можно использовать текстуру, содержащую несколько изображений для разных объектов, но при этом все равно необходимо устанавливать компонент этой текстуры каждому объекту, т.к. Present отключает использование текстур после отрисовки объекта.
  • Цвета вершин и текстуры смешиваются (умножаются): прозрачный тексель остается прозрачным, черный остается черным, белый заменяется вторым цветом).

Анимация

Последовательная отрисовка заранее подготовленных кадров анимации может быть реализована несколькими способами:

  • Создать для каждого кадра отдельный объект и в дальнейшем отрисовывать эти объекты последовательно (например, перемещая указатель на следующий объект при обновлении сцены).
  • Создать объект, на который наложена текстура, содержащая все кадры анимации, после чего отрисовывать этот объект, используя компонент State.Scissor для выделения одного кадра и перемещая сам объект таким образом, чтобы отображалась нужная часть текстуры.
  • Создать объект, на который наложена текстура, содержащая все кадры анимации, после чего отрисовывать этот объект, используя шейдер, который будет менять текстурные координаты таким образом, чтобы отображалась нужная часть текстуры.
covellite::api::Vertex
Класс входит в проект Covellite.Api Класс формата вертексного буфера.
Definition: Vertex.hpp:34