Pull to refresh

Comments 40

Относительно текстур, Unity хранит их в формате позволяющем использовать сразу в OpenGL без перекодирования.
Для андроида и текстур с альфой это RGBA32 или RGBA16, для текстур без альфы (jpg) это ETC. Загружая текстуру через Texture2D.LoadImage() для jpg вы лишитесь сжатия, сильно увеличив потребление памяти.
К сожалению, сжатие в наших условиях приводит к видимым артефактам на картинках. Кроме того, у нас нет POT-текстур, которые поддаются сжатию по ETC.
Важнее размера — возможность работы с SD.
Мобильный интернет недорог, а вот место в памяти пользователю дорого. Мне с моими 16Gb на Samsung galaxy tab 8.4 приходится регулярно оценивать отношение полезности программ к весу и удалять все худшее. Программы, которые можно перенести на SD, сразу улетают на SD. Но если что-то работает только во внутренней памяти, и это не жизненно необходимая программа — будет снесена при следующей чистке обязательно.
Статья, как делать не надо.

поскольку там картинки представляются в BMP
.
Это не так и формат зависит от того, что было выбрано.
а вот оставлять Automatic Truecolor не рекомендуется, потому что (как показал опыт) он может восприняться системой как RGBA32.

Опыт показал наплевательское отношение к подготовке контента — юнити переводит текстуру в RGBA16 / RGBA32 только при наличии альфа-канала и никогда при его отсутствии. Т.е. нужно бить по рукам тем, кто готовит контент и пропускает такой треш в проект. Так же — всегда оставлять для текстур без альфы автокомпрессию — на android они автоматически сожмутся в ETC1, что просто несоизмеримо по размерам с RGB24 с минимальным ухудшением качества. На ios это будет PVRTC4.
Снизить качество изображений в настройках импорта по схеме: 32 bit -> 24 bit -> 16 bit, наблюдая за тем, чтобы уровень качества изображения оставался в пределах допустимого.

Никогда так не делать. Для непрозрачных текстур всегда автокомпрессия, для прозрачных — выбирать RGBA16 или RGBA32, в зависимости от наличия градиентов. Ну и отключать мипмапы для текстур гуя.
Ограничить maxTextureSize для изображений, для которых это возможно с сохранением приемлемого качества.

Опять же — наплевательское отношение к контенту. Пережимание текстур в юнити убивает их качество, правильнее делать даунскейл до нужного размера в том же фотошопе с правильным фильтром. Для gles2 максимальный размер текстур — 2048х2048, как бы ни хотелось делать больше — нельзя.
Тщательно вручную удалять неиспользуемые ассеты в папках Resources

Принять за правило: папка Resources — должна быть пуста в принципе. Это не хранилище контента для всего проекта. Если нужно что-то инстанцировать по имени и не обойтись явным указанием префаба — только тогда туда складывать нужный ассет.
Для изображений JPG: сменить расширение файла на bytes и он превратится в TextAsset. После чего воспользоваться функцией Texture2D.LoadImage() для загрузки картинки.

Никогда так не делать — LoadImage грузит текстуру исключительно в RGB24 / RGA32 ориентируясь на наличие альфа-канала в ней с соответствующим потреблением памяти. Texture2D.Compress не работает на мобильных платформах — выпилен юнитеками сознательно.
По возможности использовать MP3, а не WAV.

Стараться не играть больше 4 звуков одновременно и не более 1 длинного звука, например, фоновой мелодии. Все держать в wav — это позволит выбирать метод компрессии и битрейт прямо в юнити + позволит убрать обрезание последней полсекунды музыки в mp3 — есть такая особенность в юнити, хорошо видна на зацикленных мелодиях. Так же на ios можно будет поставить галочку аппаратной декомпрессии 1 аудиопотока. Все короткие звуки (sfx-ы взрывов, выстрелов и т.п.) держать прямо в wav — декомпрессия хорошо жрет процессор и вызывает просадки фпс на бюджетных устройствах. Ну и разумеется — все только моно, 22кгц — sfx, 32кгц — музыка, битрейт для всего достаточен в 96кбит. Для голоса параметры нужно подбирать отдельно.
Потому, получается, что официальная документация Unity в этом месте не совсем релевантна.

Потому что System будет использована в любом случае, различается только в каком виде и будет ли отрезан неиспользуемый остаток (microkernel, strip code). С приходом il2cpp это будет неактуально.
А вот использования других (необязательных) dll желательно избегать во имя снижения размера сборки.

