在虚幻引擎4中在雪中创建轨道


如果您玩现代AAA游戏,您可能已经注意到倾向于使用积雪覆盖的景观。 例如,他们在《 地平线零黎明》《古墓丽影》和《 战神》中 。 在所有这些游戏中,积雪都有一个重要特征:您可以在上面留下痕迹!

通过与环境的互动,增强了玩家对游戏的沉浸感。 它使环境更加逼真,说实话-这很有趣。 如果您可以让玩家跌倒并结成雪天使,为什么还要花大量时间来创建一个好奇的机制?

在本教程中,您将学到以下内容:

  • 使用场景捕捉来创建足迹,以掩盖靠近地面的物体
  • 使用具有地形材质的遮罩来创建可变形的雪
  • 为了进行优化,仅在播放器旁边的雪中显示足迹

注意:据了解,您已经熟悉使用虚幻引擎的基础知识。 如果您是新手,请查看我们的面向初学者的虚幻引擎教程系列。

开始工作


下载本教程的材料 。 解压缩它们,转到SnowDeformationStarter并打开SnowDeformation.uproject 。 在本教程中,我们将在一个字符和几个框的帮助下创建轨迹。


在开始之前,您需要知道本教程中的方法将仅在给定区域而不是在世界范围内保存跟踪,因为速度取决于目标渲染的分辨率。

例如,如果我们要存储大面积的迹线,则必须提高分辨率。 但这也会增加场景捕获对目标渲染的游戏速度和内存大小的影响。 为了优化,您需要限制范围和分辨率。

处理了这个问题之后,让我们找出实现雪中足迹的方法。

雪中​​足迹的实现


创建跟踪的第一件事是目标渲染 。 渲染目标将是灰度蒙版,其中白色表示存在迹线,黑色表示不存在迹线。 然后,我们可以将目标渲染投影到地面上,并使用它来混合纹理并移动顶点。


我们需要的第二件事是仅遮盖影响雪的对象的方法。 这可以通过首先在“ 自定义深度”中渲染对象来实现。 然后,您可以将场景捕获后期处理材料一起使用,以掩盖“自定义深度”中渲染的所有对象。 然后,可以在目标渲染中显示蒙版。

注意:场景捕获本质上是具有输出目标渲染能力的摄像机。

捕获场景最重要的部分是其位置。 以下是从顶视图捕获的目标渲染的示例。 在此,将隐藏第三人称角色和框。


乍一看,顶视图适合我们。 表格看起来适合于网格,所以应该没有任何问题,对吧?

不完全是 从顶视图捕获的问题是,它在最宽的范围内无法捕获任何内容。 这是一个例子:


想象一下,黄色箭头一直到地面。 对于立方体和圆锥体,箭头将始终保留在对象内部。 但是,在球体的情况下,接近地球时会从该点出现。 但是根据摄像头,尖端始终位于球体内。 相机的球面如下所示:


因此,即使与地球接触的面积很小,球形蒙版也会比应有的更大。

此外,我们很难确定物体是否与土地有关,这一问题得到了补充。


为了解决这两个问题,您可以使用底部的捕获。

底握


从下面捕获如下:


如您所见,相机现在捕获了下侧,即地面的那一侧。 这消除了从上方捕获时出现的“最宽区域”的问题。

要确定物体是否接触地面,可以使用后处理材料进行深度检查。 它检查物体的深度是否大于地球的深度, 以及是否低于预定的偏移量。 如果两个条件都满足,那么我们可以掩盖该像素。


下面是一个发动机内部的示例,该发动机在地面上方具有20个单位的捕获区域。 请注意,仅当对象通过特定点时才会显示遮罩。 另请注意,当物体接近地面时,遮罩会变白。


首先,创建后处理材料以执行深度检查。

创建深度测试材料


要执行深度检查,您需要使用两个深度缓冲区-一个用于地面,另一个用于影响雪的物体。 由于捕获场景只能看到地球,因此景深将推断出地球的深度。 为了获得对象的深度,我们将渲染它们的Custom Depth

注意:为了节省时间,我已经在“自定义深度”中渲染了角色和框。 如果要添加影响雪的其他对象,则必须为其启用“ 渲染CustomDepth传递 ”。

首先,您需要计算每个像素到地面的距离。 打开Materials \ PP_DepthCheck并创建以下内容:


接下来,您需要创建一个捕获区域。 为此,添加突出显示的节点:


现在,如果像素在地球的25个单位内,则它将出现在蒙版中。 掩蔽亮度取决于像素离地面的距离。 单击“ 应用”并返回主编辑器。

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

创建场景捕捉


首先,我们需要在其中捕获场景捕获的目标渲染。 转到RenderTargets文件夹并创建一个名为RT_Capture的新Render Target

现在让我们创建一个场景捕获。 在本教程中,我们将场景捕获添加到蓝图中,因为稍后我们将需要一个脚本。 打开Blueprints \ BP_Capture并添加Scene Capture Component 2D 。 将其命名为SceneCapture


首先,我们需要设置捕获转弯,使其朝向地面。 转到“详细信息”面板,并将“ 旋转度”设置 (0,90,90)


接下来是投影的类型。 由于遮罩是场景的2D表示,因此我们需要消除透视失真。 为此,将Projection \ Projection Type设置为Orthographic


接下来,我们需要告诉场景捕获要记录到的目标渲染。 为此, Scene Capture \ Texture Target选择RT_Capture的值。


最后,我们需要使用深度检查材料。 将PP_DepthCheck添加到“ 渲染功能\后期处理材料”中 。 为了使后处理正常工作,我们还需要将Scene Capture \ Capture Source更改为RGB中的最终颜色(LDR)


现在已经配置了场景捕获,我们需要指定捕获区域的大小。

设定拍摄区域的大小


由于最好将低分辨率用于目标渲染,因此我们需要有效地利用空间。 也就是说,我们必须选择一个像素覆盖的区域。 例如,如果捕获区域和目标渲染的分辨率相同,那么我们将得到1:1的比率。 每个像素将覆盖1×1区域(以世界为单位)。

对于雪中的轨道,不需要1:1的比例,因为我们很可能不需要这样的细节。 我建议使用较大的比率,因为这将使您可以在低分辨率下增加捕获区域的大小。 但是不要使比例过大,否则细节将开始丢失。 在本教程中,我们将使用8:1的比例,即每个像素的大小将是8×8个世界单位。

您可以通过更改场景捕获\正交宽度属性来调整捕获区域的大小。 例如,如果要捕获1024×1024的区域,则将值设置为1024。由于我们使用8:1的比率,因此将值设置为2048 (目标渲染的默认分辨率为256×256)。


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

地面材料还需要获取捕获大小以正确投影目标渲染。 最简单的方法是将捕获大小保存在“ 材料参数集合”中 。 这实质上是任何材料都可以访问的变量的集合。

保存捕获大小


返回主编辑器,然后转到“ 材质”文件夹。 创建将在“ 材质和纹理”中使用的“ 材质参数集合 。 将其重命名为MPC_Capture并打开。

然后创建一个新的标量参数,并将其命名为CaptureSize 。 不用担心设置其值-我们会直言不讳。


返回BP_Capture并将突出显示的节点添加到Event BeginPlay中 。 将Collection设置为MPC_Capture ,并将参数名称设置为CaptureSize


现在,任何材质都可以通过从CaptureSize参数中读取来获得“ 正交宽度”的值。 到目前为止,我们已经完成了场景捕获。 单击编译,然后返回主编辑器。 下一步是将目标渲染器投影到地面上,并使用它来变形景观。

