Pull to refresh

Comments 69

Концепция хороша, но habrahabr вроде бы про код, а его тут не наблюдается.
Хабр для разработчиков, а по своему, возможно и небольшому опыту, сложнее идею или концепцию придумать, продумать и отполировать в голове, а не код настрочить.
А зачем нужно исходную текстуру сохранять целой на атласе? Можно же каждый треугольник отдельно размещать, а не как часть общей исходной текстуры — это должно позволить укладывать еще плотнее. При этом, чтобы не границе треугольника не появлялись ореолы, нужно просто на атласе каждую текстуру треугольника на пару пикселей «расширить» во все стороны.
Может, сжатие основано на смежности сторон треугольников?
Это приведёт к тому что будут проявляться швы на стыках при фильтрации и мипмапинге
При этом, чтобы не границе треугольника не появлялись ореолы, нужно просто на атласе каждую текстуру треугольника на пару пикселей «расширить» во все стороны.

Что мешает вокруг каждого треугольника оставить еще небольшой «запас» оригинальной текстуры?
Это решит проблему с фильтрацией, но не с мипмапингом.
У пустого и полного бассейнов совпадают крайние треугольники. Т.е. можно было ещё удалить повторы и освободить место?
Также отзеркаливанием и наклонами в 90° можно было бы разместить плотнее?
Повторы чего? Графики? Она не симметрична. Треугольников? Так сохранится всего пара треугольников, ради чего городить огород?
Каждый бассейн состоит из 40 треугольников и только 13 из них не идентичны, т.е. повтор больше половины. Конечно, это мелочь, если во всех атласах больше нет таких текстур в разных состояниях.
Именно графики. Если у нас 15 прямоугольников 1 на 2 и один прямоугольник 2 на 1, то, используя поворот, можно упаковать плотнее. Вопрос только в том, можно ли напрямую рисовать такие текстуры?
Такой бассейн логичнее хранить 2 картинками — пустой бассейн и слой «вода + блики», которую сверху накладывают. Но в данном конкретном случае выигрыш может оказаться незначительным.
Как вариант, можно ещё использовать пространственную симметрию (хранить левую/правую половины фонарного столба). И разбить большие объекты с однородными областями на несколько примитивных (много избыточной информации в центральной части бассейна — разбить на квадраты, хранить пару уникальных, остальные выкинуть).
При этом автоматический поиск повторов будет очень сложным и долгим, а ручной не оправдан. И все эти старания убьёт градиент, который дизайнер наложит на всю картинку в следующей версии чтобы ее немного «оживить». Так что ваш комментарий можно воспринять как шутку.
А вот паковать треугольники по отдельности — это была хорошая идея. выше. Только чтобы не сломать мипмаппинг надо не поворачивать треугольники, а паковать их как есть, но вперемежку. Это уменьшит сложность для паковщика и облегчит применение, с кажем, генетических алгоритмов для оптимизации.
Натравите на текстуру любой современный кодек с внутрикадровым предсказанием, он сам найдёт повторяющиеся части.
Это, быть может, не совсем подходящая задача для production'а, если результат нужен вчера, но для небольшого research'а будет в самый раз.
Думаю кодек будет искать паттерны с некоторой точностью, а никто не обрадуется артефактам в текстурах. Может быть для каких-то целей и можно приспособить вашу идею, но, имхо, не в гламурных игрушках, а, например, в предварительном анализе массивов каких-то научных данных.
UFO just landed and posted this here

Возможно какие то особенности архитектуры. Лет 10 назад очень сильно зависело от самого девайса. =)


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


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

Забавно, что люди после начинают жаловаться, мол, жесткий диск стал наполовину полон после установки игрули. И это не прибавило оптимизма
Для подобных спрайтов общее количество треугольников смехотворно малое, что с классическим подходом, что с таким. Современное мобильное железо способно обрабатывать очень большое их число, и о влиянии количества треугольников на производительность уместно говорить для 3D игры со сложными моделями, но никак не для спрайтовой графики средней сложности (конечно, многое зависит от сложности шейдеров, но опять же в среднем случае для спрайтовой графики они тривиальны). Количество вызовов отрисовки тоже не зависит от числа треугольников в спрайте. А вот количество используемых текстур и их переключение всегда влияет ощутимо и на производительность и тем более на потребление памяти. Так что такой подход вполне оправдан. Я бы ещё и треугольники спрайтов по атласу разбросал, как заметили выше.
Тем кто хочет поддерживать старые девайся типа iPad2 экономия по RAM все еще очень критичена. А по fps выигрышь будет даже на iPad Air2, хоть и не значительный. Для мобильных GPU рисование полупрозрачной геометрии является узким местом, а вот количество треугольников уже давно — нет. Исключение, разве что Tegra K1.
Забавно то, что при гигабайте памяти приложение может вылетать с ошибкой нехватки памяти при попытке занять чуть более 200 Мб памяти. То есть, далеко не вся память устройства доступна игре. Об этом как-то было на хабре (но не помню, статья или просто в комментариях к статье какой-то, но всё же, по-моему, статья; может кто-нибудь подскажет ссылку?). При разработке одной игры пришлось порядка двух месяцев потратить на оптимизацию графики, системы анимаций и т.п., а графику резать на отдельные кусочки (включая отражение, вращение и т.п.), чтобы игра стабильно работала вплоть до вторых ipad'ов и на android планшетах/телефонах среднего ценового диапазона. Ну и пару раз приходилось оптимизировать чужие проекты, ровно с той же самой проблемой. Было пару лет назад, но по памяти как раз и принимали в расчёт планшеты/телефоны минимум с гигом памяти (не припомню, чтобы у нас на тестах вообще хоть одно устройство было с меньшим объёмом).
UFO just landed and posted this here
Исключительно для cocos2d-x > 3.9 — важно отметить
Он имел в виду, что cocos2d до версии 3.9 не может принимать такие атласы.
Я думаю, что это экономия на спичках: не занятые рисунком пикселы — окрашены в один и тот-же цвет, это прекрасно сжимается само по себе, разве нет?
Оно сжимается при сохранении на диск, а в оперативной памяти текстуры хранятся в исходном виде, так иначе придется разжимать по 60 раз в секунду.
> окрашены в один и тот-же цвет, это прекрасно сжимается само по себе
Для DXT например без разницы, один там цвет или нет, он делит изображение на блоки 4х4 пикселя, и на каждый выделяет одинаковое число бит. Для PVRTC другой принцип, но тоже фиксированное число бит на точку.

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

Интересно, а помимо перфоманса сколько такая упаковка вам уменьшила общий размер ассетов/бинаря?

В случае PNG размер остаётся практически тот же. В случае PVRTC, естественно, уменьшается, причём профит существенный. Впрочем, упаковка в zip может нивелировать разницу, если подшаманить над PVRTC — забить пустые пиксели нулями, а не мусором.
Возможно имеет смысл после того как текстуры уже уложены попробовать сделать уменьшение количества треугольников за счет оставшегося пустого места между текстурами?
Поддерживаю. В примере точно можно значительно уменьшить количество полигонов.
Занимательно! Раз в компании движок собственной разработки, то было бы интересно почитать о том, как боролись с производительностью в контексте проблемы overdraw.
Немного странно наблюдать наличие почти белых треугольников. Потенциальное место для тюнинга
Подкину еще идею.

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

Как построить:
1) Ищется сетка треугольников как сейчас.
2) Ищется сетка треугольников для областей имеющих прозрачность.
3) Делаем булево вычитание из первой сетки второй.
4) Сохраняем треугольники из второго и третьего шага отдельно.

Идея состоит в том, что доля областей с прозрачностью обычно исчезающе мала. Поэтому на выходе мы получим более чем на четверть меньшую текстуру + значительно меньшую площадью текстуру с альфаканалом.

Осталось только проверить производительность на практике.
Потребуется серьезная переработка процесса рендеринга. Решение в лоб — когда для каждого спрайта рисуем прозрачные и непрозрачные области, а затем переходим к следующему — будет делать минимум по draw call на спрайт + переключение состояния между ними, которые на мобильных устройствах довольно дороги.

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

Второй — шаманство с z-buffer, которое, теоретически, даст прирост производительности:
— Каждому спрайту назначаем собственную z-координату в порядке отрисовки
— Сначала выводим все непрозрачные треугольники с включенной записью в z-buffer.
— Выводим прозрачные треугольники от дальнего к ближнему с выключенной записью в z-buffer.

Работоспособность второго способа я тестировал на игрушечном примере, но, имхо, лишняя сложность не компенсируется полученными плюшками.

> Последствия для производительности в отличие от одной текстуры, думаю, очевидны.
Можете развернуть ответ для меня, как человека без особого опыта написания шейдеров?

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

Разве передача в шейдер второй текстуры сама по себе сильно скажется? Или дело в условии, которые как я слышал не очень желательны? И не сыграет ли положительной роли тот факт, что для большей доли площади используется ветка отдающая константную прозрачность?

При этом, опять же, метод выигрывает еще больше площади в атласе, что положительно скажется на батчинге. Какая-никакая компенсация в некоторых случаях будет.

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

