@semenyakinVS
Писатель кода и не только

Как правильно рисовать игровую сцену в openGL?

Большинство толковых уроков по openGL сводится к рисованию цветного треугольничка. Максимум что рассказывают - как загрузить модель. Примеры таких вступительных уроков: вот, вот ещё урок на гугл-коде и вот (последний - подробный вводный урок, увы, однако, не заходящий дальше всё того же треугольника).

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

Лирика по поводу вопроса

Примечание: Дальше я буду называть объектом "экземпляр" модели. В случаи с играми, объектом будет то, что игрок видит как юнит. Ясно, что в сцене могут быть несколько юнитов одного типа (соответственно, это будет несколько объектов одной модели).

На текущем уровне понимания openGL я вижу следующие способы нарисовать сцену с более чем одним объектом модели:

1. Неэффективный по скорости. Вызывается draw одного и того же VAO с вершинами модели, но с разными матрицами. Вызов draw, как я понимаю, дорогая операция - требует явной синхронизации чего-то там и прочих неприятных вещей. Если для каждого объекта вызывать draw (которых в RTS, например, могут быть тысячи) - работать такой код может намного дольше, чем код, в который мы скопом передаём все вершины сцены.
Эта техника обсуждалась тут, тут и тут... Везде, короче.

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

3. Индексирование. В openGL есть возможность индексирования вершин. В случаи использования этого метода, вместо атрибутов, привязываемых через VAO, можно отсылать массив индексов вершин, по которым будут формироваться треугольники объектов. В этом случаи дублирование данных всё равно будет - дублирование элементов индексных массивов (см. картинку, индексы в массиве мировых моделей будут от 0 до N, где N - это количество вершин модели). Но места индексы будут занимать меньше места.

16b1fc7954d7466893d5869989532bb0.png

Ещё одна проблема - отсылка информации о матрицах преобразования для объектов (на картинке - строчка transforms). Ясно что передавать матрицу с каждой вершиной будет избыточно (для каждой вершины одного объекта модели используется одна и та же матрица преобразования). С другой стороны, если опять-таки сделать что-то вроде индексации. То есть отсылать матрицы в большом UBO и к индексам вершин привязывать индекс матрицы преобразования соответствующей модели (не уверен что знаю как это сделать). К тому же, так как объекты могут создаваться и исчезать достаточно активно в течение сцены, использование UBO может тормозить процесс рендера (насколько я понимаю, UBO грузить на видяху дольше чем VAO).


Сам вопрос... Точнее, вопросы.

1. Действительно ли команда draw настолько дорога? По логике должна быть дорога - ведь если мы рисуем сцену маленькими порциями по несколько сот треугольников вместо отрисовки сразу миллионов треугольников всей сцены - отрисовку сложнее запараллелить.

2. Если ответ на первый вопрос "Да", то есть ли команда рисования в openGL, принимающая на вход массив промежутков индексов (в формате "индекса начала - индекс конец") и вызывающая пайпалайн для этих промежутков так, как glDrawArrays вызывается для одного VAO, заданного подряд идущими, не разнесёнными в памяти вершинами?.. Блин, звучит, кажется, вообще непонятно... Я имею в виду что-то вроде такого:

Код

// В следующей строчка - код отсылки вершин моделей. Скорее всего, в едином VAO
// или UBO.
. . .

// Смещения вершин на отрисовку и количество вершин в каждом наборе.
int worldObjectRanges[][] = {{0, model1_VertexCount}, {0, model0_VertexCount },
{ model0_VertexCount, model1_VertexCount },
{ model0_VertexCount + model1_VertexCount, model2_VertexCount }};

matrix worldObjectTransforms[] = { obj0Trans, obj1Trans, obj2Trans, obj3Trans };

// В следующей строчке кода идёт отправка трансформаций моделей
// worldObjectTransforms (и, кстати, ещё какой-нибудь уникальной для моделей
// сопроводительной инфы) - предположительно через UBO.
. . .

// Тут делается магия. Пайплайн вызывается для всех вершин, находящихся
// в промежутках заданных через worldObjectRanges. При нужно чтобы в вершинный
// шейдер передавался индекс элемента worldObjectRanges, на основе которого шейдер
// был вызван для данной вершины (фактически, это будет индекс объекта); это нужно
// чтобы по индексу из UBO с трансформациями моделей получить трансформацию
// рисуемой в шейдере модели.
glDrawIndexedRanges(worldObjectRanges);


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

Лирика по поводу сверхзадачи

P.S.: Планирую поднять ещё несколько тем по openGL. В общем и целом, темы перечисленны в этом вопросе. После того, как чуть разберусь, планирую написать учебную статью (возможно, цикл статей) на хабре. Делать это буду по схеме, при которой комментирование статьи знающими людьми будет приводить к модификации статьи (схему такой работы я описывал в этом вопросе этом вопросе).
Помимо меркантильной пользы "самому разобраться", надеюсь заполнить циклом пробел, который имеется в учебных темах по openGL. Дело в том, что, как упоминалось в начале этого вопроса, есть простые статьи, есть сразу статьи очень сложные (чтобы понять такие нужно курить openGL не один год), а промежуточных нету. Это дело нужно исправлять.
  • Вопрос задан
  • 2161 просмотр
Решения вопроса 1
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы