Как решить большие требования к видеопамяти в 2D-игре?
Мы разрабатываем 2D-игру (Factorio) на языке Allegro C / C ++, и мы сталкиваемся с проблемой увеличения требований к видеопамяти по мере увеличения игрового контента.
В настоящее время мы собираем всю информацию об изображениях, которые будут использоваться первыми, обрезаем все эти изображения в максимально возможной степени и организуем их в большие атласы настолько плотно, насколько это возможно. Эти атласы хранятся в видеопамяти, размер которой зависит от системных ограничений; в настоящее время обычно это 2 изображения размером до 8192x8192, поэтому им требуется от 256 до 512 МБ видеопамяти.
Эта система работает очень хорошо для нас, поскольку с некоторыми пользовательскими оптимизациями и разделением потока рендеринга и обновления мы можем рисовать десятки тысяч изображений на экране со скоростью 60 кадров в секунду; у нас на экране много объектов, и разрешение большого уменьшения является критическим требованием. Поскольку мы хотели бы добавить больше, возникнут некоторые проблемы с требованиями к видеопамяти, поэтому эта система не может удержаться.
Одна из вещей, которую мы хотели попробовать, - иметь один атлас с наиболее распространенными изображениями, а второй - в качестве кэша. Изображения будут перемещены туда из растрового изображения памяти, по требованию. У этого подхода есть две проблемы:
- Рисование из растрового изображения памяти в растровое видео очень болезненно, в аллегро.
- В allegro невозможно работать с растровым изображением, отличным от основного потока, поэтому его практически невозможно использовать.
Вот некоторые дополнительные требования, которые у нас есть:
- Игра должна быть детерминированной, поэтому проблемы с производительностью и временем загрузки никогда не могут изменить игровое состояние.
- Игра в реальном времени и скоро станет многопользовательской. Нам нужно избегать даже самого маленького заикания любой ценой.
- Большая часть игры представляет собой один непрерывный открытый мир.
Тест состоял из рисования 10 000 спрайтов в партии для размеров от 1x1 до 300x300, несколько раз для каждой конфигурации. Я делал тесты на Nvidia Geforce GTX 760.
- От растрового изображения к графическому растровому изображению потребовалось 0,1 мкс на спрайт, когда исходное растровое изображение не менялось между отдельными растровыми изображениями (вариант атласа); размер не имел значения
- Растровое изображение видео к растровому изображению видео, в то время как исходное растровое изображение переключалось между чертежами (не атласный вариант), занимало 0,56 мкс на спрайт; размер тоже не имел значения.
- Память растрового изображения на видео растрового рисунка была действительно подозрительной. Размеры от 1x1 до 200x200 занимали 0.3us за растровое изображение, поэтому не так уж и медленно. Для больших размеров время начало увеличиваться очень резко, с 9us для 201x201 до 3116us для 291x291.
Использование атласа увеличивает производительность более чем в 5 раз. Если у меня было 10 мс для рендеринга, с атласом я ограничен 100 000 спрайтов на кадр, а без него - 20 000 спрайтов. Это было бы проблематично.
Я также пытался найти способ проверить сжатие растрового изображения и растровый формат 1bpp для теней, но я не смог найти способ сделать это в allegro.
источник
Ответы:
У нас похожий случай с нашей RTS (KaM Remake). Все юниты и дома являются спрайтами. У нас есть 18 000 спрайтов для юнитов, домов и местности, а также еще ~ 6 000 для командных цветов (в качестве масок). Длинно растянутые у нас также есть около 30 000 символов, используемых в шрифтах.
Итак, есть некоторые оптимизации против используемых вами атласов RGBA32:
Сначала разбейте пул спрайтов на множество небольших атласов и используйте их по требованию, как описано в других ответах. Это также позволяет использовать разные методы оптимизации для каждого атласа в отдельности . Я подозреваю, что у вас будет немного меньше впустую ОЗУ, потому что при упаковке с такими огромными текстурами обычно есть неиспользуемые области внизу;
Попробуйте использовать палитровые текстуры . Если вы используете шейдеры, вы можете «применить» палитру в коде шейдеров;
Возможно, вы захотите добавить опцию использования RGB5_A1 вместо RGBA8 (если, например, тени для шахматной доски подходят для вашей игры). По возможности избегайте 8-битной альфы и используйте RGB5_A1 или эквивалентные форматы с меньшей точностью (например, RGBA4), они занимают половину пространства;
Убедитесь, что вы плотно упаковываете спрайты в атласы (см. Алгоритмы упаковки бинов), вращайте спрайты, когда это необходимо, и посмотрите, можете ли вы перекрывать прозрачные углы для спрайтов ромба;
Вы можете попробовать форматы аппаратного сжатия (DXT, S3TC и т. Д.) - они могут значительно сократить использование ОЗУ, но проверить наличие артефактов сжатия - на некоторых изображениях разница может быть незаметной (вы можете использовать это выборочно, как описано в первом пункте), а на некоторых - очень произносится. Различные форматы сжатия вызывают разные артефакты, поэтому вы можете выбрать тот, который лучше всего подходит для вашего художественного стиля.
Обратите внимание на разделение больших спрайтов (конечно, не вручную, а в пакете атласа текстуры) на статический фоновый спрайт и меньшие спрайты для анимированных частей.
источник
Прежде всего вам нужно использовать больше, меньшие текстурные атласы. Чем меньше у вас текстур, тем сложнее и жестче будет управление памятью. Я бы предложил размер атласа 1024, в этом случае у вас было бы 128 текстур вместо 2, или 2048, и в этом случае у вас было бы 32 текстуры, которые вы могли бы загружать и выгружать по мере необходимости.
Большинство игр делают это управление ресурсами, имея границы уровней, в то время как на экране загрузки отображаются все ресурсы, которые больше не нужны на следующем уровне, выгружаются, а ресурсы, которые необходимы, загружаются.
Другой вариант - загрузка по требованию, которая становится необходимой, если границы уровней нежелательны или даже один уровень слишком велик, чтобы поместиться в памяти. В этом случае игра будет пытаться предсказать, что игрок увидит в будущем, и загрузить это в фоновом режиме. (Например: вещи, которые в настоящее время находятся на расстоянии 2 экранов от плеера.) В то же время вещи, которые больше не использовались, будут выгружены.
Однако есть одна проблема: что происходит, когда происходит нечто неожиданное, чего игра не смогла предвидеть?
источник
Ого, а это, я полагаю, огромное количество анимационных спрайтов, сгенерированных из трехмерных моделей?
Вы действительно не должны делать эту игру в сыром 2D. Когда у вас фиксированная перспектива, происходит забавная вещь, вы можете легко смешивать предварительно визуализированные спрайты и фоны с живыми 3D-моделями, которые активно использовались в некоторых играх. Если вам нужны такие прекрасные анимации, это кажется самым естественным способом сделать это. Получите 3D-движок, настройте его для использования изометрической перспективы и визуализируйте объекты, для которых вы продолжаете использовать спрайты, в виде простых плоских поверхностей с изображением на них. И вы можете использовать сжатие текстур с 3D-движком, что само по себе является большим шагом вперед.
Я не думаю, что загрузка и выгрузка будут вам полезны, так как вы можете видеть почти все на экране одновременно.
источник
Во-первых, найдите наиболее эффективный формат текстур, который вы можете, оставаясь довольным визуальными эффектами игры, будь то RGBA4444, или сжатие DXT и т. Д. Если вас не устраивают артефакты, сгенерированные в альфа-сжатое изображение DXT, будет ли он жизнеспособным? сделать изображения непрозрачными, используя сжатие DXT1 для цвета в сочетании с 4 или 8-битной текстурой маскировки оттенков серого для альфы? Я полагаю, вы бы остаться на RGBA8888 для графического интерфейса.
Я выступаю за разделение текстур на более мелкие текстуры в любом формате, который вы выбрали. Определите элементы, которые всегда на экране и, следовательно, всегда загружены, это могут быть ландшафты и атласы GUI. Затем я бы разбил оставшиеся элементы, которые обычно отображаются вместе, как можно больше. Я не думаю, что вы потеряете слишком много производительности, даже если наберете 50-100 вызовов на ПК, но поправьте меня, если я ошибаюсь.
Следующим шагом будет создание версий этих текстур в формате mipmap, как указано выше. Я не буду хранить их в одном файле, но отдельно. Таким образом, вы получите в итоге версии 1024x1024, 512x512, 256x256 и т. Д. Каждого файла, и я буду делать это до тех пор, пока не достигну самого низкого уровня детализации, который я когда-либо хотел бы отобразить.
Теперь, когда у вас есть отдельные текстуры, вы можете создать систему уровня детализации (LOD), которая загружает текстуры для текущего уровня масштабирования и выгружает текстуры, если они не используются. Текстура не используется, если отображаемый элемент не отображается на экране или не требуется для текущего уровня масштабирования. Попробуйте загрузить текстуры в видеопамять в потоке, отдельном от потоков обновления / рендеринга. Вы можете отображать самую низкую LOD текстуру, пока не загрузите требуемую. Иногда это может привести к видимому переключению между текстурой с низкой детализацией / высокой детализацией, но я думаю, что это произойдет только тогда, когда вы выполняете чрезвычайно быстрое уменьшение и при перемещении по карте. Вы можете сделать систему интеллектуальной, попытавшись предварительно загрузить то место, куда, по вашему мнению, человек будет перемещаться, или увеличивать и загружать как можно больше в рамках текущих ограничений памяти.
Это то, что я хотел бы проверить, помогает ли это. Я полагаю, что для получения экстремальных уровней масштабирования вам неизбежно понадобится система LOD.
источник
Я считаю, что лучший подход - разделить текстуру на несколько файлов и загружать их по требованию. Возможно, ваша проблема в том, что вы пытаетесь загрузить большие текстуры, которые понадобятся вам для полноценной 3D сцены, и вы используете для этого Allegro.
Для большого уменьшения, которое вы хотите применить, вы должны использовать mipmaps. Мип-карты - это версии текстур с более низким разрешением, которые используются, когда объекты находятся достаточно далеко от камеры. Это означает, что вы можете сохранить свой 8192x8192 как 4096x4096, а затем еще 2048x2048 и т. Д., И вы переключитесь на меньшее разрешение, тем меньше вы увидите спрайт на экране. Вы можете сохранить их как отдельные текстуры или изменить их размер при загрузке (но создание мипмапов во время выполнения увеличит время загрузки вашей игры).
Надлежащая система управления будет загружать требуемые файлы по требованию и освобождать ресурсы, когда их никто не использует, плюс другие вещи. Управление ресурсами является важной темой в разработке игр, и вы сводите свое управление к простому отображению координат в единую текстуру, которая практически не имеет никакого управления.
источник
Я рекомендую создавать больше файлов атласа, которые можно сжимать с помощью zlib и выводить из сжатия для каждого атласа, и, имея больше файлов атласа и файлов меньшего размера, вы можете ограничить объем активных данных изображения в видеопамяти. Кроме того, реализуйте механизм тройного буфера, чтобы подготовить каждый кадр рисования раньше, и у вас будет возможность завершить быстрее, чтобы заикания не появлялись на экране.
источник