Как открыть файл texture2d

Обновлено: 05.07.2024

Импортирование 2D-ресурсов в Unity

В этом уроке вы научитесь импортировать 2D-ассеты в Юнити, чтобы создать спрайты как из одного изображения, так и из таблицы спрайтов (атлас спрайта), содержащей несколько ассетов, которые вы настроите в редакторе спрайтов.

1. Подготовка 2D-ресурсов изображений для импортирования в Юнити

Импортирование двумерных ассетов, известных как спрайты, в Unity относительно простой и легкий процесс.

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

Называйте ассеты так, чтобы их назначение было понятным. Такое название, как Title_Screen_Background упростит распознавание, чем, например, названия файлов New Image 27 или Untitled3 . Если спрайт является частью анимации, хорошей практикой будет дать каждому кадру в этой анимации такое же имя, пронумеровав каждый так, чтобы они отображались по порядку в окне Project. Например, может быть двенадцать спрайтов с именами от Player_Barbarian_Walk_North_00 до Player_Barbarian_Walk_North_11 .

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

Большинство инструментов анимации могут экспортировать таблицы спрайтов, а их тщательная организация и присвоение имен могут автоматизировать настройку спрайтов в Unity.

2. Импортирование одного спрайта

При импортировании одного спрайта Юнити назовет этот спрайт именем файла изображения. Например, Hamburger.jpg станет Hamburger. Создайте или откройте 2D-проект в Unity и поместите спрайт (Рисунок 1) в окно Project, или где-нибудь внутри Assets используя проводник Windows (или Finder на Mac).

Рисунок 1: Пример спрайта, увеличен для наглядности

Нажмите на только что импортированный спрайт в окне Project, чтобы открыть инспектор для настроек импорта (Рисунок 2).

Настройки импортирования спрайтов в Unity

Рисунок 2: Параметры импорта по умолчанию для спрайтов. Обязательно примените изменения, которые делаете для настроек, нажав на Apply в правом нижнем углу. Если вы забудете это сделать, то Unity предложит применить изменения.

Для большинства задач важны следующие параметры:

Pixels Per Unit: Определяет сколько Unity единиц в размере спрайта. Обычно это число будет одинаковым для всех спрайтов в проекте. В большинстве случаев вы будете выбирать это число перед созданием ассетов, так как этот параметр указывает относительный масштаб спрайтов относительно друг друга, мира, и ортогональной камеры.

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

Этот параметр полезен, если вы создаете игровой мир на основе тайлов; параметр Pixels Per Unit равный размеру одной единицы мира облегчает быстрое построение миров, удерживая клавишу Ctrl (Command на Mac), чтобы двигать их на одну единицу за раз.

Pivot: опору можно рассматривать как точку привязки спрайта. Чтобы сталактит свисал с потолка нужно установить его pivot на значение Top, а его позицию установить как у потолка. Для любого персонажа или элемента декора, который должен стоять на земле, обычно устанавливается значение Bottom. Существует 10 вариантов поворота: любое из трех горизонтальных положений (центр и края) в сочетании с любым из трех вертикальных положений или настраиваемый поворот, указанный в диапазоне 0–1 в обоих измерениях. Центр установлен по умолчанию и подходит для многих целей.

3. Импортирование и настройка нескольких спрайтов в одно изображение при помощи Sprite Editor

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

Создайте или скачайте изображение с несколькими спрайтами и импортируйте его в проект (Рисунок 3).

Настройки для параметра Type следующие:

Automatic: Редактор спрайтов определяет области, которые окружены прозрачностью, и назначает их как спрайты.

Grid By Cell Size: Сетка нарезается равномерно с указанием пользователем размера каждой ячейки. Это наиболее распространенный параметр при построении проектов на основе тайлов, или проекты, в которых большая часть или вся область спрайтов на листе одинакового размера (например, значки или другие унифицированные элементы интерфейса, или строительные блоки игрового мира).

Grid By Cell Count: Сетка нарезается равномерно с указанием пользователем количества строк и столбцов спрайтов. Можно использовать этот параметр вместо Grid By Cell Size, если таблица спрайтов была сгенерирована программой, в которой вы уже определили количество строк и столбцов.

Cell Size: определяется либо Pixel Size, либо количеством Column и Row, в зависимости от того, какой неавтоматический параметр вы выбираете.

Offset: позволяет вам отметить начальный (слева сверху) угол в таблице спрайтов, и бывает полезным в ситуациях, когда спрайты разделены сеткой.

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

