程序化生成

发布时间:
Max Oberberger's avatar
Max Oberberger

到目前为止,我们已经介绍了如何在现有的工作图应用程序中添加网格节点,以及 HelloMeshNodes 示例如何使用它们来绘制 Koch 雪花分形。此外,我们还讨论了一些网格节点的使用最佳实践和其他技巧。在本节中,我们将探讨程序化网格节点示例如何使用工作图和网格节点来生成和渲染一个完全程序化的世界。

程序化生成网格节点示例展示了完全由 GPU 驱动的渲染。整个世界每帧都完全程序化生成,并通过多个不同的网格节点在 GPU 上完全渲染。许多程序化效果,例如程序化草、蜜蜂和蝴蝶,都基于 GDC 2024 网格节点演示。您可以在 此处 找到该示例的完整源代码和预编译二进制文件,并可以在我们即将在 HPG 2024 上发表的 论文 中了解更多关于 GDC 2024 演示中的程序化效果。

我们还有一个 Work Graphs Ivy Generation sample 可用。

在没有工作图的情况下在 GPU 上程序化生成整个世界是困难的,因为整体输出的限制通常是无法预先知道的。工作图与网格节点结合使用极大地简化了这一点。动态地实时调度工作(包括绘制)消除了为整个世界预留输出内存的需要。

分层生成网格

为了生成具有粗糙结构(如地形)和精细细节(如草、花和蜜蜂)的详细世界,我们将世界组织成一个分层网格。每个级别都由每个级别的一个节点(数组)表示,每个级别生成下一个较低级别的多个记录。从最粗糙的级别开始,我们有以下节点:

  • World:图的入口节点。此节点将摄像机视锥体的角投影到 xzxz 平面,并计算外边界框。该边界框根据块网格间距进行量化,然后用于启动一个二维的块线程组网格。右侧可以看到此的可视化。
  • Chunk:处理世界的大方块。每个块由一个线程组组成,包含 8×88\times8 个线程。整个线程组负责调用具有正确细节层次的Terrain Mesh Node。块中的每个线程负责启动一个tile
    Tile 由其中央占主导地位的生物群落进行分类。稍后会详细介绍。
  • Tile[3]:包含三个不同节点的节点数组,每个节点对应一种生物群落类型。每个 Tile 由一个线程组组成,包含 8×88\times8 个线程。每个线程负责粗粒度的程序化生成,例如树木、花朵、岩石。
    在林地或草原生物群落 Tile 中的每个线程还负责生成程序化草。由 Tile 中的线程生成稀疏(即较低 LOD)的草丛。对于密集(即较高 LOD)的草丛,每个线程都可以启动一个详细 Tile
  • DetailedTile:处理 16×1616\times16 块密集草丛的生成。您可以在我们关于 程序化草渲染的博客文章 中找到有关如何渲染这些草丛的更多信息。

将程序化世界生成组织成多个级别有几个优点。首先也是最重要的,它允许我们在每个级别剔除不可见的网格单元,从而减少整体工作量。其次,我们可以将不同的程序化效果分解到不同的级别。我们不必使用一个大型着色器来生成整个世界,而是可以使用专门的着色器(或节点)来仅生成某些方面,例如地形、草或树木。工作图与网格节点结合使用,使我们能够以不同的方式组合和重用这些节点,而无需跟踪它们的内存分配。

生物群落

如上所述,Tile 被分为三种不同的生物群落:山地、林地和草原。这些生物群落中的每一种都由 Tile 节点数组中的一个不同节点表示,并且在生成进一步的程序化效果时具有不同的特征。例如,山地生物群落会生成树木和岩石簇,而草原生物群落会生成花朵、蜜蜂和蝴蝶。

要将每个 Tile 分类到生物群落,我们需要为每个生物群落创建一个权重图。我们可以通过将不同频率和振幅的 Perlin 噪声分层来程序化生成这样的图。在组合不同生物群落的权重图时,我们可以修改它们,以确保始终存在一个占主导地位的生物群落。结果可以在右侧的图像中看到,红色代表山地生物群落,绿色代表林地生物群落,蓝色代表草原生物群落。您可以在 此处 找到生成此图的源代码。请注意,我们不将此图存储在纹理中,而是随时需要采样生物群落权重图时评估 GetBiomeWeights 函数。

然后,我们可以查询每个 Tile 中心的生物群落图来确定其生物群落。生成的生物群落索引随后用作 Tile 节点数组的索引。使用节点数组,Chunk 线程组中的每个线程都可以输出到此节点数组中的不同节点。结果分类如下所示。

如前所述,这些特定于生物群落的 Tile 具有不同的程序化生成特征:

  • MountainTile:山地 Tile 可以在平坦区域和山脊处生成岩石。此外,如果地形不太陡峭,它还可以生成少量松树。
  • WoodlandTile:林地 Tile 主要生成树木。树的类型(橡树或松树)取决于地形的陡峭程度以及到下一个山地生物群落的距离。每棵树旁边还会生成一小簇蘑菇。此外,林地 Tile 还生成稀疏的草丛,当摄像机足够近时,这些草丛会被替换为详细的 Tile。
  • GrasslandTile:草原 Tile 生成一个有花朵、蜜蜂和蝴蝶的草甸。它使用与林地 Tile 相同的逻辑来生成稀疏的草丛或详细的 Tile。每个线程可以在 Tile 内生成多个花朵,并且在一些花朵上生成程序化的蜜蜂群。此外,每个线程还可以生成一群蝴蝶。

我们已经看到了工作图如何允许我们将程序化生成分解为多个独立的节点。这种关注点分离也使我们能够构建可重用的组件(即节点)以用于不同的程序化效果。接下来,我们将研究如何创建一个网格节点来渲染树干和树枝的参数样条,以及这样的节点如何作为渲染树冠和岩石的构建块。

程序化地形

为了生成和渲染程序化地形,我们首先需要一个高度图,用于确定地形在 xzxz 平面上的任意位置的高度,从而确定其位置。为了生成这样的高度图,我们再次将不同频率的 Perlin 噪声分层,类似于我们之前生成的生物群落权重图。此外,我们可以使用生物群落权重图来影响地形高度图生成,并提高山地生物群落的整体高度级别。您可以在右侧看到生成的高度图。与生物群落权重图类似,我们不将此图存储在内存中,而是在每次需要采样地形高度时评估其生成函数。

通常会使用镶嵌着色器来实现渲染这种程序化高度图地形。由于它们在工作图中不受支持,因此我们改用网格着色器。每个网格着色器线程组生成一个 8×88\times8 网格部分。这些网格部分中的每一个都由 9×9=819\times9=81 个顶点组成,这些顶点由 8×8×2=1288\times8\times2=128 个三角形连接。然后,我们可以使用多个此类网格着色器线程组来渲染连续、无缝的地形。

地形网格节点的启动由 Chunk 节点处理。为了实现多级细节,每个块都可以调整地形顶点之间的间距,从而调整渲染块地形所需的线程组数量。这从每个块的 8×88\times8 网格节点线程组(最高细节级别)到仅一个线程组(远处块)不等。

LOD 0LOD 1LOD 2LOD 3

构建模块:树和样条

如开头所述,工作图允许我们将程序化生成分解为多个独立的节点。这种关注点分离也使我们能够构建可重用的组件(即节点)以用于不同的程序化效果。接下来,我们将研究如何创建一个网格节点来渲染树干和树枝的参数样条,以及这样的节点如何作为渲染树冠和岩石的构建块。

树干可以由样条表示。每个样条由一系列控制点定义,这些控制点共同形成 3D 空间中的一条曲线。要渲染样条,我们在每个控制点生成一个顶点环,并用三角形连接相邻的环。因此,我们的样条具有以下参数:

  • 一个控制点列表,每个控制点包含:
    • 3D 空间中的位置
    • 要在顶点环中生成的顶点数
    • 顶点环的半径
    • 添加到每个顶点位置的随机噪声量的因子

有了这样的节点,我们就可以通过修改样条参数或组合多个样条轻松地生成具有不同形状和分支的树。

我们可以通过为每个样条添加颜色和风力强度参数来扩展样条网格着色器。通过调整样条参数形成一个球体并将其着色为绿色,我们也可以用同一个网格着色器渲染橡树的树冠。对于松树,我们可以将控制点成对排列,并调整顶点环半径以形成锯齿状。岩石(至少在渲染它们的上下文中)只是橡树树冠的灰色版本。

