在C ++中为体素引擎存储体素


9

我正在尝试编写一个小的体素引擎,因为它很有趣,但是却很难找到存储实际体素的最佳方法。我知道我将需要某种类型的块,因此我不需要在内存中拥有整个世界,而且我知道我需要以合理的性能渲染它们。

我了解了八叉树,据我所知,它以1个多维数据集开头,在那个多维数据集中可以再有8个多维数据集,在所有这8个多维数据集中可以是另外8个多维数据集,等等。但是我认为这不适合我的体素引擎,因为我的体素多维数据集/项目都将完全相同。

因此,另一种选择是只创建一个大小为16 * 16 * 16的数组,并将其作为一个大块,然后用项目填充它。没有零件的零件的值将为0(0 =空)。但是,这恐怕会浪费大量内存,而且速度不会很快。

然后另一个选择是每个块的向量,并用多维数据集填充它。多维数据集在块中保持其位置。这样可以节省内存(没有空气障碍),但是在特定位置查找多维数据集要慢得多。

所以我真的找不到一个好的解决方案,我希望有人可以帮助我。那么,您将使用什么?为什么?

但是另一个问题是渲染。使用OpenGL读取每个块并将其发送到GPU很容易,但是非常慢。每块生成一个网格会更好,但是这意味着每次我打破一个块时,我都必须重建整个块,这可能会花费一些时间,导致轻微但明显的打ic,我显然也不想这样做。因此,这将更加困难。那么我该如何渲染立方体呢?只需在每个块的一个顶点缓冲区中创建所有多维数据集,然后渲染该多维数据集,然后尝试将其放入另一个线程中,或者有其他方法吗?

谢谢!


1
您应该使用实例化渲染多维数据集。您可以在此处Learnopengl.com/Advanced-OpenGL/Instancing中找到教程。对于存储多维数据集:您在硬件上是否有强大的内存约束?16 ^ 3个多维数据集似乎没有太多的内存。
Turms

@Turms感谢您的评论!我没有强大的内存限制,它只是一台普通PC。但是我认为,如果每块最上面的块都是50%的空气,并且世界非常大,那么一定会有很多浪费的内存。但这可能不像您说的那么多。那么我应该只使用静态块数量的16 * 16 * 16块吗?而且,您说我应该使用实例化,这真的需要吗?我的想法是为每个块生成一个网格,因为这样我可以忽略所有不可见的三角形。

6
我不建议像Turms所描述的那样对多维数据集使用实例化,这只会减少您的绘制调用,但是对透支和隐藏的面却无济于事-实际上,它使您无法解决该问题,因为实例化可以处理所有多维数据集必须相同-您无法删除某些立方体的隐藏面或将共面的面合并为更大的单个多边形。
DMGregory

选择最佳的体素引擎可能是一个挑战。要问自己的一个大问题是:“我需要对体素执行哪些操作?” 指导操作。例如,您担心弄清楚八叉树中哪个体素在哪里有多难。八叉树算法非常适合那些可以在遍历树时根据需要生成此信息的问题(通常以递归方式)。如果您在某些特定问题上花了太多钱,则可以考虑其他选择。
科特·阿蒙

另一个大问题是体素多久更新一次。如果某些算法可以预处理数据以有效地存储数据,则效果很好,但如果不断更新数据(如数据可能在基于粒子的流体模拟中),则效率较低
Cort Ammon

Answers:


23

将块存储为位置和值实际上效率很低。即使没有因使用的结构或对象引起的任何开销,您也需要在每个块中存储4个不同的值。只有在固定块的四分之一是固态的情况下,才将其用于“固定阵列中的存储块”方法(如前所述)才有意义,这样您甚至无需将任何其他优化方法用于帐户。

实际上,八位字节对于基于体素的游戏非常有用,因为它们专门存储具有较大功能(例如同一块的补丁)的数据。为了说明这一点,我使用了四叉树(基本上是2d中的八叉树):

这是我的初始设置,包含32x32瓦片,等于1024个值: 在此处输入图片说明

将其存储为1024个单独的值似乎效率不高,但是一旦达到与游戏类似的地图尺寸(例如Terraria),加载屏幕将需要几秒钟。而且,如果将其增加到第三维,它将开始用完系统中的所有空间。

四叉树(或3d中的八叉树)可以帮助解决这种情况。要创建一个,您可以从图块开始并将它们分组在一起,或者从一个巨大的单元格开始进行划分,直到到达图块。我将使用第一种方法,因为它更易于可视化。

因此,在第一个迭代中,您将所有内容分组为2x2单元,如果一个单元仅包含相同类型的图块,则删除这些图块并仅存储该类型。经过一轮迭代,我们的地图将如下所示:

在此处输入图片说明

红线表示我们存储的内容。每个平方只是1个值。这将大小从1024个值减小到439个,减少了57%。

但是你知道咒语。让我们进一步走一步,将它们分组到单元格中:

在此处输入图片说明

这将存储值的数量减少到367。这仅是原始大小的36%。

显然,您需要进行这种划分,直到块内的每个4个相邻单元(3d中的8个相邻块)被存储在一个单元内,实际上将一个块转换为一个大单元。