Размер dll-ек обычно копеечный по сравнению с текстурами и аудио-данными. Конкретно что сколько занимает можно посмотреть в Editor log после билда — там будет все расписано.
Просьба рассматривать данный материал как более-менее систематизированные результаты исследования, а не как универсальное руководство к действию.

Можно приписать в заголовок «вредные советы» — тогда это можно использовать по назначению.
Приятно читать именно такие комментарии, надеюсь вы и статьи продолжите писать.
Размер dll-ек обычно копеечный по сравнению с текстурами и аудио-данными

System.XML весит два метра, в то время как можно воспользоваться JSON с действительно копеечным размером )
Но это скорее исключение, зато часто встречающееся.
Не видел живого xml уже лет 5, исключительно json. :) xml продолжают крутить залетные ынтерпрайзщики, которые решили попробовать геймдев.
Что часто и происходит — у нас в компании единицы разработчиков, которые до этого не были аспнетчиками — фронтендщиками.
И я один из них (из единиц, в смысле))
А можно как-то парсить JSON в Unity без всяких MiniJSON и т.п.?
Нет, штатных средств нет, ибо mono 2.6.3 (FW3.5 с ограничениями и default-ами из FW4). Штатный JSON появился вроде с FW4.5. Чем не нравятся всякие MiniJSON-ы? Автомапинг на типы с переименованием — очень вкусная и приятная вещь.
У нас JSON.Net, платный, зато клёвый )
Добрый день! Большое спасибо за интересные советы и дельную критику! Анализирую предложенное!

И заодно попробую объяснить, почему мы используем некоторые «вредные» методики и, судя по всему, вынуждены это делать и в дальнейшем.

для текстур без альфы автокомпрессию — на android они автоматически сожмутся в ETC1,

1) Для ETC1 картинка должна быть NPOT. У нас так просто невозможно. Если заимпотрить её как NPOT, то идею pixel-perfect можно похоронить. Ведь так?
2) вот такие видимые артефакты даёт ETC1 на нашей графике. Так что ухудшение качества сильное, к сожалению.
TrueColor (или RGB 24): AutoCompressed (ETC1):

Таким образом, в нашем случае — точно не AutoCompressed.

Второй пока неразгаданный факт.
юнити переводит текстуру в RGBA16 / RGBA32 только при наличии альфа-канала

Графика у нас отличная и без альфа канала. Всё происходит так, как Вы и пишете. Но перед сборкой мы её иногда скейлим и реимпортируем с использованием средств C#… после этого изображения без альфа-канала начинают восприниматься как RGBA 32. Наверное, альфа-канал появляется во время скейла. Будем искать. Благодарю за это замечание.

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

Опять же, есть риск получить покорёженную графику, когда она будет автоматом конвертиться в ETC1. У нас 2D игра с pixel-perfect… артефакты недопустимы. Потому всё так же выставляем RGBA32 -> RGBA16 в зависимости от качества или AutomaticTruecolor (если стоит выбор между RGBA32 или RGB24).
Получается, в этом случае Texture2D.LoadImage() тоже вполне подходит, потому что у нас RGB32/RGB24 и потребление памяти всё равно никак не снизить.

Все держать в wav — это позволит выбирать метод компрессии и битрейт прямо в юнити

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

Всё-таки используемые методы снижения размера сборки зависят от особенностей графики проекта, и это играет определяющую роль

Снова буду очень рад конструктивной критике!
Для ETC1 картинка должна быть NPOT. У нас так просто невозможно. Если заимпотрить её как NPOT, то идею pixel-perfect можно похоронить.

NPOT — это Non-Power-Of-Two, т.е. как раз наоборот :) Да, все текстуры должны быть POT, причем желательно квадратные — PVRTC умеет только такие сжимать, для ETC на android такого требования нет. Советую сразу готовить текстуры под сжатие, чтобы потом не было мучительно больно при переносе на ios. Еще один потенциальный плюс от POT-текстур — мипмапы работают только на них. Для гуя это не критично, но если есть разные по размеру объекты в 3д — это важно. На pixel-perfect это по идее никак не должно влиять — оставшуюся часть можно использовать под другие спрайты, как атлас. Но саму идею pixel-perfect я считаю ущербной, это верно. На реальных девайсах при качественном контенте разница минимальна между sd / hd, видно только на мелких деталях, привлекающих внимание — шрифтах.
Таким образом, в нашем случае — точно не AutoCompressed.

В вашем случае — взять квадратик 4х4, в нем сделать градиент точками 4х4 и положить на задник, растянув на весь экран. Кустики сделать спрайтами и разместить руками.
Но перед сборкой мы её иногда скейлим и реимпортируем с использованием средств C#

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

