Серия «Relict Engine»

5

Relict Engine: DevLog 20260119

Серия Relict Engine

Краткий список изменений:

  • Ядро переключено на новую кодобазу с метаклассами

  • Удалены сущности DefaultObject

  • Изменен принцип регистрации сущностей со статичных вызовов на делегаты

  • Заменен старый глобальный тикер на новый через метакласс

  • Добавлен функционал Subticks с заданным интервалом

    Функция тикера вызывается каждые N миллисекунд, где N заданное произвольное число

  • Куча мелких исправлений (и еще столько же предстоит исправить)

Комментарий:

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

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

UPD: inline Class _Class должен быть inline Class& _Class (заметил после публикации поста)

UPD: inline Class _Class должен быть inline Class& _Class (заметил после публикации поста)

После фикса все встало на свои места

После фикса все встало на свои места

Миграция все еще в процессе и займет довольно продолжительное кол-во времени. Благо в начале Февраля начинается отпуск. Там, думаю, дела пойдут веселее и в Марте переключусь уже непосредственно на графоний, чтобы не отставать от намеченного плана.

Показать полностью 3
8

Relict Engine: DevLog 20260108

Серия Relict Engine

Краткий список изменений

  • Добавлены структуры инциализаторы шаблонного класса RelictClass

    На данный момент просто как сущность. Функционал для них пока не готов.

  • Добавлен класс Relict::Class как хранилище инициализационных структур

  • Перенесена часть функционала, касающаяся регистрации/удаления объектов из ObjectStorage в Relict::Class

  • Упрощена работа Сборщика мусора путем перевода его на событийную модель

  • Добавлены заготовки на контейнеры сетевых/скриптовых полей Property

    Добавлены в качестве залипухи на будущее. В логике работы движка они ни коем образом не участвуют.

Комментарий

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

Касательно структур, решил сделать чуть иначе, чем в концепт-коде, приведенном в пред. посте: Relict Engine: Начало сезона 2026
Вместо некрасивого нагромождения различных скобок в шаблоне (который еще и очень не нравится IDE, несмотря на то, что стандарт допускает такое использование) вынес их отдельно. Теперь это выглядит вот так:

Не обращайте внимание на префикс Nv. Это нужно для совместимости со старым кодом. Во время переключения вернется старый добрый TRelictClass

Не обращайте внимание на префикс Nv. Это нужно для совместимости со старым кодом. Во время переключения вернется старый добрый TRelictClass

Показать полностью 1
13

Relict Engine: Начало сезона 2026

Серия Relict Engine

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

Отказ от Атомов.

Как показала практика использования, система атомов не состоятельна. Нет, ее можно использовать на уровне проектов для внедрения дополнительного функционала, но не на уровне ядра. Поэтому она идет под нож. Заместо нее появятся конфигурационные структуры для шаблонов классов. Выглядеть они будут примерно так:

концепт код: <a href="https://pikabu.ru/story/relict_engine_nachalo_sezona_2026_13570983?u=https%3A%2F%2Fgodbolt.org%2Fz%2FEjnMYr153&t=https%3A%2F%2Fgodbolt.org%2Fz%2Fq1zPeMeE3&h=4c6de45e1a4af5a22e8239524a02c7adae7804da" title="https://godbolt.org/z/EjnMYr153" target="_blank" rel="nofollow noopener">https://godbolt.org/z/q1zPeMeE3</a>

концепт код: https://godbolt.org/z/q1zPeMeE3

Так-же, подобные структуры позволят нам реализовать ряд интересных вещей. Например, после реализации мета классов, они позволят нам связать создание одного объекта с другим объектом (Например класс RenderObject с SceneUnit) для более прозрачной работы конвейера.


Остальные улучшения

