Беги, Роб, беги: создание музыкально-реактивного Goo с помощью Three.js и WebGPU | Кодропс

Беги, Роб, беги: создание музыкально-реактивного Goo с помощью Three.js и WebGPU | Кодропс



Run Rob Run — это одностраничное портфолио, построенное на движении, звуке, типографике и взаимодействии. Основная сцена представляет собой специальный объект Goo, который ведет себя как цифровой материал: он бездействует, реагирует на музыку, реагирует на наведение курсора и трансформируется при прокрутке из органического пятна в структурированный куб.

Проект возник в результате длительного цикла строительства, сноса и восстановления, и я никогда не чувствовал, что работа готова представлять меня должным образом. В течение многих лет я возобновлял работу над идеями портфолио, прежде чем они успели быть реализованы. Эта версия не о том, чтобы назвать сайт идеальным. Речь идет о том, чтобы наконец достичь чего-то, с чем я могу сидеть, делиться и продолжать строить.

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

В этом разбивке основное внимание уделяется слизи: как она структурирована, как прокрутка перемещает ее из капли в куб и как формируется музыкальная реакция, чтобы движение казалось музыкальным, а не хаотичным.

В качестве основного интерфейса в сцене используется Three.js/WebGPU со многоуровневой геометрией, настраиваемой логикой деформации, поведением, реагирующим на музыку, состоянием прокрутки и некоторыми поверхностными деталями, такими как показания при наведении и анимированная пыль.

Концепция

Целью не было создание фоновой анимации. Я хотел, чтобы сцена ощущалась как объект с присутствием. Что-то тактильное, несовершенное и отзывчивое. Слизь стала эмоциональным центром сайта: преломляющая оболочка вокруг оранжевого ядра, постоянно меняющаяся между мягкостью и структурой.

Дизайн построен на контрасте:

  • Мягкое органическое движение против окончательно структурированного куба.
  • Прозрачная преломляющая оболочка вокруг плотного оранжевого ядра.
  • Тонкое поведение в режиме ожидания против более сильных толчков, вызванных музыкой.
  • Игривые детали при наведении, которые появляются только тогда, когда пользователь исследует поверхность.

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

Выполнение

Структура сцены

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

  • Внешняя оболочка обеспечивает преломляющий эффект стекла.
  • Оранжевое ядро ​​придает объекту вес и индивидуальность.
  • Тонкий внешний слой придает поверхности мерцание и детализацию.
  • Прокрутка постепенно уменьшает органическую деформацию и приближает форму к кубу.
  • Взаимодействия при наведении добавляют временные детали поверхности, такие как показания сетки и анимированная пыль.

На высоком уровне каждый кадр вычисляет состояние прокрутки, считывает текущие значения, реагирующие на музыку, облегчает их, а затем передает в функцию деформации для каждого слоя.

const scrollProgress = scrollPauseState.getSceneScrollProgress();
const {
  morphProgress,
  splitProgress,
  sharedBlobBumpScale,
  forceAllNormals,
} = getMorphRenderState(scrollProgress);
const musicReactiveState = musicReactiveInput.getState();
const musicReactive = getMusicReactiveDeformState({
  elapsed,
  musicReactiveState,
});

Прокрутка Морфа

Переход прокрутки перемещает объект из мягкого органического состояния в более структурированный куб. Система деформации сочетает в себе две идеи:

  • Органическое пятно, основанное на направлении, с лепестками и поверхностным шумом.
  • Проекция куба, в которой каждая вершина притягивается к ближайшей грани куба.
const organicMix = 1 - smoothstep(0.55, 0.98, morphProgress);
const cubeMix = smoothstep(0.68, 0.995, morphProgress);

Когда смесь кубов увеличивается, каждая точка постепенно приближается к своему положению в кубе.

if (cubeMix > 0) {
  const cubeRadius = baseRadius * THREE.MathUtils.lerp(
    1.0,
    0.9,
    morphProgress
  );
  const cubePoint = getCubePoint(
    dx,
    dy,
    dz,
    cubeRadius
  );
  px = THREE.MathUtils.lerp(px, cubePoint.x, cubeMix);
  py = THREE.MathUtils.lerp(py, cubePoint.y, cubeMix);
  pz = THREE.MathUtils.lerp(pz, cubePoint.z, cubeMix);
}

Это позволяет сцене сохранять липкий характер на ранних этапах, сохраняя при этом чистое конечное состояние куба.

Музыкальная реакция

