用于渲染不同类型的图形对象的引擎



我正在尝试编写一个类(某种图形引擎),基本上它的目的是渲染我传入的任何内容。在我见过的大多数教程中,对象都会自行绘制。我不确定事情是否应该这样运作。我一直在互联网上搜索,试图想出不同的方法来解决这个问题,我一直在一遍又一遍地审查函数模板和类模板(这听起来像是我可能正在寻找的解决方案),但是当我尝试使用模板时,对我来说似乎很混乱(可能是因为我不完全了解如何使用它们),然后我会想把模板类拿下来, 然后我会再试一次,但后来我又把它拿下来了,我不确定这是否是要走的路,但它可能是。最初它只是基于平铺的(包括屏幕上的可移动播放器以及相机系统),但现在我尝试编写一个平铺地图编辑器,其中包含工具栏、列表、文本,甚至将来屏幕上的基元等内容,我想知道如何使用某个程序将所有这些元素绘制到屏幕上(该过程现在并不重要, 我稍后会发现的)。如果你们中的任何人要编写图形引擎类,您将如何让它区分不同类型的图形对象,例如未绘制为精灵的基元或未绘制为三角形基元的球体基元等?任何帮助将不胜感激。:)

这是它的标题,它现在不起作用,因为我一直在对它进行一些编辑,只需忽略我使用"new"关键字的部分,我仍在学习,但我希望这能为我想要完成的事情提供一个想法:

//graphicsEngine.h
#pragma once
#include<allegro5allegro.h>
#include<allegro5allegro_image.h>
#include<allegro5allegro_primitives.h>
template <class graphicObjectData>
class graphicsEngine
{
public:
    static graphicObjectData graphicObject[];
    static int numObjects;
    static void setup()
    {
        al_init_image_addon();
        al_init_primitives_addon();
        graphicObject = new graphicObjectData [1]; //ignore this line
    }
    template <class graphicObjectData> static void registerObject(graphicObjectData &newGraphicObject) //I'm trying to use a template function to take any type of graphic object
    {
        graphicObject[numObjects] = &newObject; 
        numObjects++;
    }
    static void process() //This is the main process where EVERYTHING is supposed be drawn
    {
        int i;
        al_clear_to_color(al_map_rgb(0,0,0));
        for (i=0;i<numObjects;i++) drawObject(graphicObject[i]);
        al_flip_display();
    }
};

我是模板的忠实粉丝,但在这种情况下您可能会发现它们很麻烦(尽管不一定是错误的答案)。 由于您可能希望在绘图容器中具有不同的对象类型,因此继承实际上可能是一个更强大的解决方案。

您需要一个提供抽象绘图接口的基类型。 此类所需要的只是一些为实际绘制过程提供机制的函数。 它实际上并不关心绘图是如何发生的,重要的是派生类知道如何绘制自己(如果你想将你的绘图和你的对象分开,请继续阅读,我将尝试解释一种方法来实现这一点):

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等,这些对象允许我以任何我想要的方式组合对象。 这次我使用的不仅仅是继承(尽管为顶点缓冲区、纹理等提供实现仍然是必要的)。

我提出这个问题的原因是因为你在谈论获得更大程度的分离。 使用我描述的系统,我可以构建一个这样的世界对象:

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();
}

我的技术可能真的很糟糕,但我这样做。

class entity {
public:
    virtual void render() {}
};
vector<entity> entities;
void render() {
    for(auto c : entities) {
        c->render();
    }
}

然后我可以做这样的事情:

class cubeEntity : public entity {
public:
    virtual void render() override {
        drawCube();
    }
};
class triangleEntity : public entity {
public:
    virtual void render() override {
        drawTriangle();
    }
};

并使用它:

entities.push_back(new cubeEntity());
entities.push_back(new triangleEntity());

人们说使用动态继承是不好的。他们比我聪明得多,但这种方法已经工作了一段时间。确保使所有析构函数都虚拟!

SFML图形库绘制对象的方式(我认为最易于管理的方式)是让所有可绘制对象继承自"可绘制"类(如David Peterson的答案中的类),然后可以将其传递给图形引擎以进行绘制。

要绘制对象,我需要:

基类:

class Drawable 
{
    int XPosition;
    int YPosition;
    int PixelData[100][100]; //Or whatever storage system you're using
}

这可用于包含所有可绘制类共有的信息(如位置和某种形式的数据存储)。

派生子类:

class Triangle : public Drawable
{
    Triangle() {} //overloaded constructors, additional variables etc
    int indigenous_to_triangle;
}

由于每个子类在很大程度上都是唯一的,因此可以使用此方法创建从精灵到图形基元的任何内容。

然后,这些派生类中的每一个都可以通过引用传递给引擎

引用基类的"Draw"函数:

void GraphicsEngine::draw(const Drawable& _object);

使用此方法时,不再需要模板。不幸的是,您当前的 graphicObjectData 数组不起作用,因为派生类将被"切片"以适应它。但是,创建"const Drawable*"指针(或最好是智能指针)的列表或向量对于保持所有对象的选项卡同样有效,尽管实际对象必须存储在其他地方。

您可以使用这样的东西来使用指针向量绘制所有内容(我试图保留您的函数和变量名称):

std::vector<const Drawable*> graphicObject; //Smart pointers would be better here
static void process()
{
    for (int i = 0; i < graphicObject.size(); ++i) 
        draw(graphicObject[i]);
}

您只需要确保在创建时将每个对象添加到列表中即可。如果你对此很聪明,你甚至可以在建造和破坏中做到这一点:

class Drawable; //So the compiler doesn't throw an error
std::vector<const Drawable*> graphicObject;
class Drawable
{
    Triangle() {} //overloaded constructors, additional variables etc
    int indigenous_to_triangle;
    std::vector<const Drawable*>::iterator itPos; 
    Drawable() {
        graphicObject.push_back(this);
        itPos = graphicObject.end() - 1;
    }
    ~Drawable() {
        graphicObject.erase(itPos);
    }
}

现在你可以只创建对象,当调用 process() 时,它们会自动绘制!一旦它们被销毁,它们甚至会从列表中删除!

以上所有想法在过去对我都有很好的帮助,所以我希望我能帮助你,或者至少给你一些思考。

相关内容

  • 没有找到相关文章

最新更新