【问题标题】:How do I implement infinite voxel chunks without slowing down the performance in c++?如何在不降低 C++ 性能的情况下实现无限体素块?
【发布时间】:2021-10-07 11:43:17
【问题描述】:

所以我目前正在处理一个大小为 16x256x16 的块,并为块类型创建一个整数网格。但我的问题是如何实现无限体素块??? 顺便说一句,我使用的是 SFML 1.6。

这是我的代码:

头文件(chunk.hpp):

#ifndef CHUNK_HPP
#define CHUNK_HPP

#pragma once
#include <SFML/Graphics.hpp>
#include "player.hpp"
#include "types.hpp"
#include "frustumcull.hpp"
#include "noise_generator.hpp"

const int horiz_chunksize = 16;
const int vert_chunksize = 256;

class Chunk {
public:

    Chunk();
    ~Chunk();

    int get(int x, int y, int z); // get block type in position
    void set(int x, int y, int z, int type); // set block type in position
    void render(Player &p, FrustumCull &cull, int renderDistance = 20); // render chunk
    int getTerrainHeight(int x, int y); // returns noise height on the given position

private:

    Perlin_Noise m_noise;
    void update(int x, int y, int z, int type); // update block faces
    
};

#endif // CHUNK_HPP

来源(chunk.cpp):

我将 m_blockgrid 放在 .cpp 中,因为如果我将它放在标题中,它会 只画一个立方体,在 Chunk::~Chunk() delete[] chnk::m_blockgrid 中也有必要???

#include "chunk.hpp"
#include "block.hpp"
#include "maths.hpp"
#include <iostream>

namespace chnk {
    int m_blockgrid[horiz_chunksize][vert_chunksize][horiz_chunksize]; // block grid
    Block *m_block;
}

Chunk::Chunk() {
    chnk::m_block = new Block(); // initialize block class
    m_noise.setSeed(sf::Randomizer::Random(2736473, 8476864));

    for(int x = 0; x < horiz_chunksize; x++) {
        for(int z = 0; z < horiz_chunksize; z++) {
            int heightmap = 16;
            for(int y =-1; y < heightmap; y++) {
                if(y > heightmap-2) set(x, y, z, BlockType::GRASS);
                if(y < heightmap-1 && y > heightmap - 4) set(x, y, z, BlockType::DIRT);
                if(y < heightmap-3 && y > 0) set(x, y, z, BlockType::STONE);
                if(y == 0) set(x, y, z, BlockType::BEDROCK);
            }
        }
    }

}

Chunk::~Chunk() {
    delete[] chnk::m_blockgrid;
}

int Chunk::get(int x, int y, int z) {
    // check boundary
    if((x<0) || (x>=horiz_chunksize) ||
       (y<0) || (y>=vert_chunksize) ||
       (z<0) || (z>=horiz_chunksize)) return BlockType::AIR;
    return chnk::m_blockgrid[x][y][z];
}

void Chunk::set(int x, int y, int z, int type) {
    chnk::m_blockgrid[x][y][z] = type;
    m_update = true;
}

void Chunk::render(Player &p, FrustumCull &cull, int renderDistance) {
    int px = p.m_position.x / chnk::m_block->m_size;
    int py = (p.m_position.y + p.m_bottom) / chnk::m_block->m_size;
    int pz = p.m_position.z / chnk::m_block->m_size;
    float radius = sqrt(Maths::sqr(chnk::m_block->m_size) * 5);

    glEnable(GL_CULL_FACE); // hide back face
    glEnable(GL_DEPTH_TEST); // depth testing

    // render object(s)
    for(int x = 0; x < horiz_chunksize; x++) {
        for(int z = 0; z < horiz_chunksize; z++) {
            for(int y = 0; y < vert_chunksize; y++) {
                int type = get(x, y, z);
                if(!cull.sphereInFrustum(sf::Vector3f(chnk::m_block->m_size * x + chnk::m_block->m_size / 2, chnk::m_block->m_size * y + chnk::m_block->m_size / 2, chnk::m_block->m_size * z + chnk::m_block->m_size / 2), radius)) continue;
                update(x, y, z, type); // update for block texture & etc.
            }
        }
    }
}

