
Всем привет! Меня зовут Шико, я работаю в Яндекс Маркете в команде 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
В какой-то момент я решил попробовать Unreal Engine. Оказалось, из коробки он поддерживает импорт USDZ (встроенный плагин в бета-версии), только работает он весьма специфично.
Собственно, первая более-менее успешная попытка конвертировать файл вышла по следующему алгоритму:
-
Импорт файла USDZ в UnrealEngine.

-
Экспорт в .obj формат.

-
Попытка применить текстуры методом тыка в Blender.

Чем этот способ не подходил: Unreal Engine довольно тяжёлый, он требует много ресурсов, и в нём неудобно импортировать/экспортировать и редактировать. При этом на некоторых модельках он неправильно импортировал нормали полигонов или UV-координаты, что приводило к странным артефактам.

В общем, я столкнулся с тем, что в свободном доступе нет ни одного конвертера, который смог бы перенести модельки из USDZ в любой другой формат.
Снова Blender
Параллельно со всем этим я решил всё-таки ещё раз покопать тот плагин к Blender. Я нашёл несколько ошибок в коде. Получилось пофиксить все краши (или почти все), но только я так и не смог пофиксить ошибку при чтении некоторых данных — на многих модельках неправильно считывалась матрица трансформации.
Попробуйте угадать, что это?
Я начал прочёсывать GitHub и вышел на библиотеку Pixar для работы с USDZ. Оказалось, они предоставили все исходники для работы с этим форматом. На всякий попробовал собрать их.
И вот тут мне улыбнулась удача. Оказалось, среди библиотек есть несколько интересных утилит. Среди них меня особенно заинтересовал USDView. Она позволяет просмотреть визуально модельку с дополнительной информацией по ней.

Полученную информацию я решил использовать для дебага плагина к Blender. Спустя пару вечеров, проведённых в USDView, питоновском дебаггере и Blender, я выяснил, что автор библиотеки самостоятельно написал код для парсинга бинарного файла. На каком-то из шагов по непонятной мне ошибке (потратил почти 4 дня на эти трансформации!) этот код иногда пропускает одну из множества матриц трансформаций, что и приводит к покорёженным результатам.
Руками править модельки нереально, учитывая сжатые сроки. Фикс этой проблемы мною был оценен на уровне «вроде изян» по
Начнём с парсинга. В текстовом представлении 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, которая помогла мне продвинуться в решении задачи. Без удобных инструментов отладки из этой библиотеки я бы не смог закончить задачу в срок.# Точка входа в конвертер def try_to_convert(input_file, blend_file, output_file, debug_file=None): # Ищем утилиту usdcat в окружении usd_cat = find_exe('usdcat') # Создаём пустую сцену в blender create_empty_scene() # Запускаем usdcat, скармливаем ему модельку, считываем результат file_data = read_file(usd_cat, input_file) # Придётся распаковать файл, чтобы была возможность импортировать текстуры в Blender extracted_path = extract_file(input_file) # На всякий случай сохраняем полученные данные, чтобы была возможность просто и быстро дебажить, если что-то пойдёт не так save_data(file_data, debug_file) # Парсим полученное текстовое представление файла parsed_data = parse_data(file_data) # Нам интересно вот это # По распаршенным данным создаём модельку import_scene_data(parsed_data, extracted_path) # и это # Сохраняем полученную сцену для дальнейшего анализа ошибок save_scene(blend_file) # Экспортируем в glb export_scene(output_file) # Очищаем ресурсы clean(extracted_path)Код
#usda 1.0 ( customLayerData = { string creator = "usdzconvert preview 0.62" } defaultPrim = "modul_01" metersPerUnit = 1 upAxis = "Y" ) def Xform "modul_01" ( assetInfo = { string name = "modul_01" } kind = "component" ) def Scope "Materials" { def Material "DVP_komod_2_Letta_Malta_modul_01" { token outputs:surface.connect = </modul_01/Materials/DVP_komod_2_Letta_Malta_modul_01/surfaceShader.outputs:surface> def Shader "surfaceShader" { uniform token info:id = "UsdPreviewSurface" color3f inputs:diffuseColor.connect = </modul_01/Materials/DVP_komod_2_Letta_Malta_modul_01/diffuseColor_texture.outputs:rgb> float inputs:roughness = 1 token outputs:surface } ... } ... def Scope "Geom" { def Mesh "DVP_komod_2_Letta_Malta_modul_01" { uniform bool doubleSided = 1 int[] faceVertexCounts = [3, 3,...] rel material:binding = </modul_01/Materials/DVP_komod_2_Letta_Malta_modul_01> point3f[] points = [(-0.34295827, 0.2727909, -0.1854379), (0.3439359, 0.27179047, -0.1864381), ...] normal3f[] primvars:normals = [(2.7196302e-7, 2.3841858e-7, 1), (1, -0.0000013478456, -1.2789769e-13), ...] texCoord2f[] primvars:st = [(0.6426813, 0.66292274), (0.8511379, 0.32041615), ...] uniform token subdivisionScheme = "none" quatf xformOp:orient = (1, 0, 0, 0) float3 xformOp:scale = (1, 1, 1) double3 xformOp:translate = (0, 0, 0) uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:orient", "xformOp:scale"] } ... }
Код
class ParseConstants: usda_desc = '#usda' up_axis = 'upAxis' meters_per_unit = 'metersPerUnit' scope = 'def Scope ' mesh = 'def Mesh ' xform = 'def Xform ' subset = 'def GeomSubset ' material = 'def Material ' shader = 'def Shader' # operations matrix4d = 'matrix4d' op_orient = 'quatf xformOp:orient' op_scale = 'float3 xformOp:scale' op_translate = 'double3 xformOp:translate' op_transform = 'matrix4d xformOp:transform' op_order = 'uniform token[] xformOpOrder' ops_map = { 'xformOp:translate': Operation.TRANSLATE, 'xformOp:orient': Operation.ORIENT, 'xformOp:scale': Operation.SCALE, 'xformOp:transform': Operation.TRANSFORM } double_sided = 'uniform bool doubleSided' face_vertex_count = 'int[] faceVertexCounts' face_vertex_index = 'int[] faceVertexIndices' material_binding = 'rel material:binding' points = 'point3f[] points' interpolation = 'interpolation' normals_indices = 'int[] primvars:normals:indices' normals = 'normal3f[] normals' normals_primvars = 'normal3f[] primvars:normals' int_primvars = 'int[] primvars:' subdivision_scheme = 'uniform token subdivisionScheme' extent = 'float3[] extent' text_coord = 'texCoord2f[] ' element_type = 'uniform token elementType = "face"' family_name = 'uniform token familyName = "materialBind"' indices = 'int[] indices' # materials token = 'token' metallic = 'metallic' roughness = 'roughness' emissive_color = 'emissiveColor' normal = 'normal' occlusion = 'occlusion' diffuse_color = 'diffuseColor' opacity = 'opacity' specular_color = 'specularColor' use_specular_workflow = 'useSpecularWorkflow' varname = 'varname' file = 'file' st = 'st' default = 'default' bias = 'bias' scale = 'scale' ior = 'ior' displacement = 'displacement' clearcoat = 'clearcoat' clearcoat_roughness = 'clearcoatRoughness' opacity_threshold = 'opacityThreshold' wrap_s = 'wrapS' wrap_t = 'wrapT' st_primvar_name = 'stPrimvarName' surface = 'surface' result = 'result' rgb = 'rgb' r = 'r' b = 'b' g = 'g' a = 'a' connect = '.connect' type_to_mat_type = { 'uniform token': InfoType.UNIFORM_TOKEN, 'float': InfoType.FLOAT, 'color3f': InfoType.COLOR3F, 'normal3f': InfoType.NORMAL3F, 'int': InfoType.INT, 'float2': InfoType.FLOAT2, 'float3': InfoType.FLOAT3, 'float4': InfoType.FLOAT4, 'token': InfoType.TOKEN, 'asset': InfoType.ASSET, } desc_to_direction = { 'inputs': InfoDirection.INPUT, 'outputs': InfoDirection.OUTPUT, } token_id_to_shader_type = { 'UsdPreviewSurface': ShaderTokenType.PREVIEW_SURFACE, 'UsdPrimvarReader_float2': ShaderTokenType.UV_COORDINATES, 'UsdUVTexture': ShaderTokenType.UV_TEXTURE, }Код
def read_material(data, counter): current_line = data[counter.line] name = read_name(current_line) shaders = [] outputs = [] while True: counter.inc() current_line = data[counter.line] if current_line.startswith(ParseConstants.token): outputs.append(read_mat_info(current_line)) elif current_line.startswith(ParseConstants.shader): shaders.append(read_shader(data, counter)) elif current_line == '{': continue elif current_line == '}': break else: raise_parse_error(current_line) return Material( name=name, shaders=shaders, outputs=outputs, )def remove_mat_dummy(): bpy.ops.object.select_all(action='DESELECT') # Снимаем выделение со всех объектов, чтобы ненароком не удалить ничего лишнего bpy.data.objects['materials_dummy'].select_set(True) # Выделяем нужный нам объект bpy.ops.object.delete() # Вызываем операцию удаленияКод
def import_xform(xform, materials, parent_matrix, parent=None): matrix = xform.matrix4d * parent_matrix if len(xform.meshes) == 0: if len(xform.children) == 0: return obj = create_empty_object(xform.name) add_to_default_collection(obj) if parent is not None: obj.parent = parent import_xforms(xform.children, materials, matrix, obj) else: # todo read uvs for mesh in xform.meshes: obj = create_mesh_object(mesh.name) add_to_default_collection(obj) add_mesh_data(obj, mesh, materials, matrix) if parent is not None: obj.parent = parent import_xforms(xform.children, materials, matrix, obj)Импорт геометрии
Код
def add_mesh_data(obj, data, materials, parent_matrix): # Для каждой текстуры создаём слот для uv-координат for name, coordinates in data.text_coordinates.items(): obj.data.uv_layers.new(name=name) # Считаем матрицу трансформации matrix = data.transform * parent_matrix counts = data.face_vertex_count indices = data.face_vertex_indices verts = data.points faces = [] smooth = [] index = 0 for count in counts: # Записываем полигоны. По сути полигоны состоят из двух массивов: массив координат точек и массив, описывающий соединения этих точек. Здесь описываем соединения. faces.append(tuple([indices[index + i] for i in range(count)])) if len(normals) > 0: smooth.append(len(set(normals[index + i] for i in range(count))) > 1) else: smooth.append(True) index += count bm = bmesh.new() bm.from_mesh(obj.data) # Сохраняем вершины v_base = len(bm.verts) for vert in verts: bm.verts.new(vert) bm.verts.ensure_lookup_table() # Применяем материалы main_mat_index = 0 if data.material is not None: main_mat_index = add_material_to_obj(obj, data.material, materials) mat_indices = [main_mat_index for _ in range(len(faces))] # Некоторые полигоны могут отличаться от основного материала, здесь это учитываем for s in data.subsets: if s.indices is not None and s.material is not None: index = add_material_to_obj(obj, s.material, materials) for i in s.indices: mat_indices[i] = index # Add the Faces for i, face in enumerate(faces): if len(face) == len(set(face)): f = bm.faces.new((bm.verts[i + v_base] for i in face)) f.material_index = mat_indices[i] f.smooth = smooth[i] # Сохраняем uv-координаты for name, coordinates in data.text_coordinates.items(): uv_indices = data.indices[name] if name in data.indices else data.face_vertex_indices mapped_uv = [coordinates[i] for i in uv_indices] obj.data.uv_layers.new(name=name) uv_index = bm.loops.layers.uv[name] index = 0 for f in bm.faces[-len(faces):]: for i, l in enumerate(f.loops): if index + i < len(mapped_uv): l[uv_index].uv = mapped_uv[index + i] else: l[uv_index].uv = (0.0, 0.0) index += len(f.loops) bm.to_mesh(obj.data) bm.free() # Применяем матрицу трансформации obj.data.transform(matrix=tuple(x for x in matrix.tolist())) obj.data.update() mat_indices.clear()Импорт материалов

