渲染引擎设计 - 抽象出资源的API特定代码

mig*_*tin 14 c++ opengl graphics rendering

我的渲染代码中有一个非常大的设计绊脚石.基本上这是什么,不需要API特定代码(例如OpenGL代码或DirectX).现在我已经考虑了很多方法来解决这个问题,但是我不确定使用哪一个,或者我应该如何改进这些想法.

举一个简短的例子,我将以纹理为例.纹理是表示GPU存储器中的纹理的对象,实现方式它可以以任何特定方式类似,即实现是否使用GLuintLPDIRECT3DTEXTURE9类似于纹理.

现在,我已经想到了实际实现这一点的方法.我不确定是否有更好的方式,或者哪种方式比另一方更好.


方法1:继承

我可以使用继承,这似乎是这件事最明显的选择.但是,此方法需要虚函数,并且需要TextureFactory类才能创建Texture对象.这需要调用new每个Texture对象(例如renderer->getTextureFactory()->create()).

以下是我在这种情况下考虑使用继承的方法:

class Texture
{
public:

    virtual ~Texture() {}

    // Override-able Methods:
    virtual bool load(const Image&, const urect2& subRect);
    virtual bool reload(const Image&, const urect2& subRect);
    virtual Image getImage() const;

    // ... other texture-related methods, such as wrappers for
    // load/reload in order to load/reload the whole image

    unsigned int getWidth() const;
    unsigned int getHeight() const;
    unsigned int getDepth() const;

    bool is1D() const;
    bool is2D() const;
    bool is3D() const;

protected:

    void setWidth(unsigned int);
    void setHeight(unsigned int);
    void setDepth(unsigned int);

private:
    unsigned int _width, _height, _depth;
};
Run Code Online (Sandbox Code Playgroud)

然后为了创建OpenGL(或任何其他API特定的)纹理,必须制作一个子类,例如OglTexture.

方法2:使用'TextureLoader'或其他类

这个方法听起来很简单,我使用另一个类来处理纹理的加载.这可能会也可能不会使用虚拟功能,具体取决于具体情况(或者我认为是否有必要).

例如,多态纹理加载器

 class TextureLoader
 {
 public:

      virtual ~TextureLoader() {}


      virtual bool load(Texture* texture, const Image&, const urect2& subRect);
      virtual bool reload(Texture* texture, const Image&, const urect2& subRect);
      virtual Image getImage(Texture* texture) const;
 };
Run Code Online (Sandbox Code Playgroud)

如果我要使用它,Texture对象将只是一个POD类型.但是,为了使其工作,必须在Texture类中存在句柄对象/ ID .

例如,这就是我很可能实现它的方式.虽然,我可以使用基类来概括整个ID事物.例如Resource基类,其中包含图形资源的ID.

方法3:Pimpl成语

我可以使用pimpl习语,它实现了如何加载/重载/等.纹理.这很可能需要抽象工厂类来创建纹理.我不确定这比使用继承更好.这个pimpl习惯用法可以与方法2结合使用,即Texture对象将具有对其加载器的引用(指针).

方法4:使用概念/编译时多态

另一方面,我可以使用编译时多态,基本上使用我在继承方法中提供的内容,除非没有声明虚函数.这可以工作,但如果我想从OpenGL渲染动态切换到DirectX渲染,这将不是最好的解决方案.我只想把的OpenGL/D3D特定的代码结构类,那里还能有多重纹理类的一些乜同一界面内(负载/重装/的getImage /等),包裹里面的一些命名空间(外形酷似的API,它使用,例如ogl,d3d等).

方法5:使用整数

我可以使用整数来存储纹理对象的句柄,这看起来相当简单,但可能产生一些"杂乱"的代码.


此问题也存在于其他GPU资源(如"几何","着色器"和"ShaderPrograms")中.

我还想过让Renderer类处理图形资源的创建,加载等.但是这会违反SPR.例如

Texture* texture = renderer->createTexture(Image("something.png"));
Image image = renderer->getImage(texture);
Run Code Online (Sandbox Code Playgroud)

有人可以指导我,我想我是在考虑这个问题.我已经尝试过观察各种渲染引擎,例如Irrlicht,Ogre3D以及我在网上找到的其他渲染引擎.Ogre和Irrlicht使用继承,但我不确定这是最好的选择.正如其他一些人只使用void*,整数,或者只是将API特定(主要是OpenGL)代码放在他们的类中(例如直接在Texture类中的GLuint).我真的无法确定哪种设计最适合我.

我要针对的平台是:

  • 在Windows/Linux的/苹果机
  • iOS版
  • 可能是Android

我考虑过只使用OpenGL特定代码,因为OpenGL适用于所有这些平台.但是,我觉得如果我这样做,如果我希望移植到其他无法使用OpenGL的平台,例如PS3,我将不得不更改我的代码.对我的情况有任何建议将不胜感激.

Kil*_*705 11

从高层次的角度来考虑它.您的渲染代码如何与其他游戏/应用程序模型一起使用?换句话说,您如何计划在场景中创建对象以及模块化程度如何?在我之前使用引擎的工作中,精心设计的引擎的最终结果通常具有遵循模式的逐步过程.例如:

//Components in an engine could be game objects such as sprites, meshes, lights, audio sources etc. 
//These resources can be created via component factories for convenience
CRenderComponentFactory* pFactory = GET_COMPONENT_FACTORY(CRenderComponentFactory);
Run Code Online (Sandbox Code Playgroud)

获得组件后,通常可以使用各种重载方法来构造对象.使用sprite作为示例,a SpriteComponent可以包含子组件形式的sprite可能需要的所有内容; 像TextureComponent例如.

//Create a blank sprite of size 100x100 
SpriteComponentPtr pSprite = pFactory->CreateSpriteComponent(Core::CVector2(100, 100));

//Create a sprite from a sprite sheet texture page using the given frame number.
SpriteComponentPtr pSprite = pFactory->CreateSpriteComponent("SpriteSheet", TPAGE_INDEX_SPRITE_SHEET_FRAME_1);

//Create a textured sprite of size 100x50, where `pTexture` is your TextureComponent that you've set-up elsewhere.
SpriteComponentPtr pSprite = pFactory->CreateSpriteComponent(Core::CVector2(100, 50), pTexture);
Run Code Online (Sandbox Code Playgroud)

然后,只需将对象添加到场景中即可.这可以通过创建一个实体来完成,该实体只是一个包含场景操作所需内容的通用信息集合; 位置,方向等.对于场景中的每个实体,您的AddEntity方法将默认将新实体添加到渲染工厂,从子组件中提取其他与渲染相关的信息.例如:

//Put our sprite onto the scene to be drawn
pSprite->SetColour(CColour::YELLOW);
EntityPtr pEntity = CreateEntity(pSprite);
mpScene->AddEntity(pEntity);
Run Code Online (Sandbox Code Playgroud)

你拥有的是一种很好的创建对象的方法和一种编码应用程序的模块化方法,而不必引用'draw'或其他特定于渲染的代码.一个好的图形管道应该是这样的:

在此输入图像描述

是渲染引擎设计的一个很好的资源(也是上面图像的来源).跳到第21页并继续阅读,您将看到如何深入解释场景图如何操作以及通用引擎设计理论.


Rus*_*ser 6

我不认为这里有任何正确答案,但如果是我,我会:

  1. 计划仅使用OpenGL开始.

  2. 保持渲染代码与其他代码分开(这只是好的设计),但不要试图将它包装在额外的抽象层中 - 只做对OpenGL最自然的事情.

  3. 图说如果当我被移植到PS3,我将有什么,我需要我的渲染代码做一个更好的把握,所以将是重构并拉出一个更抽象的接口正确的时间.


mig*_*tin 3

我决定采用混合方法,将来使用方法(2)、(3)、(5)和(4)。

我基本上所做的是:

每个资源都有一个附加的句柄。该句柄描述了该对象。每个句柄都有一个与其关联的 ID,它是一个简单的整数。为了与每个资源与 GPU 对话,为每个句柄创建了一个接口。该界面目前是抽象的,但如果我将来选择这样做,可以使用模板来完成。资源类有一个指向接口的指针。

简单地说,句柄描述了实际的GPU对象,而资源只是句柄的包装以及将句柄和GPU连接在一起的接口。

它基本上是这样的:

// base class for resource handles
struct ResourceHandle
{  
   typedef unsigned Id;
   static const Id NULL_ID = 0;
   ResourceHandle() : id(0) {}

   bool isNull() const
   { return id != NULL_ID; }

   Id id;
};

// base class of a resource
template <typename THandle, typename THandleInterface>
struct Resource
{
    typedef THandle Handle;
    typedef THandleInterface HandleInterface;