void Chunk::update(int x, int y, int z, int type) {
    // only show face in outside not inside
    // I use get(x, y, z) to get block position at given grid
    if(BlockType::getSolidBlocks(type)) {
        if(BlockType::getSolidBlocks(get(x, y+1, z)) == 0 && get(x, y+1, z) != type) {
            chnk::m_block->setupBlock(x, y, z, Block::Top); // Top Face
        }
        if(BlockType::getSolidBlocks(get(x, y-1, z)) == 0 && get(x, y-1, z) != type) {
            chnk::m_block->setupBlock(x, y, z, Block::Bottom); // Bottom Face
        }
        if(BlockType::getSolidBlocks(get(x, y, z-1)) == 0 && get(x, y, z-1) != type) {
            chnk::m_block->setupBlock(x, y, z, Block::Front); // Front Face
        }
        if(BlockType::getSolidBlocks(get(x, y, z+1)) == 0 && get(x, y, z+1) != type) {
            chnk::m_block->setupBlock(x, y, z, Block::Back); // Back Face
        }
        if(BlockType::getSolidBlocks(get(x-1, y, z)) == 0 && get(x-1, y, z) != type) {
            chnk::m_block->setupBlock(x, y, z, Block::Left); // Left Face
        }
        if(BlockType::getSolidBlocks(get(x+1, y, z)) == 0 && get(x+1, y, z) != type) {
            chnk::m_block->setupBlock(x, y, z, Block::Right); // Right Face
        }
    }
}

int Chunk::getTerrainHeight(int x, int y) {
    return ( m_noise.getHeight(x, y) + 64 ); // total height of the given coordinates
}

有些人使用 unordered_map 来存储加载/卸载的块,但我不知道如何使用它以及它是否有效??

有人愿意帮助我吗? :)

【问题讨论】:

  • 欢迎@rentheprogrammer。 “不减速”到底是什么意思:你想访问 O(k) 中的一个块吗?你想在 O(k) 中访问给定块内的特定块吗? “无限”有多大?
  • m_update 不包含在代码中,顺便说一句抱歉。 :)
  • @AdrianMaire O(k) 是什么?
  • "Big O" (en.wikipedia.org/wiki/Big_O_notation) 是一个表示算法在给定输入时有多快的符号。 O(k) 表示输入是什么,它在恒定时间内返回,O(n) 表示线性(就像迭代所有元素),O(n^2) 是......你猜对了吗?
  • @AdrianMaire 我真的不知道任何代数公式,但是我明白了,谢谢。

标签: c++ sfml


【解决方案1】:

我会做出以下假设:

  1. 对给定块的访问是优先级,它们每帧被访问多次,因此访问需要 O(k)
  2. 需要尽可能快地插入和删除块,因为它们会在运行中生成并在运行中删除。没有访问权限那么重要。
  3. 大多数情况下,内存中的块数量几乎是恒定的。

现在,让我们看看我们的可能性:

std::
container
Insertion Access Erase Find Persistent
Iterators
vector/
string
Back: O(1) or O(n)
Other: O(n)
O(1) Back: O(1)
Other: O(n)
Sorted: O(log n)
Other: O(n)
No
deque Back/Front: O(1)
Other: O(n)
O(1) Back/Front: O(1)
Other: O(n)
Sorted: O(log n)
Other: O(n)
Pointers only
list/
forward_list
Back/Front: O(1)
With iterator: O(1)
Index: O(n)
Back/Front: O(1)
With iterator: O(1)
Index: O(n)
Back/Front: O(1)
With iterator: O(1)
Index: O(n)
O(n) Yes
set/map O(log n) - O(log n) O(log n) Yes
unordered_set/
unordered_map
O(1) or O(n) O(1) or O(n) O(1) or O(n) O(1) or O(n) Pointers only
priority_queue O(log n) O(1) O(log n) - -

std::unordered_map 是最合适的结构,因为它允许持续访问和(大部分时间)常量插入/删除:

struct ChunkCoordinate
{
    int32_t x,
    int32_t y
};
class ChunkCoordinateHash{
public:
    size_t operator()(const ChunkCoordinate &val) const
    {
        static_assert(sizeof(size_t)==8);
        return (static_cast<size_t>(val.x)<<32ull) + (static_cast<size_t>(val.y)&0xffffffff);
    }
};

std::unordered_map<ChunkCoordinate, unique_ptr<Chunk>, ChunkCoordinateHash> m_chunks;

注意:unordered_map 上的插入和擦除通常是O(k)(常量),除非空间缺失并且所有地图都需要重新定位。由于您通常加载了恒定数量的块,因此您可以unordered_map::reserve 足够数量的块,使其永远不会重新定位。

注意 2:我使用指向 Chunk 的指针来加快任何重定位。

以下是一些用法示例: https://onlinegdb.com/FiGzEzHgD

【讨论】:

  • 我做了一个大O表的markdown版本。你介意我把它放进去吗?
  • 酷。我会把你的原件留在那儿,这样如果我犯了任何错误,很容易发现。编辑:嗯 - 它看起来不像我编辑它时那样好:-/哦,好吧,如果你认为它太乱了,你可以随时回滚。 :)
猜你喜欢
  • 1970-01-01
  • 2012-04-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多