这种情况的优秀设计是什么?

时间:2012-02-17 17:01:31

标签: c++ direct3d

我正在制作一个基本的渲染引擎。

为了让渲染引擎对各种几何体进行操作, 我做了这个课:

class Geometry
{
protected:
ID3D10Buffer* m_pVertexBuffer;
ID3D10Buffer* m_pIndexBuffer;

public:
[...]
};

现在,我希望用户能够通过继承此类来创建自己的几何体。 所以让我们假设用户做了class Cube : public Geometry 用户必须在初始化时创建vertexbuffer和indexbuffer。

这是一个问题,因为每次创建一个新的Cube对象时它都会重新创建vertexbuffer和indexbuffer。每个派生类应该只有一个vertexbuffer和indexbuffer实例。无论是那种,还是完全不同的设计。

解决方案可能是为继承类创建单独的static ID3D10Buffer*,并将继承类的指针设置为等于构造函数中的指针。

但这需要一个静态方法,如static void CreateBuffers(),用户必须在他的应用程序中为他决定从Geometry继承的每种类型显式调用一次。这似乎不是一个很好的设计。

这个问题的解决方案是什么?

3 个答案:

答案 0 :(得分:4)

您应该将实例的概念与网格的概念分开。这意味着您为一个多维数据集创建一个Geometry版本,该多维数据集表示多维数据集的顶点和索引缓冲区。

然后,您将引入一个名为GeometryInstance的新类,其中包含一个转换矩阵。该类还应具有指向Geometry的指针/引用。现在,您可以通过创建几何所有引用相同Geometry对象的GeometryInstances来创建新几何实例,而不是复制内存或在创建新框时工作。

编辑: 假设你有问题中的Geometry类和评论中的Mesh类,你的Mesh类看起来应该是这样的:

class Mesh {
  private:
  Matrix4x4 transformation;
  Geometry* geometry;
  public:
  Mesh(const Matrix4x4 _t, Geometry* _g) : transformation(_t), geometry(_g) {}
}

现在,在创建场景时,你想要做这样的事情

...
std::vector<Mesh> myMeshes;
// OrdinaryGeometry is a class inheriting Geometry 
OrdinaryGeometry* geom = new OrdinaryGeometry(...);
for(int i = 0; i < ordinaryGeomCount; ++i) {
  // generateTransform is a function that generates some 
  // transformation Matrix given an index, just as an example
  myMeshes.push_back(Mesh(generateTransform(i), geom);
}
// SpecialGeometry is a class inheriting Geometry with a different
// set of vertices and indices
SuperSpecialGeometry* specialGeom = new SuperSpecialGeometry(...);
for(int i = 0; i < specialGeomCount; ++i) {
  myMeshes.push_back(Mesh(generateTransform(i), specialGeom);
}

// Now render all instances
for(int i = 0; i < myMeshes.size(); ++i) {
  render(myMeshes[i]);
}

请注意我们如何只有两个在多个网格之间共享的几何对象。理想情况下,这些应该使用std :: shared_ptr或类似的东西进行重新计算,但它超出了问题的范围。

答案 1 :(得分:2)

在立方体示例中对几何进行子分类有什么意义?立方体只是几何体的一个实例,它具有一组特定的三角形和索引。 Cube类和Sphere类之间没有区别,除了它们用不同的数据填充它们的三角形/索引缓冲区。所以数据本身就是重要的。您需要一种方法来允许用户为您的引擎提供各种形状数据,然后在创建之后以某种方式引用该数据。

要提供形状数据,您有两种选择。你可以决定保持Geometry的细节是私有的,并提供一些接口,它接受来自文件的字符串等原始数据,或者填充在某个用户自定义函数中的float数组,为该数据创建Geometry实例,然后给出用户对该实例的一些句柄(或允许用户指定句柄)。或者,你可以创建一些像GeometryInfo这样的类,它有用户填充他/她自己的方法addTriangle,addVertex等,然后有一些接受GeometryInfo的函数,为该数据创建一个Geometry实例,然后再给用户一些句柄。

在这两种情况下,你需要提供一些界面,允许用户说“这里是一些数据,从中取出一些东西并给它一些处理。最小化它会有我所描述的功能。你需要维护一个映射引擎中创建的几何实例的某处。这样就可以强制执行每个形状规则的一个实例,这样就可以将用户想要的内容(“球”,“立方体”)与引擎需要的内容相关联(带填充缓冲区的几何体) )。

现在关于句柄。我要么让用户将数据与名称相关联,比如“Ball”,要么返回一些用户随后会与某个“Ball”实例关联的整数。这样,当您制作Rocket类时,用户可以从您的引擎请求“Ball”实例,其他各种对象可以使用“Ball”,一切都很好,因为它们只是存储句柄,而不是球本身。我不建议存储指向实际Geometry实例的指针。网格物体不拥有几何体,因为它可以与其他网格物体共享。它不需要访问几何的成员,因为渲染器处理grunt工作。所以这是一种不必要的依赖。唯一的原因是速度,但使用散列处理你的句柄也会同样好。

现在举一些例子:

提供形状数据:

//option one
engine->CreateGeometryFromFile("ball.txt", "Ball");

//option two
GeometryInfo ball;

ball.addTriangle(0, 1, 0, 1);
ball.addTriangle(...);
...

engine->CreateGeometryFromInfo(ball, "Ball");

使用句柄引用该数据:

class Drawable
{
    std::string shape;
    Matrix transform;
};

class Rocket : public Drawable
{
    Rocket() { shape = "Ball";}
    //other stuff here for physics maybe
};

class BallShapedEnemy : public Drawable
{

    BallShapedEnemy() { shape = "Ball";}
    ...
}

...

...in user's render loop...

for each (drawable in myDrawables)
{
    engine->Render(drawable.GetShape(), drawable.GetTransform());
}

现在,为每个不同的游戏对象(例如Rocket)设置一个单独的类是有争议的,并且完全是另一个问题的主题,我只是从评论中看起来像你的例子。

答案 2 :(得分:1)

这可能是一种草率的方式,但你不能只做一个单身人士吗?

#pragma once
#include <iostream>
#define GEOM Geometry::getInstance()

class Geometry
{
protected:
    static Geometry* ptrInstance;
    static Geometry* getInstance();

    float* m_pVertexBuffer;
    float* m_pIndexBuffer;
public:
    Geometry(void);
    ~Geometry(void);

    void callGeom();
};

#include "Geometry.h"

Geometry* Geometry::ptrInstance = 0;

Geometry::Geometry(void)
{
}


Geometry::~Geometry(void)
{
}

Geometry* Geometry::getInstance()
{
    if(ptrInstance == 0)
    {
        ptrInstance = new Geometry();
    }
    return ptrInstance;
}

void Geometry::callGeom()
{
    std::cout << "Call successful!" << std::endl;
}

这种方法唯一的问题是你只有一个Geometry对象而我假设你可能需要多个?如果不是它可能有用,但我认为Lasserallan的方法可能是一个更好的实现你想要的。