C++ 引擎渲染不同类型的图形对象
我试图写一个类,某种图形引擎,基本上它的目的是渲染我传递给它的任何东西。在我看过的大多数教程中,对象都是自己绘制的。我不确定事情是否应该这样。我一直在互联网上搜索,试图找到不同的方法来处理这个问题,我一遍又一遍地回顾函数模板和类模板,这听起来像是我可以寻找的解决方案,但当我尝试使用模板时,对我来说,这看起来很混乱,可能是因为我不完全理解如何使用它们,然后我想把模板类取下来,然后我会再试一次,但我只是再次取下来,我不确定这是不是一种方法,但可能是这样。最初它是基于平铺的,只包括屏幕上的一个可移动播放器和一个摄像头系统,但现在我正在尝试编写一个平铺地图编辑器,它有工具栏、列表、文本,将来可能甚至在屏幕上显示原语,等等,我想知道我将如何用一个特定的程序将所有这些元素绘制到屏幕上。这个程序现在并不重要,我稍后会发现这一点。如果有人要编写图形引擎类,您将如何让它区分不同类型的图形对象,例如未绘制为精灵的基本体或未绘制为三角形基本体的球体基本体等。?任何帮助都将不胜感激 这是它的标题,它现在不起作用,因为我已经对它做了一些编辑,只需忽略我使用新关键字的部分,我仍在学习,但我希望这能为我尝试实现的目标提供一个想法:C++ 引擎渲染不同类型的图形对象,c++,graphics,rendering,C++,Graphics,Rendering,我试图写一个类,某种图形引擎,基本上它的目的是渲染我传递给它的任何东西。在我看过的大多数教程中,对象都是自己绘制的。我不确定事情是否应该这样。我一直在互联网上搜索,试图找到不同的方法来处理这个问题,我一遍又一遍地回顾函数模板和类模板,这听起来像是我可以寻找的解决方案,但当我尝试使用模板时,对我来说,这看起来很混乱,可能是因为我不完全理解如何使用它们,然后我想把模板类取下来,然后我会再试一次,但我只是再次取下来,我不确定这是不是一种方法,但可能是这样。最初它是基于平铺的,只包括屏幕上的一个可移动播
//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();
}
};
我的技术可能真的很糟糕,但我就是这样做的
class entity {
public:
virtual void render() {}
};
vector<entity> entities;
void render() {
for(auto c : entities) {
c->render();
}
}
使用它:
entities.push_back(new cubeEntity());
entities.push_back(new triangleEntity());
人们说使用动态继承是不好的。他们比我聪明得多,但这种方法已经很好地工作了一段时间。确保所有的析构函数都是虚拟的 我非常喜欢模板,但在这种情况下,你可能会发现它们很麻烦,尽管答案不一定是错误的。由于您可能希望图形容器中的对象类型多样化,因此继承实际上可能是一个更强大的解决方案 您将需要一个为绘图提供抽象接口的基类型。这个类所需要的只是一些函数,它为实际的绘图过程提供了一种机制。它实际上并不关心绘图是如何发生的,重要的是派生类知道如何绘制自己如果您想将绘图和对象分开,请继续阅读,我将尝试解释一种实现此目的的方法:
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!
}
当然,派生类Circle、Tetraedron和DrawableText需要更新它们的draw签名以采用新的程序状态,但这将允许您通过为图形绘制而设计的对象来完成所有低级绘图,而不是让主类负担此功能。您提供的状态完全取决于您和您的设计。它很灵活
大更新-使用合成的另一种方法
我一直在仔细考虑,并决定分享我一直在做的事情。我上面写的在过去对我很有用,但这次我决定用我的引擎走一条不同的路线,完全放弃场景图。我不确定我是否可以推荐这种方法,因为它会使事情变得复杂,但它也为极大的灵活性打开了大门。实际上,我已经编写了较低级别的对象,如VertexBuffer、Effect、Texture等,它们允许我以任何方式合成对象。这一次我使用的模板不仅仅是继承,尽管在为顶点缓冲区、纹理等提供实现时仍然需要继承
我之所以提出这个问题,是因为你说的是获得更大程度的分离。使用我描述的系统,我可以构建如下的世界对象:
class cubeEntity : public entity {
public:
virtual void render() override {
drawCube();
}
};
class triangleEntity : public entity {
public:
virtual void render() override {
drawTriangle();
}
};
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将实际负责d
绘制世界,并根据需要维护VertexBuffer和其他较低级别的图形对象
如您所见,这与您最初要求的更加接近,因为几何体实际上不仅仅用于渲染。这是关于世界多边形的更多数据,但可以提供给WorldGeometry和WorldOccluder,它们不做任何绘图。事实上,世界类的存在只是为了将这些相似的类分组在一起,但是WorldDrawer可能并不依赖于世界对象。相反,它可能需要一个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();
}
SFML图形库绘制对象的方式,以及我认为最易于管理的方式,是让所有可绘制对象从“可绘制”类继承,如David Peterson的答案中所述,然后可以将该类传递给图形引擎以便绘制 要绘制对象,我需要: 基类:
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 */ }
};
这可以用来包含所有可绘制类(如position)的公共信息,以及某种形式的数据存储
派生子类:
因为每个子类在很大程度上都是唯一的,所以可以使用此方法创建从精灵到图形原语的任何内容
然后,可以通过引用将这些派生类中的每个类传递给引擎
引用基类的“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 */ }
};
使用此方法,不再需要模板。不幸的是,您当前的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);
}
}
现在,您可以创建对象,当调用该过程时,它们将自动绘制!一旦他们被摧毁,他们甚至会从名单上删除
以上所有的想法在过去对我都很有用,所以我希望我已经帮了你,或者至少给了你一些思考的东西。谢谢你的回复,这对我通常渲染图形的方式完全有意义,也是我看到其他人这样做的方式,但在某些情况下,我听说对象不需要担心与绘图有关的任何事情,图形引擎完全有责任根据给定的数据找出如何绘制对象比如向量、大小和颜色。专业程序员就是这样做的吗?我不知道专业人士会怎么做,但如果类似的话,我也不会感到惊讶。事实上,我一直在开发自己的引擎,在过去,我就是这样处理的。然而,这一次我做了一些不同的事情,甚至没有包括场景图。这听起来可能有些奇怪,但我这样做是为了保持灵活性。不过,由于目前还没有通用绘图系统,这确实使编写程序变得更加困难。对象被抽象为VertexBuffer、Effect、Texture之类的东西,我用它们组成了其他类型。我会用信息更新我的答案。祝你假期愉快!我很惊讶你居然在做3D!我主要还没有开始3D,除了尝试像自上而下的RPG那样实现sprite z-order,有一次我画了一个由线和顶点组成的立方体,但我不知道我怎么会到处都有各种各样的3D。然而现在,我有一些想法需要回顾。虽然你提供了很好的建议,我将使用其中的大部分,但我相信我最好将我的图形引擎分成两个或多个部分
多上课;一个是为我的tile编辑器界面图形而构建的,即:类editorInterface和另一个类worldDrawer,如您所建议的。我认为拥有独立的图形引擎会更现实,所以当我调用它们时,它们将能够直接绘制它们的构建目的,而不会混淆不同的数据类型。你比我更了解你的引擎,所以做最有效的。我很高兴我能帮助激发一些想法!:感谢您的回复,您的方式非常容易理解,但我不确定是应该这样做,还是应该将实体的所有数据传递给一个类,即可以使用所有数据来绘制图形的图形引擎类。大多数程序员都是这样做的吗?@Blahblahblah我不知道。我很欣赏这里的所有答案,它们都非常详尽。我会记住他们,看看我能做些什么
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);
}
}