Каковы некоторые рекомендации для кодирования OpenGL (особенно объектная ориентация w.r.t.)? - PullRequest
50 голосов
/ 03 октября 2008

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

Я пришел из объектно-ориентированного фона, поэтому я пытаюсь поместить все, что мы делаем, в повторно используемые классы. Я успешно справился с созданием класса камеры, поскольку он зависит в основном от одного вызова gluLookAt (), который практически не зависит от остальной части конечного автомата OpenGL.

Однако у меня возникли некоторые проблемы с другими аспектами. Использование объектов для представления примитивов не было для меня успешным. Это связано с тем, что фактические вызовы рендеринга зависят от очень многих внешних факторов, таких как текущая связанная текстура и т. Д. Если вы вдруг захотите перейти от нормалей поверхности к вершинам нормалей для определенного класса, это вызывает сильную головную боль.

Я начинаю задаваться вопросом, применимы ли ОО-принципы в кодировании OpenGL. По крайней мере, я думаю, что я должен сделать свои занятия менее детальными.

Каково мнение сообщества по переполнению стека на это? Каковы ваши лучшие практики для кодирования OpenGL?

Ответы [ 5 ]

72 голосов
/ 03 октября 2008

Наиболее практичным подходом, по-видимому, является игнорирование большей части функциональности OpenGL, которая не применима напрямую (либо медленная, либо не аппаратно ускоренная, либо больше не подходит для аппаратного обеспечения).

ООП или нет, для рендеринга какой-либо сцены, это различные типы и сущности, которые у вас обычно есть:

Геометрия (сетки). Чаще всего это массив вершин и массив индексов (то есть три индекса на треугольник, или «список треугольников»). Вершина может быть в произвольном формате (например, только позиция float3; позиция float3 + нормаль float3; позиция float3 + нормаль float3 + текстовая координата float2 и т. Д. И т. Д.). Итак, чтобы определить кусок геометрии вам нужно:

  • определить формат вершины (это может быть битовая маска, перечисление из списка форматов; ...),
  • имеет массив вершин с чередованием их компонентов ("чередующиеся массивы")
  • имеет массив треугольников.

Если вы находитесь на земле ООП, вы можете назвать этот класс Mesh .

Материалы - вещи, которые определяют, как какой-то фрагмент геометрии отображается . В простейшем случае это может быть, например, цвет объекта. Или следует ли применять освещение. Или должен ли объект быть альфа-смешанным. Или текстуру (или список текстур) для использования. Или вершинный / фрагментный шейдер для использования. И так далее, возможности безграничны. Начните с размещения вещей, которые вам нужны в материалы. В ООП земле этот класс можно назвать (сюрприз!) Материал .

Сцена - у вас есть кусочки геометрии, коллекция материалов, время, чтобы определить, что находится на сцене. В простом случае каждый объект в сцене может быть определен с помощью: - Какую геометрию он использует (указатель на сетку), - как это должно быть отображено (указатель на материал), - Где это находится. Это может быть матрица преобразования 4x4 или матрица преобразования 4x3, или вектор (положение), кватернион (ориентация) и другой вектор (масштаб). Давайте назовем это Узлом в ООП земле.

Камера . Что ж, камера - это не что иное, как «где она находится» (опять же, матрица 4x4 или 4x3 или положение и ориентация), а также некоторые параметры проекции (поле зрения, соотношение сторон, ...).

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

Теперь, куда поместить фактические вызовы OpenGL - это вопрос дизайна. Я бы сказал, не помещайте вызовы OpenGL в классы Node, Mesh или Material. Вместо этого создайте что-то вроде OpenGLRenderer , которое может пересечь сцену и выполнить все вызовы. Или, что еще лучше, создайте что-то, что проходит через сцену независимо от OpenGL, и поместите вызовы более низкого уровня в зависимый класс OpenGL.

Так что да, все вышеперечисленное в значительной степени зависит от платформы. Пройдя по этому пути, вы обнаружите, что glRotate, glTranslate, gluLookAt и друзья совершенно бесполезны. У вас уже есть все матрицы, просто передайте их OpenGL. Вот как работает большинство реальных кодов в реальных играх / приложениях.

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

Но основная идея проста: есть геометрия, есть материалы, есть объекты на сцене. Затем какой-то небольшой фрагмент кода может их отрендерить.

В случае OpenGL установка сеток, скорее всего, создаст / активирует / изменит объекты VBO. Перед тем, как какой-либо узел будет визуализирован, необходимо установить матрицы. А настройка Material коснется большей части оставшегося состояния OpenGL (смешивание, текстурирование, освещение, объединители, шейдеры, ...).

3 голосов
/ 03 октября 2008

Преобразования объекта

Избегайте зависимости от OpenGL для ваших преобразований. Часто учебники научат вас, как играть со стеком матрицы преобразования. Я бы не рекомендовал использовать этот подход, так как позже вам может понадобиться некоторая матрица, которая будет доступна только через этот стек, и использовать ее очень долго, поскольку шина GPU рассчитана на быструю передачу от CPU к GPU, а не наоборот.

Мастер объект

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

Я советую использовать мастер-объект. Хотя он не имеет графического представления, он будет проще, поскольку вы сможете более эффективно использовать рекурсию.

Отделение менеджера сцены и рендерера

Я не согласен с @ejac, что у вас должен быть метод для каждого объекта, выполняющего вызовы OpenGL. Отдельный класс Renderer, просматривающий вашу сцену и выполняющий все вызовы OpenGL, поможет вам отделить логику вашей сцены и код OpenGL.

Это добавляет некоторые сложности при проектировании, но даст вам больше гибкости, если вам когда-нибудь придется перейти с OpenGL на DirectX или что-то еще, связанное с API.

2 голосов
/ 03 октября 2008

Стандартный метод состоит в том, чтобы изолировать влияние объектов на состояние рендеринга друг от друга, выполнив все изменения из некоторого состояния OpenGL по умолчанию в области glPushAttrib / glPopAttrib. В C ++ определите класс с конструктором, содержащий

  glPushAttrib(GL_ALL_ATTRIB_BITS);
  glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);

и деструктор, содержащий

  glPopClientAttrib();
  glPopAttrib();

и использовать класс RAII-style для переноса любого кода, который мешает состоянию OpenGL. При условии, что вы следуете шаблону, каждый метод рендеринга объекта получает «чистый лист», и ему не нужно беспокоиться о том, чтобы подтолкнуть каждый возможный измененный бит состояния openGL к тому, что ему нужно.

Как оптимизация, обычно вы устанавливаете состояние OpenGL один раз при запуске приложения в какое-то состояние, максимально приближенное к тому, что хочет все; это сводит к минимуму количество вызовов, которые должны быть сделаны в заданных областях.

Плохие новости - это не дешевые звонки. Я никогда не исследовал, сколько в секунду вы можете сойти с рук; конечно, достаточно, чтобы быть полезным в сложных сценах. Главное - попытаться максимально использовать состояния, как только вы их установите. Если у вас есть армия орков для рендеринга с различными шейдерами, текстурами и т. Д. Для доспехов и скинов, не перебирайте все орки, рендеринг armor / skin / armor / skin / ...; убедитесь, что вы установили состояние для брони один раз и отрендерили доспехи всех орков, а затем настроили рендеринг всего скина.

1 голос
/ 05 октября 2008

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

OpenScenegraph - это одно приложение с открытым исходным кодом, которое предоставляет вам большую (может быть, слишком большую) библиотеку инструментов для создания трехмерной графики OO, но есть много других.

0 голосов
/ 03 октября 2008

У меня обычно есть функция drawOpenGl () для каждого класса, который может быть визуализирован, который содержит вызовы opengl. Эта функция вызывается из renderloop. Класс содержит всю информацию, необходимую для его вызовов функции opengl, например. о положении и ориентации, чтобы он мог сделать свое собственное преобразование.

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

Это было какое-то время, но я думаю, что нечто подобное возможно с текстурами.

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

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