Skip to content

Cyber-Zhaba/RayMarching

Repository files navigation

RayMarching with LLM support

ФПМИ МФТИ весна 2025

Булдаков Арсений Б05-416

Проект представляет собой систему генерации и рендеринга 3D-сцен по текстовому описанию с использованием технологии RayMarching.

Демонстрация работы:

Содержание

  1. Что сделано и что осталось сделать
  2. Быстрый старт
  3. Библиотеки
  4. Архитектура
  5. Линтинг
  6. Тесты
  7. Как пользоваться движком?
  8. Как устроен движок?
  9. Вопросы и предложения

Что сделано и что осталось сделать

  • 3D движок
    • Простые фигуры
    • Простое освещение
    • Простые материалы
    • Динамическое освещение с помощью трассировки лучей
    • Гамма-коррекция
    • Антиалиасинг
    • Рендеринг фракталов
  • Обработка текстового описания
    • Не успел сделать (
  • Фронтенд
    • Веб-интерфейс
  • Развёртывание
    • Docker
    • CI/CD

Быстрый старт

Минимальные требования:

Шаг 1. Клонируем репозиторий

git clone https://gitlab.com/g8150507/raymarching-with-llm-support.git --depth 1

Шаг 2. Настройка переменных окружения В репозитории есть файл .env.template, в котором указаны переменные окружения. Вам нужно создать файл .env и заполнить его своими значениями. Например:

POSTGRES_USER=root
POSTGRES_PASSWORD=you-better-change-this
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_DB=root

SECRET_KEY=you-better-change-this

RABBITMQ_HOST=rabbitmq
RABBITMQ_DEFAULT_USER=root
RABBITMQ_DEFAULT_PASS=you-better-change-this

Шаг 3. Запуск приложения

docker compose up

Шаг 4. Перейдите по адресу http://localhost:8000 и посмотрите на приложение)

Технологический стек

Название Описание
numba-cuda Just in time compilator. Используется для ускорения вычислений на CUDA ядрах
numpy Библиотека для работы с многомерными массивами и матрицами. Используется для хранения вершин, нормалей и других данных
PIL Библиотека для работы с изображениями. Используется для сохранения и отображения изображений
NiceGUI Библиотека для создания графического интерфейса. Используется для создания интерфейса приложения
FastAPI Веб-фреймворк для создания API. Используется для создания API приложения

Где requirements.txt?

Я решил использовать в данном проекте uv. Вместо requirements.txt используется pyproject.toml и uv.lock. Для ручной установки зависимостей нужно выполнить команду

uv sync

Архитектура

flowchart TD
    User[<img src='https://www.svgrepo.com/show/525577/user-circle.svg' width='40' height='40' />]
    3DEngine[<img src='https://www.svgrepo.com/show/163326/coordinates.svg' width='40' height='40' />]
    UI[<img src='https://upload.wikimedia.org/wikipedia/commons/thumb/0/0e/Logo_nicegui_black.svg/500px-Logo_nicegui_black.svg.png?20231119060036' width='40' height='40' />]
    Postgre[<img src='https://www.svgrepo.com/show/354200/postgresql.svg' width='40' height='40' />]
    RabbitMQ[<img src='https://www.svgrepo.com/show/354250/rabbitmq-icon.svg' width='40' height='40' />]
    
    User --> UI
    UI --> RabbitMQ
    UI --> Postgre
    RabbitMQ --> 3DEngine
Loading

Линтинг

Для линтинга используется ruff

Для запуска линтинга сервис engine нужно выполнить команду

cd engine
uvx ruff check

Для запуска линтинга сервиса user_interface нужно выполнить команду

cd user_interface
uvx ruff check

Тесты

Увы, их нет (

Как пользоваться движком?

Движок принимает на вход JSON сцены следующего вида

{
    "width": 1080,                       // Ширина выходного изображения в пикселях
    "height": 720,                       // Высота выходного изображения в пикселях
    "filename": "cornell1",              // Базовое имя для сохраняемого файла (без расширения)
    "max_ray_march_steps": 1024,         // Максимальное количество шагов ray marching'а до срабатывания ограничения
    "max_ray_cast_depth": 1,             // Глубина рекурсии для лучей (отражения/преломления)
    "paths_per_pixel": 4,                // Количество сэмплов на пиксель для патч-трейсинга
    "max_dist": 1000000.0,               // Максимальная дистанция видимости (дальность рендеринга)
    "epsilon": 0.0005,                   // Точность определения пересечения (шаг точности)
    "sky": "empty",                      // Тип фона ["empty", "mountains", "space"]
    "gamma_correction": 1,               // Коэффициент гамма-коррекции (1.0 = отключено)
    "threadsperblock": 16,               // Размерность блоков потоков CUDA (оптимизация для GPU)
    "SSAA": 1,                           // Коэффициент суперсэмплинга (антиалиасинг)
     
     "cam": {
        "position": [1.81, 0, 0],        // Позиция камеры в мировых координатах (x,y,z)
        "direction": [1, 0, 0],          // Вектор направления взгляда (нормализованный)
        "up": [0, 0, 1],                 // Вектор "вверх" для камеры (обычно [0,0,1] или [0,1,0])
        "fov": 90                        // Угол обзора в градусах (перспективная проекция)
    },

    "world": [
        {
            "type": "cube",              // Тип объекта ["cube", "sphere", "torus", "mandelbulb"]
            "center": [3, 0, -1.5],      // Центр объекта в мировых координатах
            "scale": 1,                  // Масштаб объекта (единичный размер)
            "material": {
                "type": "diffuse",       // Тип материала ["diffuse", "metal", "glass", "emissive"]
                "color": [0.7, 0.7, 0.7] // RGB цвет [0..1] для диффузных материалов
            },
            "rotation": [0, 0, 0]        // Углы Эйлера вращения (в градусах)
        }
    ]
}

Как устроен движок?

В основе движка лежит алгоритм RayMarching. Каждый элемент сцены описывается функцией расстояния (SDF), вычисляющей минимальную дистанцию от произвольной точки до поверхности объекта.

Возьмем, к примеру, сферу. Её можно задать следующей SDF-функцией:

$$ \text{SDF}_{\text{sphere}}(\mathbf{p}) = \left| \mathbf{p} - \mathbf{c} \right| - r $$

Вот результат её визуализации:

Перед нами простая белая сфера — ничего лишнего.

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

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

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

Это хорошо заметно на фотографиях планет:

Как видно, у них тоже ярко выражена "дневная" и "ночная" сторона — ровно как в нашем рендере. Так что всё работает как задумано!

Сейчас сфера имеет цвет [1.0, 1.0, 1.0] — это классический белый в RGB-формате (где значения ограничены диапазоном от 0 до 1). Однако если поэкспериментировать и выйти за эти границы, можно получить неожиданные визуальные эффекты:

Выглядит стильно, хотя и не имеет практического применения.

Пока изображение выглядит «рваным», с чёткими ступенчатыми переходами. Но это временно — дальше мы разберём, как добавить сглаживание для более плавного рендера.

Сейчас мы можем добавить базовые тени. Алгоритм прост:

  1. Находим точку пересечения луча с объектом
  2. Вычисляем нормаль к поверхности
  3. Пускаем дополнительный луч к источнику света
  4. Если луч перекрыт другим объектом — точка в тени

Результат:

Недостаток: такие тени получаются резкими и неестественными. Поэтому переходим к более продвинутой системе — трассировка лучей.

Добавляем два типа материалов:

  1. Диффузный — отражает свет равномерно во всех направлениях (как матовая поверхность)

  2. Светящийся — сам является источником света (цвет добавляется прямо в пиксель)

Принцип рендеринга:

  • Если луч попадает на светящийся материал — сразу возвращаем его цвет
  • Если на диффузный — случайно отражаем луч и продолжаем трассировку
  • Если после нескольких отскоков луч не встретил источник света — пиксель остаётся чёрным

Иллюстрация:

Первая попытка:

Проблема: изображение получается «шумным», так как для каждого пикселя используется только один случайный луч.

Чтобы исправить это, необходимо использовать метод Монте-Карло, когда мы выбираем несколько направлений и усредняем результат.

Добавим большой источник света и посмотрим на результат:

1 луч на пиксель

10 лучей на пиксель

100 лучей на пиксель

1000 лучей на пиксель

Чем больше лучей — тем плавнее и реалистичнее результат.

Добавим новый материал — металл. Вместо того, чтобы выбирать случайное направление, мы будем отражать луч относительно нормали к поверхности.

Особенности:

  • Луч отражается зеркально (угол падения = углу отражения)
  • Цвет металла влияет на оттенок отражения

Результат:

Сейчас изображение выглядит блёклым и неестественным. Это происходит потому, что:

  • Человеческое зрение воспринимает яркость нелинейно
  • Стандартные дисплеи работают в sRGB-пространстве

Решение:

  • Гамма-коррекция (степень 2.2) - компенсирует нелинейность восприятия

  • Tone mapping - адаптирует HDR-цвета к диапазону дисплея

Результат после применения:

Разница очевидна - цвета стали насыщенными и естественными.

Сейчас, если луч никуда не попал, мы красим его в чёрный. Но мы можем добавить скайбокс — текстуру, которая будет отображаться в фоновом режиме.

Добавим два скайбокса:

  • Горы
  • Космос

Картинку гор я подрезал у автора Capricorn4049 с этого сайта В описании сказано, что при иcпользовании нужно указать автора. Поэтому вот указываю.

Посмотрим на результат:

Пришло время бороться с ступенчатыми переходами.

Самый простой способ - это сделать суперсэмплинг (SSAA) - рендерить изображение в 4 раза больше, а потом уменьшить его до нужного размера. Это позволяет сгладить резкие переходы. Остаётся только выбрать, как именно уменьшать изображение. Я выбрал метод Lanczos, так как он даёт наилучший результат.

Вот разные методы уменьшения изображения:

BICUBIC

BILINEAR

BOX

HAMMING

LANCZOS

NEAREST

Ещё один способ борьбы с ступенчатыми переходами - это использовать антиалиасинг. Он работает по тому же принципу, что и суперсэмплинг, но вместо того, чтобы рендерить изображение в 4 раза больше, он просто добавляет шум к лучам, которые мы пускаем

Нужно только выбрать на сколько процентов добавлять шум. Я выбрал 0.15% от длины луча. Это позволяет получить хороший результат без сильного ухудшения качества.

Вот разные проценты добавления шума:

1%

0.5%

0.15%

0.025%

Добавим ещё один простой объект - куб. Он описывается SDF-функцией, которая вычисляет расстояние от точки до поверхности куба.

$$ \text{SDF}_{\text{cube}}(\mathbf{p}) = \max\left( \left| p_x - c_x \right| - s_x, \left| p_y - c_y \right| - s_y, \left| p_z - c_z \right| - s_z \right) $$

Пришло время добавить более интересные объекты.

Как я уже говорил, каждый объект описывается знаковой формулой расстояния, поэтому мы можем спокойно отрисовывать объекты с бесконечной кривизной. Например, фракталы.

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

$$ \text{SDF}_{\text{mandelbulb}}(\mathbf{p}) \approx \frac{1}{2} \cdot \frac{\log r \cdot r}{dr} \cdot s $$ Где: $$ \begin{align*} r &amp;= |\mathbf{z}| \\ \theta &amp;= \arccos\left(\frac{z_z}{r}\right) \\ \phi &amp;= \arctan2(z_y, z_x) \\ \mathbf{z} &amp;= r^n \cdot \left[ \begin{array}{c} \sin(n\theta)\cos(n\phi) \\ \sin(n\theta)\sin(n\phi) \\ \cos(n\theta) \end{array} \right] + \mathbf{p}_{\text{local}} \\ dr &amp;= r^n \cdot n \cdot dr + 1 \end{align*} $$

Результат:

Это выглядит просто невероятно, особенно на фоне космоса. По правде говоря, это мой любимый рендер из всех сделанных в проекте.

На последок, попробуем повторить эксперимент Корнелла. Известный как Корнеловская коробка. Это классический тест для рендеринга, который позволяет проверить качество освещения и материалов.

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

Довольно интересно, не правда ли?

Далее идут ещё больше рендеров, которые я успел сделать.

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

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

Вопросы и предложения

Возможен ли запуска без GPU от Nvidia?

Нет. Движок использует CUDA для ускорения вычислений. Если у вас нет GPU от Nvidia, вы можете попробовать запустить эмуляцию CUDA на CPU. Я пытался - у меня не получилось. Поэтому проект нативно не поддерживает рендеринг на CPU. Даже если вы сможете запустить, производительность будет очень низкой. Если у вас есть GPU от AMD, вы можете попробовать использовать ROCm, но я не тестировал. Скорее всего работать не будет.

Проверка версии CUDA

Запустите команду

 nvidia-smi

Вы должны увидеть что-то такое

+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 576.28                 Driver Version: 576.28         CUDA Version: 12.9     |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                  Driver-Model | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA GeForce RTX 3060      WDDM  |   00000000:01:00.0  On |                  N/A |
|  0%   43C    P8             17W /  170W |    1796MiB /  12288MiB |     16%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+

Если версия CUDA меньше 12.0 или ваша команда выдает ошибку, то вам нужно установить CUDA Toolkit. Вы можете скачать его здесь

About

Проект для курса по Python МФТИ 2025

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors