Рисование множества плиток с помощью OpenGL, современный способ

35

Я работаю над небольшой компьютерной игрой на основе плитки / спрайтов с командой людей, и у нас возникают проблемы с производительностью. Последний раз, когда я использовал OpenGL, был где-то в 2004 году, я учил себя, как использовать основной профиль, и я немного запутался.

Мне нужно рисовать около 250-750 48x48 плиток на экране каждый кадр, а также около 50 спрайтов. Плитки изменяются только при загрузке нового уровня, а спрайты все время меняются. Некоторые из плиток состоят из четырех частей 24х24, и большинство (но не все) спрайтов имеют тот же размер, что и плитки. Многие тайлы и спрайты используют альфа-смешение.

Прямо сейчас я делаю все это в непосредственном режиме, который я знаю, это плохая идея. Тем не менее, когда один из членов нашей команды пытается запустить его, он получает очень плохую частоту кадров (~ 20-30 кадров в секунду), и это намного хуже, когда есть больше плиток, особенно когда много таких плиток являются такими, которые режутся на кусочки. Все это заставляет меня думать, что проблема заключается в количестве колл-вызовов.

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

ПЛИТКА:

  1. Когда уровень загружен, нарисуйте все плитки один раз в буфере кадров, прикрепленном к большой сигнальной текстуре, и просто нарисуйте большой прямоугольник с этой текстурой на каждом кадре.
  2. Поместите все плитки в статический буфер вершин, когда уровень загружен, и нарисуйте их таким образом. Я не знаю, есть ли способ рисовать объекты с разными текстурами с помощью одного вызова glDrawElements, или я хотел бы это сделать. Может быть, просто положить все плитки в большую гигантскую текстуру и использовать забавные текстурные координаты в VBO?

спрайты:

  1. Нарисуйте каждый спрайт с отдельным вызовом glDrawElements. Кажется, это связано с переключением текстур, что, как мне сказали, плохо. Полезны ли здесь массивы текстур?
  2. Как-нибудь использовать динамическое VBO. Тот же вопрос текстуры, что и в пункте 2 выше.
  3. Точечные спрайты? Это, наверное, глупо.

Являются ли какие-либо из этих идей разумными? Есть ли хорошая реализация, где я мог бы посмотреть?

Nic
источник
Если плитки не двигаются и не изменяются и выглядят одинаково на всем уровне, вам следует использовать первую идею - буфер кадров. Это будет наиболее эффективно.
zacharmarz
Попробуйте использовать текстурный атлас, чтобы вам не приходилось переключать текстуры, но оставляйте все остальное таким же. Теперь как их частота кадров?
user253751

Ответы:

25

Самый быстрый способ визуализации плиток - это упаковка данных вершин в статический VBO с индексами (как указывает glDrawElements). Запись его в другое изображение совершенно не нужна и потребует намного больше памяти. Переключение текстур ОЧЕНЬ дорого, поэтому вы, вероятно, захотите упаковать все плитки в так называемый Атлас Текстур и дать каждому треугольнику в VBO правильные координаты текстуры. Исходя из этого, это не должно быть проблемой для рендеринга 1000, даже 100000 плиток, в зависимости от вашего оборудования.

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

Вы можете посмотреть на небольшой прототип, который я сделал на C ++ с помощью OpenGL: Particulate.

Я рендерил около 10000 точечных спрайтов, со средней частотой 400 кадров в секунду на обычной машине (Quad Core @ 2.66GHz). Это процессор с ограничением, это означает, что видеокарта может рендерить еще больше. Обратите внимание, что здесь я не использую атласы текстур, поскольку у меня есть только одна текстура для частиц. Частицы визуализируются с помощью GL_POINTS, и шейдеры вычисляют фактический размер четырехугольника, но я думаю, что есть также Quad Renderer.

О, да, если у вас нет квадрата и вы используете шейдеры для наложения текстур, GL_POINTS довольно глупый. ;)

Marco
источник
Спрайты меняют свои позиции и какую текстуру они используют, и большинство из них делают это каждый кадр. Также спрайты и существа создаются и уничтожаются очень часто. С этими вещами может справиться потоковая отрисовка VBO?
Nic
2
Потоковое рисование в основном означает: «Отправьте эти данные на видеокарту и выбросьте их после рисования». Таким образом, вы должны отправлять данные снова каждый кадр, и это означает, что не имеет значения, сколько спрайтов вы визуализируете, какую позицию они имеют, какие координаты текстуры или какой цвет. Но, разумеется, отправка всех данных за один раз и позволить графическому процессору это ОЧЕНЬ быстрее, чем непосредственный
Марко
Это все имеет смысл. Стоит ли использовать для этого индексный буфер? Единственные вершины, которые будут повторяться, это два угла от каждого прямоугольника, верно? (Насколько я понимаю, индексы - это разница между glDrawElements и glDrawArrays. Это верно?)
Nic
1
Без индексов вы не можете использовать GL_TRIANGLES, что, как правило, плохо, поскольку этот метод рисования является гарантированным с максимальной производительностью. Кроме того, реализация GL_QUADS устарела в OpenGL 3.0 (источник: stackoverflow.com/questions/6644099/… ). Треугольники - это родная сетка любой видеокарты. Таким образом, вы «используете» на 2 * 6 байт больше, чтобы сохранить 2 выполнения вершинного шейдера и vertex_size * 2 байт. Таким образом, вы можете сказать, что ВСЕГДА лучше.
Марко
2
Ссылка на Particulate не работает ... Не могли бы вы предоставить новую?
SWdV
4

