Попробовать онлайн О проекте
Шесть панелей — это настоящая геометрия WebGL внутри Scene. У них есть толщина и кривизна; они перекрывают персонажа и сами перекрываются им, а порядок по глубине остаётся корректным при любом ракурсе. Это не карточки DOM, плавающие поверх canvas.
В основе всего взаимодействия лежит одно разделение ролей: 3D отвечает за исследование, 2D — за управление. Панели обеспечивают просмотр и выбор, дрейфуют вокруг персонажа и поворачиваются вслед за жестами; непосредственное управление воспроизведением остаётся за стабильным экранным интерфейсом внизу. Выбор композиции — не смена состояния, а непрерывная передача роли: панель сама летит вниз и становится плеером, а при выборе другого трека прежняя панель возвращается в свою исходную позицию в 3D.
Ключевые моменты
- При перетаскивании пустого пространства музыкальные карточки вращаются вокруг персонажа и после отпускания продолжают движение по инерции. При перетаскивании самого персонажа поворачивается только он, а камера и панели остаются неподвижными. Оба слоя медленно дрейфуют в противоположных направлениях — именно это встречное движение превращает кластер из фона в единое тело на орбите.
- По щелчку панель выходит из 3D-пространства, проносится мимо персонажа и раскрывается в горизонтальный плеер внизу экрана. Если выбрать другую панель, в воздухе одновременно окажутся две: новая движется вперёд, а прежняя складывается и возвращается точно в исходную ячейку.
- Колесо перемещает камеру по долли между 4.6 и 16 мировыми единицами: при приближении передние панели выходят за границы кадра, а при отдалении всё пространство собирается в единое музыкальное созвездие, видимое сверху.
- Рамка из четырёх углов у пользовательского курсора привязывается к реальной проекции квада каждой панели. Скобки встают точно на перспективные углы и поворачиваются вместе с наклоном панели.
Технический разбор
3D-ассеты · Исходный экспорт Tripo без изменений
Artist.glb поставляется ровно в том виде, в котором экспортирован из Tripo: 59 MB, 1.02M вершин, карта базового цвета 8192² — без децимации и изменения размера, со всей авторской детализацией. Издержки заложены заранее (~350 MB VRAM после построения mipmap и повторная отрисовка миллиона вершин в каждом кадре), а управляемыми их делают два решения. Тяжёлый меш исключён из raycasting: проверка миллиона треугольников при каждом движении указателя остановила бы главный поток; вместо него используется невидимая капсула, размер которой вычислен по собственным границам модели. Поскольку экспорт не содержит анимационных клипов, персонаж получает групповую idle-анимацию — медленное покачивание и дыхательное движение амплитудой в миллиметр — вместо искусственно созданного скелета.
Пайплайн meshopt в scripts/ уменьшает тот же файл до 2.9 MB. Модель измеряется и нормализуется во время загрузки, поэтому замена сборки не требует изменений в коде.
Материалы · Упрощение объёмного материала в экспорте Tripo
Экспорт Tripo помечает меш расширением KHR_materials_volume, которое в three.js превращается в MeshPhysicalMaterial с веткой transmission/thickness — она не нужна этой Scene и слишком дорога для неё. При загрузке каждый материал понижается до MeshStandardMaterial с сохранением полного набора PBR-карт: внешний вид остаётся прежним, а стоимость исчезает.
Пользовательские ShaderMaterials явно включают colorspace_fragment. Без него линейные значения записываются непосредственно во фреймбуфер sRGB, и результат выглядит почти чёрным.
Музыкальные панели · Настоящая геометрия на кривой
Каждая панель состоит из четырёх типов геометрии: лицевой поверхности, протянутой полосы обода с реальной толщиной, полупрозрачной задней пластины и невидимых квадов для hit-тестов. Все они используют один кэшированный набор буферов; для разных карточек меняется только групповая трансформация. Элементы создаются плоскими, а затем оборачиваются вокруг большого вертикального цилиндра. Канавка прогресса и часы запекаются в ту же кривую, а не присоединяются к ней как дочерние элементы. Заполнение прогресса реализовано через UV cutoff, внедрённый в MeshBasicMaterial: масштабирование меша подняло бы его дальний край над кривой.
Графика панели разделена между тремя canvas по частоте обновления. Поверхность 1320×680 отрисовывается один раз, полоса времени — при смене отображаемой секунды, значок — только при изменении состояния воспроизведения; в цикле рендеринга нет дорогих операций. Все шесть прозрачных панелей отключают запись глубины и в каждом кадре получают ступенчатый renderOrder по расстоянию до камеры, поэтому окклюзия остаётся корректной при любом ракурсе.
Переход из 3D в 2D · Карточка становится плеером
Меш WebGL не может превратиться в узел DOM, поэтому непрерывность перехода обеспечивают три механизма. Проектор в каждом кадре обновляет экранные границы всех карточек, включая скрытые: обратному полёту тоже нужна точка посадки. В первом кадре панель DOM показывает не воссозданную в HTML версию, которая всё равно не совпала бы идеально, а тот самый bitmap canvas, уже отрисованный для WebGL-текстуры карточки. В этом же кадре карточка скрывается: два объекта находятся в одном месте, но один из них невидим.
Расширение начинается только после ~35% полёта: если в процессе растянуть карточку 2:1 в полосу 11:1, изображение потеряет форму. Траектория построена по отклонённой в сторону квадратичной кривой Безье, поэтому карточка, летящая к месту стыковки из левого верхнего угла, огибает исполнителя. Координаты назначения считываются заново в каждом кадре: созвездие продолжает двигаться, и rect, полученный в момент старта, привёл бы панель не на место карточки, а рядом с ней.


