Использование WebGL для сложных эффектов

В стандартной архитектуре Lottie Web рендеринг основан на SVG или Canvas2D, что обеспечивает совместимость и предсказуемое поведение, но ограничивает доступ к аппаратному ускорению графического процессора. WebGL используется не как встроенный рендерер Lottie, а как внешний графический слой, в который анимация интегрируется через текстуры, буферы и промежуточные канвасы.

Ключевая идея применения WebGL в связке с Lottie заключается в том, что Lottie-анимация превращается в последовательность растровых кадров, которые затем используются как текстура внутри WebGL-сцены. Такой подход позволяет сочетать векторную анимационную модель After Effects с GPU-ускоренными эффектами: освещением, постобработкой, шейдерами и сложной композицией.


Модель интеграции: от вектора к текстуре

Lottie Web генерирует кадры анимации на основе JSON-описания, синхронизируя их через requestAnimationFrame. В момент рендеринга используется DOM-элемент <canvas>, в который записывается итоговое изображение сцены.

Дальнейший этап — извлечение пиксельного буфера:

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

const anim = lottie.loadAnimation({
  container: canvas,
  renderer: 'canvas',
  loop: true,
  autoplay: true,
  path: 'animation.json'
});

На каждом кадре содержимое canvas используется как источник текстуры для WebGL:

const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);

gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,
              gl.UNSIGNED_BYTE, canvas);

Обновление текстуры выполняется в каждом кадре, синхронно с Lottie-анимацией.


Причины использования промежуточного Canvas

WebGL не способен напрямую интерпретировать SVG или JSON-модель Lottie. Поэтому вводится промежуточный буферный слой:

  • Canvas выступает как CPU-рендерер Lottie
  • WebGL использует результат как GPU-текстуру
  • синхронизация происходит через общий таймлайн анимации

Этот слой позволяет изолировать две системы: векторную (Lottie) и растрово-графическую (WebGL).


Обновление текстуры в реальном времени

Критический аспект — производительность обновления текстуры. Простейшая схема выглядит так:

function update() {
  anim.goToAndStop(performance.now() / 16.67, true);

  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.texSubImage2D(
    gl.TEXTURE_2D,
    0,
    0, 0,
    gl.RGBA,
    gl.UNSIGNED_BYTE,
    canvas
  );

  renderScene();
  requestAnimationFrame(update);
}

update();

При таком подходе важны ограничения:

  • частые вызовы texSubImage2D создают нагрузку на GPU
  • размеры canvas напрямую влияют на bandwidth
  • необходимо избегать лишнего ресайза текстуры

Использование Three.js как слоя композиции

Часто WebGL-уровень реализуется через сценический движок, например Three.js. В этом случае Lottie-канвас становится материалом плоскости:

const texture = new THREE.CanvasTexture(canvas);

const material = new THREE.MeshBasicMaterial({
  map: texture,
  transparent: true
});

const geometry = new THREE.PlaneGeometry(2, 2);
const mesh = new THREE.Mesh(geometry, material);

scene.add(mesh);

Обновление текстуры синхронизируется с Lottie:

function animate() {
  texture.needsUpdate = true;
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}

Такой подход упрощает интеграцию с 3D-эффектами: параллакс, глубина, световые источники.


Постобработка через шейдеры

WebGL раскрывает возможности, недоступные в Lottie-рендерере:

  • bloom-эффекты
  • Gaussian blur
  • chromatic aberration
  • displacement mapping
  • LUT-коррекция цвета

Фрагментный шейдер может модифицировать Lottie-текстуру:

precision mediump float;

uniform sampler2D u_texture;
varying vec2 v_uv;

void main() {
  vec4 color = texture2D(u_texture, v_uv);

  color.rgb += vec3(0.1); // простое усиление яркости

  gl_FragColor = color;
}

Таким образом Lottie становится входным слоем для GPU-пайплайна.


Синхронизация таймлайна

Lottie управляет анимацией через внутренний таймкод, WebGL — через кадры рендера. Несинхронность приводит к дрожанию или рассинхронизации эффектов.

Используется единый источник времени:

const start = performance.now();

function getFrame() {
  const elapsed = performance.now() - start;
  return elapsed / 16.67;
}

Этот кадр передается одновременно:

  • в goToAndStop() Lottie
  • в uniform-параметры WebGL шейдеров

Оптимизация частоты обновления текстуры

Полное обновление текстуры на каждом кадре дорого стоит. Используются стратегии:

1. Кэширование кадров

  • фиксация ключевых кадров Lottie
  • повторное использование текстур

2. Понижение разрешения буфера

  • canvas рендерится в меньшем размере
  • WebGL масштабирует через фильтрацию

3. Dirty regions

  • обновление только изменяющихся частей сцены

OffscreenCanvas и многопоточность

При высоконагруженных сценах Lottie-рендеринг переносится в отдельный поток:

const offscreen = canvas.transferControlToOffscreen();

const worker = new Worker('lottie-worker.js');

worker.postMessage({
  canvas: offscreen,
  path: 'animation.json'
}, [offscreen]);

В worker происходит независимый рендер, а основной поток занимается WebGL-композицией.


Комбинирование нескольких Lottie-слоев в WebGL

WebGL позволяет накладывать несколько анимаций как текстуры:

  • фоновые Lottie-анимации
  • UI-элементы
  • декоративные эффекты
  • маски и переходы

Каждый слой — отдельный canvas-texture, объединённый в шейдере или через blend modes.


Использование альфа-канала и масок

Lottie активно использует прозрачность, которая напрямую переносится в WebGL:

gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

Это позволяет сохранять векторную природу композиции даже в GPU-слое.


Типичные архитектурные схемы

Canvas-first pipeline

  • Lottie → Canvas
  • Canvas → WebGL texture
  • WebGL → экран

Hybrid composition

  • Lottie UI в DOM
  • WebGL фон
  • синхронизация через shared timeline

Full GPU pipeline

  • Lottie растеризуется
  • кадры передаются в framebuffer
  • все эффекты в шейдерах

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

Использование WebGL поверх Lottie вводит ряд ограничений:

  • рост потребления памяти из-за текстур
  • зависимость от частоты обновления canvas
  • невозможность прямого GPU-парсинга Lottie JSON
  • задержка между векторным рендером и GPU-пайплайном

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


Поведение при масштабировании

При изменении размера контейнера необходимо пересоздавать:

  • canvas resolution
  • WebGL textures
  • viewport matrices

Несоответствие DPR (device pixel ratio) приводит к размытию текстур, поэтому синхронизация обязательна:

canvas.width = width * devicePixelRatio;
canvas.height = height * devicePixelRatio;
gl.viewport(0, 0, canvas.width, canvas.height);

Взаимодействие с фильтрами Lottie

Фильтры Lottie (blur, opacity, transforms) выполняются на CPU. WebGL может дублировать их на GPU, создавая эффект двойного уровня:

  • CPU: базовая векторная деформация
  • GPU: постэффекты изображения

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