Музыкальная реакция намеренно избирательна. Слизь не одинаково реагирует на каждый звук. Ранние версии слишком равномерно реагировали на весь трек, из-за чего загруженные песни казались шумными, а небольшие переходные звуки, такие как хай-хэт, создавали слишком большие движения.

Последняя система учитывает реакцию на более сильные ритмические события: удары ногой, более крупные хлопки и удары в барабан с четырьмя ударами по полу. Меньшие высокочастотные переходные процессы по-прежнему добавляют энергии, но они затухают и не влияют на основную форму.

const musicLow = musicReactiveState.isActive
  ? musicReactiveState.low
  : 0;
const musicLowPulse = musicReactiveState.isActive
  ? musicReactiveState.lowPulse
  : 0;
const musicMid = musicReactiveState.isActive
  ? musicReactiveState.mid
  : 0;
const musicMidPulse = musicReactiveState.isActive
  ? musicReactiveState.midPulse
  : 0;
const musicHigh = musicReactiveState.isActive
  ? musicReactiveState.high
  : 0;
const musicHighSoft = Math.min(musicHigh, 0.38);
const musicMidSoft = Math.min(musicMid, 0.72);
const crowdDensity = Math.min(
  1,
  musicMidSoft * 0.58 + musicHighSoft * 0.86
);

Система ищет доминирующие низкие частоты, но бас сам по себе не должен постоянно раздувать ядро. Отклик на удар снижается, когда трек переполнен или когда высокие частоты слишком активны.

const lowDominance = Math.min(
  1,
  Math.max(
    0,
    musicLow - (musicMidSoft * 0.22 + musicHighSoft * 0.32)
  ) * 4.4
);
const kickLift = Math.max(
  0,
  musicLowPulseSoft - (musicMidSoft * 0.12 + musicHighSoft * 0.09)
);
const kickHit = mode === "thump"
  ? Math.min(1, kickLift * 3.5)
  : Math.min(1, kickLift * 2.1);
const dominantKickHit =
  kickHit * Math.max(
    0.45,
    lowDominance,
    1 - crowdDensity * 0.26
  );

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

const highOnlyMask = Math.min(
  1,
  Math.max(
    0,
    musicHighSoft - musicMidSoft * 0.72
  ) * 3.4
);
const clapCoreImpact =
  musicMidPulse *
  Math.max(0, midBody - highOnlyMask * 0.28) *
  (1 - crowdDensity * 0.34) *
  0.54;

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

return {
  centerRoundness,
  impact,
  coreMassImpact,
  limbImpact,
  blobSpreadMultiplier:
    1 -
    centerRoundness * 0.46 +
    soloKeyboard * 0.025 +
    impact * 0.58,
  surfaceBoost:
    musicMidSoft * 0.045 +
    musicHighSoft * 0.035 +
    soloKeyboard * 0.025 +
    impact * 0.32,
  flowSpeed:
    1 +
    musicMidSoft * 0.015 +
    musicHighSoft * 0.055 +
    impact * 0.085,
  rippleShift:
    musicHighSoft * 0.24 +
    soloKeyboard * 0.035 +
    impact * 0.58,
};

Демпфирование и возврат

Движению также требовался вес. Если слизь возвращалась слишком быстро после удара, она казалась нервной и цифровой. Удар может атаковать быстро, но высвобождение происходит медленнее.

const impactEase =
  musicReactive.impact > easedMusicImpact
    ? 0.34
    : 0.075;
easedMusicImpact = THREE.MathUtils.lerp(
  easedMusicImpact,
  musicReactive.impact,
  1 - Math.pow(1 - impactEase, dt * 60),
);

Реакция конечностей имеет собственное демпфирование. Большие движения успокаиваются медленнее, что придает корпусу более тяжелое физическое ощущение.

const limbReleaseEase = THREE.MathUtils.lerp(
  0.045,
  0.022,
  easedMusicLimbImpact
);
const limbImpactEase =
  limbImpact > easedMusicLimbImpact
    ? 0.22
    : limbReleaseEase;
easedMusicLimbImpact = THREE.MathUtils.lerp(
  easedMusicLimbImpact,
  limbImpact,
  1 - Math.pow(1 - limbImpactEase, dt * 60),
);

Применение реакции

Внешняя оболочка и внутреннее ядро ​​не реагируют одинаково. Оболочка получает более мягкий отклик, поэтому остается прозрачной и преломляющей. Оранжевое ядро ​​получает больше физического движения.

