Как всем известно, жизнь разработчика мобильных игр непроста. Он должен найти свой путь на очень узкой дорожке. С одной ее стороны — требования гейм-дизайнеров, уверенно устремляющиеся к бесконечности. Больше функционала, больше красивой графики, больше эффектов, больше анимаций, больше звуков. А с другой стороны — ограниченные ресурсы мобильного устройства. И раньше всего, как правило, заканчивается оперативная память.

Например, iPad 2 — всего в нем 512 Мб RAM. Однако приложению доступно только примерно 275 Мб. Когда занимаемая приложением память будет приближаться к этой границе, операционная система пришлет так называемое «Memory warning» — мягко, но настойчиво предложит освободить память. И если лимит все же будет превышен, операционная система остановит приложение. Пользователь будет думать, что ваша игра упала и побежит писать гневное письмо в саппорт.



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

Текстуры и атласы


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

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



Как легко заметить, в нем довольно много свободного места и прямо-таки хочется упаковать текстуры поплотнее. Причина неэффективной упаковки в том, что многие текстуры содержат довольно большие прозрачные области. Поэтому однажды мы решили попробовать отказаться от обычной прямоугольной упаковки и сделать то, что в итоге получило название «полигональный атлас». Для этого нужно решить 2 задачи:

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

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

Пройдя довольно длительный путь и совершив попытку упаковки тестового атласа примерно 100 000 раз, мы наконец добились результата:



Как видно, упаковка действительно гораздо плотнее. В зависимости от текстур, выигрыш может составлять до 1/4 их площади. Можно также сформировать отладочный вариант атласа, в котором видно разбивку текстур на треугольники.



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

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

Есть еще одна проблема, с которой мы столкнулись при переходе на новые атласы. Часто требуется нарисовать не текстуру целиком, а только ее часть. Например, если надо сделать постепенное появление объекта на экране или какой-нибудь индикатор прогресса. При использовании обычных атласов задача легко решается при помощи коррекции uv-координат. В случае же полигональных атласов все становится сложнее. Посмотрим на примере. Синим на картинке выделена часть текстуры, которую надо нарисовать:


Потребуется:

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

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

Бывают и более сложные случаи. Например, круговой прогресс или какие-то искажения текстуры. Поэтому оказалось, что проще некоторые текстуры упаковывать в обычные атласы или не упаковывать совсем, чтобы не усложнять процесс отрисовки.

Немного технических подробностей. Время упаковки полигонального атласа сильно зависит от настройки качества упаковки. Поскольку при любом изменении набора текстур атласы надо пересобирать, это приходится делать довольно часто. Поэтому обычно у нас включен режим «минимальная плотность, максимальная скорость». В таком режиме 8 атласов размером 2048x2048 пакуются примерно 5 минут. В общих чертах процесс упаковки выглядит так:

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

Как правило, уже предварительная упаковка является достаточно плотной. При попытке повышения качества, время упаковки вырастает очень сильно — до нескольких часов на 1 атлас — а выигрыш может составлять 2-3 дополнительно упакованные текстуры.

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

class Drawable {
	virtual int Width() const;
	virtual int Height() const;
	virtual bool HitTest(int x, int y) const;
	virtual void Draw(SpriteBatch* batch, const FPoint& position);
}

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

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

virtual void GetGeometry(std::vector<QuadVert>& geometry) const;

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

Сейчас полигональные атласы используются во многих наших проектах и можно считать, что они прошли испытание временем. Например, в нашем следующем free-to-play проекте Gardenscapes, мировой релиз которого состоится совсем скоро, основная часть игрового процесса происходит в принадлежащем игроку саду.

Для него нарисовано просто огромное количество разнообразных текстур, некоторые из них использованы в настоящей статье как примеры. Сейчас все эти текстуры умещаются в 8 полигональных атласов 2048x2048. А вот если бы мы упаковывали атласы обычным способом, то их получилось бы уже 11. Таким образом, мы получаем экономию оперативной памяти, в зависимости от применяемого графического формата, от 6 до 48 МБ. А гейм-дизайнеры могут предложить на четверь красивеньких текстур больше!

