虚幻引擎 4 初学者教程——创建交互雪地

在此虚幻引擎 4 教程中,您将学习如何使用场景捕捉和渲染目标创建可交互的雪地

如果您一直在玩最近的 3A 游戏,您可能已经注意到积雪覆盖的景观趋势。
举一些例子如:地平线零黎明,古墓丽影崛起和战神。
在这些游戏中,有一点与雪有关:您可以创造雪地!

让玩家像这样与环境互动是增加沉浸感的好方法。
它让环境感觉更逼真,让我们面对现实吧——这真的很有趣。
当你可以扑通一声趴在地上做冰雪天使时,为什么还要花几个小时来设计有趣的机制呢?


在本教程中,您将学习如何:

  • 通过使用场景捕捉掩盖靠近地面的物体来创建轨迹

  • 使用景观材质中的遮罩来创建可交互的雪

  • 只显示玩家周围的轨迹以进行优化

注意:本教程假定您已经了解使用虚幻引擎的基础知识。
如果您是虚幻引擎新手,请点击这里查看虚幻引擎初学者教程系列

本教程是关于在虚幻引擎中使用渲染目标的 4 部分教程系列的一部分:
第 1 部分:使用渲染目标绘画

第 2 部分:创建可交互雪地

第 3 部分:创建交互草地

第 4 部分:动态网格绘制

入门

首先下载本教程的资料(您可以点击这里链接获取)。

解压缩它并导航到SnowDeformationStarter并打开SnowDeformation.uproject

在本教程中,您将使用一个角色和几个盒子来创建轨迹。


在我们开始之前,您应该知道本教程中的方法只会在定义的区域而不是整个世界中存储轨迹。

这是因为性能取决于渲染目标的分辨率。

例如,如果要存储大面积的轨迹,则需要提高分辨率。

但这也增加了场景捕捉的性能影响和渲染目标的内存大小。

要对此进行优化,您需要限制有效面积和分辨率。

现在我们已经解决了这个问题,让我们看看创建雪地需要什么。

实现雪的轨迹

创建轨迹需要做的第一件事是渲染目标。

渲染目标将是一个灰度蒙版,其中白色表示有轨迹,黑色表示没有轨迹。

然后您可以将渲染目标投影到地面上并使用它来混合纹理和置换顶点。

您需要的第二件事是一种仅屏蔽掉影响雪的对象的方法。

您可以通过首先将对象渲染到Custom Depth来做到这一点。

然后,您可以使用带有后期处理材质的场景捕获来遮蔽渲染到自定义深度的任何对象。

然后,您可以将蒙版输出到渲染目标。

注意:场景捕捉基本上是一个能够输出到渲染目标的相机。

场景捕捉的重要部分是放置它的位置。

下面是从自顶向下视图捕获的渲染目标的示例。

在这里,第三人称角色和框已被屏蔽掉。

乍一看,自上而下的捕获看起来是可行的方法。

这些形状似乎对网格是准确的,所以应该没有问题,对吧?

不完全是。自上而下捕获的问题在于它不会捕获最宽点下方的任何内容。

例如:

想象一下黄色箭头一直延伸到地面。

对于立方体和圆锥体,箭头将始终位于对象内部。

但是,对于球体,箭头将在接近地面时离开球体。但就摄像机而言,箭头始终在球体内。

这就是球体在相机中的样子:

这将导致球体的遮罩即使与地面的接触面积很小也会比正常的大,。

这个问题的一个扩展是很难确定物体是否接触地面。

解决这两个问题的一种方法是改为从底部捕获。

从底部捕获

从底部捕获看起来像这样:

如您所见,相机现在捕捉到接触地面的底部。

这解决了自上而下捕获的“最宽区域”问题。

要确定物体是否接触地面,您可以使用后处理材质执行深度检查。

这将检查对象的深度是否高于地面深度并低于指定的偏移量。

如果两个条件都为真,您可以屏蔽该像素。

下面是一个引擎内示例,捕获区距离地面 20 个单位。

请注意遮罩如何仅在对象经过特定点时出现。

另请注意,物体离地面越近,蒙版越白。

首先,让我们创建一个后处理材质来执行深度检查。

创建深度检查材质

要进行深度检查,您需要使用两个深度缓冲区。

一个用于地面,另一个用于影响雪的物体。

由于场景捕捉只会看到地面,场景深度将输出地面的深度。

要获得对象的深度,只需将它们渲染到Custom Depth即可。

注意:为了节省时间,我已经将角色和框渲染到自定义深度。
如果要添加影响雪的对象,请确保在它们上启用Render CustomDepth Pass。

首先,您需要计算每个像素到地面的距离。

打开Materials\PP_DepthCheck然后创建以下内容:

接下来,您需要创建捕获区域。

为此,添加突出显示的节点:

现在,如果像素在地面的25个单位以内,它将显示在遮罩中。

掩蔽强度取决于像素离地面的距离。

单击Apply然后返回到主编辑器。

接下来,您需要创建场景捕获。


创建场景捕捉

首先,您需要一个用于写入场景捕获的渲染目标。

导航到RenderTargets文件夹并创建一个名为RT_Capture的新渲染目标。

现在让我们创建场景捕获。

对于本教程,您将向蓝图添加一个场景捕获,因为稍后您需要为其编写一些脚本。

打开Blueprints\BP_Capture,然后添加一个Scene Capture Component 2D

将其命名为SceneCapture

首先,您需要设置捕获的旋转,使其向上看向地面。

转到 Details 面板并将Rotation设置为(0, 90, 90)

接下来是投影类型。

由于遮罩是场景的 2D 表示,因此您需要移除任何透视失真。

为此,将Projection\Projection Type设置为Orthographic

接下来,您需要告诉场景捕获要写入哪个渲染目标。为此,将Scene Capture\Texture Target设置为RT_Capture

最后,您需要使用深度检查材料。

PP_DepthCheck添加到Rendering Features\Post Process Materials

为了进行后期处理,您还需要将Scene Capture\Capture Source更改为 RGB 中Final Color (LDR)

现在场景捕获已全部设置完毕,您需要指定捕获区域的大小。

设置捕获区域大小

由于渲染目标最好使用低分辨率,因此您需要确保有效地使用它的空间。

这意味着决定一个像素覆盖多少区域。

例如,如果捕获区域和渲染目标的分辨率大小相同,则比例为 1:1。

每个像素将覆盖 1×1 区域(以世界单位表示)。

对于雪地,不需要 1:1 的比例,因为您不太可能需要那么多细节。

我建议使用更高的分辨率,因为它们可以让您在使用低分辨率的同时增加捕获区域的大小。

注意不要将比例增加太多,否则你会开始丢失细节。

对于本教程,您将使用 8:1 的比例,这意味着每个像素的大小为 8×8 世界单位。

您可以通过更改Scene Capture\Ortho Width属性来调整捕获区域的大小。

例如,如果您想要捕获 1024×1024 的区域,您可以将其设置为 1024。

由于您使用的是 8:1 的比例,因此将其设置为2048(默认渲染目标分辨率为 256×256)。

这意味着场景捕获将捕获2048×2048区域。这大约是 20×20 米。

地面材质还需要访问捕获大小才能正确投影渲染目标。

一种简单的方法是将捕获大小存储到材质参数集合中。

这基本上是任何材质都可以访问的变量集合。

存储捕获尺寸

返回主编辑器并导航到Materials文件夹。

然后,创建一个Material Parameter Collection,列在Materials & Textures下。

将其重命名为MPC_Capture,然后打开它。

接下来,创建一个新的标量参数并将其命名为CaptureSize

不要担心设置它的值——您将在蓝图中执行此操作。

返回BP_Capture并将突出显示的节点添加到Event BeginPlay

确保将Collection设置为MPC_Capture并将Parameter Name设置为CaptureSize

现在任何材质都可以通过读取CaptureSize参数来获取Ortho Width的值。

这就是现在的场景捕捉。

单击编译,然后返回到主编辑器。

下一步是将渲染目标投影到地面上并使用它来使景观变形。

场景交互

打开M_Landscape,然后转到“详细信息”面板。之后,设置以下属性:

  • 双面设置为启用。由于场景捕捉将从底部看,它只会看到地面的背面。
    默认情况下,引擎不渲染背面。这意味着它不会将地面的深度存储到深度缓冲区中。要解决此问题,您需要告诉引擎渲染网格的两侧。

  • D3D11 Tessellation设置为Flat Tessellation(您也可以使用 PN 三角形)。
    Tessellation 会将网格的三角形分割成更小的三角形。
    这有效地增加了网格的分辨率,并允许您在置换顶点时获得更精细的细节。否则,顶点密度将太低而无法产生让人觉得真实的轨迹。

启用曲面细分后,将启用世界位移曲面细分乘数

Tessellation Multipler控制曲面细分的数量。

对于本教程,请将其取消,这意味着它将使用默认值1。

World Displacement接受一个向量值,描述移动顶点的方向和移动量。

要计算此引脚的值,您首先需要将渲染目标投影到地面上。

投影渲染目标

要投影渲染目标,您需要计算其 UV 坐标。

为此,请创建以下设置:

概括:

首先,您需要获取当前顶点的 XY 位置。

由于您是从底部捕捉,因此 X 坐标被翻转,因此您需要将其翻转回来(如果您是从顶部捕捉,则不需要这样做)。

本节实际上会做两件事。首先,它将渲染目标居中,以便中间位于世界空间中的(0, 0)处

然后它将从世界空间转换为 UV 空间。

接下来,创建突出显示的节点并连接之前的计算,如下所示。

确保将Texture Sample 的纹理设置为RT_Capture

这会将渲染目标投影到地面上。

但是,捕捉区域外的任何顶点都将对渲染目标的边缘进行采样。

这是一个问题,因为渲染目标仅用于捕捉区域内的顶点。这是它在游戏中的样子:

要解决此问题,您需要屏蔽掉落在 0 到 1 范围(捕获区域)之外的任何 UV。

MF_MaskUV0-1函数是我为此构建的函数。如果提供的 UV 超出 0 到 1 范围,它将返回0如果在范围内,则返回1

将结果与渲染目标相乘将执行遮罩。

现在您已经投影了渲染目标,您可以使用它来混合颜色和置换顶点。


使用渲染目标

让我们从混合颜色开始。

为此,只需像这样将1-x连接到Lerp

注意:知道为什么使用1-x么?,那只是为了反转渲染目标并使计算更简单一些。

现在当有小径时,地面的颜色会变成棕色。

如果没有踪迹,它将是白色的。

下一步是置换顶点。

为此,添加突出显示的节点并连接所有内容,如下所示:

这将使所有雪区向上移动25个单位。非雪区将具有零位移,这就是创建路径的原因。

注意:您可以更改DisplacementHeight以降低或增加雪的高度。
另请注意 DisplacementHeight 与捕获偏移量的值相同。
将它们设置为相同的值将给出准确的变形。
但在某些情况下,您可能希望单独更改它们,这就是为什么我将它们保留为单独的参数。

单击Apply然后返回到主编辑器。

在关卡中创建一个BP_Capture实例并将其位置设置为**(0, 0, -2000)**以将其放置在地面下方。

Play并使用W、A、S和D走动,开始使雪变形。

变形有效,但没有任何痕迹!这是因为每次捕获时捕获都会覆盖渲染目标。

您在这里需要的是一些使跟踪持久化的方法。


创建持久跟踪

要创建持久性,您需要另一个渲染目标(持久缓冲区)来存储捕捉的内容,然后再将其覆盖。

之后,将持久缓冲区添加回捕捉(在它被覆盖之后)。

你得到的是一个循环,其中每个渲染目标都写入另一个。这就是创造持久性的原因。

首先,您需要创建持久缓冲区。

创建持久缓冲区

转到RenderTargets文件夹并创建一个名为RT_Persistent的新渲染目标。

对于本教程,您不必更改任何纹理设置,但对于您自己的项目,请确保两个渲染目标使用相同的分辨率。

接下来,您需要一种将捕获复制到持久缓冲区的材料。打开Materials\M_DrawToPersistent,然后添加一个Texture Sample节点。

将其纹理设置为RT_Capture并像这样连接它:

现在你需要使用绘制材料。单击Apply,然后打开BP_Capture

首先,让我们创建材质的动态实例(稍后您需要传入值)。

将突出显示的节点添加到Event BeginPlay

Clear Render Target 2D节点将确保每个渲染目标在使用前都处于空白状态。

接下来,打开DrawToPersistent函数并添加突出显示的节点:

接下来,您需要确保每一帧都绘制到持久缓冲区,因为捕捉发生在每一帧。

为此,将DrawToPersistent添加到Event Tick

最后,您需要将持久缓冲区添加回捕捉渲染目标。

返回捕捉

单击编译,然后打开PP_DepthCheck。然后,添加突出显示的节点。

确保将Texture Sample设置为RT_Persistent

现在渲染目标正在相互写入,您将获得持久的踪迹。

单击“应用”,然后关闭材料。按播放键开始追踪!

结果看起来不错,但当前设置仅适用于地图的一个区域。

如果您走出捕捉区域,轨迹将停止出现。

解决这个问题的一种方法是与玩家一起移动捕捉区域。

这意味着轨迹将始终出现在玩家区域周围。

注意:由于捕捉是移动的,因此捕捉区域之外的任何信息都将被丢弃。

这意味着如果您返回之前有小径的区域,它们将不再存在。

请继续关注下一个教程,我将向您展示如何创建半持久性。

移动捕捉

您可能认为您所要做的就是将捕捉的 XY 位置设置为玩家的 XY 位置。

但是,如果您这样做,渲染目标将会变得模糊。

这是因为您正在以小于像素的步长移动渲染目标。

发生这种情况时,像素的新位置将最终位于像素之间。

这导致多个像素插值到单个像素。这是它的样子:

要解决此问题,您需要分步移动捕捉。

您所做的是计算一个像素的世界大小,然后以等于该大小的步长移动捕捉。

现在每个像素永远不会在其他像素之间结束,因此不会发生模糊。

首先,让我们创建一个参数来保存捕捉的位置。

地面材质将需要这个用于投影。

打开MPC_Capture并添加一个名为CaptureLocation的矢量参数。

接下来,您需要更新地面材质以使用新参数。关闭MPC_Capture,然后打开M_Landscape。将投影的第一部分节点修改为:

现在渲染目标将始终投影在捕获位置。

单击“应用”,然后关闭材质。

接下来是分步移动捕获。

分步移动捕获

要计算像素的世界大小,您可以使用以下等式:

  ( 1 / RenderTargetResolution) * CaptureSize

要计算新位置,请对每个位置分量(在本例中为 X 和 Y 位置)使用下面的等式。

  ( floor (Position / PixelWorldSize) + 0.5 ) * PixelWorldSize

现在让我们在捕捉蓝图中使用它们。

为了节省时间,我为第二个方程创建了一个SnapToPixelWorldSize宏

打开BP_Capture,然后打开MoveCapture功能。之后,创建以下设置:

这将计算新位置,然后将新位置和当前位置之间的差异存储到MoveOffset中。

如果您使用的分辨率不是 256×256,请确保更改突出显示的值。

接下来,添加突出显示的节点:

这将使用计算出的偏移量移动捕捉。

然后它将捕获的新位置存储到MPC_Capture 中,以便地面材质可以使用它。

最后,您需要每帧执行位置更新。

关闭该函数,然后在Event Tick中的DrawToPersistent之前添加MoveCapture

移动捕捉只是一种解决方案。

随着捕捉的移动,您还需要移动持久缓冲区。

否则,捕捉和持久缓冲区将不同步并产生奇怪的结果。

移动持久缓冲区

要移动持久缓冲区,您需要传入计算出的移动偏移量。

打开M_DrawToPersistent并添加突出显示的节点:

这将使用提供的偏移量移动持久缓冲区。就像在地面材质中一样,您还需要翻转 X 坐标并执行遮罩。单击“应用”,然后关闭材料。

接下来,您需要传入移动偏移量。

打开BP_Capture,然后打开DrawToPersistent函数。

然后,添加高亮的节点:

写在最后

您可以点击这里获取完成的项目)。

您不必只为了下雪而使用本教程中的雪地。您甚至可以将其用于踩踏草地之类的东西(下一个教程中将向您展示高阶运用)。