Перейти к содержимому

Веб-графикаИнтерактивные виджеты

Портрет из частиц — интерактивное поле на WebGL

Фотография разбирается на двадцать тысяч точек: портрет собирается из частиц и расступается там, где проходит курсор. WebGL, один draw call, ноль зависимостей.

Период
2026
Портрет мужчины в профиль из светящихся точек на чёрном фоне

Ключевые показатели проекта

частиц в кадре — пиксели донора без пустого фона
~21 К
библиотек — один draw call на gl.POINTS
0
эффект — частицы из картинки, не 3D-сцена
2D

Состав работ по проекту

Услуги

  • Разбор фотографии на частицы
  • Подготовка донора и тонкоррекция
  • Расталкивание курсором по слоям глубины
  • Адаптация под светлую и тёмную тему

О проекте

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

Портрет мужчины в профиль из светящихся точек на чёрном фоне
Подробнее о проекте

Условие — лёгкий 2D-эффект без 3D-сцены и без сборки: WebGL, один файл, ноль зависимостей. Всё, что нужно на странице, — картинка-донор и строка подключения.

Ход работы

  • пиксели картинки-донора читаются один раз на CPU и ложатся в вершинный буфер: индекс, координата, угол и цвет — семь float на частицу. Текстура шейдеру не нужна вовсе, вершинная выборка текстур на части GPU капризна;
  • пустой фон в буфер не попадает, как и в оригинале. Но оригинал режет по 34/255 и вместе с фоном выбрасывает всю тень объекта — на контровом портрете от головы остаётся только освещённая полоса. Мы режем на порядок ниже, а за то, сколько видно в тенях, отвечает отдельный ползунок: «присутствие» точки отвязано от её тона, иначе тёмная половина лица просто исчезает;
  • тон донора правится прямо в шейдере: S-кривая разводит светлое и тёмное, порог убирает шум фона. Фотография «как есть» даёт точки почти одного размера, и лицо читается пятном, а не формой. Оба параметра — ползунки на странице;
  • кадр виджет находит сам, по массе яркости, а не по «есть пиксель ярче порога»: одно зерно шума в углу иначе растягивает кроп на весь донор, и объект собирается мелко;
  • шаг сэмплирования следует за экраном: брать точки чаще, чем экран может показать, бессмысленно — светлые места слипаются в пятно, а полутона исчезают;
  • при старте частицы «слетаются» из случайного разлёта в исходное изображение — это и есть живая сборка картинки;
  • курсор расталкивает точки в плоскости изображения: чем ближе, тем сильнее разлёт, с плавным следованием за мышью и лёгким параллаксом по глубине. Сила расталкивания привязана к глубине частицы — передний слой уходит от курсора полностью, задний стоит на месте и держит форму объекта. Без этого курсор пробивал картинку насквозь и на его месте оставалась пустая дыра;
  • весь расчёт — в вершинном шейдере: один draw call на gl.POINTS, шум и позиции считаются на GPU;
  • у поля нет собственного экрана: канвас прозрачный, живёт на фоне темы сайта, в светлой теме точки становятся «чернильными», а чёрный фон донора исчезает;
  • защита производительности: потолок devicePixelRatio и площади буфера, остановка рендера за пределами экрана, уважение системного «уменьшить движение».

Результат

Портрет живёт на странице сам: точки дышат, курсор раздвигает передний слой, картинка не повторяется дважды. Это не видео и не спрайт — двадцать тысяч частиц считаются в вершинном шейдере за один draw call.

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

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

Есть продукция, которую нужно визуализировать?

Средний бюджет проекта — от 30 000 ₽.

Небольшие отдельные задачи оцениваются индивидуально.