体素游戏的网格生成算法

Mesh generating algorithm for a voxel game

我目前正在使用 DirectX11 制作像 Minecraft 这样的体素游戏来获得乐趣。 游戏像任何其他体素游戏一样使用块系统,但我当前用于生成块网格的算法不可扩展。 块 class 有一些属性,例如块满和网格类型。

class Block
{
public:
    bool isFull = true;
    MeshType type = MeshType::FullBlock;
    Vector2i texture = { 9, 1 };
    Vector2i topTexture = { 9, 1 };
    const char* sound;

    Block(){}
    Block(bool isFull, MeshType type, Vector2i texture, Vector2i topTexture, const char* sound): isFull(isFull), type(type), texture(texture), topTexture(topTexture), sound(sound){}
    Block(bool isFull, MeshType type, Vector2i texture, const char* sound) : isFull(isFull), type(type), texture(texture), topTexture(texture), sound(sound) {}
    Block(bool isFull, MeshType type, Vector2i texture) : isFull(isFull), type(type), texture(texture), topTexture(texture) {}

};

然后在向量中初始化每个块

    blocks.reserve(64);

    Block air(false, MeshType::Empty, {0 ,0});
    blocks.emplace_back(air);

    Block grass(true, MeshType::FullBlock, { 3, 0 }, { 0, 0 }, "Audio/grass1.ogg");
    blocks.emplace_back(grass);

    Block stone(true, MeshType::FullBlock, { 1, 0 }, "Audio/stone1.ogg");
    blocks.emplace_back(stone);

    Block rose(false, MeshType::Cross, { 12 ,0 }, "Audio/grass1.ogg");
    blocks.emplace_back(rose);

    Block wheat(false, MeshType::Hash, { 8 ,3 });
    blocks.emplace_back(wheat);

    //and so on...

我有一个接受顶点向量和块数据的函数。 该函数通过一些优化循环遍历所有块,并将数据放回发送到缓冲区的向量中。

for (int x = 0; x < ChunkWidth; x++)
    {
        for (int y = 0; y < ChunkHeight; y++)
        {
            for (int z = 0; z < ChunkWidth; z++)
            {
                if (IsDrawable[x][y][z] == 1)
                {
                    switch (blocks[chunk->BlockID[x + 16 * y + 16 * 256 * z]].type)
                    {
                    case MeshType::FullBlock:
                        BuildBlock(chunk, vertices, x, y, z);
                        break;
                    case MeshType::Cross:
                        FillCross(vertices, blocks[chunk->BlockID[x + 16 * y + 16 * 256 * z]], x + chunk->x * ChunkWidth, y, z + chunk->z * ChunkWidth);
                        break;
                    case MeshType::Hash:
                        FillHash(vertices, blocks[chunk->BlockID[x + 16 * y + 16 * 256 * z]], x + chunk->x * ChunkWidth, y, z + chunk->z * ChunkWidth);
                        break;
                    }
                }
            }
        }
    }

对于每一种新的网格类型,switch 语句都会变大,我认为这不是可行的方法。 我问是否有更好的方法来做到这一点。 我先谢谢你了。

我认为 不同派生 classes 共同父 class Block是去这里的路。您在其中添加一个 virtual 方法 ,其行为在派生的 classes 中被覆盖。然后你把它们放在一个 std::shared_ptr<Block> 的多态向量中并调用它们。如果您担心由于某种原因这可能太慢,您可以将虚函数替换为 Curiously Recurring Template Pattern (CRTP) 以实现静态多态性。所以像:

基础的实现 class Block:可以与添加一个 virtual 方法 draw(...) 大致相同,它是所有派生 [=] 的公共接口49=]es:

class Block {
  public:
    bool isFull = true;
    Vector2i texture = { 9, 1 };
    Vector2i topTexture = { 9, 1 };
    const char* sound;

