Инстансинг
Инстансинг позволяет отрисовывать большое количество (сотни тысяч и миллионы в зависимости от возможностей оборудования) одинаковых объектов (одни и те же шейдеры + текстуры + вертексный + индексный буферы) в разных положениях за один вызов отрисовки.
Идея заключается в формировании общего набора компонентов одного объекта и формирования специального буфера, содержащего уникальные данные каждого конкретного объекта.
Использование
Вертексный шейдер
Компоненту вертексного шейдера добавить параметр 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 инстанс-буфера, который будет содержать уникальный данные каждого объекта.
class Instance
{
public:
float x, y, z, w;
float r, g, b, a;
int32_t i1, i2, i3, i4;
};
const ::std::size_t InstanceCount = 100000;
using cbBufferMapper_t = ::std::function<bool(void *)>;
const cbBufferMapper_t Mapper = [=](void * _pData)
{
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(
{
{ 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)
{
if (_pData == nullptr) return true;
auto * Data = reinterpret_cast<Instance *>(_pData);
_Count = 0;
for (int i = 0; i < InstanceCount; i++)
{
...
_Count++;
if (...) break;
}
return false;
};