景观变形


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

  • 对于“双面”,选择“ 启用” 。 由于捕获的场景将从下面“看”,因此只能看到地球的反面。 默认情况下,引擎不渲染网格的背面。 这意味着它将不会在深度缓冲区中存储地球的深度。 为了解决这个问题,我们需要告诉引擎渲染网格的两面。
  • 对于D3D11镶嵌,选择平面镶嵌 (也可以使用PN三角形)。 棋盘格化会将网格三角形分解为较小的三角形。 本质上,这增加了网格的分辨率,并允许我们在移动顶点时获得更精细的细节。 否则,峰的密度将太低而无法创建可信的迹线。


启用镶嵌功能后,“ 世界位移镶嵌细分倍增器”将打开。


镶嵌细分控制镶嵌的数量。 在本教程中,我们将不连接该节点,即,我们使用默认值( 1 )。

世界位移得到一个矢量值,该值描述了顶点朝哪个方向移动多少。 要计算此接触的值,我们需要首先将目标渲染投影到地面上。

项目目标渲染


要投影目标渲染,您需要计算其UV坐标。 为此,请创建以下方案:


这是怎么回事:

  1. 首先,我们需要获取当前顶点的XY位置。 由于我们是从下方捕获的,因此X坐标已翻转,因此您需要将其向后翻转(如果要从上方捕获,则不需要此)。
  2. 这部分执行两个任务。 首先,它将目标渲染居中,以使其中心位于世界空间的坐标(0,0)中 。 然后,她将坐标从世界空间转换为UV空间。

接下来,创建选定的节点并结合以前的计算,如下所示。 对于“ 纹理样本”,选择“ RT_Capture”


这会将目标渲染投影到地面。 但是,捕获区域之外的所有顶点都将对目标渲染的边缘进行采样。 这实际上是一个问题,因为目标渲染仅应用于捕获区域内的顶点。 这是游戏中的外观:


要解决此问题,我们需要屏蔽所有0到1(即捕获区域)范围之外的UV。 为此,我创建了MF_MaskUV0-1函数。 如果透射的UV在0到1的范围之外,则返回0;如果透射的UV在其中,则返回1 。 将结果乘以目标渲染,我们执行遮罩。

现在,我们已经投影了目标渲染,我们可以使用它来混合颜色和移动顶点。

使用目标渲染


让我们从混合颜色开始。 为此,我们只需将1-x连接到Lerp即可


注意:如果您不理解为什么我使用1-x ,我将进行解释-这是反转目标渲染的必要步骤,这样计算起来会更容易一些。

现在我们有了一个痕迹,地球的颜色正在变成棕色。 如果没有颜色,则保持白色。

下一步是顶点的位移。 为此,添加选定的节点并按如下所示连接所有内容:


这将导致所有降雪区域上移25个单位。 没有积雪的区域的偏移量为零,这将创建一条足迹。

注意:您可以更改DisplacementHeight以增加或减少雪水平。 另请注意,DisplacementHeight与捕获偏移量的值相同。 当它们具有相同含义时,它将为我们提供精确的变形。 但是在某些情况下,您需要单独更改它们,因此我将它们保留为单独的参数。

单击“ 应用”并返回主编辑器。 在该级别上创建一个BP_Capture实例,并将其坐标(0、0,-2000)放置在地下。 单击“ 播放”,然后用键WASD徘徊以使雪翘曲。


可以变形,但是没有任何痕迹! 发生这种情况是因为每次执行捕获时,捕获都会覆盖目标渲染。 我们需要某种方式使轨道永久化

创建永久痕迹


为了创建持久性,我们需要另一个目标渲染器( 常量缓冲区 ),所有捕获内容将在覆盖之前保存。 然后,我们将向捕获添加一个常量缓冲区(覆盖它之后)。 我们得到一个循环,其中每个目标渲染都写入另一个目标。 这就是我们将创建跟踪永久性的方式。


首先,我们需要创建一个常量缓冲区。

创建一个持久缓冲区


转到RenderTargets文件夹并创建一个名为RT_Persistent的新Render Target 。 在本教程中,我们不必更改纹理参数,但是在您自己的项目中,您需要确保两个目标渲染器使用相同的分辨率。

接下来,我们需要将捕获内容复制到永久缓冲区的材料。 打开Materials \ M_DrawToPersistent并添加一个Texture Sample节点。 选择它的RT_Capture纹理,并按如下所示进行连接:


现在我们需要使用绘图材料。 单击“ 应用” ,然后打开BP_Capture 。 首先,创建材质的动态实例(稍后我们将需要向其传递值)。 将突出显示的节点添加到Event BeginPlay中


清除渲染目标2D节点在使用前清除每个目标渲染。

然后打开DrawToPersistent函数并添加突出显示的节点:


接下来,由于捕获发生在每个帧中,因此我们需要确保在每个帧中都执行对常量缓冲区的绘制。 为此,将DrawToPersistent添加到Event Tick中


最后,我们需要向目标捕获渲染添加持久缓冲区。

记录回去捕获


单击编译,然后打开PP_DepthCheck 。 然后添加突出显示的节点。 对于纹理样本,将值设置为RT_Persistent


现在,目标渲染器将彼此写入,我们将获得剩余的痕迹。 单击“ 应用” ,然后关闭材料。 单击播放并开始离开曲目!


结果看起来不错,但是结果电路仅适用于地图的一个区域。 如果您超出捕获区域,则痕迹将不再出现。


您可以通过随播放器移动捕获区域来解决此问题。 这意味着轨迹将始终出现在播放器所在的区域周围。

注意:随着捕获的移动,捕获区域之外的所有信息都将被删除。 这意味着,如果您返回到已经有痕迹的区域,那么它们将已经消失。 在下一个教程中,我将向您展示如何创建部分保留的轨道。

捕捉动作


您可以决定将XY捕获位置绑定到播放器的XY位置非常简单。 但是,如果这样做,则目标渲染将开始模糊。 这是因为我们以小于像素的步长移动目标渲染。 发生这种情况时,新像素位置像素之间 。 结果,一个像素被几个像素插值。 看起来是这样的:


要解决此问题,我们需要分步移动捕获。 我们计算world中的像素大小 ,然后将捕获移动到等于该大小的步长。 这样,每个像素将永远不会在另一个像素之间,因此不会出现模糊。

首先,让我们创建一个参数,将在其中存储捕获位置。 地球材料将需要它来执行投影计算。 打开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中,以便地面材料可以使用它。

最后,我们需要在每个帧中执行位置更新。 关闭该函数并将其添加到DrawToPersistent MoveCapture之前的Event Tick中


移动捕获只是解决方案的一半。 我们还需要移动常量缓冲区。 否则,捕获缓冲区和持久缓冲区将不同步,并会产生奇怪的结果。


永久缓冲区移动


要移动恒定缓冲区,我们需要传递计算出的位移偏移量。 打开M_DrawToPersistent并添加突出显示的节点:


因此,常量缓冲区将偏移已传输偏移的值。 与地球材料一样,我们需要翻转X坐标并执行遮罩。 单击“ 应用”并关闭材料。

然后,您需要传输偏移量。 打开BP_Capture ,然后打开DrawToPersistent函数。 接下来,添加突出显示的节点:


这就是我们将MoveOffset转换为UV空间,然后将其传递到工程图中的方式。

单击“ 编译” ,然后关闭蓝图。 单击“ 播放”,然后运行您的心脏内容! 无论您走多远,痕迹都将始终围绕您。


接下来要去哪里?


可以从此处下载完成的项目。

不必仅将本教程中创建的轨道用于积雪。 您甚至可以将它们用于碎草之类的东西(在下一个教程中,我将展示如何创建系统的高级版本)。

如果您想使用景观和目标渲染,我建议观看Chris Murphy 使用Blueprint构建高端游戏效果的视频。 本教程将向您展示如何创建一个燃烧土和草的巨大激光!

Source: https://habr.com/ru/post/zh-CN416703/


All Articles