Об авторе: Сергей Шестаков, технический директор компании Playrix.
Поделиться с друзьями
-->

Комментарии (68)


  1. erlioniel
    27.07.2016 18:10
    -21

    Концепция хороша, но habrahabr вроде бы про код, а его тут не наблюдается.


    1. Jester92
      27.07.2016 18:38
      +16

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


  1. WingedFlame
    27.07.2016 18:41
    +3

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


    1. perfect_genius
      27.07.2016 18:59

      Может, сжатие основано на смежности сторон треугольников?


    1. Chaos_Optima
      28.07.2016 10:23
      +3

      Это приведёт к тому что будут проявляться швы на стыках при фильтрации и мипмапинге


      1. WingedFlame
        28.07.2016 10:26

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

        Что мешает вокруг каждого треугольника оставить еще небольшой «запас» оригинальной текстуры?


        1. Chaos_Optima
          28.07.2016 10:49
          +3

          Это решит проблему с фильтрацией, но не с мипмапингом.


  1. perfect_genius
    27.07.2016 18:53

    У пустого и полного бассейнов совпадают крайние треугольники. Т.е. можно было ещё удалить повторы и освободить место?
    Также отзеркаливанием и наклонами в 90° можно было бы разместить плотнее?


    1. Vilyx
      27.07.2016 19:00
      +1

      Повторы чего? Графики? Она не симметрична. Треугольников? Так сохранится всего пара треугольников, ради чего городить огород?


      1. perfect_genius
        27.07.2016 19:27
        +1

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


      1. Deosis
        28.07.2016 07:10

        Именно графики. Если у нас 15 прямоугольников 1 на 2 и один прямоугольник 2 на 1, то, используя поворот, можно упаковать плотнее. Вопрос только в том, можно ли напрямую рисовать такие текстуры?


    1. Denai
      27.07.2016 23:08

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


  1. RomanArzumanyan
    27.07.2016 18:54

    Как вариант, можно ещё использовать пространственную симметрию (хранить левую/правую половины фонарного столба). И разбить большие объекты с однородными областями на несколько примитивных (много избыточной информации в центральной части бассейна — разбить на квадраты, хранить пару уникальных, остальные выкинуть).


    1. trapwalker
      28.07.2016 23:27

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


      1. RomanArzumanyan
        03.08.2016 17:43

        Натравите на текстуру любой современный кодек с внутрикадровым предсказанием, он сам найдёт повторяющиеся части.
        Это, быть может, не совсем подходящая задача для production'а, если результат нужен вчера, но для небольшого research'а будет в самый раз.


  1. progman_rus
    27.07.2016 19:07

    есть сравнительные тесты — сколько FPS можно выжать на слабеньком смартфоне при классической организации атласов и при полигональной организации.
    И на флагманских смартфонах.?
    Какой конкретно количественный профит от сабжа или прогерам просто было по приколу сложную задачку расколоть?

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

    PS мы анимации резали на квады 32х32 пикселя, выкидывали пустые и повторяющиеся и упаковывали результат в атлас. Экономия получалась 10 кратная.


    1. Rastishka
      27.07.2016 19:47

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


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


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


    1. domix32
      27.07.2016 22:03

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


    1. playermet
      27.07.2016 22:23

      Вот тут что-то меряли: cocos2d-x-performance-optimization
      Разница есть, причем существенная.


    1. vagran
      28.07.2016 10:26
      +1

      Для подобных спрайтов общее количество треугольников смехотворно малое, что с классическим подходом, что с таким. Современное мобильное железо способно обрабатывать очень большое их число, и о влиянии количества треугольников на производительность уместно говорить для 3D игры со сложными моделями, но никак не для спрайтовой графики средней сложности (конечно, многое зависит от сложности шейдеров, но опять же в среднем случае для спрайтовой графики они тривиальны). Количество вызовов отрисовки тоже не зависит от числа треугольников в спрайте. А вот количество используемых текстур и их переключение всегда влияет ощутимо и на производительность и тем более на потребление памяти. Так что такой подход вполне оправдан. Я бы ещё и треугольники спрайтов по атласу разбросал, как заметили выше.


    1. Hottych
      28.07.2016 12:19

      Тем кто хочет поддерживать старые девайся типа iPad2 экономия по RAM все еще очень критичена. А по fps выигрышь будет даже на iPad Air2, хоть и не значительный. Для мобильных GPU рисование полупрозрачной геометрии является узким местом, а вот количество треугольников уже давно — нет. Исключение, разве что Tegra K1.


    1. DmitryMry
      31.07.2016 09:23

      Забавно то, что при гигабайте памяти приложение может вылетать с ошибкой нехватки памяти при попытке занять чуть более 200 Мб памяти. То есть, далеко не вся память устройства доступна игре. Об этом как-то было на хабре (но не помню, статья или просто в комментариях к статье какой-то, но всё же, по-моему, статья; может кто-нибудь подскажет ссылку?). При разработке одной игры пришлось порядка двух месяцев потратить на оптимизацию графики, системы анимаций и т.п., а графику резать на отдельные кусочки (включая отражение, вращение и т.п.), чтобы игра стабильно работала вплоть до вторых ipad'ов и на android планшетах/телефонах среднего ценового диапазона. Ну и пару раз приходилось оптимизировать чужие проекты, ровно с той же самой проблемой. Было пару лет назад, но по памяти как раз и принимали в расчёт планшеты/телефоны минимум с гигом памяти (не припомню, чтобы у нас на тестах вообще хоть одно устройство было с меньшим объёмом).


      1. progman_rus
        31.07.2016 14:43

        пишем под андроид. NDK
        И с такими лимитами по памяти не сталкивались.


  1. sburavtsov
    27.07.2016 19:41
    +1

    TexturePacker умеет это делать, может кому пригодится: Статья о том как


    1. Mishok2000
      27.07.2016 20:35
      +1

      Исключительно для cocos2d-x > 3.9 — важно отметить


      1. Agent_Smith
        02.08.2016 10:47

        Разве? А тут написано, что может и для Unity, и вообще это дефолтный режим в 4й версии.


        1. playermet
          02.08.2016 20:24
          +1

          Он имел в виду, что cocos2d до версии 3.9 не может принимать такие атласы.


  1. mib
    27.07.2016 20:00
    -4

    Я думаю, что это экономия на спичках: не занятые рисунком пикселы — окрашены в один и тот-же цвет, это прекрасно сжимается само по себе, разве нет?


    1. IgeNiaI
      27.07.2016 20:20

      Оно сжимается при сохранении на диск, а в оперативной памяти текстуры хранятся в исходном виде, так иначе придется разжимать по 60 раз в секунду.


    1. playermet
      27.07.2016 20:37

      > окрашены в один и тот-же цвет, это прекрасно сжимается само по себе
      Для DXT например без разницы, один там цвет или нет, он делит изображение на блоки 4х4 пикселя, и на каждый выделяет одинаковое число бит. Для PVRTC другой принцип, но тоже фиксированное число бит на точку.

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


    1. Chaos_Optima
      28.07.2016 10:32

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


  1. Nagg
    27.07.2016 20:18

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


    1. Biga
      27.07.2016 20:50
      +1

      В случае PNG размер остаётся практически тот же. В случае PVRTC, естественно, уменьшается, причём профит существенный. Впрочем, упаковка в zip может нивелировать разницу, если подшаманить над PVRTC — забить пустые пиксели нулями, а не мусором.


  1. Kain_Haart
    27.07.2016 21:12
    +1

    Возможно имеет смысл после того как текстуры уже уложены попробовать сделать уменьшение количества треугольников за счет оставшегося пустого места между текстурами?


    1. ad1Dima
      28.07.2016 09:58

      Поддерживаю. В примере точно можно значительно уменьшить количество полигонов.


  1. Tibr
    27.07.2016 21:52

    Занимательно! Раз в компании движок собственной разработки, то было бы интересно почитать о том, как боролись с производительностью в контексте проблемы overdraw.


  1. domix32
    27.07.2016 22:07
    +2

    Немного странно наблюдать наличие почти белых треугольников. Потенциальное место для тюнинга


  1. playermet
    27.07.2016 22:18
    +1

    Подкину еще идею.

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

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

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

    Осталось только проверить производительность на практике.


    1. meduzik
      28.07.2016 00:11

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

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

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

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


      1. playermet
        28.07.2016 08:28

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

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

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

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


        1. Chaos_Optima
          28.07.2016 10:36

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

          Ветвлением? Ветвление на шейдере просаживает производительность намного сильнее чем семплинг 2 текстур, но и сам семплинг 2 текстур дороже одной.


          1. vagran
            28.07.2016 10:55

            Я там ниже свою идею описал, как брать две без ветвления. В принципе, сейчас мультитекстурирование повсеместно используется. В OpenGL ES минимум восемь слотов стандартом предусмотрено. Есть подозрение, что это должно быть оптимизировано и не должно критично влиять на производительность. Судя по всему, требуются тесты, чтобы оценить, стоит ли свеч эта идея с двумя текстурами.


            1. Chaos_Optima
              28.07.2016 10:58

              Не стоит, семплинг 2 текстур всегда дороже семплинга 1 текстуры, а с подобной упаковкой выигрыш по памяти будет не таким уж и существенным.


          1. playermet
            28.07.2016 11:00

            > Ветвление на шейдере просаживает производительность
            Разве ветвление по значению uniform переменной не оптимизируется? Эппловский гайд считает этот вариант приемлемым.


            1. playermet
              28.07.2016 11:04

              ^Игнорируйте ответ выше, я по незнанию спутал uniform с атрибутом.


        1. meduzik
          28.07.2016 15:37

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

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

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

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


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

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


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

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

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

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


      1. vagran
        28.07.2016 10:49

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


  1. s1dd0k
    28.07.2016 00:52
    +2

    Очень крутая тема, жалко не open-source, а то добавил бы себе в движок.

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

    В Cocos2D-X были добавлены Polygon Sprites и мы работаем над тем, чтобы добавить функционал и в наш движок.


  1. gaki
    28.07.2016 04:06
    -1

    Я что-то не догоняю — в статье речь про 2D или про 3D игру?


    1. vagran
      28.07.2016 10:58
      +1

      2Д, очевидно, раз речь о спрайтах.


      1. gaki
        28.07.2016 11:01
        -2

        Ну вот и я смотрю — на иллюстрациях, вроде бы, спрайты. А в статье почему-то речь о «текстурах».


        1. vagran
          28.07.2016 19:56
          +1

          Просвещаю — в современных устройствах (ПК, мобильные, любые) графический конвеер один, и унифицирован под вывод 3Д графики в том числе. 2Д графика выводится текстурированными треугольниками в ортографической проекции.


          1. gaki
            29.07.2016 03:59
            +1

            Это что же — каждый раз, когда я открываю очередную фоточку в графическом редакторе, он создаёт под неё прямоугольник из двух треугольников на всё окно?


            1. vagran
              29.07.2016 09:30

              Зависит от деталей реализации. Может и не из двух. Может на всё окно один прямоугольник, а рисуется всё в текстуру. Вариантов много, конвеер один.


              1. gaki
                29.07.2016 09:42
                +2

                «Век живи, век учись, дураком помрёшь.» Спасибо.


  1. s_shestakov
    28.07.2016 08:15
    +1

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

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

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

    Собственно, пару лет назад мы все это и затеяли и именно чтобы решить проблему с оперативной памятью. Игра рассчитана не только на флагманские устройства. До тех пор пока устройства с 512 Мб памяти есть у достаточно большого числа пользователей, нам придется с ними считаться. А то QA быстро перечислит нам список устройств на которых игра падает по памяти.
    По FPS действительно особенного выигрыша не получается. В примере тут cocos2d-x-performance-optimization
    подобрана немного жульническая картинка. Если бы дядя не так широко расставлял руки, разница overdraw была бы куда меньше и FPS был бы примерно такой же. При переходе на полигональные атласы на FPS начинает влиять слишком много факторов, и нельзя точно сказать что он во всех случаях упадет или во всех случаях вырастет.


    1. playermet
      28.07.2016 09:58

      Из-за особенностей работы OpenGL ему могут понадобиться пиксели за пределами рисуемого треугольника. Если там не будут пиксели исходной текстуры — появление артефактов неизбежно. Как правило достаточно 1-2 пикселя, но бывает что надо и больше. Сохранение исходной текстуры в атласе в неизменном виде гарантирует что артефактов не будет.
      Но на приведенном в статье атласе текстуры касаются друг друга точками, а иногда и целыми ребрами. Разве это не должно давать те же самые артефакты, в случае их возможности?
      В примере тут cocos2d-x-performance-optimization подобрана немного жульническая картинка. Если бы дядя не так широко расставлял руки, разница overdraw была бы куда меньше и FPS был бы примерно такой же.
      Ну так и в примере в статье у большинства объектов половина площади сэкономлена.


      1. s_shestakov
        28.07.2016 10:31
        +1

        Перед тем как разбивать текстуру на треугольники, к ней уже применена 1-пиксельная обводка. Вообще, обводка обязательна как для полигональных, так и для обычных атласов. Борьба с артефактами OpenGL это отдельная и сложная тема, и возможно про нее будет отдельная статья. Stay tuned!


        1. playermet
          28.07.2016 10:43

          Перед тем как разбивать текстуру на треугольники, к ней уже применена 1-пиксельная обводка.
          Это то понятно. Мысль была в том, что если в текущей реализации (запас минимум 2 пикселя до соседа) «гарантированно» нет артефактов, то при разбиении на треугольники (запас минимум 2 пикселя до фона/соседа) артефактов тоже не должно быть. Ведь расстояние до потенциальных пикселей неправильного цвета одинаковое.


          1. vagran
            28.07.2016 11:03

            Чего-то не верю я в случайные артефакты. Скорее всего, когда речь о требуемых «и даже больше пикселей», то имеется в виду построение мип-мапов, а их, очевидно, нельзя использовать в атласах.


            1. s_shestakov
              28.07.2016 14:51

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


              1. vagran
                28.07.2016 19:58

                А, ну тогда понятно. Я предполагал, что спрайты выводятся «пискель в пиксель». Для разных разрешений ведь можно подготовить разные атласы.


          1. s_shestakov
            28.07.2016 11:25
            +1

            Не совсем так. Текстуры по краям уходят в альфу, и там 1-пиксельной обводки достаточно. А если распиливать текстуру на треугольники то получится что-то типа «склейки» текстур, и там все сложнее. Кроме того, надо учитывать что при конвертации в pvrtc текстура обрабатывается блоками 4x4 и если в этот блок попадут треугольники от разных текстур, то все опять же будет некрасиво.То есть да, теоретически можно попробовать разбить текстуру на треугольники с обводкой, но практически могут возникнуть проблемы при отрисовке и плюс неизвестно насколько велик будет реальный выигрыш памяти, потому что придется применять обводку к каждому треугольнику. Тут надо отдельно экспериментировать.


  1. engune
    28.07.2016 17:09

    А какая у Вас прослойка в коде для Mac->OpenGL ES, Windows->OpenGL ES (SDL, Angle, GLFW)? Для нативных платформ ясно iOS и Android — там вызовы стандартны. Интересует именно этап отладки кода на Windows\OSX.


    1. s_shestakov
      28.07.2016 18:11

      Под Windows — Angle. Под OSX — просто вызовы OpenGL.


      1. engune
        28.07.2016 21:27

        Еще вопрос — в варианте OSX — это симулятор iOS или у Вас одновременно OpenGL ES и OpenGL используется в проекте? Насколько сложно транслировать OpenGL ES в OpenGL?


        1. s_shestakov
          28.07.2016 22:21

          OpenGL ES — это подмножество OpenGL, ничего не надо транслировать


  1. mrguardian
    28.07.2016 19:01

    Раз уж вы всё равно разбиваете атлас на меши, так почему не использовать те же треугольники для сортировки прозрачных/непрозрачных частей для оптимизации отрисовки и блендинга?