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

时间:2014-12-30 02:11:08

标签: c++ graphics rendering

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

这是它的标题,它现在不起作用,因为我已经对它进行了一些编辑,只要忽略我使用" new&#的部分34;关键字,我仍然在学习,但我希望这能为我想要实现的目标提供一个想法:

//graphicsEngine.h

#pragma once
#include<allegro5\allegro.h>
#include<allegro5\allegro_image.h>
#include<allegro5\allegro_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();
    }

};

3 个答案:

答案 0 :(得分:0)

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

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

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

答案 1 :(得分:0)

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

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

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和friends中)。设置你需要的任何绘图上下文并循环遍历调用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()签名以采用新程序状态,但这将允许您通过设计的对象执行所有低级绘图用于图形绘制而不是使用此功能为主类增加负担。您提供的状态完全取决于您和您的设计。它非常灵活。


BIG UPDATE - 使用合成的另一种方法

我一直在仔细考虑,并决定分享我所做的事情。我上面写的内容过去对我有用,但这次我决定用我的引擎走另一条路并完全放弃一个场景图。我不确定我是否可以推荐这种做事方式,因为它可以使事情变得复杂,但它也为大量的灵活性打开了大门。实际上,我已经编写了较低级别的对象,如VertexBuffer,Effect,Texture等,它允许我以任何我想要的方式组合对象。我这次使用的模板不仅仅是继承(尽管为了提供VertexBuffers,Textures等的实现仍然需要继承。)

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

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

答案 2 :(得分:0)

SFML图形库绘制对象的方式(以及我认为最易管理的方式)是让所有可绘制对象都继承自&#39; Drawable&#39; class(就像David Peterson的答案中的那个),然后可以传递给图形引擎以便绘制。

要绘制物体,我有:

A基类:

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

因为每个子类都很独特,所以您可以使用此方法创建从精灵到图形基元的任何内容。

然后可以通过

引用将这些派生类中的每一个传递给引擎

A&#39; Draw&#39;引用基类的函数:

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

使用此方法,不再需要模板。不幸的是,您当前的graphicObjectData数组不起作用,因为派生类将会被切片&#39;为了适应它。但是,创建一个&#39; const Drawable *&#39;的列表或向量。指针(或最好是智能指针)也可以用来保持所有对象的标签,但实际的对象必须存储在别处。

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

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()时自动绘制它们!一旦他们被摧毁,他们甚至会从列表中删除!

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