WebGL-эффекты

Библиотека ScrollMagic предоставляет механизм синхронизации прокрутки страницы с анимациями. В контексте WebGL это позволяет управлять состоянием 3D-сцены, параметрами шейдеров, положением камеры и пост-эффектами на основе позиции скролла.

Основная идея — связать прогресс сцены ScrollMagic (progress от 0 до 1) с параметрами WebGL.


Архитектура интеграции

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

  • ScrollMagic Controller
  • Scene с триггером
  • WebGL-рендерер (например, через Three.js или чистый WebGL)
  • Обновление параметров внутри onUpdate

Пример базовой связки:

const controller = new ScrollMagic.Controller();

const scene = new ScrollMagic.Scene({
  triggerElement: "#trigger",
  duration: 1000
})
.on("progress", (event) => {
  const progress = event.progress;
  updateWebGL(progress);
})
.addTo(controller);

Функция updateWebGL изменяет состояние сцены.


Управление камерой через скролл

Один из базовых эффектов — перемещение камеры.

function updateWebGL(progress) {
  camera.position.z = 5 - progress * 3;
  camera.position.y = progress * 2;
  camera.lookAt(scene.position);
}

Результат:

  • при скролле создаётся эффект приближения
  • появляется ощущение “погружения” в сцену

Анимация объектов

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

Линейная интерполяция

function lerp(start, end, t) {
  return start + (end - start) * t;
}

function updateWebGL(progress) {
  mesh.rotation.y = lerp(0, Math.PI * 2, progress);
}

Более сложные кривые

Использование easing-функций:

function easeInOut(t) {
  return t < 0.5
    ? 2 * t * t
    : 1 - Math.pow(-2 * t + 2, 2) / 2;
}

function updateWebGL(progress) {
  const eased = easeInOut(progress);
  mesh.scale.set(eased, eased, eased);
}

Работа с шейдерами

ScrollMagic особенно мощен при управлении uniform-переменными в шейдерах.

Пример: плавное искажение

uniform float uProgress;

void main() {
  vec3 pos = position;
  pos.z += sin(pos.x * 5.0 + uProgress * 10.0) * 0.2;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}

JS:

function updateWebGL(progress) {
  material.uniforms.uProgress.value = progress;
}

Эффект:

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

Параллакс WebGL-слоев

Создание глубины достигается за счёт разной скорости движения объектов.

function updateWebGL(progress) {
  background.position.z = -10 + progress * 2;
  midground.position.z = -5 + progress * 4;
  foreground.position.z = progress * 6;
}

Это создаёт:

  • ощущение многослойности
  • кинематографический эффект

Постобработка и эффекты

При использовании EffectComposer (например, в Three.js) можно управлять пост-эффектами:

Изменение интенсивности bloom

function updateWebGL(progress) {
  bloomPass.strength = progress * 2;
}

Цветокоррекция

function updateWebGL(progress) {
  colorPass.uniforms.saturation.value = 1 - progress;
}

Пиннинг WebGL-сцены

Часто используется закрепление canvas на экране:

new ScrollMagic.Scene({
  triggerElement: "#webgl-section",
  duration: 2000,
  triggerHook: 0
})
.setPin("#canvas-container")
.addTo(controller);

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

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

Разбиение на сцены

Для сложных эффектов используется несколько сцен:

// Первая сцена
new ScrollMagic.Scene({ duration: 500 })
.on("progress", (e) => animateIntro(e.progress))
.addTo(controller);

// Вторая сцена
new ScrollMagic.Scene({ offset: 500, duration: 500 })
.on("progress", (e) => animateMiddle(e.progress))
.addTo(controller);

Так достигается:

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

Синхронизация с requestAnimationFrame

ScrollMagic не заменяет рендер-цикл WebGL.

Правильная структура:

function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}

animate();

ScrollMagic только изменяет параметры, но не управляет отрисовкой.


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

Ограничение вычислений

Не стоит пересчитывать сложные операции внутри onUpdate:

let lastProgress = 0;

function updateWebGL(progress) {
  if (Math.abs(progress - lastProgress) < 0.001) return;
  lastProgress = progress;

  // обновления
}

Минимизация работы с DOM

Canvas должен быть изолирован:

  • без частых пересчётов layout
  • без привязки к сложным DOM-структурам

Использование буферов

При работе с большим количеством объектов:

  • использовать InstancedMesh
  • избегать частых пересозданий геометрии

Управление состояниями сцены

Вместо прямого изменения параметров лучше использовать state-модель:

const state = {
  progress: 0,
  rotation: 0,
  scale: 1
};

function updateWebGL(progress) {
  state.progress = progress;
  state.rotation = progress * Math.PI;
}

В рендере:

mesh.rotation.y = state.rotation;

Это упрощает:

  • отладку
  • масштабирование проекта

Комбинация с GSAP

ScrollMagic часто используется вместе с GSAP:

const tween = gsap.to(mesh.rotation, {
  y: Math.PI * 2
});

new ScrollMagic.Scene({
  duration: 1000
})
.setTween(tween)
.addTo(controller);

GSAP:

  • управляет интерполяцией
  • ScrollMagic задаёт прогресс

Реализация интерактивных переходов

Пример: переход между сценами WebGL

function updateWebGL(progress) {
  if (progress < 0.5) {
    scene1.visible = true;
    scene2.visible = false;
    scene1.opacity = 1 - progress * 2;
  } else {
    scene1.visible = false;
    scene2.visible = true;
    scene2.opacity = (progress - 0.5) * 2;
  }
}

Работа с текстурами

Скролл может управлять:

  • смещением UV
  • прозрачностью
  • смешиванием текстур
function updateWebGL(progress) {
  material.uniforms.uMix.value = progress;
}

В шейдере:

vec4 color = mix(texture1, texture2, uMix);

Создание “скролл-историй”

Комбинация всех техник:

  • закреплённая сцена
  • постепенные изменения камеры
  • смена объектов
  • пост-эффекты

Каждый участок скролла — отдельная “глава”.


Отладка

ScrollMagic предоставляет индикаторы:

scene.addIndicators();

Это показывает:

  • начало сцены
  • длительность
  • текущий прогресс

Распространённые проблемы

Дёргание анимации

Причины:

  • слишком сложные вычисления
  • отсутствие интерполяции
  • резкие изменения значений

Решение:

  • использовать easing
  • сглаживать значения

Несинхронность

Если WebGL отстаёт:

  • проверить FPS
  • уменьшить нагрузку на GPU
  • оптимизировать шейдеры

Проблемы с мобильными устройствами

Рекомендации:

  • снижать детализацию
  • уменьшать количество объектов
  • отключать тяжёлые пост-эффекты

Расширенные техники

Scroll-driven shader timelines

Создание полноценной “линейки времени” внутри шейдера:

if (uProgress < 0.3) {
  // первая фаза
} else if (uProgress < 0.7) {
  // вторая
} else {
  // третья
}

Морфинг геометрии

function updateWebGL(progress) {
  geometry.morphTargetInfluences[0] = progress;
}

Реакция на скорость скролла

let last = 0;

function updateWebGL(progress) {
  const velocity = progress - last;
  last = progress;

  material.uniforms.uVelocity.value = velocity;
}

Используется для:

  • инерции
  • динамических эффектов

Структурирование проекта

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

  • scroll.js — управление ScrollMagic
  • scene.js — WebGL сцена
  • animations.js — логика анимаций
  • shaders/ — GLSL-код

Ключевые принципы

  • ScrollMagic управляет временем
  • WebGL отвечает за визуализацию
  • Прогресс сцены — универсальный параметр
  • Интерполяция — основа плавности
  • Производительность — критический фактор

Такое разделение позволяет строить сложные, управляемые скроллом WebGL-интерфейсы с высокой степенью контроля и предсказуемости поведения.