    Block() {
      return;
    }
    Block(bool isFull, Vector2i const& texture, Vector2i const& topTexture, const char* sound)
    : isFull(isFull), texture(texture), topTexture(topTexture), sound(sound) {
      return;
    }
    Block(bool isFull, Vector2i const& texture, const char* sound)
    : isFull(isFull), texture(texture), topTexture(texture), sound(sound) {
      return;
    }
    Block(bool const& isFull, Vector2i const& texture)
    : isFull(isFull), texture(texture), topTexture(texture) {
      return;
    }

    // Virtual method that every derived class should override
    // Could contain default behaviour but here I declared it as pure virtual method (therefore the = 0)
    // Didn't know the data types for chunk and vertices so I used Chunk and Vertices respectively
    virtual void draw(Chunk const& chunk, Vertices const& vertices, int x, int y, int z, int chunkWidth) = 0;
};

不同类型的块作为派生的 classes 引入,它们继承了构造函数(或者您也可以实现一个新的构造函数)和 override draw(...) 的行为class。如果您不打算继承此派生 class,那么您可以将其标记为 final,或者如果您不打算在派生 class 中覆盖 draw,您可以将其标记为只有 draw 作为 final

class Empty: public Block {
  public:
    using Block::Block; // Use the constructor of the base class
    
    // Overwrite behaviour of the base class here
    void draw(Chunk const& chunk, Vertices const& vertices, int x, int y, int z, int chunkWidth) override {
      return;
    }
 };

 class FullBlock: public Block {
  public:
    using Block::Block;

    void draw(Chunk const& chunk, Vertices const& vertices, int x, int y, int z, int chunkWidth) override {
      // Move contents of BuildBlock here
      BuildBlock(chunk, vertices, x, y, z);
      return;
    }
 };

 class Cross final: public Block {
  public:
    using Block::Block;

    void draw(Chunk const& chunk, Vertices const& vertices, int x, int y, int z, int chunkWidth) override {
      // Move contents of FillCross here! No need to pass blocks[i] or rewrite FillCross to take something else than a Block, e.g. a std::shared_ptr<Block>
      FillCross(vertices, *this, x + chunk->x * chunkWidth, y, z + chunk->z * chunkWidth);
      return;
    }
 };

 class Hash final: public Block {
  public:
    using Block::Block;

    void draw(Chunk const& chunk, Vertices const& vertices, int x, int y, int z, int chunkWidth) override {
      // Same here
      FillHash(vertices, *this, x + chunk->x * chunkWidth, y, z + chunk->z * chunkWidth);
      return;
    }
 };

然后将所有块添加为 std::shared_ptr 或更好的 std::unique_ptr 如果资源不共享! (来自 #include <memory> 的普通指针的包装)

// Consider using std::unique_ptr if you are not using the individual objects outside of the std::vector
std::vector<std::shared_ptr<Block>> blocks = {};
blocks.reserve(64);

auto air = std::make_shared<Empty>(false, {0 ,0});
blocks.emplace_back(air);

auto grass = std::make_shared<FullBlock>(true, { 3, 0 }, { 0, 0 }, "Audio/grass1.ogg");
blocks.emplace_back(grass);

auto stone = std::make_shared<FullBlock>(true, { 1, 0 }, "Audio/stone1.ogg");
blocks.emplace_back(stone);

auto rose = std::make_shared<Cross>(false, { 12 ,0 }, "Audio/grass1.ogg");
blocks.emplace_back(rose);

auto wheat = std::make_shared<Hash>(false, { 8 ,3 });
blocks.emplace_back(wheat);

您可以调用不同的派生 classes 的实现,如下所示:

for (int x = 0; x < chunkWidth; x++) {
  for (int y = 0; y < chunkHeight; y++) {
    for (int z = 0; z < chunkWidth; z++) {
      if (IsDrawable[x][y][z] == 1) {
        blocks[chunk->BlockID[x + 16 * y + 16 * 256 * z]]->draw(chunk, vertices, x, y, z, chunkWidth);
      }
    }
  }
}

Here 我整理了一个简化的工作示例,以便在在线编译器中试用。