虚拟贴图理论之寻址

虚拟贴图第一个需要解决的问题就是寻址问题。我们需要将虚拟贴图的uv坐标转换到对应的物理缓存中。我们使用一张Indirect texture贴图存储了转换相关的数据。如下图:

每一个虚拟Page都会对应indirect texture中的一个像素,通过像素中存储的参数,我们可以将虚拟地址转换到物理地址。接下来我们就探讨一下indirect texture中存储的参数到底是什么?首先我们必须先了解一个概念,虚拟Page代表的是一块uv坐标,这块uv坐标中的所有坐标变换使用相同变换参数,如下图:

A点和B点分别代表虚拟Page和物理Page的左下角坐标,我们根据A点和B点求解出坐标变换参数,那么C点就可以根据这个参数正确的变换到D点了。这有点像骨骼计算,我们先算出骨骼的变换矩阵,然后蒙皮的顶点就可以根据这个矩阵进行变换了。上图中假设了虚拟Page和物理Page的size是相同的那么scale就是5/2。用A点乘以scale就得出了A'点,A'点就是A点在物理缓存中映射的位置。B点是A点实际在物理缓存中的位置,因为A'点被偏移了,所以我们用B点减去A'点就能得出偏移量offset。至此我们得到了坐标变换的两个参数一个是scale一个是offset。我们可以将c点乘以scale再加上offset就可以获得D点了。(图中写错了,大家见谅)。我们把参数变成实际中使用的变量:

scale = (VirtPageCount * VirtPageSize) / (PhyPageCout * PhyPageSize);

VirtPageCount和PhyPageCout分别是虚拟Page页数和物理Page页数, VirtPageSize和PhyPageSize分别是虚拟Page和物理Page的尺寸,注意这里是uv两个方向,我这里没有声明是float2。通常虚拟页的大小应该和物理页的大小相等,但是为了纹理过滤,物理Page可能会增加几个像素的宽度。所以公司稍微修改一下变为:

VirtPageSize = PhyPageSize - PageBorder * 2

scale = (virtPageCount * (PhyPageSize - PageBorder * 2)) / (PhyPageCout * PhyPageSize)

如果考虑MipMap那么需要将virtPageCount变成下层的数量:

scale = ((virtPageCount >> MipmapLevel) * (PhyPageSize - PageBorder * 2)) / (PhyPageCout * PhyPageSize)

接下来我们计算offset:

我们已知VirtPageId和PhyPageId,通过这两个参数我们能够计算出对应的uv坐标。

VirtPageUV = VirtPageId/VirtPageCout

PhyPageUV = PhyPageId/PhyPageCout

Offset = PhyPageUV  - VirtPageUV * scale  = PhyPageId/PhyPageCout - VirtPageId/VirtPageCout * scale

如果考虑物理贴图增加的像素,那么实际的物理坐标应该是:

PhyPageUV = (PhyPageId * PhyPageSize + PageBorder) / PhyPageCount * PhyPageSize

如果考虑MipMap那么虚拟坐标应该是

VirtPageUV =  VirtPageId/(VirtPageCout >> MipmapLevel)

综上所述:

scale = ((virtPageCount >> MipmapLevel) * (PhyPageSize - PageBorder * 2)) / (PhyPageCout * PhyPageSize)

offset = ((PhyPageId * PhyPageSize + PageBorder) / PhyPageCount * PhyPageSize) - VirtPageId/(VirtPageCout >> MipmapLevel) * scale

接下来我们看一下Indirect texture的存储格式,目标有两个一个是满足计算要求,另一个是尽量减少内存/显存的占用。

如果page页是正方形,我们只需要一个scale参见即可,但是offset需要两个一个是u方向,一个是v方向。

为了保证精度我们需要使用32位浮点数进行存储,如果用16位存储会有精度缺失。为什么要这么高的精度,算一下PageBorder/(PhyPageCount*PhyPageSize)如果精度不够这部分永远是0。

我们可以使用FP32x4,这样可以保证精度,但是对于某些特大的虚拟贴图,Indirect texture占用的内存可能会过大。

为了解决贴图过大的问题,我们可以再引入一张间接贴图,每一个virtpage对应这张贴图的一个像素,只不过这里不存储scale和offset,存储的是物理贴图的id,然后物理贴图里面会存储scale和offset,这样可以减少内存大小但是会多一次贴图读取。

另外一种做法是存储物理PageId以及虚拟贴图的mipmap,在shader中计算scale和offset,如上面的公式,只有物理pageid和mipmap这两个参数是变量,其他都是常量或者是根据虚拟坐标可以推导的变量比如虚拟pageid。如果存储物理pageId和mipmap那么只需要R8G8B8A8就可以搞定了,还多出了8位可以做为debug,如下图:

如果对内存还有压力可以使用5:6:5格式但是通常R8G8B8A8就应该可以了。

上面介绍的是传统虚拟贴图的寻址,接下来介绍自适应虚拟贴图的寻址:

自适应虚拟贴图与传统虚拟贴图在寻址上唯一的差别就是,自适应虚拟贴图找不到对应的indirect texture。因为每一个Frame,虚拟贴图都会改变,我们不能通过uv坐标计算出对应的pageid。为了解决这个问题我们需要引入sector概念,如下图:

我们将世界划分成一个一个sector,这样我们就可以根据世界坐标或者是uv坐标计算出这个点在哪个sector块中(其实就是空间划分的grid网格)比如Far cry4中的10k*10K地图按照64m划分就是157 * 157块sectors。因为每个sector使用的贴图每一帧都在发生变化,比如64k,32K。为了让虚拟贴图的atlas有合理的布局,每个sector会根据大小不同改变其存储的位置,如下图:

因此,我们需要创建一个索引纹理或者是使用常量缓存区,保存一些sector的信息来帮组我们寻址。这个贴图不必很大针对Far cry4只需要256*256即可。

这里我们需要存储的是sector的左下角对应的Indirect texutrue的id,以及sector使用的贴图占用了多少个物理page。我们定义两个新的变量:

sectorPageId,也就是这块sector左下角所在的virtpage对应的VirtPageId,这个值在分配虚拟贴图atlas时设置。

sectorPageCount,这个就是sector所用的贴图占了多少个phypage,比如64K/256=250块。

有了这两个变量我们就可以根据虚拟uv计算得出其所在的indirect textrue中的位置,从而进一步计算物理地址。

sectorId = floor(VirtUV * SectorCount)

sectorStep = frac(VirtUV * SectorCount) 

VirtPageId = sectorPageId + sectorStep  * sectorPageCount - 1

获取了VirtPageId 就和传统的虚拟贴图寻址一样了,另外floor和frac函数可能会在某些设备上成为性能杀手。硬件虚拟贴图的寻址是通过硬件自动完成的,这里就不介绍。

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值