Keep Empty Rects: определяет спрайт даже если в нем нет изображения в автоматически сгенерированной области, при нарезке по размеру ячейки или количеству.

Pivot: определяет опорную точку спрайта. Например, спрайт с параметром Pivot, у которого установлено значение Center (по умолчанию), будет центрировано вокруг исходной точки, если для его позиции устанавливаются значения 0, 0, 0.

Вот все, что касается импортирования 2D-ассетов в Unity. Выбранные вами настройки будут во многом зависеть от вашего проекта, и с опытом вы найдете то, что лучше всего вам подходит.

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


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

Раньше на проекте War Robots у нас был устоявшийся и вполне рабочий пайплайн по импорту текстурных массивов, на выходе которого мы получали массивы в конечном формате (ASTC, ETC2), отлично удовлетворяющие нашим требованиям для мобильных платформ. С этим все у нас было хорошо — до поры. Проблемы начались тогда, когда возникла необходимость релиза на ПК.

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

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

Текстурные массивы (Texture2DArray в Unity) — это ассет-файлы, в которых записано несколько бинарных представлений текстур, уже пожатых в необходимом для конечного девайса формате. Такие массивы позволяют объединить несколько текстур в один ресурс, который можно единожды привязать к графичеcкому пайплайну (binding — в терминах графических API), и затем множество вызовов отрисовки смогут использовать этот ресурс без необходимости перепривязки между вызовами. Это снижает нагрузку на ЦПУ, поскольку уменьшает количество обращений к графическому API, что в итоге положительно сказывается на производительности. В Unity текстурные массивы позволяют использовать один материал для множества объектов, а значит — при рендере будет работать batching, и их отрисовка будет более эффективной.

Текстурные массивы можно рассматривать как альтернативу текстурным атласам, лишенной их недостатков. Так, при тайлинге использование текстурного пространства становится гораздо эффективнее. Поскольку не нужно добавлять «защитные» области, с помощью закраски (dilation) или дополнительными проверками в коде шейдера в случае с текстурными массивами выборкой данных полноценно занимается специализированный блок — sampler. В результате более эффективно используются вычислительные ресурсы и текстурный кэш GPU. Также для текстурных массивов можно использовать привычный механизм генерации mip-уровней, не боясь появления артефактов в виде швов. Однако, в отличие от атласов, все текстуры в массиве обязательно должны быть одинакового разрешения и формата.

Что удобнее в вашем конкретном случае, решение уже за вами — в нашем, например, использование текстурных массивов было очевидным.


Пример текстурного атласа и текстурного массива

В документации написано, что в Unity2019.4 (на которой сейчас разрабатывается War Robots) не существует пайплайна для импорта текстурных массивов, и предлагается создавать массивы либо прямо в рантайме, либо в эдиторных скриптах с последующим сохранением их, используя AssetDatabase.CreateAsset.

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

Исходный флоу для текстурных массивов на War Robots

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


Мы используем несколько пресетов качества (HD и LD), и для каждого из них можно выбирать основные настройки будущего текстурного массива: разрешение и компрессию.

Самый первый алгоритм создания текстурных массивов выглядел примерно так. Первым шагом выполнялся реимпорт исходной текстуры, содержащей несжатые данные.

Зачем вообще там нужен реимпорт текстур?

Использование любого сжатого формата должно производиться именно из оригинальной исходной текстуры. Исходная текстура — это не одно и то же, что разжатая. Проблема в том, что все без исключения форматы с компрессией являются сжатием с потерями (losy compression), и на самом деле так или иначе «уродуют» текстуру в зависимости от выбранного алгоритма сжатия. Таким образом, из любого сжатого формата восстановить исходную текстуру уже не получится: разжатая текстура будет иметь ровно те же артефакты, что и сжатая. Именно поэтому категорически нельзя делать что-то вроде «ETC2 → ASTC → DXT», поскольку на выходе будет нечто, вобравшее в себя артефакты компрессий всех трех форматов. Всегда нужно работать только с исходной текстурой и никак иначе.

Важно понимать почему обязательно следует использовать исходную текстуру без сжатия: слева картинка сжата в ASTC, а справа — каскад ETC → DXT ​​→ ASTC. Как можно заметить, справа видны артефакты после применения последовательности компрессий.