Ветвлением? Ветвление на шейдере просаживает производительность намного сильнее чем семплинг 2 текстур, но и сам семплинг 2 текстур дороже одной.
Я там ниже свою идею описал, как брать две без ветвления. В принципе, сейчас мультитекстурирование повсеместно используется. В OpenGL ES минимум восемь слотов стандартом предусмотрено. Есть подозрение, что это должно быть оптимизировано и не должно критично влиять на производительность. Судя по всему, требуются тесты, чтобы оценить, стоит ли свеч эта идея с двумя текстурами.
Не стоит, семплинг 2 текстур всегда дороже семплинга 1 текстуры, а с подобной упаковкой выигрыш по памяти будет не таким уж и существенным.
^Игнорируйте ответ выше, я по незнанию спутал uniform с атрибутом.
> Разве передача в шейдер второй текстуры сама по себе сильно скажется?
Передача сама по себе — нет, а вот два сэмплинга на фрагмент — могут, но надо тестировать на конкретном железе.

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

Т.е. фактически какой-нибудь

if ( condition ) a = f(x); else a = g(x);


На самом деле эквивалентен такому коду:

a = f(x) * condition + g(x) * (1 - condition);


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

> И не сыграет ли положительной роли тот факт, что для большей доли площади используется ветка отдающая константную прозрачность?
За все устройства не скажу, но очень сомневаюсь, что хоть где-то это так. По крайней мере для моего Fly это не имеет значения — рисование полностью полупрозрачной текстуры и полностью непрозрачной происходит с одинаковой скоростью, а вот если выключить альфа-смешивание — фпс взлетает.

> Разве ветвление по значению uniform переменной не оптимизируется?
Мы не можем использовать uniform переменную, иначе опять же, придется ее постоянно менять и делать отдельный draw call на каждый спрайт. Значит, будет varying.

> При этом, опять же, метод выигрывает еще больше площади в атласе, что положительно скажется на батчинге.
Обычно стараются поместить все текстурки, которые в игре выводятся «рядом», в один атлас. Я еще использовал трюк с кубической текстурой в качестве атласа — тогда нам доступно сразу 6 атласов на один батч, а этого хватит почти всегда.
Идея имеет право на жизнь. Понятно, что два раза на спрайт не надо рисовать и условий в шейдере вводить нежелательно. Есть такая мысль — смешиваем всегда обе текстуры, передавая два набора координат. Альфа канал прозрачной текстуры трансформируем особым образом — от 0 до 0.5 — брать цвет из непрозрачной текстуры, от 0.5 до 1.0 — брать из прозрачной, замапив альфу на интервал 0.0 до 1.0 (т.е. результирующая альфа уменьшается на один бит, что не очень критично). Соответственно, в непрозрачных треугольниках второй набор координат ставится в специально выделенный пиксель прозрачной текстуры с альфой 0.0. При таком подходе в шейдере можно обойтись без if'ов, и все спрайты рисуются однообразно с минимальным количество DC.
Очень крутая тема, жалко не open-source, а то добавил бы себе в движок.

На самом деле это решает еще одну большую проблему современных игр — fill rate. Меньше пустых пикселей — больше шейдерного времени.

В Cocos2D-X были добавлены Polygon Sprites и мы работаем над тем, чтобы добавить функционал и в наш движок.
Я что-то не догоняю — в статье речь про 2D или про 3D игру?
2Д, очевидно, раз речь о спрайтах.
Ну вот и я смотрю — на иллюстрациях, вроде бы, спрайты. А в статье почему-то речь о «текстурах».
Просвещаю — в современных устройствах (ПК, мобильные, любые) графический конвеер один, и унифицирован под вывод 3Д графики в том числе. 2Д графика выводится текстурированными треугольниками в ортографической проекции.
Это что же — каждый раз, когда я открываю очередную фоточку в графическом редакторе, он создаёт под неё прямоугольник из двух треугольников на всё окно?
Зависит от деталей реализации. Может и не из двух. Может на всё окно один прямоугольник, а рисуется всё в текстуру. Вариантов много, конвеер один.
«Век живи, век учись, дураком помрёшь.» Спасибо.
А зачем нужно исходную текстуру сохранять целой на атласе? Можно же каждый треугольник отдельно размещать, а не как часть общей исходной текстуры — это должно позволить укладывать еще плотнее. При этом, чтобы не границе треугольника не появлялись ореолы, нужно просто на атласе каждую текстуру треугольника на пару пикселей «расширить» во все стороны.

Именно из-за возможных проблем с артефактами отказались от этой идеи. Из-за особенностей работы OpenGL ему могут понадобиться пиксели за пределами рисуемого треугольника. Если там не будут пиксели исходной текстуры — появление артефактов неизбежно. Как правило достаточно 1-2 пикселя, но бывает что надо и больше. Сохранение исходной текстуры в атласе в неизменном виде гарантирует что артефактов не будет.

Мое имхо что по производительности рендера разницы или не будет — сколько выиграли на уменьшении кол-ва атласов а как следствие и на переключении атласов столько мы проиграем на усложнении обработки вершин на CPU и усложнении кода
Такой подход наверное был актуален пру лет назад когда RAM в смартфонах было кот наплакал

Собственно, пару лет назад мы все это и затеяли и именно чтобы решить проблему с оперативной памятью. Игра рассчитана не только на флагманские устройства. До тех пор пока устройства с 512 Мб памяти есть у достаточно большого числа пользователей, нам придется с ними считаться. А то QA быстро перечислит нам список устройств на которых игра падает по памяти.
По FPS действительно особенного выигрыша не получается. В примере тут cocos2d-x-performance-optimization
подобрана немного жульническая картинка. Если бы дядя не так широко расставлял руки, разница overdraw была бы куда меньше и FPS был бы примерно такой же. При переходе на полигональные атласы на FPS начинает влиять слишком много факторов, и нельзя точно сказать что он во всех случаях упадет или во всех случаях вырастет.
Из-за особенностей работы OpenGL ему могут понадобиться пиксели за пределами рисуемого треугольника. Если там не будут пиксели исходной текстуры — появление артефактов неизбежно. Как правило достаточно 1-2 пикселя, но бывает что надо и больше. Сохранение исходной текстуры в атласе в неизменном виде гарантирует что артефактов не будет.
Но на приведенном в статье атласе текстуры касаются друг друга точками, а иногда и целыми ребрами. Разве это не должно давать те же самые артефакты, в случае их возможности?
В примере тут cocos2d-x-performance-optimization подобрана немного жульническая картинка. Если бы дядя не так широко расставлял руки, разница overdraw была бы куда меньше и FPS был бы примерно такой же.
Ну так и в примере в статье у большинства объектов половина площади сэкономлена.
Перед тем как разбивать текстуру на треугольники, к ней уже применена 1-пиксельная обводка. Вообще, обводка обязательна как для полигональных, так и для обычных атласов. Борьба с артефактами OpenGL это отдельная и сложная тема, и возможно про нее будет отдельная статья. Stay tuned!
Перед тем как разбивать текстуру на треугольники, к ней уже применена 1-пиксельная обводка.
Это то понятно. Мысль была в том, что если в текущей реализации (запас минимум 2 пикселя до соседа) «гарантированно» нет артефактов, то при разбиении на треугольники (запас минимум 2 пикселя до фона/соседа) артефактов тоже не должно быть. Ведь расстояние до потенциальных пикселей неправильного цвета одинаковое.
Чего-то не верю я в случайные артефакты. Скорее всего, когда речь о требуемых «и даже больше пикселей», то имеется в виду построение мип-мапов, а их, очевидно, нельзя использовать в атласах.
Мип-мапов нет. Сколько внешних пикселей понадобится OpenGL — это зависит от масштаба с которым рисуется текстура. Если текстура при отрисовке уменьшается в 2 раза, то может потребоваться 2 внешних пикселя, и т. д.
А, ну тогда понятно. Я предполагал, что спрайты выводятся «пискель в пиксель». Для разных разрешений ведь можно подготовить разные атласы.
Не совсем так. Текстуры по краям уходят в альфу, и там 1-пиксельной обводки достаточно. А если распиливать текстуру на треугольники то получится что-то типа «склейки» текстур, и там все сложнее. Кроме того, надо учитывать что при конвертации в pvrtc текстура обрабатывается блоками 4x4 и если в этот блок попадут треугольники от разных текстур, то все опять же будет некрасиво.То есть да, теоретически можно попробовать разбить текстуру на треугольники с обводкой, но практически могут возникнуть проблемы при отрисовке и плюс неизвестно насколько велик будет реальный выигрыш памяти, потому что придется применять обводку к каждому треугольнику. Тут надо отдельно экспериментировать.
А какая у Вас прослойка в коде для Mac->OpenGL ES, Windows->OpenGL ES (SDL, Angle, GLFW)? Для нативных платформ ясно iOS и Android — там вызовы стандартны. Интересует именно этап отладки кода на Windows\OSX.
Под Windows — Angle. Под OSX — просто вызовы OpenGL.
Еще вопрос — в варианте OSX — это симулятор iOS или у Вас одновременно OpenGL ES и OpenGL используется в проекте? Насколько сложно транслировать OpenGL ES в OpenGL?
OpenGL ES — это подмножество OpenGL, ничего не надо транслировать
Раз уж вы всё равно разбиваете атлас на меши, так почему не использовать те же треугольники для сортировки прозрачных/непрозрачных частей для оптимизации отрисовки и блендинга?
Sign up to leave a comment.