Структура программы OpenGL и ООП - PullRequest
24 голосов
/ 26 марта 2012

Я работал над различными демонстрационными проектами с OpenGL и C ++, но все они включали просто рендеринг одного куба (или аналогично простой сетки) с некоторыми интересными эффектами.Для простой сцены, подобной этой, данные вершин куба могут храниться в неэлегативном глобальном массиве.Сейчас я рассматриваю рендеринг более сложных сцен с несколькими объектами разных типов.

Я думаю, что имеет смысл иметь разные классы для разных типов объектов (Rock, Tree, Characterи т. д.), но мне интересно, как аккуратно разбить данные и функции рендеринга для объектов в сцене.Каждый класс будет хранить свой собственный массив положений вершин, текстурных координат, нормалей и т. Д. Однако я не уверен, куда поместить вызовы OpenGL.Я думаю, что у меня будет цикл (в классе World или Scene), который перебирает все объекты в сцене и отображает их.

Должен ли их рендеринг включать вызов метода render вкаждый объект (Rock::render(), Tree::render(),...) или один метод рендеринга, который принимает объект в качестве параметра (render(Rock), render(Tree),...)?Последний кажется более чистым, поскольку у меня не будет дублирующегося кода в каждом классе (хотя это может быть уменьшено путем наследования от одного RenderableObject класса), и он позволяет легко заменить метод render (), если я захочу позжепорт для DirectX.С другой стороны, я не уверен, смогу ли я хранить их отдельно, так как в любом случае мне могут понадобиться специфические типы OpenGL, хранящиеся в объектах (например, буферы вершин).Кроме того, кажется немного громоздким иметь функциональность рендеринга отдельно от объекта, поскольку для получения данных от объектов придется вызывать множество методов Get().Наконец, я не уверен, как эта система будет обрабатывать объекты, которые нужно рисовать по-разному (разные шейдеры, разные переменные для передачи в шейдеры и т. Д.).

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

Ответы [ 3 ]

22 голосов
/ 26 марта 2012

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

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

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

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

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

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

Все это объединяется в цикл высокого уровня, где вы обновляете каждую подсистему в зависимости от времени и рисуете сцену в текущем кадре.

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

Ваш вопрос draw(renderable) против renderable.draw() более или менее не имеет значения, пока вы не определите, как все детали соединяются друг с другом.

[Обновить] Поработав в этом пространстве немного больше, некоторые добавили понимание :

Сказав, что в коммерческих движках это обычно больше похоже на draw(renderBatch), где каждый пакет рендеринга представляет собой агрегацию объектов, которые однородны каким-то значимым образом для GPU, начиная с итерации по гетерогенным объектам (в «чистом» ООП Граф сцены через полиморфизм) и вызов obj.draw() один за другим имеют ужасную локальность кэша и, как правило, неэффективное использование ресурсов графического процессора. Очень полезно использовать ориентированный на данные подход к проектированию того, как движок взаимодействует с базовыми графическими API-интерфейсами наиболее эффективным способом, максимально упорядочивая процессы, не оказывая негативного влияния на структуру кода / читаемость.

Практическое предложение - написать первый движок, использующий наивный / «чистый» подход, чтобы по-настоящему ознакомиться с доменным пространством. Затем, на втором проходе (или, возможно, переписать), сфокусируйтесь на аппаратных средствах: вещах, таких как представление памяти, локальность кэша, состояние конвейера, пропускная способность, пакетная обработка и параллелизм. Как только вы действительно начнете рассматривать эти вещи, вы поймете, что большая часть вашего первоначального дизайна выходит за рамки. Хорошо повеселиться.

3 голосов
/ 26 марта 2012

Я думаю, OpenSceneGraph является своего рода ответом. Взгляните на него и его реализацию . Он должен предоставить вам некоторые интересные идеи о том, как использовать OpenGL, C ++ и ООП.

1 голос
/ 19 октября 2017

Вот то, что я реализовал для физического моделирования, и что работало довольно хорошо и было на хорошем уровне абстракции. Сначала я бы разделил функциональность на такие классы, как:

  • Объект - контейнер, в котором хранится вся необходимая информация об объекте
  • AssetManager - загружает модели и текстуры, владеет ими (unique_ptr), возвращает необработанный указатель на ресурсы для объекта
  • Renderer - обрабатывает все вызовы OpenGL и т. Д., Распределяет буферы в GPU и возвращает дескрипторы рендеринга ресурсов для объекта (при желании, чтобы рендерер рисовал объект, я вызываю рендерер, давая ему дескриптор рендеринга модели, дескриптор текстуры и модель матрица), средство визуализации должно собирать такую ​​информацию, чтобы иметь возможность рисовать ее партиями
  • Физика - вычисления, которые используют объект вместе с его ресурсами (особенно вершины)
  • Сцена - связывает все вышеперечисленное, также может содержать некоторый граф сцены, зависит от характера приложения (может иметь несколько графов, BVH для коллизий, другие представления для оптимизации отрисовки и т. Д.)

Проблема в том, что GPU теперь GPGPU (GPU общего назначения), поэтому OpenGL или Vulkan больше не являются только средой рендеринга. Например, физические расчеты выполняются на GPU. Поэтому рендерер теперь может преобразовываться в нечто вроде GPUManager и других абстракций над ним. Также самый оптимальный способ рисовать - это один вызов. Другими словами, один большой буфер для всей сцены, который также можно редактировать с помощью вычислительных шейдеров для предотвращения чрезмерной связи ЦП <-> с графическим процессором.

...