Unity: стискаючи стислий


Результат: інформація про колір займає 1/64 від вихідної площі при досить високій якості результату. Тестове зображення взято з цього сайту.

Текстури практично завжди є найбільш значимим споживачем місця на диску, так і в оперативній пам'яті. Стиснення текстур в один з підтримуваних форматів щодо допомагає у вирішенні цієї проблеми, але що робити, якщо навіть у цьому випадку текстур дуже багато, а хочеться ще більше?

Історія почалася приблизно півтора року тому, коли один гейм-дизайнер (назвемо його 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 видав ідею, як можна переписати шматок, що генерує «бранчинг», без умов, за що йому велике спасибі. Частина обчислень за умовою було винесено в lookup-текстуру, яка генерувалася скриптом в редакторі і потім просто використовувалася в шейдере. У результаті всіх оптимізацій складність впала до 17 alu, 3 текстурних вибірок і відсутності «бранчинга».
Начебто перемога, але не зовсім. По-перше, така складність — все одно надмірна для мобільних пристроїв, потрібно як мінімум в 2 рази менше. По-друге, все це тестувалося на контрастних картинках, зафарбованих суцільним кольором.


Приклад артефактів (кликабельно): зліва помилковий, праворуч — еталонний варіанти

Після тестів на реальних зображеннях градієнтами та іншими принадами (фотографії природи) з'ясувалося, що дана реалізація дуже примхлива до комбінації дозволу «кольоровий» карти з налаштуваннями mipmap-ів і фільтрації: з'являлися очевидні артефакти, викликані змішуванням даних текстур в шейдере і помилками округлення / стиснення самих текстур. Так, можна було використовувати текстури без стиснення, з POINT-фільтрацією і без сильного зменшення розміру «кольорової карти», але цей експеримент втрачав будь-який сенс.

Третя спроба
І тут допомогла чергова допомога залу. Товариш, люблячий «графоний, некстген, ось це все» і люблячий читати всі доступні дослідження по цій темі (назвемо його Belfegnar) запропонував інший простір — YCbCr і викотив виправлення до мого тестового стенду, що підтримують його. В результаті складність шейдера з ходу впала до 8 alu, без «бранчинга» і lookup-текстур. Також мені були скинуті посилання на дослідження з формулами всяких тямовитих математиків, які перевіряли різні колірні простору на можливість / доцільність їх існування. З них були зібрані варіанти для RDgDb, LDgEb, YCoCg (можна погуглити", знайдеться тільки останній, перші 2 можна знайти за посиланнями: sun.aei.polsl.pl/~rstaros/index.html, sun.aei.polsl.pl/~rstaros/papers/s2014-jvcir-AAM.pdf). RDgDb і LDgEb засновані на одному базовому каналі (використовувався в якості повнорозмірного «грейскейла») і відносно двох каналів до нього. Людина погано сприймає різницю в кольорі, але досить добре визначає різницю в яскравості. Тобто при сильному стисненні «кольоровий» карти губився не тільки колір, але і контраст — якість сильно страждала.


«Кольорова» карта — упаковані дані (CoCg) містяться у RG-каналах, B-канал порожній (може бути використаний для користувальницьких даних).

В результаті «переміг» YCoCg — дані засновані на яскравості, добре переносять стиск «кольоровий» картки (на сильному стисненні «миляться» сильніше, ніж YCbCr — у того «картинка» краще зберігає контраст), складність шейдера менше, ніж у YCbCr.
Після реалізації базової знову почалися танці з бубном заради оптимізації, але в цьому я не сильно досяг успіху.

Підсумок

Ще раз картинка з результатом: дозвіл кольоровий текстури можна змінювати в широких межах без відчутних втрат в якості.
Експеримент пройшов досить вдало: шейдер (без підтримки прозорості) зі складністю 6 alu і 2 текстурними вибірками, 2.8 х стиснення пам'яті. В кожному матеріалі можна вказувати колірний канал з «грейскейл»-атласу, який буде використаний в якості яскравості. Точно також для шейдера з підтримкою прозорості вибирається колірний канал «грейскейл»-атласу для використання в якості альфи.
Джерело: Github
Ліцензія: CC BY-NC-SA 4.0.

Всі персонажі вигадані, і будь-який збіг з реально живуть, чи коли-небудь жили людьми випадково. Жоден дизайнер в ході цього експерименту не постраждав.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.