Всем привет! Меня зовут Шико, я работаю в Яндекс Маркете в команде Android-разработки. Сегодня я расскажу историю, которая случилась в 2021 году. Как-то раз перед еженедельным синком я увидел вопрос в рабочем чате: «Кто хочет поучаствовать в проекте связанным с 3D?» А я, пока учился в университете, занимался 3D-моделированием. Тогда для меня это было просто хобби, но я решил вспомнить, каково это, и предложил свою кандидатуру. Суть задачи была в следующем: нужно было добавить в мобильное приложение AR (то есть, дополненную реальность). Оно нужно, чтобы товар с Маркета можно было «примерить» в интерьер. Например, оно полезно, когда вы хотите купить телевизор, но вам сложно представить, будет ли он гармонировать с мебелью и влезет ли он вообще в имеющееся пространство.  На iOS к проекту подключился один разработчик, а на Android нас было двое. Сначала я расстроился: показалось, что ничего особо интересного не будет — всего-то подключить ARCore и делов. Но это ровно до тех пор, пока не выяснилось, что большинство файлов моделек было в USDZ-формате, а ArCore на тот момент с ним не работал. То есть, когда на iOS в процессе разработки таких проблем не возникало, нам нужно было придумать способ перевести существующие модельки в другой формат — GLB. Казалось бы, скачай конвертер и нажми на кнопку «Конвертировать». Не тут-то было.  И в этой статье я расскажу, какие методы конвертации я пробовал, почему они не подошли и с чем не смогли справиться Blender и Unreal Engine. Спойлер: в итоге мне пришлось написать собственный плагин и я покажу его код. Чтобы решить эту интересную задачку, я попробовал несколько способов решить её малой кровью. Двигался я по такому списку: онлайн-конвертеры; Blender; другие 3D-редакторы; Unreal Engine. Онлайн-конвертеры оказались слишком слабыми: они отваливались через 5—10 минут обработки файла. Просто выдавали ошибку. Для Blender уже был написан плагин, который позволял импортировать USDZ. К сожалению, на большинстве файлов он не работал и падал из-за ошибок в коде. Я пробовал дебажить код и разбираться, в чём же дело. Но было очень много разных ошибок, и я забросил эту идею. Поиски других 3D-редакторов, которые поддерживали бы формат USDZ, не увенчались успехом. На страницах 3Ds Max и Maya нашёл инфу, что можно подключить плагин, который находится в альфе или бете, что тоже мне не подходило. Другие редакторы уже не припомню. При этом всём я разрабатывал на Linux, соответственно, многие платные редакторы тоже отсеивались. В какой-то момент я решил попробовать Unreal Engine. Оказалось, из коробки он поддерживает импорт USDZ (встроенный плагин в бета-версии), только работает он весьма специфично. Собственно, первая более-менее успешная попытка конвертировать файл вышла по следующему алгоритму: Импорт файла USDZ в UnrealEngine. Экспорт в .obj формат. Попытка применить текстуры методом тыка в Blender. Чем этот способ не подходил: Unreal Engine довольно тяжёлый, он требует много ресурсов, и в нём неудобно импортировать/экспортировать и редактировать. При этом на некоторых модельках он неправильно импортировал нормали полигонов или UV-координаты, что приводило к странным артефактам. В общем, я столкнулся с тем, что в свободном доступе нет ни одного конвертера, который смог бы перенести модельки из USDZ в любой другой формат. Параллельно со всем этим я решил всё-таки ещё раз покопать тот плагин к Blender. Я нашёл несколько ошибок в коде. Получилось пофиксить все краши (или почти все), но только я так и не смог пофиксить ошибку при чтении некоторых данных — на многих модельках неправильно считывалась матрица трансформации. Попробуйте угадать, что это? Я начал прочёсывать GitHub и вышел на библиотеку Pixar для работы с USDZ. Оказалось, они предоставили все исходники для работы с этим форматом. На всякий попробовал собрать их.  И вот тут мне улыбнулась удача. Оказалось, среди библиотек есть несколько интересных утилит. Среди них меня особенно заинтересовал USDView. Она позволяет просмотреть визуально модельку с дополнительной информацией по ней. Полученную информацию я решил использовать для дебага плагина к Blender. Спустя пару вечеров, проведённых в USDView, питоновском дебаггере и Blender, я выяснил, что автор библиотеки самостоятельно написал код для парсинга бинарного файла. На каком-то из шагов по непонятной мне ошибке (потратил почти 4 дня на эти трансформации!) этот код иногда пропускает одну из множества матриц трансформаций, что и приводит к покорёженным результатам.  Руками править модельки нереально, учитывая сжатые сроки. Фикс этой проблемы мною был оценен на уровне «вроде изян» по универсальной таблице оценки задачи разработчиками, поэтому решил забросить эту идею. Я продолжил изучать библиотеку. Тогда у меня возникла первая шальная мысль написать собственный конвертер, изучив кишки библиотеки. Но эта задача тоже выглядела «вроде изян». И только я собрался отказаться от идеи, как я нахожу в этой библиотеке софтину Usdcat. Как оказалось, Pixar сделали три возможных формата файла: USDC — бинарный файл, который хранит в себе всю инфу по сетке и трансформациям, а также информацию о материалах без текстур. USDA — то же, что и USDC, только в текстовом формате. USD — он может быть как бинарным так и текстовым. USDZ — по сути, это zip-архив, внутри которого есть USDC или USDA файл и текстуры. Так вот, Usdcat позволяет конвертировать бинарный файл модели в его текстовое представление, что очень удобно для чтения данных и отладки процесса конвертации.  На этом этапе я собрал Blender из исходников как внешнюю библиотеку для Python, и в качестве эксперимента начал писать прототип конвертера. Внимание: дальше идёт не очень хороший код от новичка на Python. Уберите впечатлительных разработчиков от экрана. Процесс конвертации я разбил на несколько этапов: Распаковка USDZ-файла. Конвертация USDC в USDA. Парсинг USDA, преобразование его в более удобочитаемую структуру в памяти. Обработка структуры, преобразование её в набор команд для Blender для создания сцены с моделькой. Сохранение в *.blend файл для последующей отладки. Конвертация сцены в GLB. Думаю, из всего перечисленного самое интересное — парсинг и команды для Blender.  Начнём с парсинга. В текстовом представлении USDC/USDZ/USDA выглядит следующим образом:  На что я обратил внимание. Практически во всех файлах всегда были: Заголовок с описанием сцены (масштабы, верхняя ось и другие данные). Xform — структура, которая может содержать другие xform, инфу о геометрии и материалах, а также информацию о матрице трансформации. Scope — по сути, это разные xform, объединённые по разному признаку (например, геометрия или материалы). Mesh — информация о геометрии модели (все вершины, грани, полигоны и прочее), также может содержать информацию о трансформации. Material — информация о материале, набор шейдеров разного типа. Парсер из себя представляет обычный конечный автомат с большим количеством переходов. Так как задача подразумевала разово преобразовать пару десятков или сотен моделей, в красоту и удобство кода я не вкладывался совсем. Алгоритм простой: проходим по всем строкам, на начало каждой. Если совпадает с константой — переходим в нужное состояние или считываем нужный тип данных, если нет — крашим парсинг с указанием неизвестной структуры. Список констант, который пришлось обработать:  Пример кода чтения материала (примерно то же самое для других структур, только больше строк):  После парсинга получаем структуру, в которой есть заголовок, набор материалов и набор геометрии. Преобразуем их в 3D-сцену. Основные операции в Blender выполняются через объект bpy. Позабавило, что некоторые операции выглядят как описание действий непосредственно в редакторе. Например, вот удаление объекта: Рекурсивно проходимся по всем xform и отрисовываем их контент. При этом не забываем посчитать матрицу трансформации — это произведение матрицы дочернего объекта на матрицу родительского.  Самые сложные части в конвертации: импорт геометрии и импорт материалов. Работа с геометрией была во многом подсмотрена в плагине к Blender, на который я ссылался выше.  Здесь я использовал материал BSDF_PRINCIPLED, для которого мог задать в качестве ввода следующие параметры: Base Color, Specular, Metallic, Roughness, Clearcoat, Clearcoat Roughness, Emissive, IOR, Opacity, Normal. Как это могло выглядеть в сцене (скрин с новой версии blender, но суть та же):  Конвертер не был завершён. Я остановился на отметке 80-90% сконвертированных файлов — этого вполне хватило для фичи.  Но на старте мы решили запустить ещё одну идею. Быстро сгенерировать руками модели тысяч телевизоров, стиралок, холодильников, мебели и прочих предметов невозможно, а фичу хочется максимально заиспользовать. Было принято решение нагенерировать «коробок» с определёнными размерами и показывать их в качестве заглушки, чтобы пользователь мог «примерить» товар у себя дома хотя бы по габаритам. Я сделал это с помощью USDA-файла. А именно создал файл-шаблон, в котором уже описана вся геометрия и материалы, только вместо позиций вершин стоят заглушки.  А дальше всё просто: копируем файл и текстуры в отдельную папку; скриптом на Python считаем правильные позиции вершин; подменяем в скопированном файле заглушки и подменяем их на рассчитанные позиции и другие параметры; архивируем и меняем расширение файла. И вот такую модельку мы получаем в итоге: Имея базу данных размеров холодильников, телевизоров и прочих крупногабаритных товаров, можно за пару минут сгенерировать несколько тысяч легковесных моделей. Фича успешно работает: при желании, можно найти товары с 3D-моделями и «примерить» у себя дома. Тем более, вы теперь знаете, сколько всего стоит за каждой моделькой. Спустя примерно месяц после доработок, я узнал, что в Blender версии 3.0.1 в экспериментальном режиме реализовали импорт USDZ. Из интереса поставил beta версию, чтобы проверить — материалы они так и не импортировали! Так что, по сути, моя неидеальная версия конвертера на тот момент оказалась чуточку продвинутей. Сейчас последняя версия Blender умеет и в материалы. Написать конвертер оказалось проще, чем казалось на первый взгляд. Это был очень интересный опыт — тот редкий случай, когда чем сложнее задача, тем интереснее её решать. Для себя я пришёл к таким выводам: Не бойтесь сложных задач, они позволяют получить уникальный опыт и позволяют устроить стресс-тест имеющимся навыкам и знаниям. Изучайте подручные инструменты, зачастую они могут чуть больше, чем кажется. Если бы у меня не получилось окольными путями сконвертировать хотя бы одну модельку, я бы быстро перегорел и ушел с проекта. Внимательно изучайте инструменты, которые относятся к предметной области. Я случайно наткнулся на библиотеку Pixar, которая помогла мне продвинуться в решении задачи. Без удобных инструментов отладки из этой библиотеки я бы не смог закончить задачу в срок.  Костыль-мастер