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

Инстансинг

Инстансинг позволяет отрисовывать большое количество (сотни тысяч и миллионы в зависимости от возможностей оборудования) одинаковых объектов (одни и те же шейдеры + текстуры + вертексный + индексный буферы) в разных положениях за один вызов отрисовки.

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

Использование

Вертексный шейдер

Компоненту вертексного шейдера добавить параметр instance со значением, описывающим структуру данных инстанс-буфера. Описатель - строка вида 'f4f4i4', где f4 соответствует типу float4, а i4 - int4 (поддерживаются только эти типы).

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

struct Vertex
{
float4 Position;
float2 TexCoord;
float4 Extra;
float4 iValue1;
float4 iValue2;
int4 iValue3;
};
Заметки
Функция вертексного шейдера будет вызвана столько раз, сколько отрисовывается объектов, при этом данные iValue1-3 при каждом вызове будут соответствовать следующему объекту, поэтому функцию шейдера следует писать как для обработки одного объекта.

Другие компоненты

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

Present

Компоненту Present помимо индексного буфера добавить компонент Data.Buffer инстанс-буфера, который будет содержать уникальный данные каждого объекта.

// Структура данных одного объекта, соответствующая параметру instance
// вертексного шейдера
class Instance
{
public:
float x, y, z, w; // iValue1
float r, g, b, a; // iValue2
int32_t i1, i2, i3, i4; // iValue3
};
// Количество объектов, которые будут отрисованы
const ::std::size_t InstanceCount = 100000;
using cbBufferMapper_t = ::std::function<bool(void *)>;
// Функция обратного вызова, которая будет вызываться в каждом кадре перед
// отрисовкой
const cbBufferMapper_t Mapper = [=](void * _pData)
{
// Запрос необходимости обновления буфера; вернуть false, если обновление
// инстанс-буфера в этом кадре не требуется
if (_pData == nullptr) return true;
auto * Data = reinterpret_cast<Instance *>(_pData);
for (int i = 0; i < InstanceCount; i++)
{
// Заполняем инстанс-буфер
Data[i].x = ...;
...
}
return false;
};
// Набор компонентов для отрисовки с использованием инстансинга
Component_t::Make( // Инстанс-буфер
{
{ uT("type"), uT("Data") },
{ uT("kind"), uT("Buffer") },
{ uT("mapper"), Mapper },
{ uT("count"), InstanceCount },
{ uT("size"), InstanceCount * sizeof(Instance) },
}),
Component_t::Make( // Индексный буфер
{
{ uT("type"), uT("Data") },
{ uT("kind"), uT("Buffer") },
{ uT("content"), Indices },
}),
Component_t::Make( // Present.Instance
{
{ uT("id"), uT("Present.UniqueId") },
{ uT("type"), uT("Present") },
{ uT("kind"), uT("Instance") },
})
Заметки
  • При первом вызове Mapper'a инстанс-буфер следует заполнить целиком, в дальнейшем его можно обновлять частично.
  • Значение InstanceCount должно быть кратно 16.

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

using cbBufferMapper_t = ::std::function<bool(void *, ::std::size_t &)>;
const cbBufferMapper_t Mapper = [=](void * _pData, ::std::size_t & _Count)
{
// Запрос необходимости обновления буфера; вернуть false, если обновление
// инстанс-буфера в этом кадре не требуется
if (_pData == nullptr) return true;
auto * Data = reinterpret_cast<Instance *>(_pData);
_Count = 0;
for (int i = 0; i < InstanceCount; i++)
{
// Заполняем инстанс-буфер
...
_Count++;
if (...) break;
}
// Если _Count будет присвоено значение, большее, чем InstanceCount, то при
// рендеринге будет использовано значение InstanceCount.
return false;
};