Открою тайну — всем наплевать на размер билда, но не наплевать на вылетающее и тормозящее приложение. Всегда следует следить за памятью в пределах сцены иначе то, что работает на тестовых топовых железках — будет рандомно крашиться на пользовательских девайсах, где памяти может просто не хватить. С единицами в карму, соответственно. Поэтому нужно следить именно за размером оперативной памяти, потребляемой в пределах каждой сцены. Например, выгоднее размножить несколько спрайтов по разным атласам, чтобы не грузить здоровый атлас ради одной необходимой картинки — это правильное решение и позволит избежать крашей.
Размер сборки важен до определенного размера — и это явно менее 30-40мб, для пользователей мобильного интернета. Если билд уже занимает 40-50мб, то оно будет закачиваться по wifi в любом случае.
Спасибо за интересную дискуссию!

Да, да, конечно POT! запарился, простите.

Кстати, по поводу ETC1 вот что говорит Unity:

Наша графика не так проста. Тот кусочек — лишь кусочек большого изображения. 40% картинок по типу dl.dropboxusercontent.com/u/3232059/jungle_bg_1920x1200.png (здесь все кроме RGB24 несказанно портит качество), а остальное — с прозрачностью и тоже с хорошим градиентом. То есть, о других форматах кроме RGBA32, RGB24 и местами RGBA16 речи пока не идёт.
Но в общем Ваш совет я понял!

Какими атласами Вы пользуетесь? от сторонних плагинов или родной packer от Unity?

Ну, кроме прочего, всё равно мы работаем в условиях соблюдать pixel-perfect и все же стараться снизить размер билда. От этого пока никуда не деться :)

Нельзя работать с текстурами мимо юнити

Ну там мы работаем несколько сложнее… Хотя будем ревизировать.

Поэтому нужно следить именно за размером оперативной памяти,

Да, это другая сторона медали. За этим следим чётко.

Видимо, надо было в статье прописать чёткие условия работы: характер картинок, требования pixel-perfect, чтобы это не казалось вредными советами;)
Наша графика не так проста.

Глядя на пример, могу посоветовать попробовать делать это чисто геометрией без текстур и расскрашивать повертексно. Даже 10к вертексов займут меньше места, чем любая текстура и будут отрисованы быстрее.
Какими атласами Вы пользуетесь?

Штатный, Texture2D.PackTextures. На самом деле без разницы.

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

PS
Увел в избранное ))
У нас на проекте (Pocket Troops, не реклама, а референс) 90% арта жмется в PVRTC.
И дабы избежать артефактов, применяем следующий метод:
1. открываем атлас в фотошопе, вторым слоем создаем серую заливку
2. накладываем на нее шум (Noise -> Add Noise 12.5%)
3. применяем Soft Light формулу смешивания
4. применяем маску, чтобы шум не ложился на прозрачные участки.
5. Прозрачность слоя с шумом выставляется в 20-30%, так что бы шум был еле заметен, но заметен при прииближении
6. Профит!
Заметка: желательно выдерживать 4px оффсета между спрайтами в атласе — спасет от артефактов по краям спрайта.
Это не для всего контента возможно. Рассказываю трик — отрезаем альфу, получаем 2 RGB текстурки, каждая из которых жмется в ETC1/PVRTC4 с хорошим качеством и без дитеринга, в шейдере делаем вычитку из грейскейла и пишем в альфу. Для совсем пробитых фанатов оптимизации — 3 грейскейла можно сложить в 1 текстурку по каналам и выбирать ее в шейдере через fixed3-маску и dot во фрагментном шейдере.
Вариант очень интересный. Инетерснее только — градиентная раскраска интерфейсов (если ограничить художников набором цветов). =)
Но даже на RGB текстуру стоит накладывать шум (да, еще не помешает наложить фильтр Posterize), ведь PVRTC дает артефакты не только на прозрачностях, градиенты он вообще не любит )
Ну без альфы градиенты не так страшны — точности почти хватает. По поводу градиент мапинга — там делается dependency read из LUT-а, а это плохо на gles2 девайсах, на gles3 штрафа нет. Ну и сильная зависимость от палитры — на произвольной нельзя делать фильтрацию (индексы будут смешиваться и давать новый номер в палитре), нельзя использовать мипмапы (по той же причине). Палитру нужно готовить — делать плавные градиенты между разрешенными цветами и потом индексировать в нее, тогда все заработает как надо. Но к сожалению такое возможно только для текстур, рисованных руками и с заранее известной палитрой.
Попробовал трик — качество на прозрачных текстурах возросло, но по размеру проиграли
2к RGBA текстура весит 5,3Мб
2к RGB весит 2,7Мб + 2к Alpha8 весит 4Мб
Так что по весу заметно проигрываем
Правда, альфу можно сделать в два раза меньшей, но появляется мыло по границам спрайта
Проигрыш со сжатием в ETC1 / PVRTC4? смысл в том, чтобы убрать альфу и расширить место в палитре под большее количество цветов. Альфа — в такую же сжатую текстурку ETC1/PVRTC4 + качество получится выше тоже. Да, будет проигрыш если сравнивать с 1 текстуркой, но по качеству не сравнимо + универсально. Ну и не сравнимо с RGBA16 / RGBA32.
Ну и можно положить 3 альфы в 1 текстурку без потери в размере и качестве — тогда, возможно, будет выигрыш. :)
В свойства материала запихать маску выбора (ставить 1 в нужный канал) и в шейдере фильтровать. Как-то так:
fixed4 _LayerMask;
...
o.a = dot(tex2D(_AlphaTex, i.uv), _LayerMask);
Премного благодарен, опробовал собрать три текстуры в одну — выйграл 5 метров билда =)
Интересно, какой оверхед такой способ дает на производительности?
Ну тестить надо. Но дает, да. Вообще, надо сначала тестить, а потом принимать решение — стоит оно того или нет. :)
Документ приводит список обязательно включаемых в сборку dll и призывает минимизировать количество дополнительных dll (особенно тяжёлых). В частности, по возможности не использовать System.dll (добавляет к APK 2 Мбайт). Однако даже если мы будем избегать ссылок на методы из этой библиотеки, System.dll всё равно (по состоянию на Unity 4.5.5) включается в сборку, так как её подтягивает обязательная Mono.Security.dll. Потому, получается, что официальная документация Unity в этом месте не совсем релевантна.

