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


Таким образом мы получили реализацию постейшего геометрического шейдера. Но импользую схожий подход, можно реализовать как более сложные геометрические шейдеры, так и тесселяцию средствами webgl2.
Геометрические шейдеры позволяют на каждому входному примитиву генерировать целый набор примитивов, что позволяет размножить одну модель, или сгенерировать принципиально новую модель налету.
Поле с колышащейся травой: классическое применение для геометрических шейдеров.
Тесселяционные шейдеры позволяют разбивать данные примитивы на более мелкие и усложнять геометрию, что позволяет реализовать, например, бампмэппинг.
Приблизительная схему работы тесселяции.
Общим свойством этих двух технологий является то, что они позволяют получить усложнённую геометрию, не увеличивая нагрузку на шину видеокарты. Так как передача данных туда и обратно очень быстро становится бутылочным горлышком, эти методы активно используются для оптимизации графики или улучшения её качества без потерь производительности.
К сожалению, webgl2 не поддерживает ни то, ни другое. Однако их поведение можно имитировать с помощью вершинного шейдера. Рассмтрим реализацию простейшего геометрического шейдера, отрисовывающего одну модель несколько раз за один вызов.
Для начала необходимо разместить данные в модели в текстуру. Для такой текстуры разумно выбрать формат gl.R32F, подразумевающий, что в каждом пикселе только красная компонента, но она — тридцатидвухбитный флоат.
Каждая строчка пикселей такой текстуры будет соответствовать одной вершине, а столбец — конкретному значению, привязанному к этой вершине. Такую текстуру можно рассматривать как матрицу над полем действительных чисел.
При вызове отрисовки, текстуру с данными надо забиндить, как юниформ, чтобы получить из шейдера доступ к её содержимому.
Перед вызовом drawArrays не нужно биндить никаких атрибутов. Параметр count должен быть равен произведению количества вершин в одной модели и количества экземпляров, которые мы хотим отрисовать.
В шейдере производим следующие операции: используя волшебную переменную glsl gl_VertexId, находим номер вершины в модели и номер отрисовываемого экземпляра, после чего с помощью функции texelFeth достаём нужные данные из текстуры.
void main() {
int particleIdx = gl_VertexID / vertsInParticle;
int vertexIdx = gl_VertexID % vertsInParticle;
ivec2 vertexTexel = ivec2(0, vertexIdx);
gl_Position = projection * transforms[particleIdx] * vec4(
texelFetch(verts, vertexTexel + ivec2(0, 0), 0).r,
texelFetch(verts, vertexTexel + ivec2(1, 0), 0).r,
texelFetch(verts, vertexTexel + ivec2(2, 0), 0).r,
1.0
);
textCoord = vec2(
texelFetch(verts, vertexTexel + ivec2(3, 0), 0).r,
texelFetch(verts, vertexTexel + ivec2(4, 0), 0).r
);
}