Даже при таком количестве вызовов отрисовки вы не должны видеть такого снижения производительности - немедленный режим может быть медленным, но не таким медленным (для справки, даже дорогой старый Quake может управлять несколькими тысячами непосредственных вызовов в кадре без падения) так плохо).

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

Максимус Минимус
источник
Я провел некоторое профилирование, хотя это неудобно, потому что проблемы с производительностью возникают не на одной машине с разработкой. Я немного скептически отношусь к тому, что проблема в другом месте, потому что проблемы определенно увеличиваются с количеством плиток, и плитки буквально ничего не делают, кроме как быть нарисованными.
Nic
Как насчет изменений состояния? Вы группируете свои непрозрачные плитки по штатам?
Максимус Минимус
Это возможность. Это определенно заслуживает большего внимания с моей стороны.
Nic
2

Хорошо, так как мой последний ответ вроде как вышел из-под контроля, вот новый, который может быть более полезным.


О 2D-перформансе

Сначала несколько общих советов: 2D не требует современного оборудования, будет работать даже неоптимизированный код. Это не означает, что вы должны использовать промежуточный режим, хотя бы убедитесь, что вы не меняете состояния, когда они не нужны (например, не связывайте новую текстуру с помощью glBindTexture, когда та же самая текстура уже привязана, если проверка ЦП на тонны быстрее, чем вызов glBindTexture) и не использовать что-то настолько неправильное и глупое, как glVertex (даже glDrawArrays будет намного быстрее и не сложнее в использовании, хотя и не очень "современным"). С этими двумя очень простыми правилами время кадра должно быть как минимум до 10 мс (100 кадров в секунду). Теперь, чтобы получить еще большую скорость, следующим логическим шагом является пакетирование, например, объединение как можно большего количества вызовов отрисовки в один, для этого вам следует рассмотреть реализацию текстурных атласов, Таким образом, вы можете минимизировать количество текстурных привязок и, таким образом, увеличить количество прямоугольников, которые вы можете нарисовать за один вызов, до большого количества. Если у вас сейчас нет 2ms (500fps), вы делаете что-то не так :)


Плитка карты

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

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


Спрайты

Спрайты требуют большой гибкости, что затрудняет их оптимизацию, помимо тех, которые обсуждались в разделе «О производительности 2D». И если вы не хотите, чтобы на экране одновременно находилось десять тысяч спрайтов, это, вероятно, не стоит усилий.

API-Beast
источник
1
И даже если у вас есть десять тысяч спрайтов, современное оборудование должно запускать его с приличной скоростью :)
Marco
@ API-Beast ждать что? Как вы рассчитываете текстуры UV в фрагментном шейдере? Ты не должен был посылать ультрафиолетовые лучи фрагментному шейдеру?
HgMerk
0

Если все остальное не удается...

Установите метод рисования триггера. Обновляйте только каждый другой спрайт за раз. Хотя даже с VisualBasic6 и простыми бит-блитами вы можете активно рисовать тысячи спрайтов на кадр. Возможно, вам следует изучить эти методы, так как ваш прямой метод рисования спрайтов, похоже, не работает. (Похоже, вы используете «метод рендеринга», но пытаетесь использовать его как «игровой метод». Рендеринг - это ясность, а не скорость.)

Скорее всего, вы постоянно перерисовываете весь экран снова и снова. Вместо того, чтобы просто перерисовывать только измененные области. Это много накладных расходов. Концепция проста, но не легка для понимания.

Используйте буфер для девственного статического фона. Само по себе это никогда не отображается, если на экране нет спрайтов. Это постоянно используется для «возврата» туда, где был нарисован спрайт, для удаления спрайта при следующем вызове. Вам также нужен буфер для «рисования», который не является экраном. Вы рисуете там, затем, когда все нарисовано, вы переворачиваете это на экран, один раз. Это должен быть один экранный вызов на все ваши спрайты. (В отличие от рисования каждого спрайта на экране по одному или попытки сделать все сразу, что приведет к сбою альфа-смешивания.) Запись в память выполняется быстро и не требует экранного времени для «рисования» ». Каждый вызов на розыгрыш будет ожидать сигнала возврата, прежде чем попытаться снова нарисовать. (Не v-sync, а аппаратный тик, который намного медленнее, чем время ожидания, которое имеет RAM).

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

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

JasonD
источник
-1

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

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

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

Dreta
источник
-1: Instancing - худшая идея, чем чисто шейдерное решение Mr. Beast. Экземпляр лучше всего подходит для производительности при рендеринге объектов средней сложности (~ 100 треугольников или около того). Каждый треугольник, требующий координат текстуры, не является проблемой. Вы просто создаете сетку с кучей свободных четырехугольников, которые образуют карту тайлов.
Николь Болас
1
@NicolBolas хорошо, я собираюсь оставить ответ ради обучения
dreta
1
Для ясности, Никол Болас, что вы предлагаете, как со всем этим справиться? Поток Марко? Где-нибудь я могу увидеть реализацию этого?
Nic
@Nic: Потоковая передача в буферные объекты не является особенно сложным кодом. Но на самом деле, если вы говорите только о 50 спайтах, это ничего . Хорошие шансы на то, что именно ваш рельеф местности вызвал проблему с производительностью, поэтому переключение на статические буферы для этого, вероятно, будет достаточно.
Николь Болас
На самом деле, если бы инстансинг работал так, как мы могли бы подумать, это было бы лучшим решением - но, поскольку это не так, выпекание всех инстансов в один статический vbo - это путь.
Яри ​​Комппа