C++ 什么';这是一种适合这种情况的设计模式吗?

C++ 什么';这是一种适合这种情况的设计模式吗?,c++,design-patterns,C++,Design Patterns,我试图实现一个模型(gfx)类,但似乎找不到合适的设计 我拥有的是:[伪代码] class Material { public: virtual void SetPerFrameInfo(void* pData)=0; //[...] } class SpecificMaterial : public Material { public: void SetPerFrameInfo(void* pData); //[...] } void Specif

我试图实现一个模型(gfx)类,但似乎找不到合适的设计

我拥有的是:[伪代码]

class Material {
  public:
    virtual void SetPerFrameInfo(void* pData)=0;
    //[...]
}

class SpecificMaterial : public Material {
  public:
    void SetPerFrameInfo(void* pData);
    //[...]
}

void  SpecificMaterial::SetPerFrameInfo(void* pData) {
    ThisMaterialPerFrameInfoStruct* pInfo = (ThisMaterialPerFrameInfoStruct*)pData;
    //[...]
}

class Model {
  public:
    Model(Material* pMaterial){m_pMatirial = pMaterial;}
    void Draw();
  private:
    Material* m_pMaterial
    //[...]
}

void Model::Draw() {
    PerFrameInformation info;
    m_pMaterial->SetPerFrameInfo(&info);
}
如何使用它:

Material* pMaterial = new SpecificMaterial();
Model someModel(pMaterial);
正如你可能看到的,我有两个主要问题:

1) 传递给SetPerFrameInfo()的结构类型取决于实际的材质,因此我应该如何填写它

2) 模型类可能需要一些其他数据成员,具体取决于实际的材质,因为对于某些材质,我可能决定实现计时器,而对于某些材质,我可能不会。对于所有材质,在模型类中包含datamembers将是一种内存浪费

请帮忙,我似乎找不到任何合适的设计模式。

1。)我认为您遇到的问题是由于试图在基类中添加太多内容。既然模型在调用SetPerFrameInfo函数时需要知道它使用的是什么特定的材质类型,为什么不为每个材质派生类提供自己的Set..()函数,该函数采用正确的类型而不是void*?因为Material基类无法与其每帧信息交互(它是void*),那么为什么要将其概括为基类呢

2.)只需将特定于材质的数据成员放入派生材质类中即可。我假设每个材质实例对应一个唯一的模型,因此该模型的材质特定信息应该在其材质派生类中

针对你的澄清: 好吧,我误解了这两门课之间的关系。基本上,在理想场景中,您需要一组不知道模型细节的Materials类,以及一组可以处理任何材质而不知道材质细节的模型。因此,您可能需要将粗糙材质实例应用于六种不同的模型

我认为,如果每个材质实例由多个模型共享,而每个模型实例对不同材质一无所知,则每个模型对象需要第三个实例来存储计时器或绘制所需的任何实例数据。因此,每个模型都会接收自己的AppliedMaterial对象,该对象指向共享材质实例以及将材质应用于该模型所需的数据成员。每个材质类型都必须有不同的AppliedMaterial子类,存储与每个材质相关的数据值。使用第三个上下文对象的示例:

struct GlossMaterialContext
{
    Timer t;
    int someContextValue;
};

class GlossMaterial : Material
{
    void * NewApplicator() { return new GlossMaterialContext; }
    void FreeApplicator(void *applicator) { delete (GlossMaterialContext*)applicator; }
    void ApplyMaterial(void *applicator)
    {
        // Set up shader...
        // Apply texture...
    }
};

class 3DModel
{
    Material *material;
    void *materialContext;

    void SetMaterial(Material *m)
    {
        material = m;
        materialContext = m->NewApplicator();
    }
    void Draw()
    {
        material->ApplyMaterial(materialContext);
    }
};
这不是一个令人难以置信的清洁技术。我总觉得对付虚空的指针是件麻烦事。您还可以让第三个上下文对象基于类,如:

class AppliedGlossMaterial : AppliedMaterial
以及:


如果datamembers同时依赖于模型类型和材质类型,则模型和材质的耦合过于紧密,无法将它们分成单独的类,而这些类本应互不了解。您必须创建模型的子类:Glossy3DModel、Rough3DModel等。

我认为大多数渲染引擎将实际渲染收集到一个中心算法/算法系列中,因此
draw()
将在一个中央渲染器类中实现,该类将保存下一帧中要渲染的所有对象的集合。然后,它将调用方法来获取适当的低级数据(顶点和颜色、着色器等),这些数据将通过模型和材质接口公开。这是因为材质可能知道很多关于材质的信息,但它可能不应该知道任何关于DirectX或OpenGl的信息

但是,根据您发布的内容,您可以实现以下功能:

class PerFrameData
{
    //Methods to get data in a standard way
}

class Material
{
    setPerFrameData(PerFrameData data)
    {
        if (data.hasVertices())
        // or whatever
    }
}

class Model
{
    PerFrameData data;
    Material material;

    draw()
    {
        material->setPerFrameData(data);
    }
}

让我先说一句,我以前从未实施过这样一个系统,但以下是我的想法

问题源于这样一个事实,
Model::Draw
在不知道材质所属的特定类别时,会自行更改材质。这就要求在室外进行“材料修改”

void Model::Draw() {
    // do draw by using the current material
}

void Model::UpdateMaterial(Material* mat) {
    m_pMaterial = mat
}
由于您在每个帧的其他位置修改了材质,因此此时您应该知道其类型。此更改的材质将排队等待特定对象和帧

然后,可以在渲染帧之前进行准备阶段,在该阶段执行必要的簿记并将材质替换为新材质


我还建议您使用类似的工具来管理
材质
对象,因为跟踪哪些对象正在共享材质以及是否需要删除材质是一件非常麻烦的事情。

根据您所说的,材质类似乎抽象了着色器,材质之间的PerFrameData不同,因为每个着色器可以接受不同的输入集。问题是您有一个单一的数据存储类Model,它可以存储各种输入排列。你的模型是否包含纹理和顶点颜色?使用什么顶点格式?三角形是否索引为条带或列表?模型是否公开法线贴图?光照图?规格?等等

您确实需要定义一组渲染系统可以理解的标准组件,以及一个了解所有这些组件的界面,因此可以作为模型和材质之间的桥梁。Material需要能够通过该接口查询模型的数据,并验证它是否公开了正确的内容。模型可以构造类并将其传递给要渲染的材质


另外要考虑的是性能。如果渲染器在所有模型对象上循环调用
Render
,这可能会非常无性能,因为每个模型都可以有多种材质,因此,在每帧多次切换着色器状态可能会招致巨大的惩罚。如果改为按材质对渲染过程进行分组,则可以节省大量时间

如果模型类不知道
SetPerFrameInfo
的参数类型,它将如何传递正确的对象?另一方面,如果它知道,你可以改变它
void Model::Draw() {
    // do draw by using the current material
}

void Model::UpdateMaterial(Material* mat) {
    m_pMaterial = mat
}