前言
当物体放在地形上并与地形有穿插时,其交界处通常会看到硬接缝。在以前,使用RenderTargets是一个解决方案:将地形等信息渲染到贴图上,其他物体就可以采样这张贴图了,随后根据接近地形的程度,来与渲染的贴图进行不同程度的混合。
在当前的虚幻版本中有了RuntimeVirtualTexture(运行时虚拟纹理),可以取代之前的RenderTargets。(关于它和RenderTargets方式的比较,在这里有讨论,可以参考)。在解决这个物体与地形混合过渡的问题上,RVT的操作和RenderTargets类似,而在步骤上更为简洁。但是最为重要的区别是,它的底层使用了 VirtualTexture(虚拟纹理) 技术,本文末将观察这项技术的使用。
本篇的步骤主要参考了教程How to Blend Objects with Your Landscape - UE4 Runtime Virtual Texturing (RVT) Tutorial - YouTube,不过使用了更简单的资源做测试,算是一个简化。
测试场景准备
我在创建工程时使用了“初学者内容包”。
随意创建一个地形并添加了些随意的噪声。创建一个地形材质时我使用了 T_Ground_Gravel_D 和 T_Ground_Gravel_N 作为基础色贴图与法线贴图。
然后在场景中放入一个石头模型SM_Rock。
当前,可以看到石头与地形之间有明显的接缝:
0. 启用 Virtual Texture
项目设置 =》 引擎-渲染 =》启用虚拟纹理支持
(启用后需要重启UE)
1. 创建RVT资源
在内容浏览器中右键 材质和纹理 =》运行时虚拟纹理。
接下来需要两个RVT,一个针对于地形的高度vt_height,一个针对于地形的材质vt_mat
对于vt_height,选择“场景高度”作为其 虚拟纹理内容。
而对于vt_mat,其 虚拟纹理内容 则留作默认的“底色、法线、粗糙度、高光度”。但是其尺寸可能需要调高一些:
2. 在场景中创建RVT的Volume
接下来需要在场景中放置RuntimeVirtualTextureVolume
,可以从放置面板中搜索此Actor然后拖入场景中。
然后设置其对应的RVT资源:
RuntimeVirtualTextureVolume
用来指定渲染VT的位置与范围,可以手动指定范围,也可以自动与场景中的地形对齐:选择场景中的地形然后点击“设置边界”。
由于vt_mat也需要一个这样的Volume,所以可以复制当前Volume,然后设置其对应的RVT:
3. 在地形材质中指定渲染RVT
在地形材质中使用Runtime Virtual Texture Output
节点来将数据写入RVT:
基本上,就是将原本输出给最终结果的数据,也同时输出给了Runtime Virtual Texture Output
节点。
不过有些需要做些处理:
- 对于“法线”,需要将其从切线空间转换为世界空间。
- 对于“高度”,需要对原本从“绝对世界位置”节点中获取的三维向量,取其RGB中的B(因为虚幻中XYZ中的Z代表向上的高度)
4. 指定地形输出的RVT资源
选择地形Actor,指定将vt_height和vt_mat填入其虚拟纹理的数组中。填入后就可看到RVT渲染出了内容:
5. 在石头材质中采样RVT
Runtime Virtual Texture Sample
节点可以在材质中对RVT进行采样。
在石头材质中添加一个此节点,然后设置其RVT资源为vt_mat,这个RVT中将有地形的底色和法线等。
随后,将其数据连入石头材质的最终输出:
需要注意的是:
- 由于之前在地形材质中,写入RVT中的法线是世界空间的,而这里石头材质的输出需要的是切线空间,所以需要添加节点将世界空间转换为切线空间。
随后,可以在场景中看到现在石头材质的表现:
可以注意到,其与地形交界处已经没有明显接缝(不过还是能看出接缝,因为UV的拉伸度有突变)。
当然,目前的效果不是最终想要的,因为期望是其只在接近地形的位置处才趋近于地形材质。因此,接下来就需要使用地形高度来决定混合程度了。
6. 在石头材质中采样地形高度RVT并进行材质混合
为了方便混合,我将采样的地形材质通过一个Make Material Attributes
封装为一个整体:
而石头材质原本的输出连到另一个Make Material Attributes
节点中:
接下来,就是通过Blend Material Attributes
节点依据高度将二者混合,并输出给最终结果了:(最终输出节点需要勾选“使用材质属性”,这样才能变为一个读取整个Material Attributes的引脚)
此处的 Alpha 就是混合系数,此时:
A
l
p
h
a
{
=
0
:
采
样
的
地
形
材
质
=
1
:
石
头
原
本
的
材
质
0
∼
1
:
由
地
形
到
石
头
过
渡
Alpha \begin{cases} =0 : & 采样的地形材质\\ =1: & 石头原本的材质\\ 0 \sim 1: &由地形到石头过渡 \end{cases}
Alpha⎩⎪⎨⎪⎧=0:=1:0∼1:采样的地形材质石头原本的材质由地形到石头过渡
而这个 Alpha 期望根据与地形高度的距离进行计算。
在先前地形材质中已经设定了将高度输出到RVT中,所以同理在石头材质中再创建一个Runtime Virtual Texture Sample
节点,但其RVT资源应设置为vt_height(“虚拟纹理内容”应该也会被自动设置为“场景高度”):
接着,便可以将当前的位置的高度减去地形高度,获得的与地形的距离记为 d。
对于 d 它期望有如下表现:
d
{
⩽
0
:
采
样
的
地
形
材
质
⩾
过
渡
范
围
:
石
头
原
本
的
材
质
0
∼
过
渡
范
围
:
由
地
形
到
石
头
过
渡
d \begin{cases} \leqslant 0 : & 采样的地形材质\\ \geqslant 过渡范围: & 石头原本的材质\\ 0 \sim 过渡范围: &由地形到石头过渡 \end{cases}
d⎩⎪⎨⎪⎧⩽0:⩾过渡范围:0∼过渡范围:采样的地形材质石头原本的材质由地形到石头过渡
为了将 d 变 Alpha,需要做两件事:
- 将 “0 ~ 过渡范围” 变换为 “0 ~ 1” ,为此需要将 d 除以“过渡范围”。
- 将 0 ~ 1 范围之外的值 clamp 到 0 ~ 1 。
我将这个“过渡范围”提取为参数range,然后连接下面的节点:
最终节点:(隐藏了左侧石头原本的材质节点)
效果
在range取值为“20”的情况下,效果如下:
其和不使用的效果还是有很大差别:
可以随意拖动感受效果:
至此,效果已经实现。
不过作为延伸,也可以稍微观察一下虚拟纹理技术:
*观察虚拟纹理技术
启用UE的RenderDoc插件后,使用它来截一帧:
随后,可以在左侧找到绘制石头所用的DrawCall,我这里是:
Scene -> BasePass -> BasePass -> TestRockMat SM_Rock。
在这次DrawCall的右侧的输出纹理中,可以看到石头将其本身的法线和颜色等信息输出到了GBuffer上:
接下来就是虚拟纹理技术有趣的地方了。
按照本篇操作的步骤,逻辑上是:先把地形的材质等信息渲染到了一张大纹理中,随后石头渲染时将会采样这张地形的大纹理并进行混合。然而实际上,这张大纹理从没有真正存在过,它是虚拟的。
如果观察这次DrawCall的输入纹理,你不会找到地形的大纹理,你只能找到:
可以看到它由一些图块组成。而这些图块如何得到,渲染时如何采样,便是虚拟纹理系统所考虑的问题了。