А вот использования других (необязательных) dll желательно избегать во имя снижения размера сборки.


Вы меня конечно можете подправить, но разве DLL не используется чисто в Windows ОС?

DLL (англ. Dynamic Link Library — «библиотека динамической компоновки», «динамически подключаемая библиотека») (Расширение приложения) в операционных системах Microsoft Windows и IBM OS/2 — динамическая библиотека, позволяющая многократное использование различными программными приложениями. K DLL относятся также элементы управления ActiveX и драйверы. В системах UNIX аналогичные функции выполняют так называемые общие объекты (англ. shared objects).
Когда создавали .Net, то решили использовать тот же самый подключаемый контейнер кода — dll + добавили в заголовок признак того, что это не нейтивный код, а MSIL, а от dll там только заголовок. Т.е. внутри находится кроссплатформенный полуфабрикат для исполнения средой .Net, поэтому оно будет работать везде, где она есть (ну или mono) и если не использует платформозависимые вещи.
тогда эт наверно надо делать акцент на уточнение какого рода DLL, в C# можно запросто подключить DLL написанную на Си С++, Pascal. А судя по выше сказанному они не будут исполнятся виртуальной машиной .NET. Надо делать наверно на это акцент) а то путаница какая то получится) (ей богу не могли назвать скажем DLL2 ) — тип DLL второго поколения.
Прошло 13 лет с момента появления .Net и ни у кого не возникло никаких проблем за все это время. Какие акценты? К тому же существуют mixed сборки, содержащие в себе managed + native код. Ну и можно вызывать экспортированные функции из нейтивной либы.
mixed сборки наверно привязаны к определенном платформе?
Именно, как и native. А в чем вопрос-то? :) Это не специфика unity, это специфика .Net / Mono. Mono в юнити работает в качестве рантайма по исполнению пользовательского кода, под ios транслируется полностью в native.
т.е и DLL сборки под MSIL. будут транслированы полностью в native? под IOS. Вопрос чисто для себя) т.к что то не нахожу по этому поводу инфы)
Если хочется использовать внешние native либы, то они должны быть собраны именно под ios (armv7, arm64), MSIL код будет транслирован в native в момент экспорта из юнити в xcode-проект. Потом оно все линкуется в бинарник и собирается пакет установки.
ей богу не могли назвать скажем DLL2

Assembly (сборка)
Для того, чтобы посмотреть, в каком формате у вас текстуры, надо всего лишь открыть APK (это ведь ZIP) и посмотреть. Вы увидите, что текстуры не зависят от изначального формата (jpg, png). Они там хранятся в том формате, какое сжатие вы выбрали (32 бита на пиксель и т.п.)
Если разархивировать АПК, то найти там текстуры в приемлемом для чтения формате мне не удалось. Судя по всему, Unity хранит их в формате, далёком от привычных для человека. Быть может, в моих словах кроется ошибка...?
Sign up to leave a comment.

Articles