Далее выполнялась поканальная перепаковка текстур для каждого слоя. В результате из таких текстур в конфигурации для слоя 0…


. мы получаем две поканально перепакованные текстуры. Первая — AS, содержит Albedo + Smoothness, вторая — MNAO, содержит Metalness + Normals + Ambient Occlusion:



Эти текстуры не сохраняются в проекте и попадают в разные текстурные массивы (AS/MNAO). По каждому слою (из рисунка с конфигом) получаем две поканально перепакованные текстуры, каждая из которых является будущим слоем для своего текстурного массива.

Далее применяем компрессию, которая была указана в конфиге, создаем из этих текстур объект Texture2DArray (в данном примере мы создаем сразу два Texture2DArray — для AS и для MNAO) и сохраняем каждый как ассет, используя AssetDatabase.CreateAsset, как нам и советовала документация Unity. В итоге получается так называемый «запеченный» текстурный массив, над которым можно произвести только две операции: назначить его в материал или удалить из проекта. Изменить размер и/или тип компрессии у него уже невозможно.

Всё это отлично работало для мобильных платформ — ровно до момента релиза на ПК-платформы. Проблема заключается в том, что компрессия ASTC не поддерживается на standalone-платформах, в частности — под Windows. В результате почти для каждого релиза требовалось создание второго набора ассетов, но уже в формате DXT5/BC7, поддерживаемого standalone-платформами. Такое ветвление довольно сильно усложняло процесс подготовки контента, сборки билдов и, что хуже всего, требовало постоянной рутинной ручной работы в довольно большом объеме. По факту требовалась возможность создания всего одно ассета вместо нескольких (под мобильные и ПК платформы), в котором под каждую платформу можно указать настройки разрешения и компрессии по аналогии с обычными текстурами. При этом сама методика создания массивов и наполнения слоев должна была остаться прежней.

Тогда на помощь пришел ScriptedImporter из Unity. Он позволяет для любого типа файлов написать свой алгоритм импорта, чтобы Unity понимал, как работать с этим типом ассетов.

При реализации кастомного импортера было несколько требований:

исправляем исходный алгоритм создания массива только при крайней необходимости, а лучше вообще его не трогать;

результирующий ассет (текстурный массив) не должен зависеть от исходных текстур;

результирующий ассет (текстурный массив) должен попадать в кэш акселератора.

Первый вариант импортера: так делать нельзя

В одном из первых вариантов реализации предлагалось внутри каждого ассета массива хранить ссылку на нужный конфиг, а также название целевого качества и типа массива (AS/MNAO). Выглядело это примерно вот так:


То есть, это был обычный json, из которого можно было сделать полноценный Unity-ассет.

При первых попытках написать импортер сразу вылезла проблема из-за реимпорта текстур: мы не можем внутри одного импорта вызвать другой импорт. Точнее, можем, но вложенный вызов импорта по факту начнет работать только тогда, когда мы выйдем из вызывающего импорта.

На основе этой идеи оперативно родилось это чудовище — алгоритм, который мы, к счастью, не применили:


Это была «трехходовка», описание прилагается:

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

Читаем данные внутри ассета — в нашем случае это был просто json, в котором указывался Guid конфига, качество и тип целевых текстур. Загружаем по Guid конфиг, в котором присутствуют ссылки на исходные текстуры.

Для каждой текстуры проверяем, есть ли ее дубликат во временной директории.

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

Выходим из импорта — только после этого по факту начнут копироваться текстуры.

Так как в шаге 4 мы добавили зависимости от текстур и инициировали их копирование, то после копирования Unity снова вызовет импорт нашего ассета (шаг 1). Но на этот раз все текстуры уже лежат во временной папке, так что мы доходим до шага 6.

Проверяем настройки временных текстур — у них должен быть одинаковый целевой размер и формат — кроме того, текстуры должны быть isReadable и т. д.

Если по каким-то причинам какая-либо временная текстура не удовлетворяет этим требованиям, мы правим настройки ее импортера и инициируем ее реимпорт.

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

Так как в шаге 7 мы сменили настройки и инициировали реимпорт текстур (помним, что в шаге 4 мы добавили зависимость от этих текстур), то после их реимпорта Unity снова вызовет импорт нашего текстурного массива, и мы снова попадем в шаг 1. Но в этот раз текстуры уже лежат во временной папке, и все их параметры соответствуют требованиям нашего алгоритма для создания текстурного массива. Поэтому на этот раз мы подходим к шагу 9.

