我正在尝试实现一个模型(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)Model类可能需要一些其他数据库,具体取决于实际材料,因为对于某些材料我可能决定实现一个计时器,而对于某些我可能不会。对于所有材料,在Model类中使用数据库将是一种内存浪费。
请帮助,我似乎无法找到任何合适的设计模式。
答案 0 :(得分:1)
1。)我认为你遇到的问题是由于试图在基类中投入太多。由于Model在调用SetPerFrameInfo函数时需要知道它正在使用什么特定的材质类型,为什么不给每个Material派生类赋予自己的Set ..()函数,该函数采用正确的类型而不是void *?因为Material基类无法与其每帧信息进行交互(它是void *),那么为什么要将它概括为基类呢?
2.)只需将特定于材料的数据库放入派生材料类中。我假设每个Material实例对应一个唯一的Model,因此Model的材料特定信息应该在Material-derived类中。
好吧,我误解了两个班级之间的关系。在理想情况下,基本上您需要一组不知道模型细节的Materials类,以及一组可以使用任何材质的模型,而不需要知道材料的细节。因此,您可能希望将粗糙材质实例应用于六种不同的模型。
我认为没有任何问题,如果每个Material实例由多个Model共享,并且每个Model实例都不知道不同的Materials,则需要第三个每个Model对象来存储定时器或者任何实例数据是需要绘图。因此,每个Model都会收到自己的AppliedMaterial对象,该对象指向共享的Material实例以及将材质应用于该模型所需的数据成员。每种材料类型都必须有不同的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);
}
};
这不是一种令人难以置信的清洁技术。我一直觉得处理void *指针是一个kludge。您还可以让第3个上下文对象基于类,例如:
class AppliedGlossMaterial : AppliedMaterial
和
class GlossMaterial : Material
{
AppliedMaterial * NewApplicator() { return new AppliedGlossMaterial; }
...
如果数据成员依赖于两者模型类型和材料类型,那么您的模型和材料将紧密耦合,使它们成为彼此无关的单独类。您必须创建Model的子类:Glossy3DModel,Rough3DModel等。
答案 1 :(得分:1)
让我先说明我以前没有实施过这样的系统,但这是我的想法。
问题源于这样一个事实:当Model::Draw
不了解材料所具有的特定类时,void Model::Draw() {
// do draw by using the current material
}
void Model::UpdateMaterial(Material* mat) {
m_pMaterial = mat
}
会自行更改材料。这种“材料修改”的尖叫声要在外面完成。
Material
由于您在每帧的其他位置修改了材质,因此您应该知道该点的类型。此更改的材料排队等待特定对象和框架
然后,您可以在渲染框架之前准备一个准备阶段,在那里进行必要的簿记并换出新材料。
我还建议使用类似boost::shared_ptr
之类的东西来管理您的{{1}}对象,因为跟踪哪些对象共享材料以及是否需要删除这些对象是一个巨大的痛苦
答案 2 :(得分:1)
根据您的说法,听起来您的Material类抽象着色器,并且PerFrameData在材质之间有所不同,因为每个着色器可以采用不同的输入集。问题是你有一个数据存储类Model,存储可能是各种输入排列的东西。即,你的模型是否包含纹理,顶点颜色,两者?使用什么顶点格式?三角形是否被索引为条带或列表?模型是否暴露了法线贴图?光照贴图?规格?等等。
您确实需要定义渲染系统理解的一组标准组件,以及一个了解所有这些组件的接口,因此可以成为模型和材料之间的桥梁。材料需要能够通过此接口查询Model的数据,并验证它是否公开了正确的事物。 Model可以构造类并将其传递给Material来渲染。
要考虑的另一件事是表现。如果您的渲染器遍历调用Render
的所有Model对象,那么这可能非常不具有性能,因为每个模型都可以有各种材质,因此您可能会因为每帧多次切换着色器状态而受到巨大的惩罚。如果您改为按材质对渲染过程进行分组,则可以节省大量时间。
答案 3 :(得分:0)
我认为大多数渲染引擎将实际渲染收集到一个中心算法/算法族中,因此draw()
将在一个中心渲染器类中实现,该类将保存即将到来的所有对象的集合。帧。然后,它将调用方法来获取将通过Model和Material接口公开的适当的低级数据(顶点和颜色,着色器等)。这是因为材料可能对材料有很多了解,但它可能不应该知道有关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);
}
}