company_banner
27 июля 2016 в 17:53

Как оптимизировать игру с помощью полигональных атласов

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

Например, 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.
Автор: @Kuen
Playrix
рейтинг 24,03
Разработчик мобильных free-to-play игр
Похожие публикации

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

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

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

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

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

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


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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

          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 до 0.5 — брать цвет из непрозрачной текстуры, от 0.5 до 1.0 — брать из прозрачной, замапив альфу на интервал 0.0 до 1.0 (т.е. результирующая альфа уменьшается на один бит, что не очень критично). Соответственно, в непрозрачных треугольниках второй набор координат ставится в специально выделенный пиксель прозрачной текстуры с альфой 0.0. При таком подходе в шейдере можно обойтись без if'ов, и все спрайты рисуются однообразно с минимальным количество DC.
  • +2
    Очень крутая тема, жалко не open-source, а то добавил бы себе в движок.

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

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

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

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

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

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка