16

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

举一个简单的例子,我将使用一个纹理作为例子。纹理是在 GPU 内存中表示纹理的对象,在实现方面,它可以以任何特定方式相似,即实现是否使用纹理GLuintLPDIRECT3DTEXTURE9类似于纹理。

现在这里是我想到的实际实现这一点的方法。我很不确定是否有更好的方法,或者哪种方法比另一种更好。


方法一:继承

我可以使用继承,这似乎是这件事最明显的选择。但是,此方法需要虚函数,并且需要 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;
};

然后为了创建 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;
 };

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

例如,这就是我很可能实现它的方式。虽然,我可以使用基类来概括整个 ID 事物。例如Resource基类,在这种情况下保存图形资源的 ID。

方法 3:Pimpl 成语

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

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

另一方面,我可以使用编译时多态性并基本上使用我在继承方法中介绍的内容,除非不声明虚函数。这可行,但如果我想从 OpenGL 渲染动态切换到 DirectX 渲染,这不是最好的解决方案。我只是将 OpenGL/D3D 特定代码放在 Texture 类中,其中会有多个纹理类具有一些相同的接口(load/reload/getImage/etc.),包装在某个命名空间中(类似于它使用的 API,例如ogl,d3d等)。

方法 5:使用整数

我可以只使用整数来存储纹理对象的句柄,这看起来相当简单,但可能会产生一些“混乱”的代码。


其他 GPU 资源(例如 Geometry、Shaders 和 ShaderPrograms)也存在此问题。

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

Texture* texture = renderer->createTexture(Image("something.png"));
Image image = renderer->getImage(texture);

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

我要定位的平台是:

  • Windows/Linux/Mac
  • iOS
  • 可能是安卓

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

4

3 回答 3

12

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

//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);

获得组件后,通常可以使用各种重载方法来构造对象。以 sprite 为例,aSpriteComponent可以包含子组件形式的 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);

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

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

然后,您拥有的是一种创建对象的好方法和一种对应用程序进行编码的模块化方法,而无需引用“绘图”或其他特定于渲染的代码。一个好的图形管道应该是这样的:

在此处输入图像描述

是渲染引擎设计的一个很好的资源(也是上图的来源)。跳转到第 21 页并继续阅读,您将看到关于场景图如何操作和一般引擎设计理论的深入解释。

于 2013-03-23T11:21:52.523 回答
6

我认为这里没有一个正确的答案,但如果是我,我会:

  1. 计划仅使用 OpenGL 开始。

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

  3. 假设当我移植到 PS3 时,我会更好地掌握我需要我的渲染代码做什么,所以将是重构和提取更抽象接口的正确时机。

于 2013-03-21T13:29:53.940 回答
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;
};

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

Renderer renderer;

// create a texture
Texture texture(renderer);

// load the texture
texture.load(Image("test.png");

从哪里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

*/

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

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

于 2013-04-16T10:51:37.953 回答