差不多该动手实现ProjectS中地形的RVT了,所以整理一波学习资料,依旧是以FarCry为主体

注意本文中同样的词汇在不同上下文中有不同的意思,以RVT为例,在Procedural Virtual Textures部分,RVT代表Procedural Virtual Textures,但在Adaptive Virtual Textures部分,RVT则代表Adaptive Virtual Textures,基于历史发展,简单排列下术语

  • VT:Virtual Texture,包含SVT(Mega-Texture),RVT(Procedural Virtual Textures,经典RVT),AVT(Adaptive Virtual Textures,Far Cry4提出的改良版RVT)
  • Page:VT的其中一部分,其实叫Tile会更加合理一些,可以理解为VT是由一个个Page组成的,也是我们渲染到RT上的最小单位
  • Indirection Texture:VT和真实RT映射表,也叫Page Table
  • Physical Texture Cache:最终用于渲染的的RenderTexture

大纲

  1. 概述虚拟纹理技术
  2. Far Cry4的地形
  3. Adaptive Virtual Textures - AVT(一般我们常说的RVT就是这个Adaptive Virtual Textures)
  4. VT渲染挑战
  5. 结果,性能,总结

Virtual Texturing

对于一个大世界项目来说,地形纹理将会非常巨大,并不适合一次性全部载入内存中,所以我们将其分tile,比如256x256,或128x128一个Tile(也可以叫Page,不过我觉得Tile更加贴切一些),我们用一个较小的真实的纹理内存来缓存这些tile。

我们还会使用一个Indirection Texture来记录整个游戏地形纹理Tile真实纹理Tile的索引信息和UV信息

Virtual Texturing示意图

VT在游戏中一般有两种应用方式,Mega-Texture和程序化虚拟纹理

Mega-Texture

也叫SVT(Stream Virtual Texture)

游戏中整个地形的纹理都被烘焙成一个巨大的VT(即分Tile),运行时根据相机位置流式加载,指定Tile被加载到Tile Cache,然后Page Table更新信息(感觉这个顺序有问题?应该是先更新Page Table的信息,再加载相应Tile,否则很难保证状态的干净)

image-20250130153027336

Procedural Virtual Textures

之所以叫程序化VT是因为他不进行离线烘焙VT,而是通过运行时渲染到RenderTexture上,即RVT(Runtime Virtual Texture)

不再需要从硬盘读取VT,对于需要的Tile,实时渲染

利用每帧画面的连续性来减少渲染开销,因为连续意味着可以复用已经渲染到Cached Texture上的结果,减少渲染开销,这一点在地形渲染上尤为有效

PVT

Far Cry 4的地形

Far Cry 4地形概述

远景使用预烘焙的几何和纹理,近景则复杂一些:

  1. 首先从height-map进行地形mesh渲染
  2. 基于一个Mask texture进行4层Material的混合
  3. 有道路和贴花,大量贴花对性能影响很大,可以通过将贴花直接烘焙到VT上来减少开销
  4. 目标分辨率:10纹素/厘米
  5. 基于RVT

Procedural Virtual textures in Far Cry4

下面是Far Cry 4中的早期RVT示例:

RVT

整个游戏世界的VT大小为512K x 512K,Indirection Texture为2K x 2K,最终的真实RT为9K x 9K,对于VT和Indirection Texture来说,做了11级Mip来优化性能,平衡表现

Indriection Texture格式

Indirection Texture的作用是记录VT真正的Tile索引和对应的UV信息,每个整数坐标代表一个VT的Tile,公式为

坐标 = Page 坐标(VT的Tile坐标) / Page 大小(单个Tile的大小),对结果向下取整,例如,假设Tile大小为512,那么对于世界空间5120处的某个Tile,公式就是5120 / 512 = 10

每个纹素内容为32 bit整数

  1. 8位PageOffsetX
  2. 8位PageOffsetY
  3. 8位Mip等级
  4. 8位Debug

那么这种实现方式有什么问题?以目前的规格来看,10km x 10km的世界用了512k x 512k的Virtual Texture,纹素密度是0.5texel/cm,如果我们想要10texel/cm的纹素密度,就需要512k x 20 = 10 million x 10 million的VT,虽然只是个结构体,但亿级别数据也很大了,再加上他会顺带把Indirection Texture给撑大(在这里会达到40k x 40k的分辨率),这是完全无法接受的

原始RVT的问题

实际上我们也只需要对近处的场景要求高纹素密度,远处场景可以降低要求,AVT就应运而生了

Adaptive Virtual Textures

AVT概述

AVT基于Procedural Virtual Textures,10km x 10km的世界被切分为64m x 64m的一个个sectors

每个需要渲染的Sector会在VT上分配一块区域

对于近处的Sector,其渲染图片分辨率更大,最近的地方可以满分辨率渲染,例如64k x 64k(64k / 64m = 10 texels/cm)

对于远处的Sector,渲染的图片分辨率更小,视距离而定,例如32k,16k。。。1k

AVT示例

相机视锥中的所有Sector都被分配到VT上,可以看到这些Sector在VT上大小不一,因为近处的Sector分辨率高,远处的低

基于距离相机的远近分配不同大小的VT Page

理所应当的,当玩家移动的时候,一些Sector大小会变动

Upscale某个Sector,当移动时

同样的,一些Sector也会变小

DownScale Virtual Image

那么当Sector发生变化时,我们需要立即重新渲染吗?答案是不需要,对于大部分Sector来说,已经渲染好了,可以利用Mipmap机制异步渲染,延迟等待

UpScale a virtual image

以放大Sector为例,各mip依次复用,只重新渲染Mip0的结果

例如32k -> 64k,可以复用mip0到mip1,mip1到mip2。。。以此类推,这样我们只需要渲染一次mip0的VT Page即可,当渲染未完成时,将会使用mip1的VT

Mipmap复用详细示例

从图中可以看到一条关键信息,VT,Indirection Texture中的Sector大小都是不固定的,16,32,64等大小可以同时出现在同一张VT和Indirection Texture中,但Physical Texture中的Page大小依旧是固定的(例如都是256x256分辨率),即VT单个Page对应单个Indrection Texture Page对应多个Physical Texture Page,当Mip发生变化时,将会最大程度的复用已渲染完毕的结果,再单独延迟异步渲染所需要的结果到Physcial Texture,这也正是AVT的核心理念

Update mip0 Pages

对于需要重新渲染的mip0,由于其渲染需要时间,且我们并不希望其阻塞主线程,所以在其未就绪时,将会使用mip1的数据直接UpSample到mip0,模糊归模糊,可以先凑活着用

临时升采样mip1到mip0

映射关系,可以看到VT和Indirection Texture都在变,唯独Physcial Texture在复用

需要注意的是,对于mip0-mip1来说,我们可以升采样,但是不可以降采样,因为降采样意味着Sector在Physcial Texture覆盖区域变少1/4,如果强行降采样则需要保留这些已经用不上的Page,浪费内存,推荐方式是直接移除mip0,然后渲染mip1

UpScale示例和数据内容示例

Downscale a virtual image

缩小virtual image

对于mip0-mip1来说需要做的事情就是移除旧的mip,直接重渲染新的,对于mip1-mip10,则依次复用

Downscale示例

VT处理总结

相信大家看到这里可能会有这样一个疑问,64kx64k的纹理要怎么渲染到Physcial Texture上,毕竟一个Physcial Texture Page才256x256的分辨率

值的注意的是,下面这张图表示了,同一个Indirection Texture Page里有多个Entry Content,每个Entry Content记录的mip值并不一定是一样的,可以再根据镜头的远近来渲染不同mip的Physical Texture Page,64mx64m到地面以64k x 64k分辨率为例,8m为mip0,1分界线的话,只需要多个page组合成一个8kx8k + 多个低分辨率page即可达成目标

Mipmap复用详细示例

而且Far Cry 5的地形渲染也提到,可以有多张Physcial Texture:

多张Physcial Texture示例

AVT的挑战

减少Page Table内存占用

最简单的自然是直接降分辨率

image-20250131140043526

限制每帧更新的VT

image-20250131140134881

说到底Render to RT这个操作还是很耗的,所以需要限制更新频率,尤其是在快速移动时

以及分帧渲染Page:

image-20250131140254299

Anisotropic Filtering

如果基于双线性过滤来混合VT,一些陡峭的表面将会看起来很模糊,所以我们想实现8x各向异性过滤

image-20250131140612190

但相邻的Page在世界空间可能并不连续,所以我们需要给每个Page加上4像素的边界

image-20250131140703965

如果只使用双线性过滤,当Mip改变时,将会有明显的接缝,所以我们使用三线性过滤

image-20250131140813443

这是效果对比

image-20250131140837747

结果(性能报告)

image-20250131140917311

对于静态场景来说用了离线烘焙优化,动态场景则需要实时RVT,需要注意对最后的Physcial Texture做压缩,否则内存会非常大

image-20250131140939001

引用

https://zhuanlan.zhihu.com/p/138484024

https://www.youtube.com/watch?v=SVPMhGteeuE&t=827s

https://github.com/lifangjie/HDRPVirtualTexture

https://zhuanlan.zhihu.com/p/300731406