Вторым крупным улучшением (которое уже внедрено и работает), стало замена тикера Сборщика мусора на событийную модель. Это сильно повысило производительность, т.к. я избавился от цикла перебора всех созданных движком объектов. И, смотря на перспективу, возможно, сборщик мусора будет объеден с глобальным хранилищем объектов, ибо уже сейчас бессмысленно ее держать отдельно. Но это уже потом; узелок на память завязал.

Третьим улучшением, которое частично будет реализовано сейчас, а частично очень потом - это внедрение мета классов аналогов UClass в Unreal Engine, для хранения константных проинициализированных структур инициализаторов и мета методов для работы с Lua. Что позволит нам сильно упростить работу с скрипт подсистемой. Которая, возможно, перейдет из состояния подсистемы в ядро (но это не точно, поэтому и откладываю на потом). А так-же избавит от необходимости таскать в дефолтном конструкторе Initializer. И в дополнение, они позволят нам избавится от такого рудимента, как DefaultObject (он используется для проверки Флагов в хранилище объектов).

Показать полностью 1
14

Relict Engine: Итоги 2025

Серия Relict Engine

Традиционно подвожу итоги года и даю себе месяц на отдохнуть; примерно распланировать 2026 год. Правда в этот раз итоги получаются не за год, а за год с хвостиком, но не суть.

Что было сделано за этот год:

  • Разработана архитектура и модель памяти движка.
    Создана фабрика всех движковых объектов, Сборщик мусора, отвечающий за автоматическое удаление и освобождение памяти от ненужных сущностей. И центральное хранилище объектов. Продуманы системы подгрузки подсистем, встроенных модулей и взаимодействие с проектами высокого уровня (читай играми).

  • Создана скрипт подсистема на основе языка Lua и внедрена в виде подключаемой подсистемы.

    Подробнее: Relict Engine: Скриптовая подсистема + DevLog 20250405

  • Разработана утилита для импорта внешних форматов в формат движка

    Подробнее: Relict Engine: RatTools. Меши. Статичные Меши

  • Разработан формат хранения статичной геометрической сетки (статичный меш)

    Подробнее: Relict Engine: Формат Статичного меша

  • Разработана система работы с асинхронными задачами

    Подробнее: Relict Engine: ThreadManager и DevLog 20250822

  • Разработана структура сцен
    Подробнее: Relict Engine: Мир и DevLog 20250928

  • Разработана система вывода графики и работа с ней

    Подробнее: Relict Engine: DevLog 20251120

    Иными словами за этот год я смог сделать некий полнофункциональный скелет. И теперь, на этот скелет следует наращивать мяско.


Что планируется на 2026

Начало 2026 года я планирую посвятить работе над ошибками. Например, очень хочется организовать очередь создания объектов, дабы была возможность управлять порядком их создания (сейчас некоторые объекты, например RenderObject, создают свои дефолтные экземпляры вне main рутины, а ДО нее, что не есть очень хорошо, и может привести к проблемам в какой-то момент).

Так-же будет нужно сделать шейдерный pbr и доделать для них внятную систему материалов; добавить ассет текстур; добавить различные юниты для работы со светом, системами частиц, итд. Поэтому, остальной 2026 год будет посвящен работе "в ширь". Успею ли сделать что-то еще зависит от множества неподконтрольных мне факторов, поэтому загадывать по максимуму не буду.

Еще хочу напомнить, что у движка есть дорожная карта, ознакомится с которой можно вот тут: https://yougile.com/board/8o3quozyj1in , и где видно сколько было сделано. И сколько еще предстоит сделать.


И на этом год 2025 считаю закрытым.

И в заключении, от себя и от лица команды Delta-Proxima Team хотим заранее поздравить пользователей Пикабу, и отдельно Лигу разработчиков Видеоигр с новым, 2026м годом. Желаем, чтобы ваши проекты покоряли вершины чартов, а каждый новый релиз становился событием в мире игровой индустрии.
Пусть код пишется легко, баги находятся быстро, а вдохновение никогда не покидает вас!
А на кухне никогда не заканчиваются кофе и печеньки ;)

