Instanced rendering

Instanced rendering в контексте Deck.gl представляет собой ключевую технику GPU-оптимизации, позволяющую отрисовывать тысячи и миллионы однотипных геометрических примитивов с минимальными затратами на CPU и передачу данных в WebGL. В основе подхода лежит идея разделения геометрии на базовую модель (geometry) и набор инстансов (instances), каждый из которых переопределяет только параметры трансформации и визуальные атрибуты.

В классическом WebGL без инстансинга каждый объект сцены требует отдельного набора вершинных данных или отдельного draw call. Это приводит к узкому месту на CPU из-за частых вызовов отрисовки.

Instanced rendering решает эту проблему за счёт использования расширений:

  • ANGLE_instanced_arrays (WebGL 1.0)
  • встроенной поддержки drawArraysInstanced и drawElementsInstanced (WebGL 2.0)

Ключевая идея:

  • одна геометрия
  • множество экземпляров
  • разные атрибуты на экземпляр

GPU получает базовый меш один раз и применяет его к множеству трансформаций.

Модель данных в Deck.gl

Deck.gl строит instanced rendering поверх концепции Layer. Каждый слой определяет:

  • геометрию (например, куб, точка, линия)
  • данные (data array)
  • функции доступа к атрибутам (accessors)
  • шейдерную логику

Типичный слой с инстансингом хранит данные в следующем виде:

  • position
  • color
  • size
  • orientation
  • extruded height (для 3D)

Каждый элемент массива данных соответствует одному инстансу.

GPU-атрибуты и instanced attributes

В Deck.gl атрибуты делятся на два уровня:

Геометрические атрибуты (per-vertex)

Используются внутри базовой модели:

  • координаты вершин
  • нормали
  • UV-развёртка

Инстанс-атрибуты (per-instance)

Применяются ко всему объекту:

  • позиция (instancePositions)
  • цвет (instanceColors)
  • масштаб (instanceSizes)
  • поворот (instanceAngles)

Каждый инстанс получает собственный набор этих параметров, которые передаются в vertex shader через attribute divisor = 1.

Пример архитектуры слоя

Типичный instanced layer в Deck.gl состоит из трёх компонентов:

  1. CPU слой (JavaScript Layer class)
  2. Attribute Manager (управление буферами)
  3. GPU слой (GLSL shaders)

JavaScript слой

import {Layer} from '@deck.gl/core';
import {Model, Geometry} from '@luma.gl/core';

class InstancedExampleLayer extends Layer {
  initializeState() {
    this.state = {
      model: this._getModel()
    };

    this.state.model.setInstanceCount(this.props.data.length);
  }

  _getModel() {
    return new Model(this.context.device, {
      geometry: new Geometry({
        attributes: {
          positions: new Float32Array([
            -1, -1,
             1, -1,
             1,  1,
            -1,  1
          ])
        }
      }),
      vertexShader: `...`,
      fragmentShader: `...`
    });
  }
}

Attribute Manager и обновление буферов

Deck.gl использует систему AttributeManager для автоматического обновления GPU-буферов.

При изменении data:

  1. вычисляются dirty attributes
  2. пересоздаются только изменённые буферы
  3. данные загружаются в GPU через bufferData

Это позволяет избежать полной перезагрузки сцены при каждом обновлении данных.

Особенно важно:

  • обновление происходит батчами
  • используется typed arrays
  • минимизируется GC pressure

Vertex shader и инстансинг

В instanced rendering vertex shader выполняет два уровня трансформации:

  1. базовая геометрия
  2. инстанс-трансформация

Пример логики:

attribute vec3 positions;
attribute vec3 instancePositions;
attribute vec3 instanceColors;

uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;

varying vec3 vColor;

void main() {
  vec3 pos = positions + instancePositions;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);

  vColor = instanceColors;
}

В реальных слоях Deck.gl используется более сложная схема с матрицами трансформации:

  • model matrix per instance
  • view-projection shared
  • scaling/rotation decomposition

Model matrix per instance

Для 3D объектов часто используется полноценная матрица 4x4:

  • translation
  • rotation
  • scale

Это позволяет представлять каждый инстанс как независимый объект в пространстве.

Преимущество:

  • отсутствие CPU-side пересчёта вершин
  • гибкость трансформаций
  • поддержка сложных анимаций

Недостаток:

  • увеличенный объём атрибутов (16 float на инстанс)

Использование в стандартных слоях Deck.gl

Instanced rendering лежит в основе большинства высокопроизводительных слоёв:

ScatterplotLayer

Каждая точка — отдельный инстанс.

  • position
  • radius
  • fill color

ColumnLayer

Вертикальные столбцы:

  • base position
  • height
  • radius
  • color

IconLayer

Иконки как instanced quads:

  • position
  • size
  • texture coordinates
  • rotation

HexagonLayer

Инстансинг используется для каждого гексагона, включая агрегацию данных на GPU.

Оптимизация передачи данных

Ключевые оптимизации instanced rendering в Deck.gl:

Typed arrays

Все данные передаются как:

  • Float32Array
  • Uint8Array
  • Int32Array

Это снижает overhead сериализации.

Partial updates

Обновляются только изменённые атрибуты:

  • позиция отдельно
  • цвет отдельно
  • размер отдельно

Binary data mode

Deck.gl поддерживает бинарный формат данных, позволяющий:

  • избегать JSON парсинга
  • напрямую маппить массивы в GPU буферы

Picking и instanced rendering

Интерактивность реализуется через picking:

Каждый инстанс получает уникальный identifier:

  • instanceId
  • encoded picking color

При рендере в picking buffer:

  • вместо цвета пишется ID
  • GPU отрисовывает сцену в offscreen framebuffer
  • CPU декодирует выбранный пиксель

Это позволяет:

  • кликать по миллионам объектов
  • избегать CPU-итераций по данным

Instanced rendering и производительность

Основные факторы ускорения:

  • снижение draw calls до единицы на слой
  • перенос вычислений на GPU
  • минимизация CPU-GPU синхронизации
  • использование shared geometry

Типичная производительность:

  • 10 000 объектов: без проблем
  • 100 000 объектов: стабильно при правильных атрибутах
  • 1 000 000 объектов: возможно при упрощённой геометрии

Ограничения подхода

Несмотря на эффективность, instanced rendering имеет ограничения:

  • сложные уникальные меши плохо масштабируются
  • высокое потребление памяти при больших матрицах
  • ограниченная динамическая топология (все инстансы должны быть одинаковой формы)
  • зависимость от возможностей WebGL2 для максимальной эффективности

Комбинирование с другими техниками

Deck.gl часто комбинирует instanced rendering с:

  • aggregation (GridLayer, HexagonLayer)
  • screen-space rendering
  • tile-based streaming данных
  • GPU-based filtering

Особенно эффективно сочетание instancing с:

  • spatial indexing (quadtrees, H3 grid)
  • LOD (level of detail)
  • clustering

Shader injection и кастомизация инстансов

Deck.gl позволяет модифицировать шейдеры через injection hooks:

  • vs:#decl
  • vs:#main-start
  • fs:#main-end

Это даёт возможность:

  • добавлять кастомные атрибуты
  • изменять поведение инстансов
  • внедрять процедурную анимацию

Пример динамического смещения:

pos.z += sin(uTime + instancePositions.x * 0.1) * 5.0;

Instanced rendering в контексте архитектуры Deck.gl

На уровне архитектуры instanced rendering занимает центральное место:

  • Layer отвечает за декларацию данных
  • AttributeManager — за управление GPU памятью
  • Model — за связь геометрии и шейдеров
  • WebGL context — за выполнение draw calls

Именно это разделение позволяет Deck.gl масштабироваться от сотен до миллионов объектов без изменения пользовательского кода.