【问题标题】:Efficient method for rendering cubes with different textures on each side for a Minecraft-like game?为类似 Minecraft 的游戏渲染每侧具有不同纹理的立方体的有效方法?
【发布时间】:2012-02-27 10:48:04
【问题描述】:

我正在尝试确定在类似 Minecraft 的游戏中渲染一堆具有不同纹理的立方体最有效的方法是什么。

我发现了instanced rendering。我所做的是创建了一个“立方体模型”,它存储了立方体的所有顶点、法线和纹理坐标,我从中创建了一个数组缓冲区并将其传递给 GPU 一次。然后我创建了一个(平移向量,纹理索引)结构数组,并使用实例化渲染一遍又一遍地重绘同一个立方体,每次都翻译和with the appropriate texture

(希望 Notch 在我自己制作之前不会介意我使用他的纹理)

问题在于,并非所有 6 个面都将始终具有相同的纹理,我正在尝试弄清楚如何使它们对于每种块类型都不同。我想出的两个解决方案是:

  1. 为每种块类型使用不同的模型。这样我可以在每个顶点上指定不同的纹理坐标。我仍然可以使用实例化渲染,但我必须为每种块类型单独传递。
  2. 传递 6 个“纹理索引”(每个面 1 个)而不是每个块 1 个。

第二种解决方案需要传递更多(可能是冗余的)数据。我不确定实例化渲染的好处有多大......所以我不知道是否最好做,比如说,最多 256 个“通过”(每种块类型 1 个),或者“一个大传递”所有数据,并一次渲染每个块。

或许还有其他我不知道的方法?

【问题讨论】:

    标签: opengl 3d textures voxel


    【解决方案1】:

    我认为您不能通过实例有效地做到这一点。绝大多数面/立方体永远不可见,您可以通过不渲染它们来节省大量时间。不幸的是,这使得每个立方体都成为不同的对象。

    标准解决方案(以及它在 Minecraft 中的实现方式)是将您的地形划分为多个扇区。计算哪些人脸是可见的并将它们上传到 GPU。当多维数据集更改时,您只需要重新上传它的扇区。渲染扇区时,您只需绘制图元,无需任何其他计算。

    您可以根据sparse voxel octrees 做一些事情。这需要更多的工作,但您将能够有效且准确地分辨出您的世界的哪些部分是可见的。

    【讨论】:

    • 好点子,但在这里,我会先使用几何着色器和变换反馈提供一个简单的“实例剔除”通道,然后再结合八叉树和分区。
    • 事情突然变得复杂了很多。我打算先决定如何制作纹理,然后研究修剪方法。我以为我最终会修剪不可见的立方体,但我想现在你提到它,基于面孔来做这件事更有意义。不过,这提出了一些新问题。这基本上意味着我正在 CPU 上进行剔除/修剪,不是吗?那不是 a) 更慢,b) 重新实现 GPU 通常为我执行的所有逻辑吗?关于八叉树,您是建议我将地形数据保存在这样的数据结构中还是将其用于修剪目的...
    • ...只有? This article 表明八叉树实际上在存储地形方面效率低下,因为现在每个访问都有通过八叉树的m 层进行的开销。最后,如果修剪掉大部分立方体/面,当我想做照明时会发生什么? GPU/GLSL 会不会丢失一些相关数据?即,观看者可能看不到一张脸,但它仍然可以阻挡光线?还是我对这种规模的漂亮阴影的梦想一开始是不切实际的?
    • 当涉及到需要单独转换的渲染通道的光和阴影时,可以为每个通道应用实例剔除。只要灯光及其对应的发光立方体不移动/更改,就不需要重做剔除/阴影贴图通道。我不认为这是不现实的。要查找立方体改变的光锥或分区(用户交互),CPU 是执行此操作并启动 GPU 支持的该部分处理的正确位置。
    • 我建议在可重用的“section”类中实现实例化渲染,该类可以具有灯光或分区中立方体所需的任何相交测试所需的形状。这个部分类应该有它自己的实例数组,包含它相交的每个立方体。这将在初始化时填充并在多维数据集退出或进入时进行修改
    【解决方案2】:

    我知道这个问题已经有将近两年的历史了,但我可以制作一个 3D 纹理来存储所有单独的纹理,其中 z 纹理坐标有点像块 ID。使用 3D 纹理,您现在可以一次绑定所有单独的块纹理,这意味着您可以使用实例化渲染传递您的变换以及块 ID,以便为 3D 采样器获取正确的块纹理。

    【讨论】:

      【解决方案3】:

      在我的 nVidia 8600M GT 上,我发现实例化在顶点和实例数适中的“中间”表现最佳,但我最终实例化了几个顶点数千次以消除冗余数据以及更新的工作它。

      我会选择 2,使用纹理数组以及顶点数组中的单个实例化立方体,并使用“每个实例数组”的纹理索引来选择面纹理,其中6 个索引甚至可以打包成几个整数。 对于提供实例属性,GL_ARB_instanced_arrays 也可以使用,其中不需要使用gl_InstanceID 访问缓冲区(可预测,因此在大多数情况下更快)。 如果您需要特定于实例的纹理坐标,我会绑定一个额外的每个实例和顶点纹理坐标数组,以及相应修改的着色器。

      【讨论】:

      • 我没有使用gl_InstanceID——我想你会用它来保存你的纹理索引吗?没想到那样做。我正在使用glDrawElementsInstancedBaseVertex,并将实例数据放入带有使用提示GL_DYNAMIC_DRAW 的GL_ARRAY_BUFFER。 “ARB_instanced_arrays”在哪里发挥作用?不知道那是什么。
      • 您选择的“2”似乎与您的第一段不一致。 “1”将是“更中间”的解决方案,不是吗? 1 使用实例,每次传递中只有较小的数据块。你会选择 2 个,即使屏幕上有 20 万个立方体?
      • 编辑:是的,我会先做基准测试......你迟早会用完统一组件,所以我会使用“纹理缓冲区对象”。
      • 使用带有 gl_InstanceID 的纹理缓冲区对象的替代方法是上述实例化数组。 8600M GT 相当老了,新卡在硬件支持的实例化方面更好,由于缓存等原因,较少的顶点可能是有利的。我不能在这里给出明确的提示,但我会推迟这个以进行基准测试和优化,因为它不应该破坏你的基本概念。
      • 好吧,我正要走上一条或另一条路。正如你所说,我宁愿不要浪费太多时间在早期进行基准测试,直到它成为障碍。但问题是我已经降到了大约 60 FPS,甚至在全屏时更低(~20)。这是在任何修剪之前...这是我还没有解决的另一个问题。
      【解决方案4】:

      确实迟到的答案,但肯定有人需要它。

      在主块类中有一个返回纹理的方法,带有一个面部参数。在需要多个纹理的单个类中,重写此方法并使用 switch case 或一系列 if/else 语句。

      这是该方法在块类中的样子:

      public int getBlockTexture(int face){
           if(face = top){
               return grass top
           } else if(face = bottom){
               return grass bottom
           } else {
               return grass side
           }
      }
      

      至于如何在渲染器中使用它,请在渲染每个面之前获取纹理。类似于您进行剔除的方式。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-10-14
        • 2013-06-29
        • 2011-05-04
        • 1970-01-01
        • 1970-01-01
        • 2012-11-01
        • 2023-01-08
        相关资源
        最近更新 更多