Загрузка 0
ПОДЕЛИТЬСЯ

Мой блог

Листай вниз

Как я рендерю 3D-ролики для обучения кодом на React и Three.js

Как я рендерю 3D-ролики для обучения кодом на React и Three.js

Подробности изложены в материале первоисточника. Создавая мобильное образовательное приложение, я столкнулся с необходимостью понятно и наглядно визуализировать сложные сценарии движения объектов по строгим формальным правилам. Обычный текст здесь не помогал, а малейшая неточность в анимации могла заложить у пользователей неверные паттерны обучения. Чтобы решить эту задачу, я разработал собственный конвейер генерации видео с помощью Remotion и React Three Fiber.

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

Почему классические подходы к созданию видео не подошли

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

Реклама

Отказ от генеративного видео и ручного моделирования

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

Мой прямой конкурент в этой нише использует ручную работу 3D-моделлеров в Blender или Maya. Такой подход работает, но генерация каждого нового сценария требует часов ручной анимации и приобретения платных ассетов. Небольшая команда физически не сможет поддерживать такой темп выпуска материалов без потери качества.

Архитектурный принцип: сцена как чистая функция кадра

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

Реклама

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

Используемый стек технологий

Для реализации проекта я задействовал конкретные стабильные версии инструментов, зафиксированные в зависимостях проекта:

  • remotion версии 4.0.487;
  • @remotion/three версии 4.0.487;
  • @react-three/fiber версии 9.2.0;
  • three версии 0.178.0;
  • react и react-dom версии 19.2.3;
  • typescript версии 5.9.3.

На практике Remotion запускает headless Chromium, который для каждого отдельного кадра рендерит дерево React-компонентов внутри WebGL-канваса @react-three/fiber. Затем движок делает скриншот холста и передает готовую последовательность кадров встроенному инструменту FFmpeg. Тот моментально упаковывает кадры в финальный MP4-файл без необходимости промежуточной записи PNG-картинок на жесткий диск.

Готовые графические ассеты я не создавал с нуля, а задействовал бесплатные CC0-паки: низкополигональные модели от Kenney и качественные PBR-текстуры с платформы ambientCG. Для предварительной озвучки применяется офлайн-движок Piper TTS с русской локализацией, распространяемый под лицензией MIT. В дальнейшем для чистового релиза планируется переход на Yandex SpeechKit, поскольку текущий синтезатор из-за особенностей espeak-ng не всегда корректно обрабатывает ударения в специфических терминах.

Значительная часть рутинной работы и отладка сложных багов выполнялись при участии ассистента Claude Code. Он работал с прямым доступом к файловой системе и терминалу, самостоятельно запуская процессы рендеринга, анализируя логи и корректируя код. Тем не менее, он не был универсальным решением: часть предложенных им гипотез оказалась ошибочной, и окончательные диагнозы мы ставили путем сопоставления проблемных участков с уже работающими компонентами.

Математика движения: ориентация и позиция объекта

Серьезной ошибкой на старте было раздельное вычисление координат объекта на траектории и его угла поворота. Когда позиция определялась одной формулой, а направление взгляда интерполировалось независимо, визуально казалось, что персонаж или предмет буквально «плывет боком» по ходу движения.

Математика не гарантирует совпадение производной позиции и независимого угла поворота в каждый момент времени. Единственным надежным решением стало вычисление угла разворота (yaw) напрямую из производной кривой пути. Благодаря этому объект физически не может отклониться от направления своего реального движения.

Реклама

Устранение главной проблемы: чернеющие текстуры на длинных рендерах

Самым сложным испытанием в разработке конвейера стали PBR-текстуры, которые идеально выглядели на одиночных кадрах при использовании функции remotion still, но полностью чернели при создании последовательностей длиннее 300 кадров. Более того, этот сбой порой заражал соседние, полностью стабильные компоненты проекта.

Я последовательно проверил шесть различных гипотез исправления ситуации:

  • замена стандартной загрузки через тег img на комбинацию fetch и createImageBitmap;
  • внедрение модульного Promise-кэша для предотвращения повторной загрузки текстуры на каждом кадре;
  • обработка текстуры через CanvasTexture для точного соответствия рабочему коду из других модулей;
  • уменьшение разрешения текстур с 1024 пикселей до 512 для экономии видеопамяти;
  • добавление явного вызова метода gl.initTexture() для гарантии готовности GPU-текстуры;
  • отказ от сетевых запросов к локальному dev-серверу Remotion в пользу base64 data: URI.

Все эти методы объединяло одно: они пытались починить асинхронную загрузку ресурсов прямо во время рендеринга. Стабильную работу показали только полностью синхронные процедурные canvas-текстуры, собираемые внутри хука useMemo без единого оператора await.

Окончательное решение проблемы с асинхронностью

Финальный фикс оказался предельно лаконичным — полностью избавиться от асинхронных операций в рантайме. На этапе предварительной сборки с помощью Node.js и библиотеки PIL изображения раскодируются в чистые RGBA-пиксели, конвертируются в base64 и записываются в константы отдельных TypeScript-файлов.

В браузере выполняется только синхронная распаковка массива байтов и создание объекта DataTexture с флагом needsUpdate равным true. Вероятно, при длительном рендеринге Remotion пропускает перерисовку кадра, если асинхронный setState приходит после первого синхронного прохода, оставляя материал пустым.

Дополнительные технические находки

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

Второй нюанс касался динамической перекраски 3D-моделей. Простое умножение параметра material.color не давало нужного результата, если исходная текстура имела глубокий собственный тон — насыщенные участки сохраняли первоначальный цвет. Пришлось реализовать попиксельную перекраску canvas-текстуры по маске.

Производительность и системные требования

Для рендеринга используется обычная домашняя рабочая станция с видеокартой GTX 1650. Тестовый ролик длительностью 54 секунды (1620 кадров при 30 кадрах в секунду в разрешении 1080p) обрабатывается по следующим метрикам:

  • полное время экспорта в MP4 составляет около 52 секунд;
  • пиковое потребление оперативной памяти процессом держится на отметке около 774 МБ;
  • сцена насыщена примерно 250 детерминированными процедурными объектами окружения;
  • финансовые затраты на инфраструктуру равны нулю благодаря локальному выполнению задач.

Скорость рендеринга достигает реального времени (1 к 1), причем этап мультиплексирования через FFmpeg выполняется параллельно и не увеличивает общую длительность процесса. В отличие от облачных нейросетей, где стоимость растет пропорционально каждой минуте видео и количеству бракованных генераций, мои расходы ограничены лишь производительностью собственного железа.

Итоги и перспективы развития

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

Главное достоинство подхода заключается в масштабируемости. Для генерации нового сценария мне не нужно переснимать видео или рисовать графику вручную — достаточно скорректировать входные параметры сцены вроде координат, таймингов и реплик, сохранив базовую геометрию и физику неизменными. В будущем планируется внедрение продвинутых шейдеров, качественной пост-обработки, контактных теней и фотореалистичных материалов для достижения более кинематографичной картинки.

01.