    HandleInterface* getInterface() const { return _interface; }
    void setInterface(HandleInterface* interface) 
    { 
        assert(getHandle().isNull()); // should not work if handle is NOT null
        _interface = interface;
    }

    const Handle& getHandle() const
    { return _handle; }

protected:

    typedef Resource<THandle, THandleInterface> Base;

    Resource(HandleInterface* interface) : _interface(interface) {}

    // refer to this in base classes
    Handle _handle;

private:

    HandleInterface* _interface;
};
Run Code Online (Sandbox Code Playgroud)

这使我可以很容易地扩展,并允许使用如下语法:

Renderer renderer;

// create a texture
Texture texture(renderer);

// load the texture
texture.load(Image("test.png");
Run Code Online (Sandbox Code Playgroud)

其中Texture派生自Resource<TextureHandle, TextureHandleInterface>,并且渲染器具有用于加载纹理句柄对象的适当接口。

我在这里有一个简短的工作示例。

希望这有效,我将来可能会选择重新设计它,如果是的话我会更新。如有批评,我们将不胜感激。

编辑:

事实上,我又改变了做这件事的方式。我使用的解决方案与上述解决方案非常相似,但不同之处如下:

  1. API 围绕“后端”展开,这些对象具有通用接口并与低级 API(例如 Direct3D 或 OpenGL)进行通信。
  2. 句柄不再是整数/ID。texture_handle_type后端对于每个资源句柄类型(例如,program_handle_type, )都有特定的 typedef shader_handle_type
  3. 资源没有基类,并且只需要一个模板参数 (a GraphicsBackend)。资源存储一个句柄和对其所属图形后端的引用。然后该资源具有用户友好的API,并使用句柄和图形后端通用接口与“实际”资源进行交互。即资源对象基本上是允许 RAII 的句柄包装器。
  4. 引入 Graphics_device 对象以允许构建资源(工厂模式;例如device.createTexture()device.create<my_device_type::texture>()

例如:

#include <iostream>
#include <string>
#include <utility>

struct Image { std::string id; };

struct ogl_backend
{
    typedef unsigned texture_handle_type;

    void load(texture_handle_type& texture, const Image& image)
    {
        std::cout << "loading, " << image.id << '\n';
    }

    void destroy(texture_handle_type& texture)
    {
        std::cout << "destroying texture\n";
    }
};

template <class GraphicsBackend>
struct texture_gpu_resource
{
    typedef GraphicsBackend graphics_backend;
    typedef typename GraphicsBackend::texture_handle_type texture_handle;

    texture_gpu_resource(graphics_backend& backend)
        : _backend(backend)
    {
    }

    ~texture_gpu_resource()
    {
        // should check if it is a valid handle first
        _backend.destroy(_handle);
    }

    void load(const Image& image)
    {
        _backend.load(_handle, image);
    }

    const texture_handle& handle() const
    {
        return _handle;
    }

private:

    graphics_backend& _backend;
    texture_handle _handle;
};


template <typename GraphicBackend>
class graphics_device
{
    typedef graphics_device<GraphicBackend> this_type;

public:

    typedef texture_gpu_resource<GraphicBackend> texture;

    template <typename... Args>
    texture createTexture(Args&&... args)
    {
        return texture{_backend, std::forward(args)...};
    }

    template <typename Resource, typename... Args>
    Resource create(Args&&... args)
    {
             return Resource{_backend, std::forward(args)...};
        }

private:

    GraphicBackend _backend;
};


class ogl_graphics_device : public graphics_device<ogl_backend>
{
public:

    enum class feature
    {
        texturing
    };

    void enableFeature(feature f)
    {
        std::cout << "enabling feature... " << (int)f << '\n';
    }
};


// or...
// typedef graphics_device<ogl_backend> ogl_graphics_device


int main()
{
    ogl_graphics_device device;

    device.enableFeature(ogl_graphics_device::feature::texturing);

    auto texture = device.create<decltype(device)::texture>();

    texture.load({"hello"});

    return 0;
}

/*

 Expected output:
    enabling feature... 0
    loading, hello
    destroying texture

*/
Run Code Online (Sandbox Code Playgroud)

现场演示: http: //ideone.com/Y2HqlY

该设计目前正在我的库rojo中使用(注意:该库仍在大力开发中)。