3D Gaussian Splatting — одна из самых захватывающих технологий в компьютерной графике последних лет. Она позволяет воссоздавать трёхмерные сцены из набора фотографий с потрясающей детализацией и скоростью. В 2023 году исследователи из INRIA представили метод, который превзошёл нейронные радиационные поля (NeRF) по качеству и быстродействию. С тех пор Gaussian Splatting стал стандартом для задач 3D-реконструкции, виртуальных сред и интерактивной визуализации.
Однако реализовать Gaussian Splatting с нуля — задача непростая. Нужно написать множество математических функций: проекции эллипсоидов, сортировки по глубине, композитинга. На C++ с CUDA это выглядит пугающе, а на Python — медленно. Но есть третий путь — язык Julia, который сочетает простоту динамического языка и производительность C. А с приходом vibe coding, когда нейросеть помогает писать код, создание эффективной реализации становится доступным каждому исследователю.
В этой статье я поделюсь нашим опытом улучшения Gaussian Splatting на Julia. Расскажу, как мы использовали ИИ-ассистента для генерации кода, какие оптимизации внедрили и каких результатов достигли. Если вы интересуетесь 3D-графикой и хотите попробовать что-то новое — этот материал для вас.
Что такое Gaussian Splatting?
Gaussian Splatting — это метод рендеринга, который представляет 3D-сцену как набор гауссовых облаков. Каждый гауссиан определяется следующими параметрами:
- Позиция (x, y, z) — центр эллипсоида.
- Ковариация — матрица 3x3, описывающая форму и ориентацию.
- Цвет — обычно представлен в виде RGB.
- Прозрачность (Alpha) — степень поглощения света.
Во время рендеринга камера проецирует каждый 3D-гауссиан на 2D-плоскость. Проекция га
уссовых эллипсоидов на 2D-плоскость экрана — это не просто перенос точек. Каждый гауссиан превращается в двумерное пятно размытия, форма которого зависит от угла обзора. Затем все эти пятна сортируются по глубине и смешиваются с учётом прозрачности. На выходе получается изображение, которое выглядит как фотография, но при этом его можно облетать с любой стороны.
Математически проекция гауссиана — это преобразование его ковариационной матрицы через матрицу вида камеры. В упрощённом виде это выглядит так:
- Переводим 3D-позицию гауссиана в координаты камеры.
- Находим якобиан проекции — как изменяется координата в пикселях при малом смещении в 3D.
- Умножаем ковариационную матрицу на матрицу вида и якобиан, получая 2D-ковариацию.
- Вычисляем альфа-канал каждого пикселя по формуле гауссовой плотности.
Звучит несложно, но на практике каждая операция затрагивает тысячи или миллионы гауссианов. Именно поэтому производительность решает всё: если рендер одного кадра занимает минуту, то интерактивной визуализации не получится.
Почему Julia?
Традиционно для таких задач используют C++ и CUDA. Python с PyTorch тоже подходит, но из-за накладных расходов на каждый вызов ядра он заметно медленнее. Julia — это язык, который позиционирует себя как «Python-подобный синтаксис со скоростью C». Он использует JIT-компиляцию и множественную диспетчеризацию, что позволяет писать высокоуровневый код без потери производительности.
Для нас Julia стала особенно привлекательной по трём причинам:
- Простота разработки. Мы можем писать математические функции в привычном виде, без ручного управления памятью.
- Лёгкая интеграция с GPU. Пакеты типа CUDA.jl и AMDGPU.jl предоставляют доступ к видеокартам без обвязки C++.
- Интерактивная среда. Возможность быстро итерировать в REPL или Jupyter сильно ускоряет эксперименты.
Но самое интересное — это использование vibe coding для ускорения разработки. Мы не просто пишем код сами, а используем ИИ-ассистента для генерации, рефакторинга и оптимизации.
Vibe coding: как мы ускорили разработку
Когда мы начинали проект, у нас был прототип на Python, который работал, но крайне медленно. Мы решили переписать его на Julia и использовать нейросеть как партнёра по программированию. Подход простой: мы описываем, что нужно сделать, а ассистент предлагает код. После этого мы проверяем, тестируем и помогаем ему исправлять ошибки.
Первое, что мы сделали — сгенерировали ядро рендеринга. Вот пример запроса, который мы отправили:
Напиши на Julia функцию для проецирования 3D-гауссиана на экран.
Вход: позиция (Vector3F), ковариация (SMatrix{3,3}), параметры камеры.
Выход: 2D-координаты, ковариация 2x2 и альфа-распределение.
ИИ выдал код, используя статические массивы из StaticArrays.jl. Мы сразу получили читаемую и относительно быструю версию. Однако первая реализация была чисто CPU. Для настоящей интерактивности нужен GPU, поэтому следующим шагом стала генерация CUDA-ядер.
Тут возникла интересная проблема. ИИ хорошо знает синтаксис CUDA.jl, но иногда генерирует код, который не компилируется из-за особенностей типов. Мы научили его работать с нашими структурами данных и указывать конкретные параметры компиляции. В результате мы получили ядро, которое обрабатывает миллионы гауссианов за кадр.
Оптимизации, которые дали наибольший прирост
Мы внедрили несколько ключевых оптимизаций. Каждая из них добавила несколько миллисекунд к рендеру, а в сумме мы ускорились в разы по сравнению с исходным Python-прототипом.
1. Использование StaticArrays вместо динамических массивов.
Для гауссианов с размером 3x3 и векторами малой размерности динамические массивы — это пустая трата памяти и времени. StaticArrays.jl позволяет хранить такие данные на стеке, а не в куче. Компилятор полностью разворачивает циклы, что даёт огромный выигрыш. Это особенно критично в горячем цикле рендеринга.
2. Пакетная обработка на GPU.
Мы реорганизовали данные в структуру массивов (SoA) и передали их в CUDA-ядро. Каждый поток обрабатывает один гауссиан для одного участка изображения. Это позволило использовать параллелизм видеокарты на максимальной мощности. Более того, мы применяем сортировку по глубине на GPU с помощью радикс-сортировки из библиотеки CUB, доступной через CUDA.jl.
3. Tile-based rendering.
Вместо того чтобы каждый гауссиан писал сразу во весь кадр, мы разбиваем изображение на тайлы 16x16 пикселей. Для каждого тайла выбираются только те гауссианы, которые реально в него попадают. Это снижает количество операций слияния в десятки раз. Такой подход используется в оригинальной статье INRIA, и мы успешно адаптировали его в Julia.
4. Минимизация аллокаций.
Одной из самых частых ошибок в Julia является случайное создание временных массивов. Мы провели профилирование и нашли несколько мест, где происходили ненужные выделения памяти. После замены на буферы, переиспользуемые между кадрами, скорость выросла примерно на 30%.
5. Автономная компиляция с флагами оптимизации.
Для критических функций мы используем макросы производительности. Например, @inbounds для отключения проверки границ и @simd для векторизации циклов. Это даёт дополнительный прирост, особенно на CPU-рендеринге.
Каких результатов мы достигли
Наш бенчмарк — сцена с одним миллионом гауссианов, рендер в разрешении 1920x1080 на GPU RTX 3060. Сначала у нас было около 200 миллисекунд на кадр в чистом Julia на CPU. После перевода на GPU и оптимизаций мы достигли примерно 15-20 миллисекунд — это уже пригодно для интерактивного просмотра.
Для сравнения, наш Python-прототип на PyTorch тратил около 1,2 секунды на тот же кадр. Итого ускорение более чем в 60 раз. Мы даже сравнили со стандартной реализацией на C++/CUDA из официального репозитория: наша версия на Julia отстаёт всего лишь на 10-15% в скорости, но при этом она в разы короче и легче для понимания.
Что такое vibe coding на практике
Главный вывод из этого эксперимента — не бояться давать ИИ-ассистенту сложные и «неудобные» задачи. Например, мы попросили нейросеть объяснить, как работает радикс-сортировка на GPU, и затем сгенерировать Julia-обёртку для неё. ИИ справился с первой попытки, хотя казалось, что для этого нужен глубокий системный опыт.
Но нужно быть критичным. Иногда ассистент предлагает код, который компилируется, но даёт неверный результат. Мы потратили много времени на отладку именно таких случаев. Поэтому мы выработали свой процесс:
- Каждый сгенерированный фрагмент прогоняется через юнит-тесты.
- Для визуальных результатов используем сравнение с эталонным рендером.
- ИИ используется как коллега, а не как замена мышлению: мы всегда понимаем, что делает код.
Советы для тех, кто хочет повторить
Если вы хотите улучшить Gaussian Splatting в Julia, вот что я рекомендую:
- Начните с маленького датасета. Сотни тысяч гауссианов достаточно для первых экспериментов.
- Изучите StaticArrays и CUDA.jl. Это основа производительности.
- Используйте профилировщик. Julia имеет отличный встроенный Profiler и возможность видеть типы через
@code_warntype. - Привлекайте ИИ для генерации boilerplate-кода, но всегда проверяйте математику.
- Не забывайте про точность. Используйте Float32 для координат и цвета, чтобы ускорить вычисления на GPU. Float64 может быть избыточным и медленным.
Планы на будущее
Мы продолжаем развивать проект. В ближайшее время планируем добавить:
- Поддержку обучения гауссианов из фотографий прямо на Julia, без использования C++.
- Автоматическое определение количества гауссов в сцене.
- Экспорт моделей в стандартный формат PLY для совместимости с другими инструментами.
Также мы хотим поделиться нашим кодом в открытом доступе. Если эта тема вам интересна, подписывайтесь на обновления или напишите в комментариях — возможно, мы подготовим отдельный туториал.
Заключение
Gaussian Splatting — мощный инструмент, и Julia делает его более доступным. С помощью vibe coding мы смогли в короткие сроки получить реализацию, которая по производительности почти не уступает C++, а по скорости разработки опережает его в разы. Это замечательный пример того, как современные ИИ-инструменты меняют подход к сложным научным вычислениям.
Если у вас есть опыт работы с Julia или Gaussian Splatting — поделитесь в комментариях. А я на этом не прощаюсь: впереди новые эксперименты и новые статьи. Спасибо за чтение!
Комментарии