С уважением и наилучшими пожеланиями,
Delta-Proxima Team

Показать полностью
8

Relict Engine: Шейдеры. Часть 2. Материалы. Синтаксис

Серия Relict Engine

Как я писал в пред. посте (Relict Engine: Шейдеры. Часть 1. Теория и вопросы), нам потребуется комбайн для перегона материала (или, если угодно, настроек шейдера) в spir-v формат. Для этого необходимо создать изначальный формат, желательно в текстовом виде, который и будет входной точкой в этот процесс.

Этот формат должен отвечать следующим требованиям:

  • В нем должно быть указано, с какими "сырыми" шейдерами компоновать этот материал

  • Иметь возможность обращаться к дополнительным файлам, содержащим кастомные функции

  • Иметь возможность самому определять дополнительные функции

  • Задавать входные опции

  • Определять параметры уже самого шейдера

Исходя из выше перечисленного, использовать GLSL или HLSL как есть не выйдет. Спецификации языков не обладают нужным функционалом. Поэтому получаем нечто похожее на вот это:

первый вариант синтаксиса материала

первый вариант синтаксиса материала

Что мы тут видим:
Директива using
Она определяет компоновку с сырыми шейдерами. Определяет домен, модель смешивания альфы и модель освещения (не путать с моделью затенения из пред. поста).
Нейминг был взят из Unreal Engine, потом, возможно, поменяю на немного более логичные.

Директива in
Она определяет входные переменные для материала, используя типы GLSL

Директива include
Эта директива говорит комбайну, что следует обратится к функции, определенной в файле MaterialFunctions/getColor (Content/MaterialFunctions/getColor.minc проекта). Что важно, сам файл как самостоятельный ассет не будет использоваться, но его контент будет вставлен заместо директивы include при первичной компоновке.

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

Директива set
Эта директива передает конкретные значения в параметры "сырых" шейдеров. Иными словами, это и есть сам материал.

Показать полностью 1
12

Relict Engine: Шейдеры. Часть 1. Теория и вопросы

Серия Relict Engine

Начнем, пожалуй, с теории.

Шейдеры и модель затенения