Отрывки из кода
def create_material(material, ext_dir): mat = bpy.data.materials.new(material.name) # Создаём материал mat.use_nodes = True # Используем систему нодов for shader in material.shaders: import_shader(mat, material.shaders, shader, ext_dir) # Импортируем каждый шейдер return mat def import_shader(blend_material, shaders, shader, ext_dir): bsdf_node = get_node_by_type(blend_material, 'BSDF_PRINCIPLED') # Шейдеры бывают трёх типов: # PREVIEW_SURFACE — число или вектор; # UV_TEXTURE — текстура или картинка; # UV_COORDINATES — uv-координаты. if shader.token_id == ShaderTokenType.PREVIEW_SURFACE: import_base_preview_surface(blend_material, shaders, shader, bsdf_node, ext_dir) elif shader.token_id == ShaderTokenType.UV_TEXTURE: import_base_uv_texture(blend_material, shader, shaders, ext_dir) elif shader.token_id == ShaderTokenType.UV_COORDINATES: import_uv_coordinates(blend_material, shader) def import_base_preview_surface(blend_material, shaders, shader, bsdf_node, ext_dir): bsdf_node.name = shader.name set_node_input( blend_material=blend_material, shaders=shaders, node=bsdf_node, input_desc=shader.diffuse_color, desc='Color', node_input_name='Base Color', input_type=InfoType.COLOR3F, ext_dir=ext_dir, ) set_node_input( blend_material=blend_material, shaders=shaders, node=bsdf_node, input_desc=shader.specular, desc='Specular', node_input_name='Specular', input_type=InfoType.COLOR3F, ext_dir=ext_dir, ) # Далее проброс данных для других примитивных данных #... def set_node_input(blend_material, shaders, node, input_desc, desc, node_input_name, input_type, ext_dir): if input_desc is not None: if input_desc.info_type != input_type: raise_import_error('%s type is %s instead of %s' % (desc, input_desc.info_type, input_type)) # Если в описании есть connection — это, вероятно, текстура (в чём была разница между текстурой и UV-текстурой? уже не помню). Соответственно, нужно создать ноду текстуры и подключить вывод ноды текстуры с правильным вводом текущей ноды. if input_desc.is_connection: set_shader_input_texture( blend_material=blend_material, shaders=shaders, node=node, input_name=node_input_name, input_desc=input_desc, ext_dir=ext_dir, ) else: # Для цвета, числа или вектора всё проще: можно задать значение в самом вводе ноды, не заморачиваясь с созданием дополнительной if input_desc.info_type == InfoType.COLOR3F: set_shader_input_value(node, node_input_name, ast.literal_eval(input_desc.value) + (1,)) elif input_desc.info_type in (InfoType.FLOAT, InfoType.NORMAL3F): set_shader_input_value(node, node_input_name, ast.literal_eval(input_desc.value)) else: # При получении неизвестного типа данных падаем и разбираемся, какой ещё тип данных мы пропустили raise_import_error('Unsupported data type')Что в итоге
Код
#usda 1.0 ( customLayerData = { string creator = "Yandex.Market. All rights reserved 2021" } defaultPrim = "Box" upAxis = "Y" metersPerUnit = 1 ) def Xform "Box" ( assetInfo = { string name = "Box" } kind = "component" ) { def Scope "Geom" { def Xform "Root" { def Mesh "Cube" { uniform bool doubleSided = 1 int[] faceVertexCounts = [4, 4, 4, 4, 4, 4] int[] faceVertexIndices = [0, 1, 3, 2, 2, 3, 7, 6, 6, 7, 5, 4, 4, 5, 1, 0, 2, 6, 4, 0, 7, 3, 1, 5] point3f[] points = [(-width, 0, 0), (-width, 0, length), (-width, height, 0), (-width, height, length), (width, 0, 0), (width, 0, length), (width, height, 0), (width, height, length)] normal3f[] primvars:normals = [(-1, 0, 0), (0, 1, 0), (1, 0, 0), (0, 0, 0), (0, 0, -1), (0, 0, 1)] ( interpolation = "faceVarying" ) int[] primvars:normals:indices = [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5] uniform token subdivisionScheme = "none" rel material:binding = </Box/Materials/BoxMaterial> } def Mesh "Plane1" { uniform bool doubleSided = 0 float3[] extent = [(-0.5, 0, 0), (0.5, 1, 0.0001)] int[] faceVertexCounts = [3, 3] int[] faceVertexIndices = [0, 1, 3, 0, 3, 2] rel material:binding = </Box/Materials/LogoMaterial> normal3f[] normals = [(0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1)] point3f[] points = [(-plane1Left, plane1Bot, plane1Depth), (plane1Right, plane1Bot, plane1Depth), (-plane1Left, plane1Top, plane1Depth), (plane1Right, plane1Top, plane1Depth)] texCoord2f[] primvars:st = [(0, 0), (1, 0), (0, 1), (1, 1)] ( interpolation = "vertex" ) uniform token subdivisionScheme = "none" } def Mesh "Plane2" { uniform bool doubleSided = 0 float3[] extent = [(-0.5, 0, 0), (0.5, 1, 0.0001)] int[] faceVertexCounts = [3, 3] int[] faceVertexIndices = [0, 1, 3, 0, 3, 2] rel material:binding = </Box/Materials/MLetterMaterial> normal3f[] normals = [(0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1)] point3f[] points = [(-plane2Left, plane2Bot, plane2Depth), (plane2Right, plane2Bot, plane2Depth), (-plane2Left, plane2Top, plane2Depth), (plane2Right, plane2Top, plane2Depth)] texCoord2f[] primvars:st = [(0, 0), (1, 0), (0, 1), (1, 1)] ( interpolation = "vertex" ) uniform token subdivisionScheme = "none" } def Mesh "Plane3" { uniform bool doubleSided = 0 float3[] extent = [(-0.5, 0, 0), (0.5, 1, 0)] int[] faceVertexCounts = [3, 3] int[] faceVertexIndices = [0, 1, 3, 0, 3, 2] rel material:binding = </Box/Materials/MTailMaterial> normal3f[] normals = [(0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1)] point3f[] points = [(plane3Width, plane3Bot, plane3DepthClose), (plane3Width, plane3Bot, plane3DepthFar), (plane3Width, plane3Top, plane3DepthClose), (plane3Width, plane3Top, plane3DepthFar)] texCoord2f[] primvars:st = [(0, 0), (tex3, 0), (0, 1), (tex3, 1)] ( interpolation = "vertex" ) uniform token subdivisionScheme = "none" } def Mesh "Plane4" { uniform bool doubleSided = 0 float3[] extent = [(-0.5, 0, 0), (0.5, 1, 0)] int[] faceVertexCounts = [3, 3] int[] faceVertexIndices = [0, 1, 3, 0, 3, 2] rel material:binding = </Box/Materials/PromoMaterial> normal3f[] normals = [(0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1)] point3f[] points = [(-plane4Left, plane4Bot, plane4Depth), (plane4Right, plane4Bot, plane4Depth), (-plane4Left, plane4Top, plane4Depth), (plane4Right, plane4Top, plane4Depth)] texCoord2f[] primvars:st = [(0, 0), (1, 0), (0, 1), (1, 1)] ( interpolation = "vertex" ) uniform token subdivisionScheme = "none" } } } def Scope "Materials" { def Material "MTailMaterial" { token outputs:surface.connect = </Box/Materials/MTailMaterial/surfaceShader.outputs:surface> def Shader "surfaceShader" { uniform token info:id = "UsdPreviewSurface" color3f inputs:diffuseColor.connect = </Box/Materials/MTailMaterial/diffuseColor_opacity_texture.outputs:rgb> color3f inputs:emissiveColor = (0, 0, 0) float inputs:metallic = 0 normal3f inputs:normal = (0, 0, 1) float inputs:occlusion = 1 float inputs:opacity.connect = </Box/Materials/MTailMaterial/diffuseColor_opacity_texture.outputs:a> float inputs:roughness = 0.5 token outputs:surface float inputs:opacityThreshold = 0.5 } def Shader "st_texCoordReader" { uniform token info:id = "UsdPrimvarReader_float2" token inputs:varname = "st" float2 outputs:result } def Shader "diffuseColor_opacity_texture" { uniform token info:id = "UsdUVTexture" asset inputs:file = @textures/m_tail.png@ float2 inputs:st.connect = </Box/Materials/MTailMaterial/st_texCoordReader.outputs:result> token inputs:wrapS = "clamp" token inputs:wrapT = "clamp" float3 outputs:rgb float outputs:a } } def Material "MLetterMaterial" { token outputs:surface.connect = </Box/Materials/MLetterMaterial/surfaceShader.outputs:surface> def Shader "surfaceShader" { uniform token info:id = "UsdPreviewSurface" color3f inputs:diffuseColor.connect = </Box/Materials/MLetterMaterial/diffuseColor_opacity_texture.outputs:rgb> color3f inputs:emissiveColor = (0, 0, 0) float inputs:metallic = 0 normal3f inputs:normal = (0, 0, 1) float inputs:occlusion = 1 float inputs:opacity.connect = </Box/Materials/MLetterMaterial/diffuseColor_opacity_texture.outputs:a> float inputs:roughness = 1.0 token outputs:surface float inputs:opacityThreshold = 0.5 } def Shader "st_texCoordReader" { uniform token info:id = "UsdPrimvarReader_float2" token inputs:varname = "st" float2 outputs:result } def Shader "diffuseColor_opacity_texture" { uniform token info:id = "UsdUVTexture" asset inputs:file = @textures/m_letter.png@ float2 inputs:st.connect = </Box/Materials/MLetterMaterial/st_texCoordReader.outputs:result> token inputs:wrapS = "clamp" token inputs:wrapT = "clamp" float3 outputs:rgb float outputs:a } } def Material "LogoMaterial" { token outputs:surface.connect = </Box/Materials/LogoMaterial/surfaceShader.outputs:surface> def Shader "surfaceShader" { uniform token info:id = "UsdPreviewSurface" color3f inputs:diffuseColor.connect = </Box/Materials/LogoMaterial/diffuseColor_opacity_texture.outputs:rgb> color3f inputs:emissiveColor = (0, 0, 0) float inputs:metallic = 0 normal3f inputs:normal = (1, 1, 1) float inputs:opacity.connect = </Box/Materials/LogoMaterial/diffuseColor_opacity_texture.outputs:a> float inputs:roughness = 1.0 token outputs:surface float inputs:opacityThreshold = 0.5 } def Shader "st_texCoordReader" { uniform token info:id = "UsdPrimvarReader_float2" token inputs:varname = "st" float2 outputs:result } def Shader "diffuseColor_opacity_texture" { uniform token info:id = "UsdUVTexture" asset inputs:file = @textures/plane1Name@ float2 inputs:st.connect = </Box/Materials/LogoMaterial/st_texCoordReader.outputs:result> token inputs:wrapS = "clamp" token inputs:wrapT = "clamp" float3 outputs:rgb float outputs:a } } def Material "PromoMaterial" { token outputs:surface.connect = </Box/Materials/PromoMaterial/surfaceShader.outputs:surface> def Shader "surfaceShader" { uniform token info:id = "UsdPreviewSurface" color3f inputs:diffuseColor.connect = </Box/Materials/PromoMaterial/diffuseColor_opacity_texture.outputs:rgb> color3f inputs:emissiveColor = (0, 0, 0) float inputs:metallic = 0 normal3f inputs:normal = (0, 0, 1) float inputs:occlusion = 1 float inputs:opacity.connect = </Box/Materials/PromoMaterial/diffuseColor_opacity_texture.outputs:a> float inputs:roughness = 1.0 token outputs:surface float inputs:opacityThreshold = 0.5 } def Shader "st_texCoordReader" { uniform token info:id = "UsdPrimvarReader_float2" token inputs:varname = "st" float2 outputs:result } def Shader "diffuseColor_opacity_texture" { uniform token info:id = "UsdUVTexture" asset inputs:file = @textures/promo.png@ float2 inputs:st.connect = </Box/Materials/PromoMaterial/st_texCoordReader.outputs:result> token inputs:wrapS = "clamp" token inputs:wrapT = "clamp" float3 outputs:rgb float outputs:a } } def Material "BoxMaterial" { token outputs:surface.connect = </Box/Materials/BoxMaterial/pbr.outputs:surface> def Shader "pbr" { token info:id = "UsdPreviewSurface" color3f inputs:diffuseColor = (boxRed, boxGreen, boxBlue) color3f inputs:emissiveColor = (0.01, 0.01, 0.01) float inputs:metallic = 0.4 normal3f inputs:normal = (1, 1, 1) float inputs:occlusion = 1 float inputs:opacity = boxOpacity float inputs:roughness = 0.3 token outputs:surface float inputs:opacityThreshold = 0.2 } } } }

Заключение

ссылка на оригинал статьи https://habr.com/ru/companies/yandex/articles/743496/
Добавить комментарий