NSDT工具推荐Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - AI模型在线查看 - Three.js虚拟轴心开发包 - 3D模型在线减面 - STL模型在线切割 - 3D道路快速建模

Unreal Engine最好的部分之一就是它对那些偏艺术的人来说是无障碍的。随着虚幻引擎4的首次发布,人们发现自己可以制作整个游戏,而不必触及C++。从那时起,人们一直在挑战蓝图的极限,Epic则通过更新扩展工具集来做出回应。

Unreal引擎中一个非常强大的部分是程序化网格(procedural mesh)组件,它接收数组输入并输出静态网格。

将程序化网格与渲染到纹理(render to texture)功能相结合,可以完全在引擎中程序化地创建纹理资源!

这很有用,因为:

  • 你可以准确地看到资产在构建时的外观。
  • 在构建资产时就可以在其环境中看到该资产。
  • 更新都是实时的。

这有大量的潜在应用,我一直在研究风格化的植被(Foliage)生成,对于传统建模工具而言这是一个痛苦的任务,而程序化网格则非常适合用于解决这个问题,因为游戏树主要由简单的形状(圆柱体和平面)组成,并且更多地是关于这些形状的正确放置和轻微变化,而不是它们各自的复杂性。

使用程序网格组件构建的一些树:

如果你有兴趣试用该工具,可以在此处下载

使用此工具时,我建议增加项目设置中的最大循环计数(Max Loop Count)。如果超过项目设置的最大循环计数,我们的几何图形将无法正确构建!

1、程序化创建三角形

让我们从简单的网格对象开始,渲染一个三角形。创建新的Actor蓝图并添加程序化网格组件。创建DrawTriangle函数,并将其插入到构造脚本中:

添加两个数组:

  • 向量数组,用来表示顶点
  • 整数数组,用来表示顶点数组的目标索引

在这里我们有三角形的三个顶点和创建该三角形所需的整数顺序。注意我们需要列出顶点的顺序,如果存储0,1,2,则三角形将会方向错误。

程序化网格组件输出到Create Mesh Section(创建网格截面)函数,并将上述数组链接到对应的输入中。

嘿!我们成功制作了一个三角形!

2、程序化创建平面

现在让我们更复杂一点,做一个平面。首先需要添加更多的数组。我们需要补充:

  • 法线向量数组
  • 切线结构向量数组
  • UV二维矢量数组

别担心,我们将用一些小技巧获得法线和切线!

请记住,就像以前一样,我们需要先清理数组数据。

然后创建Draw Quad函数,并添加两个嵌套循环,用来处理x 和 y的每个 值。

然后取当前的 x 和 y 值,乘以一个值(距离),并将其添加到向量数组中,最后将UV当前 x 和 y 值除以其总计(长度)以获得归一化值。

然后,我们可以使用Create Grid Mesh Triangles(创建网格三角形)功能自动生成指定数量的三角形。虽然以后无法使用它,但就目前而言,这是生成三角形数据网格的简单方法。

我们还需要法线和切线,这样我们就可以使用Calculate Tangents for Mesh(计算网格的切线)函数来得到结果。

下面是最终得到的完整函数,点击链接查看放大的视图:

下面是构造脚本:

结果:

3、程序化创建圆柱体

一旦有了网格,将形状从平面更改为圆柱体就是一件简单的事情:我们不再将定点与网格对齐,而是将它们缠绕在轴上。

取我们之前创建的 X 和 XLength 值,将它们相除以得到归一值,然后乘以 360,从而得出角度。我们希望围绕 z 轴旋转 x 轴,因此获取这些值并将它们插入,并通过与浮点数相乘来控制距离。

像这样将此新计算与原始的 x*float 交换,点击链接 查看大图。

现在我们的平面就变成了圆柱体!

如果我们取归一化的y或z值,并用1-x反转,则创建一个圆锥体!

4、读取网格数据

我们需要的最后一个元素是读取网格数据。Get Section from Static Mesh(从静态网格体获取截面)函数可以从任何网格中获取数据并输出供我们使用。现在,我们可以对任何网格进行采样,对其进行修改,然后将其散布在表面上。


现在,我们已经有了构建程序化内容所需的基础。在下一部分中,我们将介绍如何利用这些工具制作一个简单的树生成器。

5、程序化生成植被

在深入研究植被生成工具开发之前,我强烈建议你下载并尝试一下,否则我们将讨论的一些主题将非常抽象!你可能还会注意到一些未涵盖的对pivot painter的引用。这是我目前正在开发的额外功能,尚未完成!

树叶工具分为五个单独的蓝图:

1、父蓝图 Tree Parent

  • 保存将在所有子项之间共享的数据。
  • 树干、树枝和树叶蓝图均继承自它。

2、树干 Trunk

  • 继承自Tree Parent
  • 样条控制的网格
  • 只制作一个网格
  • 提供大量用户控制

3、树枝 Branch

  • 继承自Tree Parent
  • 沿表面或随机生成多个树枝
  • 用户控制仅限于随机变量范围
  • 可以相互叠加

4、树叶 Leaf

  • 继承自Tree Parent
  • 读取网格数组并沿树干/树枝或随机放置
  • 不能有子节点

5、合成器 Compositer

  • 以TreeParent数组为输入,将网格重建为单个资产
  • 检测并合并重复材质,以减少材质数量

你可能会想,为什么要这样做!?好吧,分解树有多种好处:

  • 在层次结构上进行更改时不必重绘整个网格
  • 快速分离和隐藏资产的元素
  • 删除部分而不会破坏整个树
  • 资源将组件化,因此可以将树的一部分复制粘贴到其他树上
  • 拆分随机种子生成,以便获得更多控制

所有这些元素结合在一起,让用户构建一个模块化的组件堆栈,这些组件"应该"允许他们制作任何类型的树!

6、父蓝图 - Tree Parent

为了避免重复工作,我们应该首先创建一个作为基础的父蓝图,其他蓝图需要继承父蓝图。这很有用,因为我们放入父蓝图中的任何数据都将在其后代蓝图中共享。例如,每个资产类型都需要一个材质引用和一组网格数组,以便将信息添加到父级,以便传播到其子级。

将程序化网格组件添加到TreeParent蓝图中。

我们还需要一些变量,每个子蓝图都需要这些变量。

Base:

  • Material:应用于网格的材质,这意味着我们每个组件只支持一种材质。
  • ProcMesh,一个包含顶点、三角形、UV 和顶点颜色等制作网格所需的数组的结构。法线在具有函数后生成。
  • RandomSeed,随机种子,随机化网格的一部分。

References:

  • Parrent,树蓝图变量,用于引用其所有者。
  • Root,表示网格合成工具,用于获取存储顺序。
  • ChildRefs,父蓝图数组,用于告诉父级对象进行更新。
  • Ts,转换数组的数组,用于存储树枝信息。可以把它想象成树的骨架。父蓝图引用此内容来获取生成位置。

一个共享函数,它告诉子数组中的资产进行更新。每次更改组件时都会调用它(但请记住,在我们创建之前,这些函数或强制转换都不存在)。

如果我们不更新子组件,则树将很快断开连接。

7、树干 Trunk

树干蓝图应继承自 TreeParent,并且基于可编辑样条曲线。从添加组件下拉列表中选择样条线就可以将其添加到任何蓝图中,就像我们对程序化网格组件所做的那样。

可以看到我们已经继承的组件:

在构造脚本中,我们添加了一个名为 Draw 的新函数。我们将对蓝图自身及其所有子蓝图进行此调用。


Draw函数检查父树干是否已存在,如果存在,则沿父样条移动组件,否则将直接移动以创建网格。最后,通知子项使用"Update Children"进行更新。

I在 Create Base Segments中,我们清理历史几何数据,计算新数据,然后重新绘制网格,在这个教程中,这一模式我们将反复运用。

对于几何图形,我们需要两个整数,一个用于长度段的数量,另一个用于径向段的数量。

获取我们想要的样条长度和长度段数,将它们除以得到每个点之间的距离,并将其设置为称为对于我们的几何图形,我们需要两个整数,一个用于长度段的数量,另一个用于径向段的数量。

获取我们想要的样条长度和长度段数,将它们除以得到每个点之间的距离,并将其设置为称为Segment Distance(段距离)的浮点数。

然后循环处理每个长度段,并得到样条线上指定距离处的变换。长度将是当前循环序号 * 段距离。可以使用 x 或 y 来控制躯干的厚度。将其乘以暴露的值,为网格添加均匀大小。

你可能需要在此处旋转,以使其与树枝的旋转对齐。

将转换存储在数组中,这非常重要,因为我们需要一种通用方法来了解树枝在空间的位置,并且并非每个蓝图都有样条组件。稍后将添加到 Ts 数组(在 TreeParent 中创建)。

然后,我们在Draw Radius函数中围绕该变换绘制顶点。这类似于我们之前绘制几何图形的方式。单击链接以查看完整图表。

这里唯一的主要区别是我们制作三角形数组的方式。这对于树干来说是不必要的,但对于多个树枝来说绝对至关重要。网格三角形函数只期望单一网格,但是如果我们想制作多个单独的网格,需要偏移三角形计数器。

完成所有这些操作后,我们需要获取基本转换数组,并将其设置为 Ts 数组。此处的索引表示多个分支,但由于我们只创建一个段,因此仅将其设置为索引 0。

一旦获得了所有数据,剩下的就是绘制网格了!

8、树枝 Branch

希望你现在已经识别出这种模式,清除旧数据并生成新数据。树枝蓝图需要接收一个父级(如果存在),并沿其分散多个树枝。如果父蓝图不存在,我们将直接散落在地上。

上面没有什么新内容,因此让我们继续Make Branch Data(制作树枝数据),此函数的大部分内容是为我们正在创建的每个树枝查找生成点(点击链接查看大图)。

首先要确定是否有父蓝图。如果是这样,我们得到父级的 T 数组,这是一个转换数组。每个树枝都是一个新的转换数组。这意味着,如果父树枝有 8 个分支,而我们选择创建 4 个分支,则它总共生成 32 个分支。如果父蓝图是树干,则 T 数组将只有一个转换条目。

如果它没有父级,那么继续循环进行分支计数并计算出归一化距离。

下一步是弄清楚每个分支的起源应该是什么。如果没有父级,只需获取对象的原点即可。如果存在父级,则获取当前变换数组并根据规范化距离查找位置。还可以在此处添加钳位值以缩小范围。

这里有一个图表,如果这听起来令人困惑,可以更好地解释它。在下图中,顶部值是需要转换为转换数组的规范化距离。乘以转换索引计数,得到最接近的索引,余数是下一个索引之间的距离。此计算将给我们一个沿变换长度的粗略距离。

生成最小和最大矢量之间的随机位置。

合并位置,使用自定义宏修改旋转并修改比例。

整理出原点旋转是一个非常重要的元素。为了获得易于控制的结果,可以使用围绕索引旋转并使用方向矢量以避免锁定(链接)。

一旦获得原点后,将其添加到pivot数组并创建分支。

在树干示例中,我们使用样条线进行变换,然后围绕它构建几何图形。

我们不能在这里这样做,因为样条不存在。相反,我们必须生成这些点(链接)。

这里我们取原点。

抓取当前垂直长度。我们需要这个来抵消我们的三角形计数。三角形数组只是引用顶点数组上的索引,因此需要偏移量来保持三角形引用的正确性。生成最小值/最大值之间的随机分支长度。

然后对于分支上的每个点。

找出下一个点的位置。接受最后一个变换,通过乘以方向矢量来抵消它。通过一些受控的旋转稍微调整它。

再次创建网格数据,就像我们之前所做的那样。

分支蓝图的重要功能是它是可堆叠的。我们应该能够在树枝上的树枝上建立分支,等等。

可能遇到的唯一问题是我们的分支计数是指数级的。如果第一层有 6 个分支,第 2 层和第 3 层有 4 个分支,我们将得到 96 个分支,而不是 14 个分支。如果我们添加包含 4 个分支的第 4 层,则得到 384 个分支。整个操作都使用循环,这意味着如果数字太大,我们将遇到很多麻烦。

9、树叶 Leaf

树叶不能有子级,因此它们将始终处于各自层次结构的末尾。蓝图将对网格数组进行采样,并将它们分散到树干或树枝资源中。我们在这里可以做的一个有用的技巧是使用渲染实例而不是绘制几何图形,这可以大大提高性能。在这里,我们添加一个切换开关来表示是否利用此优化。

对于此组件,创建一个可由用户设置的网格数组。对于每个网格,使用get section from static mesh(从静态网格体获取部分)函数来获取每个对象的数据并将其存储以供以后使用(链接)。

与树枝蓝图相同,清除旧数据,检查父数据,然后循环处理。

找到与以前相同的生成点,然后创建网格或添加实例。

添加网格数据非常简单(链接)。

我们得到三角形偏移量,并选择一个要复制的网格。

按长度偏移三角形。按生成点转换顶点数据,将其全部存储在数组中。

10、合并

组装完各个组件后,需要将它们整理到一个可以导出的统一网格中。函数可以作为按钮向编辑器公开,而不是在构造脚本中运行它。这允许在运行脚本时进行更高级别的控制。

在此功能中,我们清除旧数据,构建唯一的材质数组(这避免了使用重复材料创建任何资产),必要时重建网格数据并输出网格。作为优化,我们应该重建网格以避免大量重复的材质分配。

将组件整理成一组数组(链接)。

检查我们构建的材质数组在长度上是否与组件数组相匹配。如果材质比组件少,我们知道是一些共享的材质。

此时,我们必须遍历每个组件的三角形数据并对其进行偏移,以便在组合在一起时与顶点数组对齐。

最后,采用新的数据结构并创建一个静态网格。

单击合成器上的程序网格组件,然后按"创建静态网格"按钮。这将提示选择一个位置,选择一个名称就可以了:

11、结论

这只是你可以使用过程网格组件创建的资产类型的一个示例。有各种各样的可能性,也不仅限于编辑器创建,你也可以在游戏中使用此功能!

我们也不能在不提到Houdini的情况下完成一篇关于程序化工具的文章,这是一个纯粹专注于程序化内容生成的出色工具。它有一个很棒的UE4插件,你可以用它来处理Houdini引擎资源。


原文链接:Building Procedural Art Tools in Unreal Engine 4

BimAnt翻译整理,转载请标明出处