2D графика - зачем использовать спрайт-листы?

62

Я видел много примеров того, как визуализировать спрайты из таблицы спрайтов, но я не понял, почему это самый распространенный способ работы со спрайтами в 2d играх.

Я начал с 2d рендеринга спрайтов в нескольких демонстрационных приложениях, которые я сделал, имея дело с каждым кадром анимации для каждого данного типа спрайта в качестве своей собственной текстуры - и эта коллекция текстур хранится в словаре. Кажется, это работает для меня и вполне подходит для моего рабочего процесса, так как я обычно делаю свои анимации в виде файлов gif / mng, а затем извлекаю кадры в отдельные png-файлы.

Есть ли заметное преимущество в производительности при рендеринге с одного листа, а не с отдельных текстур? С современным оборудованием, способным выводить миллионы полигонов на экран сто раз в секунду, имеет ли это значение для моих двухмерных игр, которые имеют дело с несколькими десятками прямоугольников размером 50x100 пикселей?

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

Я подозреваю, что есть некоторые очень веские причины, по которым большинство разработчиков 2D-игр, похоже, используют их, я просто не понимаю, почему.

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

Ответы:

69

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

Также учтите, что размеры текстур обычно имеют степень 2. Поэтому, если у вас Sprite размером 50x100 пикселей, вы будете размещать текстуры размером 64x128 пикселей или в худшем случае 128x128 пикселей. Это просто трата графической памяти. Лучше упакуйте все спрайты в текстуру 1024x1024px, что позволит использовать спрайты 20x10, и вы потеряете только 24 пикселя по горизонтали и вертикали. Иногда даже спрайты разных размеров объединяются в один огромный спрайт-лист, чтобы использовать текстуру максимально эффективно.

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

bummzack
источник
1
Спасибо, я не учел ни одного из этих пунктов. Вы знаете примерно, каково ограничение на количество доступных текстур? Я никогда не видел, чтобы этот стат упоминался в графических спецификациях - действительно ли лимит равен объему графической памяти?
Коломбо
Обычно пределом будет память GPU, да. Я думал, что есть некоторые ограничения на количество текстур на старых картах, но я не могу найти ссылку, которая доказывает эту точку зрения.
bummzack
12
Даже если количество не ограничено, трафик шины для перемещения большого количества маленьких изображений не так эффективен, как одна (или несколько) большая передача (и).
Мордачай
5
Хорошие моменты, но самый важный аспект - минимизировать задержки конвейера, уменьшая количество вызовов отрисовки. Графические процессоры предпочитают небольшое количество больших рабочих мест, а не большое количество мелких рабочих мест. Помещение всех ваших спрайтов в небольшое количество больших листов означает, что вы можете настроить текстовую стадию один раз и рисовать множество спитов одновременно в пакете. Spritebatch делает это для вас. Если у вас есть несколько спрайтов, убедитесь, что spritebatch сортировал по текстуре
Cubed2D
Добавление сжатия текстур превратит любое свободное пространство в очень маленькое количество накладных расходов, вдобавок ко всему
Joe Plante
36

Я бы сказал, что аргументом для использования будет возможность визуализировать несколько вещей за один вызов отрисовки. Возьмем, к примеру, рендеринг шрифта, без таблицы спрайтов вам нужно будет рендерить каждый символ с отдельной заменой текстуры, а затем вызовом отрисовки. Бросьте их в таблицу спрайтов, и вы сможете рендерить целые предложения одним вызовом отрисовки (разностные символы в шрифте выбираются просто указанием различных UV для углов). Это гораздо более эффективный способ рендеринга, когда очень много затрат на рендеринг на многих платформах связано с слишком большим количеством вызовов API.

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

Роджер Перкинс
источник
2
+1 Прикасается к розыгрышам. (что-то, чего я не увидел в принятом ответе)
Майкл Коулман,
11

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

