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

Атлас спрайтов представляет собой единое изображение, в котором упаковано множество небольших графических элементов (иконок, маркеров, символов). Вместо загрузки десятков или сотен отдельных файлов используется одна текстура, внутри которой каждый элемент имеет координаты вырезки (UV-область).

Ключевая особенность подхода — сокращение количества запросов к сети и уменьшение переключений текстур в GPU, что критично для производительности в WebGL-пайплайне.

В экосистеме deck.gl атласы спрайтов применяются преимущественно в слоях, работающих с символами и маркерами: IconLayer, частично TextLayer, а также кастомных слоях через TextureManager и шейдерные утилиты.


Принцип организации атласа

Атлас состоит из двух обязательных компонентов:

  • Изображение (PNG/WebP) — содержит все спрайты
  • Метаданные (JSON) — описывают координаты каждого элемента

Типичная структура JSON:

{
  "airport": {
    "x": 0,
    "y": 0,
    "width": 64,
    "height": 64,
    "anchorX": 32,
    "anchorY": 32
  },
  "harbor": {
    "x": 64,
    "y": 0,
    "width": 64,
    "height": 64,
    "anchorX": 32,
    "anchorY": 32
  }
}

Каждый ключ соответствует имени спрайта, а значения задают:

  • координаты в атласе
  • размеры региона
  • точку привязки (anchor), определяющую позицию относительно геометрии

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

Основной сценарий применения — отображение символов на карте.

import {IconLayer} from '@deck.gl/layers';

const layer = new IconLayer({
  id: 'icons',
  data,
  iconAtlas: '/assets/icon-atlas.png',
  iconMapping: '/assets/icon-atlas.json',

  getIcon: d => d.type,
  getPosition: d => d.coordinates,
  getSize: 32,
  sizeScale: 1
});

Здесь происходит ключевая связка:

  • iconAtlas — единое изображение
  • iconMapping — JSON с координатами
  • getIcon — выбирает ключ из JSON для конкретного объекта

В результате каждый объект данных получает свой спрайт без отдельной загрузки ресурсов.


Механика текстурирования

WebGL не работает с «вырезанием изображений» на CPU. Вместо этого используется UV-разметка:

  1. Атлас загружается как текстура
  2. Каждому спрайту соответствует прямоугольник UV-координат (0–1)
  3. Шейдер вычисляет, какую часть текстуры отрисовать

В deck.gl это реализуется через инстансинг: каждый объект передает параметры в vertex shader, включая:

  • смещение в атласе
  • размер области
  • коэффициенты нормализации

Генерация атласов

Атласы обычно создаются на этапе сборки проекта.

Распространённые инструменты:

  • TexturePacker
  • Spritesmith
  • sharp + кастомные скрипты
  • Webpack-плагины для спрайтов

Пример генерации через Node.js:

const spritesmith = require('spritesmith');
const fs = require('fs');

spritesmith.run({
  src: ['icons/*.png']
}, (err, result) => {
  fs.writeFileSync('atlas.png', result.image);
  fs.writeFileSync('atlas.json', JSON.stringify(result.coordinates));
});

На выходе формируется изображение и JSON-карта координат.


Требования к структуре спрайтов

Для корректной работы в WebGL-среде необходимо учитывать:

Выравнивание по пиксельной сетке

Размытые границы возникают при дробных координатах UV. Поэтому:

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

Padding (отступы)

Без отступов возникает bleeding эффект — смешивание пикселей соседних спрайтов.

Рекомендуется:

  • 1–4 пикселя padding
  • расширение краёв (edge extrusion)

Кратность размеров

Для Retina-дисплеев часто используются:

  • 1x атлас
  • 2x атлас (high DPI)

Работа с retina-экраном

В deck.gl масштабирование выполняется через devicePixelRatio.

Типовая схема:

  • базовый атлас создаётся в стандартном разрешении
  • при необходимости загружается увеличенная версия
  • sizeScale корректирует визуальный размер
new IconLayer({
  ...
  sizeScale: window.devicePixelRatio
});

Альтернативный подход — отдельные атласы @1x и @2x с выбором по runtime-условию.


Динамические атласы

Помимо статических изображений, возможны динамические сценарии:

  • генерация иконок на лету
  • сборка атласа из пользовательских данных
  • объединение текстовых и графических элементов

В этом случае используется Canvas API:

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');

ctx.drawImage(img1, 0, 0);
ctx.drawImage(img2, 64, 0);

Далее canvas передается как текстура в слой.


Производительность и ограничения

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

Положительные эффекты

  • снижение числа HTTP-запросов
  • уменьшение переключений WebGL текстур
  • улучшение batching (инстансинг)

Ограничения

  • размер текстуры ограничен GPU (обычно 2048–8192 px)
  • невозможность частичного обновления без пересборки
  • усложнение управления ассетами

Связь с IconLayer и TextLayer

IconLayer полностью опирается на атласы, тогда как TextLayer использует собственный glyph atlas.

Glyph atlas устроен аналогично:

  • каждый символ — отдельный спрайт
  • хранится в единой текстуре
  • кешируется на уровне шрифта

Это позволяет рендерить текст без постоянной rasterization операции.


Форматирование координат и нормализация

UV-координаты в WebGL нормализуются в диапазон [0, 1].

Преобразование:

u = x / atlasWidth
v = y / atlasHeight

При этом важно учитывать:

  • инверсию оси Y в WebGL
  • возможные padding-смещения
  • pixel ratio scaling

Типичные ошибки при работе с атласами

Неправильные anchor точки

Смещение иконок относительно геометрии приводит к визуальному дрейфу маркеров.

Отсутствие padding

Проявляется как «грязные края» при масштабировании карты.

Несоответствие JSON и изображения

Даже небольшое смещение координат делает спрайты некорректными.

Переполнение текстуры

При превышении лимитов GPU атлас может не загрузиться или обрезаться.


Оптимизация загрузки

Для крупных приложений применяются стратегии:

  • разделение атласов по категориям (transport, POI, labels)
  • lazy loading по зум-уровням
  • CDN-кэширование immutable-ресурсов
  • компрессия WebP вместо PNG

В некоторых случаях используется несколько уровней детализации, аналогично mipmapping:

  • low zoom → упрощённые иконки
  • high zoom → детализированные спрайты

Интеграция в пайплайн рендеринга deck.gl

Внутри архитектуры deck.gl атласы проходят следующий путь:

  1. загрузка JSON и изображения
  2. создание WebGL texture
  3. привязка к layer state
  4. передача UV данных в attribute buffers
  5. рендеринг через instanced drawing

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