пример работы отложенного затенения (иллюстрация <!--noindex--><a href="https://pikabu.ru/story/relict_engine_sheyderyi_chast_1_teoriya_i_voprosyi_13424189?u=https%3A%2F%2Flearnopengl.com%2F&t=https%3A%2F%2Flearnopengl.com%2F&h=9d0d5ab19c1209e085c1ddb0125053715c6b9c32" title="https://learnopengl.com/" target="_blank" rel="nofollow noopener">https://learnopengl.com/</a><!--/noindex-->)

пример работы отложенного затенения (иллюстрация https://learnopengl.com/)

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

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

А так-же есть две модели шейдинга (шейдинг ("затенение") - процесс в компьютерной графике, который придает объектам реалистичный вид, определяя их цвет и внешний вид в зависимости от освещения, текстур и материалов):
Forward (или прямое затенение) - при этой модели каждый отдельный объект обрабатывается самостоятельно. Свет и тени будут считаться отдельно для каждого объекта.
Deffered (или отложенное затенение) - отрисовка сцены происходит в несколько проходов с сохранением промежуточных данных в специальные текстуры, которые имеют общее название G-Buffer. А потом, отдельным проходом, единоразово будет просчитан свет и тень.

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


Шейдеры в Relict Engine

Таким образом, в первую очередь нам нужно выбрать, какую модель затенения использовать в движке. Т.к. проект, для которого Relict Engine создается, связан с космосом, то логично предположить, что прямое затенение даст больше производительности, нежели отложенное. И это будет верно, пока мы говорим о "сферическом космосе в вакууме". Как только появляются туманности, низкие орбиты и нагруженные космопорты на этих орбитах, все становится менее очевидно. Поэтому, несмотря на кажущуюся простоту этого вопроса , ответить на него не так уж и просто.

Следующие вопросы, который надо решить, и которые не связаны с моделью затенения напрямую, а больше относятся к архитектуре движка, является: А как хранить шейдеры? Как передавать в них параметры?

В спецификации OpenGL 4.5 появилась возможность использовать бинарные шейдеры Spir-V, которые являются основным форматом шейдеров для Vulkan. И это удобно, мы сможем использовать один и тот-же шейдер как для OpenGL, так и для Vulkan без модификации. Но это касается "статичных" шейдеров. Их нельзя изменять, можно добавить только заранее известные переменные, которые можно будет передать в шейдер через функции движка или скрипта, а если мы хотим чего-то более сложного? Например передать не текстуру, а некую математическую функцию, которая будет определять цвет пикселя по своим собственным законам?

Таким образом, нам понадобится система, которая будет компоновать наши кастомные навороты, а так-же вводные данные, в виде переданных параметров с набором функций базовых шейдеров в некую отдельную сущность, которая будет представлять из-себя скомпонованный шейдер из множества отдельно взятых функций и точки входа. И желательно, все это все равно сохранить в Spir-V формат для удобства дальнейшего перехода на Vulkan.
Более того, в зависимости от выбранной модели затенения, выходной результат так-же должен отличаться. Так например, если мы для Основного цвета (Diffuse Color) не будем использовать текстуру, а некую мат. функцию, то нам нужно будет скомпоновать эту функцию с шейдером G-Buffer, в случае использования отложенного затенения, когда как в случае прямого затенения, с итоговым фрагментным шейдером.

Иными словами только для подготовки шейдера к компиляции в кэш нам потребуется целый промежуточный комбайн. Этот комбайн будет работать с текстом, компонуя и генерируя исходный glsl код из пред написанных сниппетов и нашего материала. А на выходе должен быть Asset, уже в формате Spir-V, который и будет использоваться для компиляции уже в машинный код и исполнения на GPU.

Показать полностью
3

Relict Engine: DevLog 20251120

Серия Relict Engine

Краткий список изменений:

  • Добавлен Реликт RenderCore

  • Добавлен Реликт RenderGL

  • Добавлено дерево классов RenderObject; в частности: StaticMesh_GLRenderObject

  • Добавлен класс VertexArrayObject

  • Добавлены шаблон-делегаты

    как пример:
    using FOnAssetChanged = MulticastDelegate<StaticMesh*, StaticMesh*>;
    Работает, так-же как и делегаты в UE.

Комментарий:

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

Но немного подытожу, что у меня получилось с пайпом. А получилась довольно интересная загогулина.

Итак, первое, что у нас есть это Renderer. И как не странно, эта сущность не занимается обработкой графики. Она используется как загрузчик уже непосредственно самого класса отрисовки, а так-же как прокси к нему.

Второе, что у нас есть это RenderUnit. Производный от SceneUnit это объект игровой сцены, который может иметь что-либо связанное с выводом на экран (Меш, скелет, систему частиц, итд)

Третье что у нас есть это интерфейс IRenderObject и производные к нему. Это и есть те классы, которые занимаются непосредственно отрисовкой, т.е. сбором всех контекстов и вызовом команды графического апи на отрисовку. Примечательно то, что эти классы на прямую связаны с RenderUnit таким образом, что каждому RenderUnit соответствует свой RenderObject

Четвертое - VertexArrayObject. Название взято из спецификации OpenGL, и служит он ровно для той же цели, что и объекты VAO в спецификации. Он хранит вершины модели, индексы и их атрибуты, и прокидывает их в видеопамять. Связан с IAsset производными, но управляется из RenderObject (т.к. заполнение может быть специфическим, в зависимости от типа юнита). Но тем не менее хранит набор из ссылок на RenderObject'ы, юниты которых используют ассет, к которому привязан данный VertexArrayObject (сложна!). Почему именно так: А все просто. Чтобы максимально ускорить процедуру рендера я решил сократить издержки на переключение буферов на GPU. А для этого, мы переворачиваем порядок вызовов на отрисовке (если создаются таким образом: RenderUnit -> RenderObject->VertexArrayObject, то при вызове на отрисовку порядок переворачивается: VertexArrayObject -> RenderObject->RenderUnit). И заодно организовываем сортировку объектов по этому же VAO. Таким образом, если у нас на сцене есть, допустим 3 куба, 2 шара и одна пирамида, то отрисовано будет сначала именно 3 куба, потом 2 шара, и в конце пирамида. Таким образом, на 6 объектов, у нас придется только 3 переключения контекста (про инстансинг помню, но он отдельно и не для этого).

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

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

Показать полностью
7

Relict Engine: DevLog 20251113

Серия Relict Engine

Краткий список изменений

  • Заменен встроенный в glfw загрузчик GLAD на сгенерированный самостоятельно (https://glad.sh).

  • Добавлена документация RatTools для внутреннего использования

  • Работа с OpenGL полностью вынесена в VAO (Vertex Array Object) из последней ревизии спецификации

  • Изменена структура хранения StaticMesh для более удобного доступа к буферам из графического апи.

  • Добавлен предварительный код переключения LOD

  • Добавлен предварительный код отрисовки мульти материалов.

Комментарий:

В ближайшее время буду думать в сторону промежуточных объектов, связывающих графический пайп с игровым фреймворком. Скорее всего нужно будет навести один/два дополнительных реликта (модуля) для этого дела, да вынести отрисовку туда. Так-же нужно перепривязать эти объекты с RenderUnit на непосредственно Asset. А то сейчас получается, что один и тот-же меш будет иметь столько VAO, сколько RenderUnit на сцене, даже если они несут один и тот-же объект - это не порядок. В идеале должно быть так, чтобы был один VAO на один ассет, чтобы не напрягать GPU лишними переключениями контекста, да и в видео памяти получается по несколько раз одно и тоже лежит, что тоже есть очень не хорошо.
Правда в этом случае вылезет проблема с переключением LOD, т.к. на данный момент для переключения в VAO меняется буффер вершин. В случае, если VAO будет привязан к ассету, то переключать лоды для конкретного объекта не выйдет (только если все LOD в графическую память сразу отдавать). В общем, еще есть над чем подумать.

А пока что результат на сегодняшний день:

Лоды

Заготовка мульти материал. Один объект, два разных шейдера и очень много треугольников.

Заготовка мульти материал. Один объект, два разных шейдера и очень много треугольников.

Показать полностью 1 1
Отличная работа, все прочитано!

Темы

Политика

Теги

Популярные авторы

Сообщества

18+

Теги

Популярные авторы

Сообщества

Игры

Теги

Популярные авторы

Сообщества

Юмор

Теги

Популярные авторы

Сообщества

Отношения

Теги

Популярные авторы

Сообщества

Здоровье

Теги

Популярные авторы

Сообщества

Путешествия

Теги

Популярные авторы

Сообщества

Спорт

Теги

Популярные авторы

Сообщества

Хобби

Теги

Популярные авторы

Сообщества

Сервис

Теги

Популярные авторы

Сообщества

Природа

Теги

Популярные авторы

Сообщества

Бизнес

Теги

Популярные авторы

Сообщества

Транспорт

Теги

Популярные авторы

Сообщества

Общение

Теги

Популярные авторы

Сообщества

Юриспруденция

Теги

Популярные авторы

Сообщества

Наука

Теги

Популярные авторы

Сообщества

IT

Теги

Популярные авторы

Сообщества

Животные

Теги

Популярные авторы

Сообщества

Кино и сериалы

Теги

Популярные авторы

Сообщества

Экономика

Теги

Популярные авторы

Сообщества

Кулинария

Теги

Популярные авторы

Сообщества

История

Теги

Популярные авторы

Сообщества