Абстракция игрового рендерера — это слишком высокий уровень? [closed]

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

Я начал с создания библиотеки, sg, который послужил мне простым способом представления моделей — он предоставляет простые концепции, такие как «Узел», «Сетка», «Изображение» и «Свет», таким образом, что данные могут быть легко загружены в буферы для использования в рендеры. (Я использую программу предварительной обработки на основе Assimp для преобразования моделей и изображений в разных форматах в этот единый формат.)

После этого я сделал sgrender, которая обеспечивает это интерфейс:

namespace sgrender
{
    /** Models, images, and other resources being managed by some particular renderer.
        Ideally, all the models and images for a scene are loaded by the engine at the
        start of the scene and fed into ModelSet, so that each rendering API can use it
        to allocate buffers and store everything in an optimal format. */
    class ModelSet
    {
    public:
        virtual ~ModelSet() = default;
    };

    /** Generic renderer interface. */
    class Renderer
    {
    public:
        /** Get any SDL_Window flags that the engine should pass for this renderer to work,
            e.g. SDL_WINDOW_VULKAN, SDL_WINDOW_OPENGL, etc. The OpenGL renderer also takes
            this opportunity to call SDL_GL_SetAttribute. */
        virtual SDL_WindowFlags getWindowFlags() = 0;

        /** Initialize the rendering backend, targeting the given SDL_Window to draw to. */
        virtual void useWindow(SDL_Window *window) = 0;

        /** Clean up! */
        virtual ~Renderer() = default;

        /** Begin the process of drawing - after this point, drawModel can be called.
            In the Vulkan renderer, this is where a target image and
            command buffer are selected for the next frame, and basic
            setup (begin render pass) is written into the buffer.
            In the OpenGL renderer, this is basically a glorified glClear*/
        virtual void beginDraw() = 0;

        /** End the process of drawing and display the frame. */
        virtual void endDraw() = 0;

        /** @brief Create a renderer-specific ModelSet from a set of SG models and
            all the images they use (mapped by name.) Each Model passed into the ModelSet
            can be referenced for drawing by whichever index it occupied when passed in. */
        virtual std::shared_ptr<ModelSet> makeModelSet(const std::vector<sg::Model> &models, const std::map<std::string, sg::Image>& images) = 0;

        /** @brief Draw a model w/ the given transform - ensure beginDraw() was called first!
            Ideally, all drawModel calls for some particular modelSet are grouped back-to-back. */
        virtual void drawModel(ModelSet *modelSet, size_t model, glm::mat4 transform) = 0;
    };
}

Это реализовано двумя механизмами рендеринга, sgvk а также sggl, которые оба имеют собственное представление о ModelSet который отслеживает такие вещи, как VAO и текстуры. Я полагал, что этот метод массового распределения всего необходимого для конкретной сцены дал возможность моим серверам рендеринга выполнять оптимизацию, специфичную для рендерера, например упаковывать все текстуры в массив текстур (Vulkan) и создавать общие буферы вершин и индексов, чтобы что я могу рисовать определенные модели с некоторым целочисленным индексом.

Что меня беспокоит, так это то, тоже высокоуровневый рендерер — у меня довольно много дублированного кода между рендерерами OpenGL и Vulkan, которые я создал до сих пор, особенно примерно в то время, когда я создаю конкретный ModelSet, и примерно в то время, когда я рисую — но я также не уверен, как я могу сделать абстракцию нижнего уровня, которая допускает все явные настройки (например, макеты конвейеров, выделение памяти, макеты наборов дескрипторов), используемые для Vulkan, не будучи чрезмерно конкретный к одному API. Одна важная вещь прямо сейчас шейдеры — на данный момент я просто работаю с одной парой шейдеров, которая рисует объект в трехмерном пространстве с определенной проекцией и применяет диффузную текстуру и простое освещение … но когда я достигаю своей цели отложенного рендеринга, PBR-рендеринга позже я могу разрешить конкретным моделям использовать собственный шейдер (… и создать совершенно новый конвейер на Vulkan !? Как бы я справился с его воссозданием при изменении размера, если он не управляется средством визуализации !? И переключение конвейеров для одного объекта звучит дорого …)

Я просмотрел другие абстрактные средства визуализации, например, тот, который использовался для DOOM 3 (через эта интересная серия блогов), и они, кажется, имеют гораздо более низкоуровневые абстракции, чем мои, но мне трудно понять как им это удалось, особенно когда дело доходит до API-интерфейсов, таких как Vulkan, где вам нужно много инициализировать, прежде чем вы сможете свободно сказать своему рендереру: «Нарисуйте это!».

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *