Я пишу игру с использованием C ++ и OpenGL 2.1. Я думал, как я могу отделить данные / логику от рендеринга. На данный момент я использую базовый класс Renderable, который предоставляет чисто виртуальный метод для реализации рисования. Но у каждого объекта есть такой специализированный код, что только объект знает, как правильно установить форму шейдера и организовать данные буфера массива вершин. Я заканчиваю большим количеством вызовов функций gl * по всему коду. Есть ли общий способ рисования объектов?
21
m_renderable
членом. Таким образом, вы можете лучше отделить свою логику. Не применяйте визуализируемый «интерфейс» к общим объектам, которые также имеют физику, ai и еще много чего. После этого вы можете управлять визуализируемыми объектами отдельно. Вам нужен уровень абстракции по сравнению с вызовами функций OpenGL, чтобы еще больше отделить вещи. Поэтому не ожидайте, что хороший движок будет иметь какие-либо вызовы GL API внутри своих различных визуализируемых реализаций. Вот и все, в двух словах.Ответы:
Идея состоит в том, чтобы использовать шаблон проектирования Visitor. Вам нужна реализация Renderer, которая знает, как визуализировать реквизит. Каждый объект может вызвать экземпляр рендерера для обработки задания рендеринга.
В нескольких строках псевдокода:
Вещество gl * реализовано методами рендерера, и объекты хранят только данные, необходимые для визуализации, положение, тип текстуры, размер и т. Д.
Кроме того, вы можете настроить различные средства визуализации (debugRenderer, hqRenderer и т. Д.) И использовать их динамически, без изменения объектов.
Это также может быть легко объединено с системами Entity / Component.
источник
Entity/Component
альтернативу немного больше, поскольку она может помочь отделить поставщиков геометрии от других частей движка (ИИ, Физика, Сеть или общий игровой процесс). +1!ObjectA
иObjectB
per,DrawableComponentA
иDrawableComponentB
, внутри, и методы рендеринга, использовать другие компоненты, если вам это нужно, например:position = component->getComponent("Position");
А в основном цикле у вас есть список доступных для рисования компонентов, с которыми вызывается draw.Renderable
), который имеетdraw(Renderer&)
функцию, и все объекты, которые могут быть отображены, реализуют их? В каком случаеRenderer
просто нужна одна функция, которая принимает любой объект, который реализует общий интерфейс и вызовrenderable.draw(*this);
?gl_*
функции в рендерер (отделяя логику от рендеринга), но ваше решение перемещаетgl_*
вызовы в объекты.Я знаю, что вы уже приняли ответ Жена, но я хотел бы выложить еще один на всякий случай, если он кому-нибудь поможет.
Чтобы повторить проблему, OP хочет иметь возможность отделить код рендеринга от логики и данных.
Мое решение состоит в том, чтобы использовать другой класс для визуализации компонента, который отделен от
Renderer
класса логики и. Сначала должен бытьRenderable
интерфейс, который имеет функцию,bool render(Renderer& renderer);
иRenderer
класс использует шаблон посетителя для извлечения всехRenderable
экземпляров, учитывая списокGameObject
s, и отображает те объекты, которые имеютRenderable
экземпляр. Таким образом, Renderer не нужно знать о каждом типе объекта, и каждый тип объекта несет ответственность за информирование об этомRenderable
черезgetRenderable()
функцию. Или, в качестве альтернативы, вы можете создатьRenderableVisitor
класс, который посещает все объекты GameObject и в зависимости от индивидуальныхGameObject
условий, которые они могут выбрать, добавлять или не добавлять их для визуализации посетителю. В любом случае, основная суть в том, чтоgl_*
все вызовы находятся вне самого объекта и находятся в классе, который знает интимные детали самого объекта, а не того, который является частьюRenderer
.ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ : Я написал эти классы вручную в редакторе, так что есть большая вероятность, что я что-то упустил в коде, но, надеюсь, вы поймете эту идею.
Чтобы показать (частичный) пример:
Renderable
интерфейсGameObject
учебный класс:(Частичный)
Renderer
класс.RenderableObject
учебный класс:ObjectA
учебный класс:ObjectARenderable
учебный класс:источник
Построить систему команд рендеринга. Высокоуровневый объект, который имеет доступ как к
OpenGLRenderer
объекту сцены, так и к игровому объекту, будет выполнять итерацию графа сцены или игровых объектов и создавать пакетRenderCmds
, который затем будет представлен объекту,OpenGLRenderer
который будет рисовать каждый по очереди, и, таким образом, содержащий все OpenGL. связанный код в нем.В этом есть больше преимуществ, чем просто абстракция; в конце концов, по мере роста сложности рендеринга вы можете сортировать и группировать каждую команду рендеринга, например, по текстуре или шейдеру,
Render()
чтобы устранить многие узкие места в вызовах отрисовки, которые могут существенно повлиять на производительность.источник
Это полностью зависит от того, можете ли вы сделать предположения о том, что является общим для всех визуализируемых объектов или нет. В моем движке все объекты отображаются одинаково, поэтому нужно просто предоставить vbos, текстуры и преобразования. Затем средство рендеринга извлекает их все, поэтому вызовы функций OpenGL в разных объектах вообще не нужны.
источник
Определенно поместите код рендеринга и игровую логику в разные классы. Композиция (как предположил Теодрон), вероятно, лучший способ сделать это; каждая сущность в игровом мире будет иметь свой собственный Renderable - или, возможно, набор из них.
У вас может быть несколько подклассов Renderable, например, для обработки скелетной анимации, эмиттеров частиц и сложных шейдеров, в дополнение к вашему базовому текстурированному и освещенному шейдеру. Класс Renderable и его подклассы должны содержать только информацию, необходимую для рендеринга: геометрию, текстуры и шейдеры.
Кроме того, вы должны отделить экземпляр данной сетки от самой сетки. Скажем, у вас есть сто деревьев на экране, каждое из которых использует одну и ту же сетку. Вы хотите сохранить геометрию только один раз, но вам понадобятся отдельные матрицы местоположения и поворота для каждого дерева. Более сложные объекты, такие как анимированные гуманоиды, также будут иметь дополнительную информацию о состоянии (например, скелет, набор применяемых в данный момент анимаций и т. Д.).
Для рендеринга наивный подход состоит в том, чтобы перебирать каждую игровую сущность и указывать ей визуализировать себя. Альтернативно, каждая сущность (когда она появляется) может вставлять свой визуализируемый объект (ы) в объект сцены. Затем ваша функция рендеринга сообщает рендеру сцену. Это позволяет сцене делать сложные вещи, связанные с рендерингом, без встраивания этого кода в игровые объекты или в конкретный воспроизводимый подкласс.
источник
Этот совет не является специфическим для рендеринга, но должен помочь создать систему, которая в значительной степени разделяет вещи. Сначала попытайтесь сохранить данные GameObject отдельно от информации о местоположении.
Стоит отметить, что простая позиционная информация XYZ может быть не такой простой. Если вы используете физический движок, то данные о вашем местоположении могут храниться в стороннем движке. Вам нужно будет либо синхронизировать их (что потребует большого количества бессмысленного копирования памяти), либо запросить информацию непосредственно из движка. Но не всем объектам нужна физика, некоторые из них будут зафиксированы на месте, так что простой набор поплавков там отлично работает. Некоторые из них могут даже быть прикреплены к другим объектам, поэтому их позиция фактически является смещением другой позиции. В расширенной настройке положение может храниться только на графическом процессоре, единственное время, которое потребуется на стороне компьютера, - это скрипты, хранилище и сетевая репликация. Таким образом, у вас, вероятно, будет несколько возможных вариантов для ваших позиционных данных. Здесь имеет смысл использовать наследование.
Вместо того, чтобы объект, владеющий его положением, этот объект сам должен принадлежать структуре данных индексации. Например, у «Уровня» может быть Octree или, возможно, «сцена» физического движка. Когда вы хотите выполнить рендеринг (или настроить сцену рендеринга), вы запрашиваете в вашей специальной структуре объекты, которые видны камере.
Это также помогает обеспечить хорошее управление памятью. Таким образом, объект, который на самом деле не находится в области, даже не имеет позиции, которая имеет смысл, вместо того, чтобы возвращать 0,0 координат или координат, которые были у него, когда он был последним в области.
Если вы больше не сохраняете координаты в объекте, вместо object.getX () у вас получится level.getX (object). Проблема с поиском объекта на уровне, вероятно, будет медленной операцией, так как ему придется просматривать все свои объекты и соответствовать тому, который вы запрашиваете.
Чтобы избежать этого, я бы, вероятно, создал специальный класс 'link'. Тот, который связывает между уровнем и объектом. Я называю это «Место». Это будет содержать координаты XYZ, а также дескриптор уровня и дескриптор объекта. Этот класс ссылок будет храниться в пространственной структуре / уровне, и объект будет иметь слабую ссылку на него (если уровень / местоположение будет уничтожен, необходимо изменить ссылку на объект на null. Возможно, также стоит иметь класс Location на самом деле «владеть» объектом, таким образом, если уровень удаляется, то же самое относится и к специальной структуре индекса, расположению, которое он содержит, и его объектам.
Теперь информация о местоположении хранится только в одном месте. Не дублируется между объектом, пространственной структурой индексации, рендерером и т. Д.
Пространственные структуры данных, такие как Octrees, часто даже не нуждаются в координатах объектов, которые они хранят. Там положение сохраняется в относительном расположении узлов в самой структуре (это можно рассматривать как своего рода сжатие с потерями, жертвуя точностью ради быстрого времени поиска). С объектом местоположения в Octree тогда фактические координаты находятся внутри него, как только запрос сделан.
Или, если вы используете физический движок для управления местоположением ваших объектов или их сочетанием, класс Location должен обрабатывать это прозрачно, сохраняя весь ваш код в одном месте.
Еще одним преимуществом теперь является положение и ссылка на уровень хранится в том же месте. Вы можете реализовать object.TeleportTo (other_object) и заставить его работать на разных уровнях. Точно так же поиск пути ИИ может следовать за чем-то в другую область.
Что касается рендеринга. Ваш рендер может иметь аналогичную привязку к местоположению. За исключением того, что там будут конкретные вещи для рендеринга. Вы, вероятно, не нуждаетесь в 'Object' или 'Level', чтобы быть сохраненными в этой структуре. Объект может быть полезен, если вы пытаетесь сделать что-то вроде выбора цвета или рендеринга хитбара, плавающего над ним и т. Д., Но в противном случае средство визуализации заботится только о сетке и тому подобном. RenderableStuff будет Mesh, также может иметь ограничивающие рамки и так далее.
Возможно, вам не нужно делать это каждый кадр, вы можете убедиться, что вы берете больший регион, чем показывает текущая камера. Кэшируйте его, отслеживайте движения объекта, чтобы увидеть, находится ли ограничивающий прямоугольник в пределах диапазона, отслеживайте движение камеры и так далее. Но не начинайте возиться с такими вещами, пока не сравните их.
Ваш физический движок может иметь аналогичную абстракцию, так как ему также не нужны данные объекта, только сетка столкновений и физические свойства.
Все ваши основные данные объекта будут содержать имя меша, который использует объект. Затем игровой движок может загружать его в любом формате, который ему нравится, не обременяя ваш класс объектов кучей специфичных для рендеринга вещей (которые могут быть специфичными для вашего API рендеринга, то есть DirectX против OpenGL).
Это также держит различные компоненты отдельно. Это позволяет легко выполнять такие вещи, как замена вашего физического движка, поскольку эти вещи в основном находятся в одном месте. Это также делает юнит-тестирование намного проще. Вы можете тестировать такие вещи, как физические запросы, без необходимости устанавливать какие-либо поддельные объекты, поскольку все, что вам нужно, это класс Location. Вы также можете оптимизировать вещи проще. Это делает более очевидным, какие запросы нужно выполнять для каких классов и отдельных мест для их оптимизации (например, вышеупомянутый level.getVisibleObject был бы местом, где вы могли бы кэшировать вещи, если камера не перемещается слишком сильно).
источник