Подтвердить что ты не робот

Структура программ OpenGL и OOP

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 2

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

Ответ 3

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

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

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