Портрет из частиц — интерактивное поле на 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 ₽.
Небольшие отдельные задачи оцениваются индивидуально.
