Recent Posts

人体速写笔记


色彩光影理论



Stay Connected

A smart wordpress theme for bloggers
UE地形的实时虚拟纹理RVT

UE地形的实时虚拟纹理RVT

引言

之前在用UE5做地编的时候,遇到一个最大的问题就是地形开销太大,不仅在运行时很卡,编辑地形的时候也会经常死机。后来了解了下虚拟纹理的技术,认为这个是地编流程里必不可少的一环,所以本节来记录一下虚拟纹理相关的一些事宜。

一、虚拟纹理技术概览

首先要了解到,虚拟纹理本质上是为了降低贴图开销和内存占用的技术。在虚拟纹理诞生之前,也有很多降低内存开销的解决方案,最为熟知的就是LOD技术,其原理是根据距离的远近选择加载的纹理尺寸;而后也诞生了Virtual Mipmap技术,其原理是只加载视野当中所涉及到的mipmap部分,而超出视野范围的就会被裁剪掉。

后来,id Software便正式提出了虚拟纹理的概念。之所以取名“虚拟”,灵感其实来自于虚拟内存,即不会将完整的信息全部加载到内存中,而是根据实际需求,碎片化地加载一部分必要的信息。具体的操作方式是将纹理的mipmap chain分割曾相同大小的区块,然后通过定义的映射算法,映射到一张内存中已经存在的物理纹理,那么当相机视野发生改变时,只需要替换掉部分物理纹理即可,这样就降低了带宽消耗和显存开销。另外,由于虚拟纹理已经不再是以一整张纹理作为单位了,因此合批加载纹理也更加自由。

如上图所示,其纹理的储存方式 和计算机组成原理中学习的虚拟内存几乎是一个路子,都是将一段完整的信息切碎存储到物理空间中,然后给予虚拟地址与物理地址一一映射,这样就能通过虚拟地址还原出完整的物理信息。

既然现在已经将纹理切碎存储了,那么下一个问题就是:如何确定我们需要加载哪些碎片(即page)?这里就需要单独渲染一个pass,叫做Feedback Rendering。在延迟渲染中,我们会将相机能看到的所有片元的颜色、法线、uv等信息全部存储到G-buffer中,而Feedback Rendering就是预先将每个片元对应纹理的page信息写入G-buffer,然后在下一个pass正是渲染的时候,根据G-buffer的page信息来决定要加载哪些page。这样实际的纹理加载量一定小于一整张完整纹理的加载量,从而起到了节省内存的效果:

如图所示,第一个pass就是用来获取每个纹素的page id,然后将它写入右图的texture(或G-buffer)中,下一个pass使用这张texture来决定要加载的page。

二、虚拟纹理技术流程

流程如下:

整体步骤可以分为:feedback预渲染-分析page id并查找内存-申请物理空间-与GPU解绑-重编码压缩-与GPU绑定。

首先feedback这个步骤在上一节已经提到了,目标是创建一个 记录各纹素所对应page id的纹理;Analysis阶段就是分析有哪些page id;Fetch就是去各级内存和cache查找page,如果查找失败就需要从硬盘中加载;Allocate就是申请若干段物理内存来装载这些page。装载到内存后还没有结束,因为这些page是新加载的page,自然要和GPU做绑定,但是不排除这些page之前被使用过,可能会在GPU中拥有残余的绑定信息,因此需要先Unmap解绑,然后作为一个全新的page进行Transcode编码压缩,最后重新Map映射到GPU当中,此时这个page才正式被GPU所识别和应用。

当然,目前的虚拟纹理有一个很严重的问题没有解决,那就是我们的纹理是静态地预先储存在了硬盘中,这意味着应用虚拟纹理技术时需要反复从硬盘中加载纹理。因此又拓展出了一个新的概念,叫做运行时虚拟纹理(runtime virtual texture)。顾名思义,它可以实时地捕捉并生成纹理page,从而节省了从硬盘中加载物理纹理的开销。

三、运行时虚拟纹理技术流程

这里主要分析下UE实现runtime virtual texture的方法。

对于生成阶段,就是捕捉纹理信息、确定page id后生成映射table、加载内存这几步。首先还是通过feedback rendering来捕捉page id信息,但是需要特别强调的是,游戏可不仅仅会使用Diffuse贴图,还会有Roughness、Metallic、Normal等贴图,因此需要将其进行Pack到不同的layer当中。也就是说,对于不同的layer(贴图类型),我们都需要捕捉一张对应的page id。

现在GPU获得了page id贴图,但是GPU没有办法处理它,因此需要CPU将贴图信息回读,并用FeedbackAnalysisTask处理数据。UE引擎还做了一个统计表,用来得到page索引的同时 统计该page的出现次数,到最后会排除掉重复的page,生成唯一的page表单。

按照Virtual Texture技术流程,接下来需要从硬盘中加载物理内存了,但是因为这里讨论的是运行时虚拟纹理,因此这一步不再需要了,取而代之的是使用一个叫“DrawMeshes”的组件 去搜集所有在当前 RVT Volume 范围内的 Mesh 进行绘制。怎么解读这句话呢,大概就是开发者会在场景中放置一个专供RVT的Volume,然后动态地从这个Volume中的物体中获取对应id的纹理碎片。

这里可能听起来很懵逼,我举个例子:假如现在有一张地形,是由4个地形材质layer混合起来的。现在我们镜头里看到了地形的一部分,准备动用Virtual Texture技术去展示这部分地形的纹理。按照常规的VT技术,需要在硬盘上找到这4个地形材质的各个贴图(例如Diffuse Roughness等)并全部切碎加载到物理内存中,这样就需要疯狂与硬盘进行传输。

而Runtime VT技术则不然,它不需要去找硬盘上4个地形的各个贴图,它只需要在场景中放置一个特别大的Volume覆盖地形,此时它便可以实时地获取到 某个区域上的地形是个什么颜色,是个什么粗糙度,巴拉巴拉,这样也就免去了硬盘传输的步骤。另一个好处是,通过Volumn我们可以直接得到4个地形贴图混合出来的结果,也就是说我们不需要再重新去做地形混合了,而是直接捕捉结果,一下子就节省了4倍的开销。

(当然细节方面还有很多可以讨论的内容,例如虚拟纹理的分块采样、像素存储布局等等,但目前还没学会,只能先留白)

四、UE使用RVT做地形的流程

前面的知识都过于理论化了,接下来可以实操一下RVT,感受一下它的流程。

4-1 地形材质分层

我个人感觉 在使用多层地形材质的时候,RVT的效果是最明显的,因此我打算在做地形的时候应用RVT。

首先就是做一个最基础的分层材质:

首先创建N个MakeMaterialAttribute节点(相当于N套pbr节点),然后通过LandScape LayerBlend将它们混合起来。如此输出后,地形材质便拥有了分层材质。

接下来在地形绘制界面 为每个材质层分配一个LayerInfo,这个LayerInfo其实就是SplatMap,用来记录各材质层的地形权重值。

对地形进行材质绘制。可以发现一个问题:地形没有置换突出,石头都是扁的。按照常规流程,我们需要直接在建模模块中给地形一个高度图,但是现在我们不能这样做,因为本节的目标是使用虚拟纹理来设置地形,因此后面的步骤有所差别。

4-2 虚拟纹理配置

首先我们需要补充一下地形材质:

这部分节点的目的是设置 会被虚拟纹理所捕捉的参数,其中包括基础色、粗糙度、法线,以及高度偏移(Displacement)。对于前三个参数,直接连接到RVT节点输出就好了,对于置换参数则不行,因为置换参数说的是“偏移了多少”,而输出节点中的“WorldHeight”说的是地形的绝对高度是多少。计算公式是WorldHeight = Displacement * k + (0, 0, terrain.z)。

接下来我们需要配置一下虚拟纹理,首先在插件列表中勾选Virtual Heightfield Mesh:

然后在项目设置中勾选Virtual Texture:

然后创建两张RVT纹理,一张作为高度RVT纹理,一张作为材质参数RVT纹理(包括基础色、粗糙度、法线和AO)。

前一章讲到了RVT与VT最大的区别是:RVT会实时获取Volumn内的物体对象的材质信息,因此我们必须要为上述两张RVT纹理分别创建Volumn。

然后我们分别设置RVT对象以及Volumn的边界,边界需要正好覆盖整个地形:

此时我们的两张虚拟纹理对象就创建好并配置好Volumn了。但是没有有结束,因此我们还需要向地形声明,哪些虚拟纹理(及对应的Volumn)会从自己身上获取参数。打开地形的细节设置,将两张RVT拽上去:

总结一下,最初的地形材质声明了哪些参数可以被虚拟纹理捕捉到;然后这里我们设置了两个RVT的Volumn去捕捉整个地形,这样RVT就能实时地获取各个地形区块的材质参数了。

4-3 虚拟高度场

前面我们得到了两张虚拟纹理,然而事实上虚拟纹理没有办法直接应用到原本的常规地形上,所以我们需要创建一个虚拟高度场网格来代替原本的常规地形:

既然叫做虚拟高度场网格,说明这个虚拟网格是专门用来处理高度置换的,因此这里需要先将高度RVT赋予给它,并拷贝它的边界范围。此时这个高度场虽然有了高度RVT,但还没能提取出高度场信息(可以理解为VRT里的信息都是未解压的),所以这里需要额外手动构建一下MinMax高度场纹理(解压成真正的置换高度纹理):

到了这一步,我们的虚拟高度场网格就已经可以呈现高度置换了,将Actor Hidden In Editor的勾关掉,就可以看到置换结果了。

同理,现在这个虚拟高度场网格已经获取了材质RVT(即基础色、粗糙度那些),但是也处于未解压状态,所以我们还需要额外创建一个地形材质,将RVT上的材质信息输出出来:

此时将这个新的材质赋予为虚拟高度场网格的材质,便实现了一个完整的虚拟地形。此时我们可以看到地形上也有了高度:

此时的地形系统便得到了优化,即只有相机内看到的片元所对应的纹理区块会被加载入内存,且不需要去硬盘上访问各层材质的纹理原图,而是靠Volumn直接动态地从地形上获取,简单又高效。

总结一下地形使用RVT的流程:

创建分层材质—声明要输出到虚拟纹理的参数—创建高度RVT和材质RVT—为两个RVT分别设置一个Volumn—设置地形可以被上述两个RVT所侦测—创建VHM—将高度RVT赋予给VHM,构建MinMax高度场纹理—将材质RVT赋予给新材质,将该材质作为VHM的真正材质—在材质实例中调节高度等参数。

Leave a reply

Your email address will not be published. Required fields are marked *

Stay Connected

Instagram Feed

Recent Posts

人体速写笔记


色彩光影理论



×