Adaptive Virtual Texture Rendering in Far Cry 4
差不多该动手实现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
大纲
- 概述虚拟纹理技术
- Far Cry4的地形
- Adaptive Virtual Textures - AVT(一般我们常说的RVT就是这个Adaptive Virtual Textures)
- VT渲染挑战
- 结果,性能,总结
Virtual Texturing
对于一个大世界项目来说,地形纹理将会非常巨大,并不适合一次性全部载入内存中,所以我们将其分tile,比如256x256,或128x128一个Tile(也可以叫Page,不过我觉得Tile更加贴切一些),我们用一个较小的真实的纹理内存来缓存这些tile。
我们还会使用一个Indirection Texture来记录整个游戏地形纹理Tile
到真实纹理Tile
的索引信息和UV信息
VT在游戏中一般有两种应用方式,Mega-Texture和程序化虚拟纹理
Mega-Texture
也叫SVT(Stream Virtual Texture)
游戏中整个地形的纹理都被烘焙成一个巨大的VT(即分Tile),运行时根据相机位置流式加载,指定Tile被加载到Tile Cache,然后Page Table更新信息(感觉这个顺序有问题?应该是先更新Page Table的信息,再加载相应Tile,否则很难保证状态的干净)
Procedural Virtual Textures
之所以叫程序化VT是因为他不进行离线烘焙VT,而是通过运行时渲染到RenderTexture上,即RVT(Runtime Virtual Texture)
不再需要从硬盘读取VT,对于需要的Tile,实时渲染
利用每帧画面的连续性来减少渲染开销,因为连续意味着可以复用已经渲染到Cached Texture上的结果,减少渲染开销,这一点在地形渲染上尤为有效
Far Cry 4的地形
远景使用预烘焙的几何和纹理,近景则复杂一些:
- 首先从height-map进行地形mesh渲染
- 基于一个Mask texture进行4层Material的混合
- 有道路和贴花,大量贴花对性能影响很大,可以通过将贴花直接烘焙到VT上来减少开销
- 目标分辨率:10纹素/厘米
- 基于RVT
Procedural Virtual textures in Far Cry4
下面是Far Cry 4中的早期RVT示例:
整个游戏世界的VT大小为512K x 512K,Indirection Texture为2K x 2K,最终的真实RT为9K x 9K,对于VT和Indirection Texture来说,做了11级Mip来优化性能,平衡表现
Indirection Texture的作用是记录VT真正的Tile索引和对应的UV信息,每个整数坐标代表一个VT的Tile,公式为
坐标 = Page 坐标(VT的Tile坐标) / Page 大小(单个Tile的大小),对结果向下取整,例如,假设Tile大小为512,那么对于世界空间5120处的某个Tile,公式就是5120 / 512 = 10
每个纹素内容为32 bit整数
- 8位PageOffsetX
- 8位PageOffsetY
- 8位Mip等级
- 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的分辨率),这是完全无法接受的
实际上我们也只需要对近处的场景要求高纹素密度,远处场景可以降低要求,AVT就应运而生了
Adaptive Virtual Textures
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
相机视锥中的所有Sector都被分配到VT上,可以看到这些Sector在VT上大小不一,因为近处的Sector分辨率高,远处的低
理所应当的,当玩家移动的时候,一些Sector大小会变动
同样的,一些Sector也会变小
那么当Sector发生变化时,我们需要立即重新渲染吗?答案是不需要,对于大部分Sector来说,已经渲染好了,可以利用Mipmap机制异步渲染,延迟等待
UpScale a virtual image
例如32k -> 64k,可以复用mip0到mip1,mip1到mip2。。。以此类推,这样我们只需要渲染一次mip0的VT Page即可,当渲染未完成时,将会使用mip1的VT
从图中可以看到一条关键信息,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,模糊归模糊,可以先凑活着用
需要注意的是,对于mip0-mip1来说,我们可以升采样,但是不可以降采样,因为降采样意味着Sector在Physcial Texture覆盖区域变少1/4,如果强行降采样则需要保留这些已经用不上的Page,浪费内存,推荐方式是直接移除mip0,然后渲染mip1
Downscale a virtual image
对于mip0-mip1来说需要做的事情就是移除旧的mip,直接重渲染新的,对于mip1-mip10,则依次复用
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即可达成目标
而且Far Cry 5的地形渲染也提到,可以有多张Physcial Texture:
AVT的挑战
减少Page Table内存占用
最简单的自然是直接降分辨率
限制每帧更新的VT
说到底Render to RT这个操作还是很耗的,所以需要限制更新频率,尤其是在快速移动时
以及分帧渲染Page:
Anisotropic Filtering
如果基于双线性过滤来混合VT,一些陡峭的表面将会看起来很模糊,所以我们想实现8x各向异性过滤
但相邻的Page在世界空间可能并不连续,所以我们需要给每个Page加上4像素的边界
如果只使用双线性过滤,当Mip改变时,将会有明显的接缝,所以我们使用三线性过滤
这是效果对比
结果(性能报告)
对于静态场景来说用了离线烘焙优化,动态场景则需要实时RVT,需要注意对最后的Physcial Texture做压缩,否则内存会非常大
引用
https://zhuanlan.zhihu.com/p/138484024
https://www.youtube.com/watch?v=SVPMhGteeuE&t=827s