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

Каковы некоторые рекомендации по кодированию OpenGL (например, ориентация объектов).

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

Камера. Ну, камера - это не что иное, как "где она помещается" (опять же, матрица 4x4 или 4x3, или позиция и ориентация), плюс некоторые параметры проекции (поле зрения, соотношение сторон,...).

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

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

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

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

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

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

Ответ 2

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

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

Основной объект

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

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

Развернуть диспетчер сцен и рендеринг

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

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

Ответ 3

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

  glPushAttrib(GL_ALL_ATTRIB_BITS);
  glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);

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

  glPopClientAttrib();
  glPopAttrib();

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

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

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

Ответ 4

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

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

Ответ 5

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

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

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

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