тетрада
источник
2
Это определенно все еще имеет значение. Графическое оборудование было оптимизировано для выталкивания множества полигонов для небольшого количества детализированных моделей (например, персонажей), потому что большинство игр стремятся в этом направлении. В результате вам нужно выдвинуть как можно больше полигонов в каждом из пары десятков вызовов отрисовки. Игровые движки, которые визуализируют большие сцены, такие как города, часто объединяют несколько текстур в одну на лету, чтобы уменьшить количество вызовов отрисовки.
Джоккинг
Я определенно задаюсь вопросом, выигрывают ли текстуры спрайтов от множества текстур в 3D-оборудовании, плюс операции бит-блиттинга.
Джо Планте
7

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

Кроме того, я кодирую в AS3, и для каждого файла изображения требуется отдельная инструкция по встраиванию. Я предпочел бы иметь одно встраивание всей таблицы спрайтов, а не 24 встраивания для всех фреймов, даже если я использую отдельный класс ресурсов.

Грегори Эйвери-Вейр
источник
1
+1. Хотя OP не использует Flash, встраивание или загрузка также являются преимуществом для спрайт-листов. Это также актуально, когда ресурсы загружаются с веб-сервера, где 1 запрос предпочтительнее, чем несколько сотен запросов (для каждого «фрейма»).
bummzack
Определенно вы хотите уменьшить количество запросов к серверу для онлайн-игры. Мы рассмотрели вопрос о том, лучше ли помещать изображения в .swf или в таблицу спрайтов, потому что оба они достигают этой главной цели (в итоге мы решили, что таблицы спрайтов менее трудоемки для наших художников).
Джоккинг
7

Другая причина использования спрайт-листов с XNA заключается в том, что при разработке Xbox360 существует известная проблема, связанная с медленным временем развертывания / загрузки по сравнению с количеством файлов в вашем проекте. Таким образом, объединение множества небольших файлов изображений в один файл изображения поможет решить эту проблему.

Джордж Клингерман
источник
5

Я не буду здесь упоминать память / графический процессор, поскольку таких ответов достаточно.

Вместо этого он может прояснить ваше искусство, когда вы работаете над ним - и после. Вместо того, чтобы пролистывать 10 изображений, чтобы увидеть цикл ходьбы, вы можете увидеть все кадры за один раз. Если вы хотите распространить это, у вас есть только 1 файл вместо 10.

Кроме того, более чистым (с организационной точки зрения) было бы использовать файл character.png и врага.png от персонажа от 01 до до персонажа 10, затем от персонажа от 01 до персонажа 05 и так далее.

Коммунистическая утка
источник
4

Графическая память не имеет раздутого управления памятью, как файловые системы на дисках; обратный случай - он прост и быстр .

Одним из таких простых и быстрых способов управления памятью может быть то же самое, что иметь только один гигантский спрайт 4096x4096, который разрезается на несколько меньших спрайтов путем уменьшения его ширины и / или высоты вдвое. (Существует похожая одномерная техника управления памятью, но я забыл название.) В конце концов, должна быть выполнена дефрагментация.

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

комонада
источник
3

Также не забывайте, что операции ввода / вывода могут спасти вас во время инициализации. Если вы загружаете с CD-привода, каждый файл является дополнительным вызовом ввода / вывода, и может потребоваться некоторое время для поиска (современные жесткие диски занимают около 15 мс на файл, CD, может быть, 50-100 мс?). Это может сэкономить вам много времени инициализации, так как нужно выбрать только один файл. Это также позволяет вашей ОС кэшировать эти операции ввода-вывода, если ваше приложение выполняет что-то еще (например, ожидание графического процессора).

маркировка
источник
1
С другой стороны, если вы хотите решить проблему поиска файлов, все спрайты могут быть упакованы в один файл в произвольном формате. Большинство игр делают это.
tigrou
0

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

На мой взгляд, проще в написании кода. У меня просто есть массив объектов, таких как:

sheet = {
    img: (the actual image),
    rects: [
        {x:0, y:0, w:32, h:32},
        {x:32, y:0, w:32, h:32},
        {x:64, y:0, w:32, h:32}
    ]
};

sheets.push(sheet);

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

bridgerrholt
источник