- PVSM.RU - https://www.pvsm.ru -

Результат: информация о цвете занимает 1/64 от исходной площади при достаточно высоком качестве результата. Тестовое изображение взято с этого сайта [1].
Текстуры практически всегда являются наиболее значимым потребителем места как на диске, так и в оперативной памяти. Сжатие текстур в один из поддерживаемых форматов относительно помогает в решении этой проблемы, но что делать, если даже в этом случае текстур очень много, а хочется еще больше?
История началась примерно полтора года назад, когда один гейм-дизайнер (назовем его Akkelman) в результате экспериментов с различными режимами смешивания слоев в photoshop обнаружил следующее: если обесцветить текстуру и поверх наложить ту же текстуру в цвете, но в 2-4 раза меньшего размера с установкой режима смешивания слоев в “Color”, то картинка будет довольно сильно походить на оригинал.
В чем смысл такого разделения? Черно-белые изображения, содержащие по сути яркость исходной картинки (далее по тексту — “грейскейлы”, от англ. “grayscale”), содержат только интенсивность и могут быть сохранены в одной цветовой плоскости каждое. То есть, в обычную картинку без прозрачности, имеющую 3 цветовых канала R,G,B мы можем сохранить 3 таких “грейскейла” без потери места. Можно использовать и 4 канал — A (прозрачность), но с ним на мобильных устройствах большие проблемы (на андроиде с gles2 нет универсального формата, поддерживающего сжатие RGBA-текстур, качество при сжатии сильно ухудшается и тп), поэтому для универсальности будет рассматриваться только 3-канальное решение. Если это реализовать, то мы получим практически 3-кратное сжатие (+ несоизмеримо малую по размеру “цветовую” текстуру) для уже сжатых текстур.
Можно примерно оценить выгоду от применения такого решения. Пусть у нас есть поле 3х3 из текстур разрешением 2048х2048 без прозрачности, каждая из которых сжата в DXT1 / ETC1 / PVRTC4 и имеет размер 2.7Мб (16Мб без сжатия). Суммарный размер занимаемой памяти равен 9 * 2.7Мб = 24.3Мб. Если мы сможем извлечь цвет из каждой текстуры, уменьшим размер этой “цветной” карты до 256х256 и размером в 0.043Мб (выглядит это вполне сносно, то есть достаточно хранить 1/64 часть от общей площади текстуры), а полноразмерные “грейскейлы” упакуем по 3 штуки в новые текстуры, то получим примерный размер: 0.043Мб * 9 + 3 * 2.7Мб = 8.5Мб (размер оценочный, с округлением в большую сторону). Таким образом, мы можем получить сжатие в 2.8 раза — звучит довольно неплохо, учитывая ограниченные аппаратные возможности мобильных устройств и неограниченные желания дизайнеров / контентщиков. Можно либо сильно уменьшить потребление ресурсов и время загрузки, либо накинуть еще контента.
Ну что же, пробуем. Быстрый поиск выдал готовый алгоритм / реализацию метода смешивания “Color”. После изучения его исходников волосы зашевелились по всему телу: порядка 40 “бранчей” (условных ветвлений, которые негативно сказываются на производительности на не совсем топовом железе), 160 alu инструкций и 2 текстурных выборки. Такая вычислительная сложность — это достаточно много не только для мобильных устройств, но и для десктопа, то есть совсем не подходит для реалтайма. Об этом было рассказано дизайнеру и тема была благополучно закрыта / забыта.
Пару дней назад эта тема всплыла снова, было решено дать ей второй шанс. Нам не нужно получить 100% совместимость с реализацией photoshop-а (у нас нет цели смешивать несколько текстур в несколько слоев), нам нужно более быстрое решение с визуально похожим результатом. Базовая реализация выглядела как двойная конвертация туда-обратно между пространствами RGB / HSL с расчетами между ними. Рефакторинг привел к тому, что сложность шейдера упала до 50 alu и 9 “бранчей”, что уже было как минимум в 2 раза быстрее, но все же недостаточно. После запроса помощи зала, товарищ wowaaa [2] выдал идею, как можно переписать кусок, генерирующий “бранчинг”, без условий, за что ему большое спасибо. Часть вычислений по условию было вынесено в lookup-текстуру, которая генерировалась скриптом в редакторе и потом просто использовалась в шейдере. В результате всех оптимизаций сложность упала до 17 alu, 3 текстурных выборок и отсутствия “бранчинга”.
Вроде как победа, но не совсем. Во-первых, такая сложность — все равно чрезмерна для мобильных устройств, нужно как минимум раза в 2 меньше. Во-вторых, все это тестировалось на контрастных картинках, заполненных сплошным цветом.
[3]
Пример артефактов (кликабельно): слева ошибочный, справа — эталонный варианты
После тестов на реальных картинках с градиентами и прочими прелестями (фотографии природы) выяснилось, что данная реализация очень капризна к комбинации разрешения “цветной” карты с настройками mipmap-ов и фильтрации: появлялись очевидные артефакты, вызванные смешиванием данных текстур в шейдере и ошибками округления / сжатия самих текстур. Да, можно было использовать текстуры без сжатия, с POINT-фильтрацией и без сильного уменьшения размера “цветной карты”, но тогда этот эксперимент терял всякий смысл.
И тут помогла очередная помощь зала. Товарищ, любящий “графоний, некстген, вот это все” и любящий читать все доступные изыскания по этой теме (назовем его Belfegnar) предложил другое цветовое пространство — YCbCr и выкатил исправления к моему тестовому стенду, поддерживающие его. В результате сложность шейдера с ходу упала до 8 alu, без “бранчинга” и lookup-текстур. Также мне были скинуты ссылки на исследования с формулами всяких мозговитых математиков, проверявших разные цветовые пространства на возможность / целесообразность их существования. Из них были собраны варианты для RDgDb, LDgEb, YCoCg (можно “погуглить”, найдется только последний, первые 2 можно найти по ссылкам: sun.aei.polsl.pl/~rstaros/index.html [4], sun.aei.polsl.pl/~rstaros/papers/s2014-jvcir-AAM.pdf [5]). RDgDb и LDgEb основаны на одном базовом канале (использовался в качестве полноразмерного “грейскейла”) и отношению двух оставшихся каналов к нему. Человек плохо воспринимает разницу в цвете, но достаточно хорошо определяет разницу в яркости. То есть при сильном сжатии “цветной” карты терялся не только цвет, но и контраст — качество сильно страдало.

«Цветная» карта — упакованные данные (CoCg) содержатся в RG-каналах, B-канал пустой (может быть использован для пользовательских данных).
В результате “победил” YCoCg — данные основаны на яркости, хорошо переносят сжатие “цветной” карты (на сильном сжатии “мылятся” сильнее, чем YCbCr — у того “картинка” лучше сохраняет контраст), сложность шейдера меньше, чем у YCbCr.
После базовой реализации опять начались танцы с бубном ради оптимизации, но в этом я не сильно преуспел.

Еще раз картинка с результатом: разрешение цветной текстуры можно менять в широких пределах без ощутимых потерь в качестве.
Эксперимент прошел достаточно удачно: шейдер (без поддержки прозрачности) со сложностью 6 alu и 2 текстурными выборками, 2.8х сжатие по памяти. В каждом материале можно указывать цветовой канал из “грейскейл”-атласа, который будет использован в качестве яркости. Точно также для шейдера с поддержкой прозрачности выбирается цветовой канал “грейскейл”-атласа для использования в качестве альфы.
Исходники: Github [6]
Лицензия: CC BY-NC-SA 4.0.
Все персонажи являются вымышленными и любое совпадение с реально живущими или когда-либо жившими людьми случайно. Ни один дизайнер в ходе этого эксперимента не пострадал.
Автор: Leopotam
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-2/215993
Ссылки в тексте:
[1] этого сайта: http://wallpaperswide.com/japanese_garden_kyoto-wallpapers.html
[2] wowaaa: https://habrahabr.ru/users/wowaaa/
[3] Image: https://habrastorage.org/files/bb4/73f/3cf/bb473f3cfe88480a96bb3f3a2a6ef47f.png
[4] sun.aei.polsl.pl/~rstaros/index.html: http://sun.aei.polsl.pl/~rstaros/index.html
[5] sun.aei.polsl.pl/~rstaros/papers/s2014-jvcir-AAM.pdf: http://sun.aei.polsl.pl/~rstaros/papers/s2014-jvcir-AAM.pdf
[6] Github: https://github.com/Leopotam/UnityPackedColor
[7] Источник: https://habrahabr.ru/post/316672/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.