Применяем целевую компрессию, так как наши копии текстур в данный момент без сжатия (RGBA).

По каждому слою текстур создаем поканально перепакованные текстуры.

Из поканально перепакованных текстур создаем объект типа Texture2DArray.

Назначаем этот объект в контексте импорта как основной — именно он затем подгрузится, если мы вызовем AssetDatabase.LoadAssetAtPath<Texture2DArray>.

Удаляем копии текстур из временной папки.

Выходим из импорта.

Мы реализовали этот алгоритм, но у него было два огромных минуса:

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

Из-за удаления копий текстур (шаг 13) результирующий артефакт не попадал в кэш акселератора при сборке на TeamCity. Конечно, можно было написать очередной BuildStep для нашего кастомного билд-пайплайна, но к тому моменту уже была уверенность, что это будет совсем перебор, и нужно менять подход к созданию файла ассета и его импорту.

Второй вариант импортера: не идеальный, но рабочий

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


В процессе сериализации текстуру в виде байтового массива мы получаем через Texture2D.GetRawTextureData() , а во время десериализации восстанавливаем текстуру через Texture2D.LoadRawTextureData() .

То есть, еще раз: в новом варианте ассета мы храним не json, а bytearray своего формата, где содержатся все необходимые данные (и перепакованные текстуры в том числе) для успешного импорта нашего текстурного массива.

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


И эта схема оказалась действительно рабочей:

мы только частично поменяли флоу создания текстурного массива;

мы не поменяли алгоритм перепаковки каналов исходных текстур;

мы не добавили зависимости от исходных текстур;

наш результирующий ассет успешно попадает в кэш акселератора.

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


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

Сравнивая первый и второй варианты форматов файлов наших ассетов (обычный json vs бинарник собственного сочинения), мы получали (забудем пока про акселератор) совершенно одинаковые текстурные массивы. Всё это из-за того, что Unity не работает с содержимым файлов ассетов напрямую — он работает с результатом импорта файлов этих ассетов, которые как раз лежат в Library.

Бонус. Ненормальная работа со Scripted Importer

А теперь для закрепления темы кастомных импортеров давайте немного пошалим.

Для начала найдем в интернете картинку и просто сохраним ее URL в текстовом файле с именем, например, “pixonic.wwwtexture”, а затем добавим этот файл в проект. Если открыть этот «ассет» в текстовом редакторе, мы просто увидим нашу ссылку:


На данный момент Unity понятия не имеет, как обращаться с файлами, у которых расширение “wwwtexture”, поэтому мы видим следующую картину:


Давайте объясним Unity, что мы от нее хотим — для этого напишем вот такой маленький импортер:

Тут все должно быть максимально ясно: читаем файл ассета и получаем URL, затем скачиваем эту картинку, получаем объект Texture2D, после чего просто назначаем этот объект как основной артефакт в данном ассете. Et voila!

Картинка скачалась, случился успешный импорт нашего ассета, и теперь в инспекторе это выглядит так:


Сразу замечу: скачивать надо именно синхронно — иначе чуда не произойдет.

Теперь мы можем назначить этот ассет в материал, и все будет работать:


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

Заключение

Текстурные массивы выгодно отличаются от текстурных атласов, позволяя эффективнее использовать ресурсы GPU и значительно сэкономить память на мобильных девайсах. Однако их импорт Unity не поддерживается и требует особого внимания к методу сжатия, если речь идет о работе над билдами под разные платформы.

Unity ScriptedImporter — очень мощный инструмент, который позволяет наладить работу с форматами файлов, которые не поддерживаются Unity «из коробки». С его помощью мы:

реализовали пайплайн для импорта файлов текстурных массивов нашего формата, слегка поправив существующий пайплайн создания контента;

избавились от нужды поддержки дополнительных релизных веток (mobile/standalone) и обязательных периодических ручных трудозатрат;

получили полный карт-бланш для дальнейшей поддержки и модификации процесса импорта таких ассетов.

Автор статьи — Александр Скотников, Automation Developer в Pixonic

Благодарности

Хочу также поблагодарить своих коллег из Pixonic: Романа Вишнякова, Павла Кирсанова, Александра Агапкина, Александра Богомольца и Дмитрия Четверикова — они очень помогли в процессе работы над импортером текстурных массивов, а также оказали мощнейший саппорт в описании технической части по графике и вообще в создании данной публикации.

Читайте также: