Contents

UE4 VirtualTexture 源码解析

一.基础、原理和使用

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521170829.png

Unreal最早从2017年就开始进行VirtualTexture功能的研发工作。4.17版本初次的提交,4.23 版本正式放出SVT和RVT,4.26 版本推出Adaptive VT。

按照使用上的不同,在引擎中一共有四种不同的VT类型

  1. UVirtualTexture2D直接是继承于UTexture2D的,是最普遍上的的VT类型。
  2. ULightMapVirtualTexture2D跟 UVirtualTexture2D没有什么太大的区别,主要是使用在LightMap中使用。
  3. URuntimeVirtualTexture,RVT,后面会讲到其最终的用法。
  4. 如果RVT勾选了Adaptive,那么RVT则会变成AdaptiveRVT。

大致上我们按照生成的时机不同,主要分为两种VT的格式。UVirtualTexture2D和ULightMapVirtualTexture2D叫做SVT(Streaming Virtual texture)。而URuntimeVirtualTexture和他的衍生类别AdaptiveRVT称之为RVT((Runtime Virtual texture))。

1.1Streaming Virtual Texture

VT功能并不是UE4引擎默认开启的,如果我们想要使用Virtual texture ,需要在项目设置中设置Enable Virtual Texture Support。而勾选Enable Virtual texture lightmaps,则会将我们的Lightmap变为VT形式。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521170847.png

1.1.1 普通贴图转换为Virtual Texture

当我们重启编辑器后,我们可以把之前普通的贴图右键点击转换为VirtualTexture。使用这种方式编辑器将检索与之相关的材质使用。会将会改变使用到这张贴图的材质的采样方式。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521170854.png

多选批量转换当然也是支持的。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521170900.png

我们也可以在贴图的设置中去设置这张贴图是否是VirtualTexture。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521170907.png

不过,这样我们就必须手动的去调整材质里对于VT的采样方案。不然材质会报编译错误

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521170913.png

1.1.2 导入为VirtualTexture

之前的操作是把项目中已知的贴图进行转换,当我们想要新增贴图时,由于贴图的大小限制,我们可以把一张超大的贴图切割开,然后同时导入多张texture ,如果他的命名规则如下图所示那么他就会按照UDIM多象限UV进行排列并且识别为VirtualTexture。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521170919.png

当然,如果我们只是导入一张贴图的话,会根据项目中的配置来进行判断,分辨率如果大于这个设置,将会转换为VirtualTexture。如果比这个小的话,将视为普通的贴图处理。 https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521170930.png

1.2 Virtual Texture的目的

在这里我们希望去讨论VT的实现目的。假设我们的游戏画面是静止不动的,并且如果我们能够实时创建这么一张贴图来显示我们的场景,我们假设屏幕分辨率是1920x1080 ,用RGB8来进行存储。

920x1080x3 =6075KB≈5.93MB,(显存利用率)

也就是说,我们显卡的显存只需要用 6MB 就完全够了。这种极端的情况用现有的硬件实现起来恐怕不太现实。这种情况很难在一个动态的场景中实时的去做到,但是即使是有一些cache的冗余数据,就算到了100倍的冗余,其空间使用也小于我们目前的Texture Pool的大小设置。

因此,Virtual Texture的基本思路就是:把能看到的贴图才加载进来,没看到的不加载;进的精度高,远处的精度低。如下图所示,如果我们只把看到的部分(高亮的部分加载进来)那么内存占用上将大大减少。 https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521170940.png

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521170946.png

从中我们可以看出Virtual Texture实则是解决我们空间存储的问题。世间所有的炼金术都遵循等价交换,你既然节省了空间,那么你的时间消耗则会增加,这是一个以时间换空间的技术:在每个Tile动态加载卸载的过程中和VT采样时都将产生额外的性能开销。因此我们是否需要开启,将哪些贴图进行开启,都是一个值得权衡的问题。而不是无脑的去使用。

既然解决的是贴图的内存管理问题,其跟传统的基于Mipmap的贴图和可见性的Texture Streaming管理是不同的。传统的StreamMgr只会根据可见性和对应的Mipmap来进行流送的处理,也就是说我们并不能对单张贴图的某一部分进行更为精确的控制,也就是有了过多的冗余信息。例如我只看到了一个贴图的一个角落,却要把他整个都加载进来。

而VirtualTexture 这种将整个Texture分为Tile 的方案,则考虑了这一冗余数据的处理。VT的劣势也很明显,在镜头剧烈变化的时候,其会不停的进行加载卸载的操作,如果配置不当会导致明显的帧率问题。

1.3 Runtime Virtual Texture

首先我们将解释一下RVT(Runtime Virtual Texure) 和普通的SVT(streaming Virtual Texture)的区别。RVT是一张动态在场景中获取的VT,因此所有的数据并不需要存储在硬盘中,而是在我们需要某一块时(相机看到的时候)直接渲染出来。

RVT的经典场景是解决地形问题,如果我们想要渲染一块地形,我们指导地形是非常复杂的,其本身会有很多的layer通过权重贴图来进行混合,因此其会需要采样相当多次的贴图,并且这些贴图的大小都是非常客观的。假设我们不超过四个图层的情况下,采样9次(四个图层四次,对应四张法线贴图四次,再采样splat map一次),超过四个图层,那就采样9的倍数次。这样的开销会对美术效果有非常大的限制。

更重要的是这样的采样每帧都要进行的,但很多情况下,静态的地形数据是完全无变化的,我们完全可以把这些采样后得到的结果给记录下来,也就是把相当耗时的计算结果直接存储下来,使用的时候采样我们最后的结果就可以了。

从RVT的使用场景中可以看出来,其本身并不是解决空间存储问题的。而是解决材质复杂度的性能问题。

1.3.1 Runtime Virtual Texture的使用

RVT是一个可被创建的特殊资产,我们可以在编辑器中对一个RVT资源进行创建。 https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171003.png

下图是RVT内部的一些设置,其中很重要的概念就是Layer。RVT根据选择layer 的不同,可以记录的贴图数量是不一致的。也就是说RVT在实际中并不是一张贴图,而是很多贴图的集合。这里记录的东西跟Gbuffer里面的数据非常类似。我们根据自己的需要,可以选择我们这个RVT中实际将要含有哪几张贴图。

跟SceneCapture类似,但是它的更新方案采样方案是跟SceneCapture是不一样的。 https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171011.png

之后我们需要去指定我们RVT绘制的地点,引擎提供一个Runtime VirtualTexture Volume,我们可以拖拽到场景中,然后进行这个Volume和RVT资源的绑定。

我们很多时候其实并不需要这个Volume里面所有的物体都渲染到这个Rvt里面,因此我们对于Volume里面的物体需要选择是否要渲染到VT,还有渲染到哪张。 https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171017.png https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171022.png

我们在材质中将选择哪些东西渲染到RVT的Layer上。在同一材质上或者其他材质上选择这个RVT进行采样。 https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171029.png

1.3.2 Runtime Virtual Texture的使用案例

这里的展示选用李文磊演讲使用的RVT的案例演示。这里演示了写入RVT和采样RVT分离的一个例子。下图这个明显的包围盒就是我们的RuntimeVirtualTextureVolumehttps://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171036.png

场景中的这棵树是由两部分组成,一个是主干部分,一个是底下的面片。 https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171042.png

底下的面片的设置为只渲染到RVT,而不在主渲染中显示。 https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171049.png

其把自己的BaseColor和Normal写入了RVT。 https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171100.png

之后在地形上,进行采样。从而在地形上显示面片的内容。 https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171107.png

1.3.3 Runtime Virtual Texture优点

RVT的优势1-复杂材质缓存

可以把多层地形材质实时混合好,然后实际用的时候直接sample一层“virtual texture”即可,在地形材质层数比较多时就会有渲染性能上的优势。从上下两个地形的材质复杂度的颜色对比中就可以看到我们使用RVT的成果。右面上下两个图是最终显示上的对比,表现效果上是非常近似的。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171152.png

RVT的优势2- RVT与Decal结合

Decal actor目前无法cache到RVT,这里的Decal指的是Mesh Decal。也就是上面那个使用案例那个样子的做法。对于大地形来说,我们会有很多的decal 来增加我们的地形细节,这对性能是一个极大的考验,因此我们可以渲染到渲染到RVT上面,来解决这个问题。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171203.png

RVT的优势3- 物体与地形的blend

我们可以接触到的物体和地形之间的blend机制有很多。例如

  1. 使用世界坐标映射贴图,调整自身的高度参数。(最蠢的)
  2. 在模型边缘的地方使用一些半透明的材质
  3. Pixel Depth Offset+DitherTemporalAA 4.使用定点色,或者贴图进行材质过度
  4. 使用SDF找出过度边采样过度。

这里绝大多数的方案的首要问题是,地形材质的复杂性,和每个物体都需要地形的材质数据,这对整个资产的制作流程来说是非常耗时的。而使用RVT,我们将很好的进行这个问题的解决。下图是使用RVT解决Blend的 问题。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171211.png

1.4 Virtual Texture 的原理

Virtual Texture 的原理其实跟我们操作系统中虚拟内存的方案非常的相似。因此我们这里首先回顾一下我们的虚拟内存方案。虚拟内存(virtual memory)将用户逻辑内存和物理内存分开。这在现有物理内存有限的情况下,为程序员提供了巨大的虚拟内存。

虚拟内存能做到这一步,主要的是引入了一个虚表的概念。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171219.png

而virtual memory则能解决这个问题,其最主要的机制就是他的中断策略,也就是如果这段物理内存没有cache,我们从硬盘里加载进来来解决的。

而我们的虚拟纹理是类似的一套哲学理念。其主要步骤如下:

1.判断需要哪些Tile,判断屏幕上需要绘制哪些贴图,以及屏幕像素上那个贴图用到的 mipmap 级别。这是通过我们一种叫做Feedback数据做到的。 2.装载,靠 streaming 把需要的贴图Tile 装载进来。放入显存的一张实际要使用的贴图(physical texture。这张显存贴图,是由很多Tile拼凑而成的) 3.绘制场景物体。绘制过程中,在 pixel shader 中,把屏幕上所有景物的贴图坐标重新换算一下。以便用显存贴图来正确绘制场景物体。

下图将显示我们的采样流程

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171231.png

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171236.png

首先我们对当前屏幕中的VT贴图的使用情况进行分析,判断属于哪些贴图,哪个Tile和对应的mipmap,如果Cache未命中,我们就加载进来。之后进行采样的时候根据我们的页表(PageTable\Indirection table)来找到我们的实际的physical texture 上的位置,进行最终的采样。

因此我们可以看出,和一般的贴图使用上,其多了一个渲染资源PageTable(UE中的叫法)。shader中通过这里的PageTable找到对应的physical texture的位置。这跟virtual memory的做法是一致的。

二.VirtualTexture 的实现细节

所有的代码架构都在我们的Renderer VT文件中。我们将简单的梳理整个VT系统的整体架构。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171242.png

  1. FVirtualTextureSystem是整个Virtual Texture 的管理类,几乎所有的VT系统的操作都会通过他来进行调度。
  2. FVirtualTextureSpace用来管理Page table的,其数量上有明显的上限,只有16个。一个Page table资源对应一个FVirtualTextureSpace对象。这里限制的原因应该是标志位的限制,我们在FeedBack中仅有4位留给我们判断属于哪个Page table。
  3. FVirtualTexturePhysicalSpace用来管理Physical Texture部分。Physical Texture的数量是没有限制的。引擎会把所有拥有相同FVTPhysicalSpaceDescription且没有勾选bSinglePhysicalSpace(主要用于RVT)的将放入一个PhysicalSpace。这个过程是引擎自动判断的,需要注意的是,share PhysicalSpace和share Pagetable是矛盾的,因为share PhysicalSpace就意味着Physical Address不能是一样的,所以Page Table无法share(但可以分布在一个texture的不同channel里)。
  4. FAdaptiveVirtualTexture 用来管理Indirect 部分。在后面我们讲到。

整个Virtual Texture 的流程几乎都在各自renderer中体现出来。在Render中主要的步骤其实有三步

  1. 在Render开始阶段对FVirtualTextureSpace管理的固定数量的Page table进行判断,通过bNeedToAllocatePageTable看是否需要渲染资源的创建。也就是创建新的Page table资源,没什么好说的。
  2. 更新整个VT系统,包括解析FeedBack,更新pagetable和physical texture。
  3. 在Basspass之后,我们读取在Basspass中写入的feedback,供下一帧使用

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171314.png

2.1 FeedBack

FeedBack 是我们分析当前屏幕中,使用VT 的具体情况。UE4 中并没有单独的 VT feedback Pass,而是在材质 Shader 的最后调用 FinalizeVirtualTextureFeedback 将当前帧的 Request Page 信息 写入 feedback UAV Buffer。

从RenderDoc中我们可以看到是在Basepass中进行的写入操作,其默认空值是FFFFFFFF。如果这个物体的材质没有采样VT,那么它就不会写FeedBack,也就是默认值0xFFFFFFFF。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171320.png

出于性能考虑,并不是写入与屏幕分辨率相同大小的 Buffer,毕竟完整的像素量是非常庞大的。而是根据项目中的反馈分辨率因子设置来写入,对应 Shader 中的 VIRTUAL_TEXTURE_FEEDBACK_FACTOR,这个值越大性能越好,但粒度越粗,很可能会漏掉 VT 数据而导致渲染不正确。如果是透明材质我们会考虑到其clip 的大小来判断是否需要写入。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171326.png

2.1.1 生成FeedBack

在Basepass中的PS中写入的FeedBackBuffer的数值是如何确定的呢。Feedback的数据格式如下:

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171334.png

可以看到,FeedBack数据为32位。其中

  1. TableID:FeedBack所在的像素块的纹理对应的PageTable的ID。因为我们在材质中采样这个贴图的时候,我们是知道我们这个pageTable的ID的。(16个PageTable)
  2. vPageX:所在PageTable中的X坐标,根据UV,Tile数量和PageTable 偏移求出来。
  3. vPageY:所在PageTable中的Y坐标,根据UV,Tile数量和PageTable偏移 求出来。
  4. vLevel:这里并不是简单的Mipmap,因此UE换了个名称叫做vLevel。做法是当前的Mipmap+一个随机值,这样的话在静止画面下,这个值实际上是一个区间,每帧我们的vLevel就是不同的,这样,我们就能根据TAA来做一些混合操作。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171341.png

2.1.2 回读成FeedBack

在Basspass之后我们将回读这个Buffer,提供给下一帧的update进行分析和使用。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171350.png

2.2Update

在FVirtualTextureSystem中,最为关键和重要的步骤我们大致分为5个流程来进行。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171357.png

解析FeedBack

第一步,我们讲上一帧解析的FeedBack数据根据他的格式进行解析。将其转化位一个名为FUniquePageList的数据结构中。FUniquePageList 内部是一个 Hash Table,通过 hash Page Request 得到 Page 的索引并进行累加次数,进而统计每个Page出现的次数。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171404.png

这里的解析是实际上根据解析数量的不同可以进行多线程的操作,比如规定每一万个数据划分一个线程进行处理。这里我们按单线程的方案进行讲解。

生成UniqueRequest

我们得到的FUniquePageList并不是一个具备操作性的数据集,因为它只记录了出现的Page和Tile出现的次数,我们还需要知道这里面数据的是否需要更新。我们这里将FUniquePageList数据转化为能够为我们提供更多信息的UniqueRequest数据。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171410.png

从上图我们可以看出,整个UniqueRequest内部有多种的请求类型,例如:

  1. LoadRequest:这一帧需要画它,需要把这个Tile加载进来。这是用来更新Physical Texture的。
  2. MappingRequest:和LoadRequest对应,不过它记录的是用来更新PageTable 的数据,需要在LoadRequest完成后执行。
  3. DirectMappingRequest:physical texture存在,直接将 physical address写入PageTable。
  4. ContinuousUpdateRequest:在RVT中可以设置,这个Tile是否需要持续更新,每帧都会去更新这个tile。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171416.png

加载和sort策略

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210520115144.png

当我们获取了所有的更新数据之后,我们将进行加载排序,这是因为我们需要对加载的优先级进行判断,而不是一股脑的都在同一帧处理大量的数据。如果把所有需求都在同一帧进行加载,这将会导致相当大的卡顿。简单的介绍我们的排序策略。

  1. 当Physical Texture没有空间时,我们采用的卸载策略为LRU,优先替换最久未访问并且Mip分辨率最低的Block。我们每帧都会把当前的帧数,根据feedback的返回数据对使用到的Physical Texture进行写入。也就是说每个 Physical Texture都保留最近一次使用到的帧数,从而能够进行判断谁是最久没有被使用到的。
  2. 我们每帧最大的加载量为64,我们将计算所有的LoadRequest的优先级,如果一帧里面的请求过多,只接受优先级前64个。
  3. 如果想要加载低mipmap,需要加载这个贴图高几个mipmap的贴图。也就是说,我们优先加载比需要的这个贴图mipmap等级高的贴图,这是为了降低渲染压力。物理贴图块会分帧渲染,每帧只渲染一部分Block。当最低级的Mip都没有时,实际上分帧之后加载速度仍然是非常快的,对分帧加载这个过程也是基本无感知的。
  4. 如果出现的次数特别多,那么我们会把他设置为我们最高优先级,如果是普通的次数,我们的Priority使用的是Count和Mipmap结合的策略,Mipmap越高,优先级越高;出现的次数越多,优先级越高。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171442.png

SubmitRequests和Finalize

当我们把需要在这一帧操作的request筛选出来后,将提交我们的Requests。其会更新我们的PageTable和Physical Texture。

UE4 并未使用Compute pipeline 来更新 PageTable Texture,而是通过 Graphics Pipeline 在 VS 中生成,在 PS 中存储到 PageTable RenderTarget,并且使用 Instancing Rendering 的方式,最多一次处理 16 个 PageTable Quad,这样处理与 CS 基本相同。

而我们的Physical Texture则根据自己不同的IVirtualTextureFinalizer类型进行不同的Finalize操作。传统的svt对应的FVirtualTextureUploadCache将硬盘里的数据直接进行拷贝到对应的Physical Texture区域。而Runtime的FRuntimeVirtualTextureFinalizer将会启用RDG渲染那块区域最后进行Copy到对应的Physical Texture区域。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171449.png

VT压缩

在copy之前,我们还需要一个压缩的操作。Compression Pass,其将生成的纹理数据使用CS生成GPU支持的压缩格式,对应AddCompressPass函数。通过 CS 将之前B8G8R8A8 格式的纹理数据生成 GPU 压缩格式,这一步都在 VirtualTextureCompress.usf Shader 中处理,不同的 VT 配置使用不同的 CS 函数执行块压缩,包括 BC3, BC5, BC1,目前不支持移动平台的 ETC 压缩。压缩后的 RT 进行合并,这一步也是在 VirtualTextureCompress.usf Shader 中完成。

  1. Zlib压缩:允许使用Zlib压缩虚拟纹理。此压缩可减少VT纹理的硬盘输入/出开销(读取或写入次数),但将增加CPU解压缩开销。更改此设置需重启编辑器。
  2. Crunch压缩:其压缩的数据比Zlib压缩的数据更小,同时还减少了CPU的解压开销。但压缩均为有损,因此图像质量会有所下降。

三.PageTable和PhysicalTexture

我们再来看一下这两个的细节。

3.1 PageTable

首先值得说明的是PageTable拥有mipmap,我们将这个renderdoc中进行截取如下。看一看其具体的表现形式。 https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171509.png

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171514.png

3.1.1 UE4 中PageTable的数量

如之前内容所说,PageTable最多存在16个。如果没有特殊情况,我们将配置类型一致的VT放进同一个PageTable中。他们之间使用类似图集的操作记录下偏移来实现使用同一个PageTable。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171520.png

其创建的数量是根据需求来进行的,如果某个PageTable超过了我们预设的范围,例如超过了4K,我们就会生成另一个PageTable。

3.1.2 PageTable的大小

PageTable的贴图大小是不能够太大的,不仅仅是采样高分辨率PageTable的性能问题, 也是因为硬件的限制,目前UE里面PageTable 的大小是最大支持 4k(2^12) 纹理。这可能是考虑到移动平台的原因。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171528.png

我们如果场景中只有一张贴图是VT的,我们也不会傻乎乎的直接使用4K的pagetable,其大小是2的指数幂增长的。如果超过了数据我们会生成另一个PageTable。

3.1.3 PageTable的格式

PageTable的格式是根据Physical里面的Tile数量决定的,如果Physical Tile 的数量小于64,我们就使用16位就足以,如果高多了64个tile,我们将使用32位进行存储。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171539.png

3.1.4地址编码

PageTable存储的数据是使用四进制Morton码计算,这样编码的好处是能够快速的去获得对应的,Mip的子Tiles可以快速计算

1
000001 << (2*Mip) = 000100。

此外,Morton Code和每一级,都是一个Z字形,正好是四叉树这一级的四个节点,也因此很方便的进行各种四叉树计算。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171551.png

3.2PhysicalTexture

接下来我们来看一下PhysicalTexture。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171558.png

  1. PhysicalTexture的数量是没有硬性限制的。不过PhysicalSpaceID的只有12位,因此其最大也有最大的限制,不过一般是到达不了的(4096个)。
  2. PhysicalTexture每个Tile并不是2的次幂,这是因为其会包含几个像素的周围像素,这是为了采样的时候好进行插值。
  3. 拥有相同FVTPhysicalSpaceDescription且没有勾选bSinglePhysicalSpace(主要用于RVT)的将放入一个PhysicalSpace。也就是说不同的texture使用同一块物理贴图。区分最多的是Format,其不同格式的贴图当然不能放在一起,Layer不同的space也不能在一起。因为PhysicalSpace的Tile是均匀划分的,因此我们不同TileSize 的贴图也不能放在一起。
  4. 一个PhysicalSpace是不能grow的,我们在分配时时多少他就是多少。因此,其整个贴图的大小其实只跟他的格式相关。
  5. 而PhysicalSpace对应的Tile 的多少是跟我们的TileSize相关。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171605.png

四.采样

VirtualTexture 的采样数据跟普通的贴图采样不同。我们来看一下他的采样函数。其主要分为两部分,第一部分是获得对应的PageTable,第二步是根据PageTable 的值采样我们的physical Texture。这两个函数操作都是在我们的BasePass中,其中在TextureLoadVirtualPageTable中不仅采样整个点的PageTable,而且还进行了FeedBack 的写入。我们以RVT位例子。

4.1TextureLoadVirtualPageTable

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171613.png

我们来看第一个函数的参数,这个是和材质中这个贴图的一一对应的PageTable。存储在Material uniform 里面。因此我们可以直接进行他的采样。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171620.png

第二个参数是关于这个VT 的PageTable固定的一些信息我们将存储在material里面的两个unit4。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171625.png

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171631.png

接下来我们进入TextureLoadVirtualPageTable中。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171637.png

通过TextureComputeVirtualMipLevel 函数计算 RVT 的 mipLevel,为了实现较好的混合效果,这里根据当前帧 Id 生成交错的随机 noise 扰动 level。4bit 得到 mipLevel,最多 16 级(0~15)

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171647.png

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171656.png

五.Adptive VT

5.1 RVT的问题

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171702.png

  1. Virtual Texture(512K× 512K)如果10KM*10KM的地图,我们的 0.5texels/cm.PageTable大小有上限,纹素比过小:
  2. 更新消耗和带宽消耗都比较大
  3. 精度问题,浮点数精度

VirtualTextureSize=PhysicalTextureTileSize*PageTableTilesNum

PhysicalTextureTileSize max=1024

5.2FarCry4 Adaptive RVT

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171712.png

在FarCry4里,使用了一种叫做Adaptive Virtual Texture的方式来实现Page Texture,这个思想很像段页表,每次需要的时候就先分配一块Virtual Image,每个Virtual Image实际上就是上面一开始提到的UV Mapping的VT,然后再在Virtual Image中做进一步的VT分配。Farcry 4中的地形会切分成64m的地块,对于较近的地块,需要6464的Virutal Image,稍远的地块可能就只需要3232甚至更小的Virtual Image。使用这种方式,对于一块2K的地形,也只要128128或者256256的PageTable就可以了,而且它的大小也不会随着地形的增大而增大。

5.3 使用Adaptive VT

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171724.png

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171738.png

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171729.png

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171745.png

5.4原理

  1. Indirection Table没有Mipmap.
  2. 通过采样PageTableIndirection确定PageTable 的位置

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210521171750.png