我是模板的忠实粉丝,但您可能会发现在这种情况下它们很麻烦(尽管不一定是错误的答案)。由于看起来您可能需要绘图容器中的多种对象类型,因此继承实际上可能是一个更强大的解决方案。
您将需要一个为绘图提供抽象接口的基本类型。这个类所需要的只是一些为实际绘制过程提供机制的函数。它实际上并不关心绘图是如何发生的,重要的是派生类知道如何绘制自己(如果您想将绘图和对象分开,请继续阅读,我将尝试解释一种方法来实现这一点):
class Drawable {
public:
// This is our interface for drawing. Simply, we just need
// something to instruct our base class to draw something.
// Note: this method is pure virtual so that is must be
// overriden by a deriving class.
virtual void draw() = 0;
// In addition, we need to also give this class a default virtual
// destructor in case the deriving class needs to clean itself up.
virtual ~Drawable() { /* The deriving class might want to fill this in */ }
};
从这里开始,您只需编写从 Drawable 类继承的新类并提供必要的 draw() 覆盖。
class Circle : public Drawable {
public:
void draw() {
// Do whatever you need to make this render a circle.
}
~Circle() { /* Do cleanup code */ }
};
class Tetrahedron : public Drawable {
public:
void draw() {
// Do whatever you need to make this render a tetrahedron.
}
~Tetrahedron() { /* Do cleanup code */ }
};
class DrawableText : public Drawable {
public:
std::string _text;
// Just to illustrate that the state of the deriving class
// could be variable and even dependent on other classes:
DrawableText(std::string text) : _text(text) {}
void draw() {
// Yet another override of the Drawable::draw function.
}
~DrawableText() {
// Cleanup here again - in this case, _text will clean itself
// up so nothing to do here. You could even omit this since
// Drawable provides a default destructor.
}
};
现在,要将所有这些对象链接在一起,您可以简单地将它们放在您选择的容器中,该容器接受引用或指针(或在 C++11 及更高版本中,unique_ptr、shared_ptr 和朋友)。设置您需要的任何绘制上下文并循环调用 draw() 的容器的所有内容。
void do_drawing() {
// This works, but consider checking out unique_ptr and shared_ptr for safer
// memory management
std::vector<Drawable*> drawable_objects;
drawable_objects.push_back(new Circle);
drawable_objects.push_back(new Tetrahedron);
drawable_objects.push_back(new DrawableText("Hello, Drawing Program!"));
// Loop through and draw our circle, tetrahedron and text.
for (auto drawable_object : drawable_objects) {
drawable_object->draw();
}
// Remember to clean up the allocations in drawable_objects!
}
如果你想为你的绘图机制提供状态信息,你可以要求它作为 Drawable 基类的 draw() 例程中的参数:
class Drawable {
public:
// Now takes parameters which hold program state
virtual void draw(DrawContext& draw_context, WorldData& world_data) = 0;
virtual ~Drawable() { /* The deriving class might want to fill this in */ }
};
当然,派生类 Circle、Tetrahedron 和 DrawableText 需要更新它们的 draw() 签名以采用新的程序状态,但这将允许您通过设计的对象进行所有低级绘图用于图形绘制,而不是用这个功能给主类增加负担。您提供的状态完全取决于您和您的设计。它非常灵活。
重大更新 - 使用组合的另一种方法
我一直在仔细考虑,并决定分享我一直在做的事情。我上面写的过去对我有用,但是这一次我决定用我的引擎走一条不同的路线,完全放弃一个场景图。我不确定我是否可以推荐这种做事方式,因为它会使事情变得复杂,但它也为极大的灵活性打开了大门。实际上,我已经编写了较低级别的对象,例如 VertexBuffer、Effect、Texture 等,它们允许我以任何我想要的方式组合对象。这次我使用的模板不仅仅是继承(尽管对于为 VertexBuffers、Texture 等提供实现仍然需要继承)。
我提出这个的原因是因为你在谈论获得更大程度的分离。使用我描述的系统,我可以像这样构建一个世界对象:
class World {
public:
WorldGeometry geometry; // Would hold triangle data.
WorldOccluder occluder; // Runs occlusion tests against
// the geometry and flags what's visible and
// what is not.
WorldCollider collider; // Handles all routines for collision detections.
WorldDrawer drawer; // Draws the world geometry.
void process_and_draw();// Optionally calls everything in necessary
// order.
};
在这里,我将有多个对象,它们专注于我的引擎处理的一个方面。 WorldGeometry 将存储有关此特定世界对象的所有多边形详细信息。 WorldOccluder 将对相机和几何体进行检查,以查看实际可见的世界斑块。 WorldCollider 将处理针对任何世界对象的碰撞检测(为简洁起见,省略)。最后,WorldDrawer 实际上将负责世界的绘制,并根据需要维护 VertexBuffer 和其他较低级别的绘图对象。
如您所见,这与您最初询问的内容更接近,因为几何实际上不仅仅用于渲染。它是关于世界多边形的更多数据,但可以提供给不做任何绘图的 WorldGeometry 和 WorldOccluder。事实上,World 类的存在只是为了将这些相似的类组合在一起,但 WorldDrawer 可能不依赖于 World 对象。相反,它可能需要一个 WorldGeometry 对象,甚至是一个三角形列表。基本上,您的程序结构变得高度灵活,并且依赖关系开始消失,因为对象不经常或根本不继承,并且只请求它们绝对需要的功能。举个例子:
class WorldOccluder {
public:
// I do not need anything more than a WorldGeometry reference here //
WorldOccluder(WorldGeometry& geometry) : _geometry(geometry)
// At this point, all I need to function is the position of the camera //
WorldOccluderResult check_occlusion(const Float3& camera) {
// Do all of the world occlusion checks based on the passed
// geometry and then return a WorldOccluderResult
// Which hypothetically could contain lists for visible and occluded
// geometry
}
private:
WorldGeometry& _geometry;
};
我选择 WorldOccluder 作为示例,因为我花了一天的大部分时间为我的引擎处理类似的东西,并且使用了与上面类似的类层次结构。我有 3D 空间中的盒子根据是否应该看到它们来改变颜色。我的课程非常简洁且易于理解,我的整个项目层次结构也很容易理解(我认为无论如何)。所以这似乎工作得很好!我喜欢度假!
最后说明:我提到了模板,但没有解释它们。如果我有一个围绕绘图进行处理的对象,那么模板对此非常有效。它避免了依赖关系(例如通过继承),同时仍然提供了很大程度的灵活性。此外,编译器可以通过内联代码和避免虚拟样式调用来优化模板(如果编译器可以推断出这样的优化):
template <typename TEffect, TDrawable>
void draw(TEffect& effect, TDrawable& drawable, const Matrix& world, const Matrix& view, const Matrix& projection) {
// Setup effect matrices - our effect template
// must provide these function signatures
effect.world(world);
effect.view(view);
effect.projection(projection);
// Do some drawing!
// (NOTE: could use some RAII stuff here in case drawable throws).
effect.begin();
for (int pass = 0; pass < effect.pass_count(); pass++) {
effect.begin_pass(pass);
drawable.draw(); // Once again, TDrawable objects must provide this signature
effect.end_pass(pass);
}
effect.end();
}