deformBlob(blobGeometry, elapsed, {
  baseRadius: 1.64,
  bumpScale: sharedBlobBumpScale,
  blobSpreadMultiplier: THREE.MathUtils.lerp(
    1,
    easedMusicReactive.blobSpreadMultiplier,
    0.28,
  ),
  morphProgress,
  reactiveSurfaceBoost:
    easedMusicReactive.surfaceBoost * 0.32,
  reactivePointerBoost:
    easedMusicReactive.pointerBoost * 0.24,
  reactiveRippleShift:
    easedMusicReactive.rippleShift * 0.36,
  reactiveCenterRoundness:
    shellCenterRoundness,
});

Ядро получает большее движение, но оно зажато и не может вытолкнуться за пределы прозрачной внешней оболочки.

deformBlob(innerGeometry, elapsed, {
  baseRadius: 1.64 * 0.6 * innerCoreScale,
  bumpScale:
    sharedBlobBumpScale *
    coreSurfaceReaction *
    THREE.MathUtils.lerp(1, 0.54, limbReturnDamping),
  blobSpreadMultiplier:
    easedMusicReactive.blobSpreadMultiplier,
  lobeStrengthMultiplier:
    coreLobeReaction,
  reactiveSurfaceBoost:
    easedMusicReactive.surfaceBoost *
    coreSurfaceReaction *
    coreReturnCalm,
  reactivePointerBoost:
    easedMusicReactive.pointerBoost *
    0.8 *
    coreReturnCalm,
  sphericalDirectionBlend:
    musicReactiveState.isPlaying ? 0.68 : 0,
  maxRadius: 1.58,
});

Уточнение

Когда основное поведение заработало, большая часть времени ушла на настройку. Целью было сохранить выразительность сцены, но не сделать ее шумной или дорогой.

Детали поверхности при наведении

Взаимодействия при наведении отделены от музыкальной системы. Они задуманы как временное поверхностное поведение, а не как постоянный эффект.

Показания сетки появляются при наведении курсора мыши, показывают текущие значения и исчезают после начала прокрутки, чтобы не мешать трансформации.

Пыль действует аналогичным образом. Он появляется под курсором, но сама текстура пыли анимируется с помощью предварительно вычисленных кадров шума, аналогично слою шума по всему сайту. Благодаря этому пыль кажется живой без необходимости пересчета дорогостоящего шума в каждом кадре.

if (now - lastDustUpdate > 1000 / DUST_NOISE_SPEED) {
  lastDustUpdate = now;
  dustFrameIndex += 1;
  drawDustFrame({
    context: dustA.context,
    texture: dustA.texture,
    frames: dustFramesA,
    frameIndex: dustFrameIndex,
  });
}

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

Главный урок заключался в том, что визуальное качество зависит не только от сложности, но и от сдержанности.

  • Дорогостоящий шум был рассчитан заранее, где это было возможно.
  • Обычные обновления распределяются по слоям в шахматном порядке.
  • Музыкальный отклик сглаживается перед прикосновением к геометрии.
  • Эффекты наведения отключены во время прокрутки, поэтому морф остается чистым.
  • Версия WebGPU имеет запасные пути для устройств, которые не могут запустить основную сцену.

Сцена также избегает одинакового обращения со всеми слоями. Внешняя оболочка, сердцевина и покрытие обновляются с разной интенсивностью, что обеспечивает читаемость композиции и помогает прозрачному материалу оставаться прозрачным.

Доступность

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

  • Сцену по-прежнему можно исследовать без воспроизведения музыки.
  • Музыкальная реакция контролируется пользователем, а не воспроизводится автоматически.
  • Детали при наведении — это усовершенствования, которые не требуются для навигации.
  • На устройствах с грубым указателем слои при наведении на рабочем столе отключены.
  • Резервный рендеринг доступен для устройств, которые не могут запустить сцену WebGPU.

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

Заворачивать

Окончательная система хорошего качества — это баланс между художественным направлением и сдержанностью. Он слушает музыку, но не подчиняется каждому звуку. Удары ногами и более крупные хлопки создают более сильные толчки, в то время как высокочастотные детали уменьшают поверхностную энергию. Прокрутка превращает форму из органичной в структурированную, а взаимодействие при наведении показывает небольшие следы системы под ней.

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

Ресурсы и инструменты

  • Текстуры, созданные на основе холста
  • Отображение аудиореактивного состояния
  • Три.js
  • Веб-графический процессор
  • ГСАП/Скролтриггер



Источник

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх