游戏中可交互雪
雪迹实施
创建轨迹所需的第一件事是渲染目标。渲染目标将是一个灰度蒙版,其中白色表示有轨迹,黑色表示没有轨迹。然后,您可以将渲染目标投影到地面上,并使用它来混合纹理和置换顶点。
你需要的第二件事是如何只屏蔽掉受雪影响的物体。你可以通过首先将物体渲染为自定义深度来做到这一点。然后,你可以使用带有后处理材质的场景捕捉来屏蔽任何渲染为自定义深度的对象。然后,你可以将遮罩输出到渲染目标。
场景捕捉的重要部分是你放置它的位置。下面是一个从自顶向下视图捕获的渲染目标的例子。在这里,第三人称角色和盒子被掩盖了。
乍一看,自上而下的捕捉看起来是个好办法。形状似乎是精确到网格的,所以应该没有问题,对吗?并非如此。自上而下捕捉的问题是,它不能捕捉最宽点以下的东西。这里有一个例子。
这个问题的延伸是很难确定一个物体是否接触到地面。
自顶向上方法
为了确定物体是否接触到地面,可以使用后期处理材质来进行深度检查。这将检查物体的深度是否高于地面深度和低于指定的偏移量。如果这两个条件都是真的,你就可以屏蔽掉这个像素。
创建深度检查材质
为了进行深度检查,你需要使用两个深度缓冲区。一个用于地面,另一个用于受雪影响的物体。由于场景捕捉只能看到地面,所以场景深度将输出地面的深度。要获得物体的深度,你只需将它们渲染到自定义深度。
首先,你需要计算每个像素到地面的距离。打开Materials\PP DepthCheck
,然后创建以下内容
现在,如果像素在地面的25
个单位内,它就会显示在遮蔽中。遮罩的强度取决于像素离地面有多近。点击应用,然后回到主编辑器。
接下来,你需要创建场景捕捉。
创建场景捕捉
首先,你需要一个渲染目标,以便将场景捕捉写入其中。导航到RenderTargets文件夹,创建一个名为RT_Capture
的新渲染目标。
现在让我们来创建场景捕捉。在本教程中,你将把场景捕捉添加到蓝图中,因为你以后需要对它进行一些脚本编写。打开Blueprints/BP_Capture,然后添加一个Scene Capture Component 2D
。将其命名为SceneCapture。
板,设置旋转为(0,90,90)。接下来是投影类型。由于遮罩mask
是场景的二维表示,你需要消除任何透视变形。要做到这一点,将Projection\Projection
类型设置为Orthographic
。
下来,你需要告诉场景捕捉要写到哪个渲染目标上。要做到这一点,将Scene Capture/Texture Target
设置为RT1
(自己建的)。
最后,你需要使用深度检查材质。将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米。
地面材质也需要访问捕获的大小,以便正确地投影渲染目标。做到这一点的一个简单方法是将捕捉的尺寸存储到一个材料参数集合中。这是一个变量的集合,任何材质都可以访问。
存储区域大小
创建一个材料参数集,它被列在材料和纹理下。把它重命名为MPC_Capture
,然后打开它。
接下来,创建一个新的标量参数并命名为CaptureSize
。
回到BP_Capture,确保将set
设置为MPC_Capture,参数名称为CaptureSize
。
变形地形
打开M_Landscape
,然后转到细节面板。然后,设置以下属性
- 将
LandscapeTwo Sided
设置为启用。由于场景捕捉将从底部看,它将只看到地面的背面。默认情况下,引擎不会渲染背面。这意味着它不会将地面的深度存储到深度缓冲区。要解决这个问题,你需要告诉引擎渲染网格的两面。 D3D11 Tessellation
设置为Flat Tessellation
(也可以使用PN三角形)。这有效地提高了网格的分辨率,使你在置换顶点时可以得到更精细的细节。
一旦你启用了镶嵌,世界位移和镶嵌乘数将被启用。
Tessellation Multipler控制tessellation
的数量。在本教程中,不连接它,这意味着它将使用默认值1。
世界位移(World Displacement)接收一个矢量值,描述顶点的移动方向和移动量。为了计算这个pin
的值,你首先需要把渲染目标投射到地面上。
投射渲染目标
为了投射渲染目标,你需要计算它的UV坐标。为此,创建以下设置(在地形材质中)
-
首先,你需要得到当前顶点的XY位置。由于你是从底部捕捉的,X坐标被翻转了⭐️,所以你需要把它翻转回来(如果你是从顶部捕捉的,你就不需要这样做
-
这一部分实际上会做两件事。首先,它将使渲染目标居中,使其在世界空间中位于(0, 0)。然后,它将从世界空间转换到UV空间。
确保将Texture Sample的纹理设置为RT Capture:
注意这里的位置节点是如下的材质函数,逻辑也很简单
这将把渲染目标投射到地面上。然而,捕获区域之外的任何顶点都将采样渲染目标的边缘。这是一个问题,因为渲染目标只用于捕获区域内的顶点。为了解决这个问题,你需要屏蔽任何落在0到1范围之外的UV。MF MaskUV0-1
函数是为此而构建的函数。如果提供的UV在0到1范围之外,它将返回0;如果在范围内,它将返回1。将结果与渲染目标相乘将执行屏蔽。
使用RT
让我们从混合颜色开始:
现在,当有一个踪迹,地面的颜色将是棕色。如果没有踪迹,它将是白色的。下一步是替换顶点。为此:
然后回到主编辑器。在关卡中创建一个BP_Capture
的实例,并将其位置设置为(0, 0, -2000)
,使其位于地面之下。按下Play,用W、A、S和D走动,开始对雪进行变形。
创造持久的痕迹
那么肯定是乒乓缓冲。
创建一个名为RT Persistent的渲染目标。接下来,需要一个将捕获复制到持久缓冲区的材料。打开Materials\M_DrawToPersistent
,然后添加一个Texture Sample节点。将其纹理设置为RT Capture并像这样连接它
现在需要使用这个绘制材料。点击应用,然后打开BP_Capture
。首先,创建一个材料的动态实例(以后需要传入数值):
接下来,打开DrawToPersistent
函数并添加:
接下来,需要确保每一帧都绘制到持久缓冲区,因为捕获每一帧都会发生。为此,将DrawToPersistent添加到事件Tick中。
修改深度测试材质:
目前的设置只适用于地图的一个区域。如果走出捕获区域,踪迹将停止出现。
移动捕获
你可能认为所要做的就是将捕获的XY位置设置为玩家的XY位置。但是如果这样做,渲染目标就会开始模糊。这是因为正在以比一个像素小的步骤移动渲染目标。当这种情况发生时,一个像素的新位置最终将在像素之间。这将导致将多个像素插值到单个像素。这是它的样子
要解决这个问题,需要以离散的step
移动捕获。首先,创建一个参数来保持捕获的位置。地面材质将需要这个来进行投影数学运算。打开MPC_Capture并添加一个名为CaptureLocation
的矢量参数。
接下来,需要更新地面材质来使用新的参数:
现在渲染目标将始终投影在捕获的位置。点击应用,然后关闭材质。接下来是以离散step
移动捕获。
要计算 pixel’s world size
,可以使用以下等式:
(1 / RenderTargetResolution) * CaptureSize
要计算新的位置,请在每个位置组件(在本例中为X和Y位置)上使用下面的公式:
(floor(Position / PixelWorldSize) + 0.5) * PixelWorldSize
现在在捕获蓝图中使用它们。为了节省时间,我为第二个等式创建了一个SnapToPixelWorldSize
宏。打开BP Capture
,然后打开moveccapture
函数。然后,创建以下设置
然后:
这将使用计算出的偏移量移动捕获。然后,它将存储捕获的新位置到MPC,以便地面材料可以使用它。最后,需要在每一帧执行位置更新。关闭函数,然后在事件Tick中DrawToPersistent之前添加MoveCapture。
移动捕获只是解决方案的一半。我们还需要在捕获移动时移动持久缓冲区。否则,捕获和持久缓冲区将不同步并产生奇怪的结果。
移动持久缓存区
要移动持久化缓冲区,需要传入计算的移动偏移量。打开M_DrawToPersistent
,添加
这将使用提供的偏移量移动持久性缓冲区。就像在地面材质中一样,也需要翻转X坐标并进行遮蔽。点击应用,然后关闭该材质。
接下来,需要传入移动偏移。打开BP_Capture
,然后打开DrawToPersistent
函数。之后,添加: