用于渲染大量立方体的剔除技术

时间:2010-09-12 01:43:00

标签: c++ c algorithm rendering

我正在开展个人学习项目以制作Minecraft克隆。除了一件事,它的工作非常好。类似于我的世界,我的地形有很多立方体堆叠在Y上,所以你可以挖掘。虽然我做了截头剔除,但这仍然意味着我无用地在我下面画出所有层的立方体。立方体是X,Y和Z有序的(虽然只在1个方向,所以从技术上讲,它不是Z订购到相机)。我基本上从玩家的位置只添加指向玩家周围的立方体的指针。然后,我对这些进行了截击。我不做oct树细分。我想只是没有渲染玩家下面的图层,除非玩家向下看到一个洞时这不起作用。鉴于此,我怎么能避免在我下面渲染我看不到的立方体,或者还有其他立方体隐藏的立方体。

由于

void CCubeGame::SetPlayerPosition()
{
PlayerPosition.x = Camera.x / 3;
PlayerPosition.y = ((Camera.y - 2.9) / 3) - 1;
PlayerPosition.z = Camera.z / 3;
}

void CCubeGame::SetCollids()
{

SetPlayerPosition();

int xamount = 70;
int zamount = 70;
int yamount = 17;

int xamountd = xamount * 2;
int zamountd = zamount * 2;
int yamountd = yamount * 2;
PlayerPosition.x -= xamount;

PlayerPosition.y -= yamount;

PlayerPosition.z -= zamount;


collids.clear();
CBox* tmp;

    for(int i = 0; i < xamountd; ++i)
    {
        for(int j = yamountd; j > 0; --j)
        {
            for(int k = zamountd; k > 0; --k)
            {

                tmp = GetCube(PlayerPosition.x + i, PlayerPosition.y + j, PlayerPosition.z + k);



                if(tmp != 0)
                {
                    if(frustum.sphereInFrustum(tmp->center,25) != NULL)
                    {
                        collids.push_back(tmp);
                    }
                }

            }
        }

}

8 个答案:

答案 0 :(得分:16)

以下是我在编写自己的克隆时学到的内容:

  1. 不要只是将每个多维数据集转储到OpenGL中,也不要担心自己完成所有可见性修剪。如另一个答案所述,检查所有6个面以查看它们是否被相邻块完全遮挡。仅渲染可见的面。这大致减少了从立方项(立方体体积n * n * n)到平方项(表面仅约n * n)的面数。
  2. OpenGL可以比你更快地查看截头剔除。将所有曲面面渲染到显示列表或VBO后,只需将整个blob发送到OpenGL即可。如果你将几何体分解成切片(或Minecraft称之为块),你可以避免绘制你可以轻易确定在镜头后面的块。
  3. 将整个几何体渲染到显示列表(或列表)中,并每次重绘。如果您使用立即模式,这是一个简单的步骤,因为您只需将现有代码包装在glNewList / glEndList中并使用glCallList重绘。减少OpenGL调用次数(每帧)将比减少要渲染的多边形总量产生更大的影响。
  4. 一旦你看到生成显示列表需要多长时间而不是绘制它们,你就会开始考虑如何将更新放入一个线程中。这是转换为VBO的结果:线程呈现为普通的旧数组(例如,向数组添加3个浮点数而不是调用glVertex3f)然后GL线程只需要使用glBufferSubData将它们加载到卡中。你赢了两次:代码可以在一个线程中运行,它可以“绘制”一个点,有3个数组写入而不是3个函数调用。
  5. 我注意到的其他事情:

    VBO和显示列表具有非常相似的性能。给定的OpenGL实现很可能在内部使用VBO来存储显示列表。我跳过了顶点数组(一种客户端VBO)所以我不确定那些。使用ARB的ARB扩展版本而不是GL 1.5标准,因为英特尔驱动程序仅实现扩展(尽管声称支持1.5),并且nvidia和ATI驱动程序不关心。

    纹理地图集规则。如果您在每张脸上使用一个纹理,请查看地图集的工作原理。

    如果你想查看我的代码,请在github上找到我。

答案 1 :(得分:13)

从前到后渲染。为此,您不需要排序,使用八叉树。叶子不是单独的立方体,而是更大的群体。

每个这样的叶子的网格应该缓存在显示列表中(如Bobmitch建议的那样),或者在顶点缓冲区中更好(更新更便宜)。当您生成此网格时,以蛮力方式生成所有多维数据集。相反,对于每个立方体面,检查它是否在同一个叶子中有一个不透明的邻居,如果是这样,你根本不需要生成这个面。您还可以将具有相同材质的相邻面统一到一个长矩形中。您还可以将网格分成六组,每个主方向一组:+/- XYZ面。仅绘制可能面向相机的那些面部组。

从前到后渲染本身并没有帮助。但是,您可以使用现代硬件提供的occlusion culling来获益。在渲染八叉树叶之前,检查其bbox是否通过了遮挡查询。如果它没有通过,你根本不需要绘制它。

遮挡查询的替代方法可能是光线跟踪。光线跟踪是good for rendering such environment。您可以投射一组稀疏的光线来近似可见的叶子并仅绘制那些叶子。但是,这会低估可见性集。

答案 2 :(得分:5)

和其他人一样,我一直在玩一个使用Ogre的块世界“引擎”,并在我去的时候写一些文章(见Block World Articles)。我一直在采取的基本方法是:

  • 仅创建块的可见面(不是块之间的面)。
  • 将世界分成更小的块(只有更快更新单个块才有必要)。
  • 将块纹理合并到一个纹理文件(纹理图集)中。

使用这些可以在大型简单块世界中获得非常好的性能(例如,在合适的硬件上为1024x1024x1024)。

答案 3 :(得分:2)

我目前正在使用python / pyglet进行一次Minecraft克隆,仅仅是为了好奇。

我将数据分解成块,就像在我的世界中一样,然后为每个块创建一个基于立方体可见性的opengl displaylist。然后,我在这些块上执行简单的2d视锥体剔除,并在播放器的一定距离内调用每个显示列表。

在添加/删除多维数据集时,我重新创建了块的显示列表。

除了完全被其他立方体包围的立方体外,没有遮挡剔除。

对于简单的场景,在适度的gfx卡上可以达到600fps以上,视距大约为200个立方体。

答案 4 :(得分:1)

Octtree等肯定会起作用,但另一种解决特定问题的方法可能是存储以向每个多维数据集对象添加usigned char visiblevisible字段的值计算如下:

  • 如果右侧(沿x轴看)没有邻居,则第一位(1)将被设置。
  • 如果左侧(沿负x轴看)没有邻居,则第二位(2)将被设置。
  • 如果正面(沿z轴看)没有邻居,那么第3位(4)将被设置
  • ...依此类推,这样你就可以为立方体的6个边中的每一边添加1位

每当玩家挖掘一个立方体时,你必须更新所有邻居立方体的visible字段。

所以,但这对此有何帮助?如果一个多维数据集的visible值为0,则很容易 - 该多维数据集将永远不会显示。但是,假设多维数据集的visible值为1.然后,如果Xplayer < Xcube,则多维数据集(可能)仅可见。其他方面的工作方式类似,我认为这样一个决定立方体是否可见的功能将非常快,并且可以跳过很多隐藏的立方体。

缺点是,此测试只是每个多维数据集测试,您不能跳过完整的组。因此,可能是一个octtree(用于跳过完整区域)和像这里描述的这样的可见字段,用于跳过大量隐藏的立方体(由于这些立方体被堆叠,隐藏立方体的数量将远远高于可见的数量在该地区内部可能是一个很好的解决方案。

答案 5 :(得分:1)

你可以使用PVS(Potentially visible set),虽然它通常用于地形,同样的原则适用,剔除无法看到的东西。 Gamedev.net还有一篇地形变形文章也包括在内。

答案 6 :(得分:0)

如果只有绘图是问题(而不是旋转未使用的顶点),那么c-buffer不能有用吗?我使用它非常成功,它需要排序的多边形(例如通过画家的算法)和几乎为零的内存(与z缓冲区相反)。

答案 7 :(得分:-1)

仅跟踪描述曲面的立方体。您可以通过一个简单的数据结构来实现这一点,其中每个多维数据集都保持对它的邻居的引用。在任何现代图形卡上推动所有这些三角形应该不是问题。从后到前渲染。此外,只渲染立方体,然后距观察者特定距离。 “世界”可以从巨大的“立方体立方体”开始,立方体的外壳由立方体制成。如果有人挖掘,你可以检查邻居位置是否已包含多维数据集,如果不是,则创建这些多维数据集并将其链接。

示例:向下挖掘一个平面:移除位于挖掘位置的立方体,在下方添加9个新立方体(但检查这些位置是否已在使用中,以防使用这些位置),将表面连接在一起但链接新的立方体到被移除的邻居立方体。

所以: 对于包含1000x1000x1000多维数据集的世界,您将获得:1000 * 1000 * 2 + 998 * 999 * 4 = 5988008而不是1000 * 1000 * 1000 = 1000000000,或者因子数量减少167个。

当然你不应该绘制所有这些立方体,从与观察者的简单距离开始进行子选择。

您还可以将8个多维数据集(组)组合在一起作为1组,然后继续这样,直到最高级别您只有一个多维数据集组(已提到的oct-tree)。此树可用于光线跟踪您需要绘制而不是绘制的世界的哪个部分。如果一个组包含对其他8个多维数据集组的引用,那么后面的那些不显示。后面是这些立方体组,它们不会从用户开始与光线跟踪锥体相交或放置,并且仅通过用于测试的组的边缘。 (不好描述,但我希望你能得到一些关于可以为优化做些什么的提示)。无论如何,今天的显卡可能不需要这样做。

祝你的项目好运。