细节层次:草和花

如上所述,地形网格的程序化生成允许我们具有不同的细节级别。我们可以将类似的概念应用于草和花的生成,以减少远处物体渲染的三角形数量。


对于密集的草丛,我们重用了 程序化草渲染博客文章 中的网格着色器。这些密集草丛由 DetailedTile 节点生成。每个详细 Tile 可以生成多达 16×1616\times16 个草丛,每个草丛最多由两个密集草丛网格着色器线程组渲染。
为了减少渲染详细 Tile 中草所需的线程组数量和三角形数量,我们引入了以下细节级别系统:每个详细 Tile 被替换为单个稀疏草丛。详细 Tile 中的每个草丛现在都由单个四边形(两个三角形)渲染。稀疏草丛网格着色器可以渲染一个 8×48\times4 网格的这些四边形。因此,我们现在只需要八个线程组即可渲染详细 Tile 中的所有 16×1616\times16 个草丛。稀疏草丛在 Tile 级别生成,以将多个稀疏草丛合并为一个记录。有关更多详细信息,请参阅 提示、技巧和最佳实践


每个花丛最多可包含六朵花。因此,使用类似的每个花朵两个三角形的细节级别方案,我们可以在单个稀疏花朵网格着色器线程组中容纳多达五个花丛。
与稀疏草类似,花朵由 GrasslandTile 节点生成,并将多个花丛合并为一个记录。

DrawDenseGrassPatchDrawSparseGrassPatchDrawFlowerPatch[0]DrawFlowerPatch[1]
线程组16162=51216\cdot16\cdot2=512885511
顶点65,53665,5361,0241,0244024028888
三角形49,15249,1525125125845844444

平坦世界的终结

World 节点是我们程序化世界生成的入口节点,它使用摄像机视锥体来启动一个二维的 Chunk 线程组网格。为了提高性能,这个块网格仅限于在距离摄像机一定距离内延伸,并且最多限制为 32×3232\times32 个块。由于这个网格会跟随摄像机,我们可以看到块在摄像机视锥体远端出现和消失。

为了隐藏块的弹出和消失,我们可以增加最大块网格大小或使用距离雾等效果来隐藏块网格的边界。对于这个示例,我们决定通过将世界变成一个球体来隐藏(生成)世界的尽头。也就是说,我们的生物群落和高度图生成允许一个几乎无限的世界,因此不可能绕着这个球体环绕。换句话说,我们只是在创造一个球形世界的幻觉。

为了创造这个幻觉,我们将每个顶点位置变换到我们想象中的球体上,然后再将顶点投影到裁剪空间。在如此晚的阶段执行此变换意味着我们不必调整任何生成逻辑以适应这个球形空间。要变换顶点位置,我们需要在其到摄像机的 xzxz 平面的距离(dd)和它在 xzxz 平面上方的高度(hh)。我们可以在半径为 rr 的球体上找到一个位置,使得到摄像机位置的弧长距离为 dd。我们将该位置沿着球体的法线方向偏移,按 hh 缩放。对于非常遥远的顶点,我们还会将 hh 向零缩放,以确保即使最高的山峰也会消失在地平线后面。
生成的效果如下所示。

这样就完成了——我们希望这能帮助您并鼓励您尝试工作图中的网格节点。一如既往,我们很乐意听到您的反馈——毕竟这是一个预览版,在最终发布前仍有可能进行更改。

免责声明

第三方网站链接仅为方便用户提供,除非另有明确说明,AMD不对任何此类链接网站的内容负责,且不暗示任何认可。GD-98

Microsoft 是 Microsoft Corporation 在美国和/或其他国家/地区的注册商标。本出版物中使用的其他产品名称仅用于标识目的,并可能为其各自所有者的商标。

DirectX 是 Microsoft Corporation 在美国和/或其他国家/地区的注册商标。

Max Oberberger's avatar

Max Oberberger

Max 是 AMD GPU 架构与软件技术团队的成员。他目前专注于 GPU 工作图和 Mesh Shader 研究。

相关新闻和技术文章

相关视频

© . This site is unofficial and not affiliated with AMD.