这还有其他一些好处,主要是在发生碰撞时,但是您可能想要为此创建一个单独的八叉树,它只关心单个块是否牢固。这样,您只需对单元格进行处理,而不是检查块中每个块是否存在冲突。


感谢您的回复!(因为我的体素引擎将是3D的,所以似乎是octree的方法。)我还是想问一个问题:您的最后一张照片显示黑色部分具有较大的正方形,因为我打算使用类似Minecraft的引擎,您可以在其中修改体素地形,我希望将具有块的所有内容都保持相同大小,因为否则会使事情变得非常复杂,这是可能的吗?(我仍将简化课程的空白/空白)。 ,是否有某种有关如何编程八叉树的教程?谢谢!

7
@ appmaker1358完全没有问题。如果玩家试图修改一个大的块,然后你分解成更小的块在那一刻。当您可以说“整个块都是坚硬的岩石”时,无需存储16x16x16的“ rock”值,直到不再适用。
DMGregory

1
@ appmaker1358正如DMGregory所说,更新存储在八叉树中的数据相对容易。您要做的就是将发生更改的单元格划分开,直到每个子单元格只包含一种类型的块。这是一个带有四叉树的交互式示例。生成一个也很简单。您创建一个完全包含该块的大单元格,然后递归地遍历每个叶单元格(没有子级的单元格),检查其代表的地形部分是否包含多种类型的块,如果是,则将其细分细胞
巴林

@ appmaker1358更大的问题实际上是相反的-确保八叉树不会只有一个方块就满是叶子,这在Minecraft风格的游戏中很容易发生。但是,有许多解决问题的方法-只是选择您认为合适的方法。只有在进行大量建筑时,这才成为真正的问题。
六安

八进制不一定是最佳选择。这是一个有趣的读物:0fps.net/2012/01/14/an-analysis-of-minecraft-like-engines
Polygnome,

7

存在八进制可以完全解决您描述的问题,从而可以密集存储稀疏数据,而无需花费大量搜索时间。

体素大小相同的事实仅意味着八叉树具有固定的深度。例如。对于16x16x16的块,您最多需要5个树级别:

  • 块根(16x16x16)
    • 第一层八分圆(8x8x8)
      • 第二层八分圆(4x4x4)
        • 第三层八分圆(2x2x2)
          • 单个体素(1x1x1)

这意味着您最多只有5个步骤才能找出块中特定位置是否存在体素:

  • 块根:整个块是否具有相同的值(例如,所有空气)?如果是这样,我们就完成了。如果不...
    • 第一层:包含该位置的八分圆是否都是相同的值?如果不...
      • 第二层...
        • 第三层...
          • 现在我们正在处理单个体素,并可以返回其值。

比通过多达4096个体素的阵列扫描甚至1%的路径要短得多!

请注意,这使我们可以在存在相同值的完整八分位数的任何地方压缩数据-无论该值是全部是空值还是全部是岩石或其他任何值。只有在八分位数包含混合值的情况下,我们需要进一步细分这些值,直至达到单个体素叶节点的限制。


为了解决大块的孩子,通常我们以Morton顺序进行操作,如下所示:

  1. X- Y- Z-
  2. X- Y- Z +
  3. X- Y + Z-
  4. X- Y + Z +
  5. X + Y- Z-
  6. X + Y- Z +
  7. X + Y + Z-
  8. X + Y + Z +

因此,我们的Octree节点导航可能看起来像这样:

GetOctreeValue(OctreeNode node, int depth, int3 nodeOrigin, int3 queryPoint) {
    if(node.IsAllOneValue)
        return node.Value;

    int childIndex =  0;
    childIndex += (queryPoint.x > nodeOrigin.x) ? 4 : 0;
    childIndex += (queryPoint.y > nodeOrigin.y) ? 2 : 0;
    childIndex += (queryPoint.z > nodeOrigin.z) ? 1 : 0;

    OctreeNode child = node.GetChild(childIndex);

    return GetOctreeValue(
                child, 
                depth + 1,
                nodeOrigin + childOffset[depth, childIndex],
                queryPoint
    );
}

感谢您的回复!看来八叉树是要走的路。但是我确实有两个问题,您说八叉树的速度比扫描数组快,这是正确的。但是我不需要这样做,因为数组可以是静态的,这意味着我可以计算所需的多维数据集在哪里。那么,为什么我需要扫描?第二个问题,在八叉树的最后一层(1x1x1)中,我如何知道哪个多维数据集在哪里,因为如果我正确理解它,并且八叉树节点又有8个节点,那么如何知道哪个节点属于哪个3d位置(或者我应该记得自己?)

是的,您已经在问题中涵盖了一个详尽的16x16x16体素数组的情况,并且似乎拒绝了每块内存占用的4K(假设每个体素ID是一个字节)过多。您提到的搜索是在存储具有位置的体素列表时出现的,这迫使您浏览该列表以在目标位置找到体素。4096是列表长度的上限-通常会小于该长度,但通常仍比相应的八叉树搜索深。
DMGregory
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.