【问题标题】:Mesh generating algorithm for a voxel game体素游戏的网格生成算法
【发布时间】:2021-04-25 09:52:08
【问题描述】:

我目前正在使用 DirectX11 制作像 Minecraft 这样的体素游戏。 游戏与任何其他体素游戏一样适用于块系统,但我当前用于生成块网格的算法不可扩展。 Block 类有一些属性,例如 block full 和 mesh type。

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 语句都会变得更大,我认为这不是要走的路。 我问是否有更好的方法来做到这一点。 提前谢谢你。

【问题讨论】:

    标签: c++ graphics directx voxel


    【解决方案1】:

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

    基类Block的实现:可以大致保持不变,但添加一个virtual方法draw(...)是所有派生类的通用接口:

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

    不同类型的块作为继承构造函数的派生类引入(或者您也可以实现一个新的构造函数)和overridedraw(...) 类的行为。如果你不打算从这个派生类继承,那么你可以将它标记为final,或者如果你不会在派生类中覆盖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 &lt;memory&gt; 的普通指针的包装器)

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

    然后你可以调用不同派生类的实现,如下:

    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我整理了一个简化的工作示例,以便在在线编译器中使用。

    【讨论】:

    • 得到了我的投票,但更喜欢std::unique_ptr,除非实际上需要共享所有权。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-06
    相关资源
    最近更新 更多