[toc]

#

之前有看过一次这个 VirtualTexture 的演讲,最近又对这个产生了一些兴趣,也看了一些实现,但大都藏头露尾。
所以再次从一个二次观察的视角来看一次这个 GDC,应该会有新的发现吧

# 虚拟纹理映射

假设我们有一个无比巨大的地形贴图
如果想要保留贴图的细节并且又不想浪费内存,哪一个很直观的想法就是,动态加载贴图
VirtualTexture 就是这么诞生的 而虚拟纹理到物理纹理的映射就如下图所示 其中物理纹理就是用于存放当前内存中需要用到的对应虚拟纹理中的块
那么当物体在采样时,如何确定目标 UV 坐标是哪些呢?
这就需要用到中间的那部分了 Indirection Texture , 也可以理解为,虚拟纹理表 这个 虚拟纹理表 的格子数量和 虚拟纹理 是一一对应的,采样时,需要先检索一下 虚拟纹理表 中对应的 UV 上的值,然后通过这个值去对 物理纹理 进行采样。 这个逻辑其实很直观,但是延伸出了一个问题, 在不考虑Mipmap的前提下,如何确定每帧中,物理纹理需要加载哪一些虚拟纹理?

Mipmap 映射

上面图中的的虚拟纹理表实际只有一个 mipmap, 但是 比如使用VT渲染大地形 时,不可能把完整的地图放到内存里,所以对于远处的地图,只会选择它的高 mipmap 放到 物理纹理 上,这个时候就需要 多级的虚拟纹理映射表 了,如下图所示: 需要注意的是,其中每一级的 mipmap 对应的虚拟纹理的范围是一致的,但是同样大小下的 UV 坐标范围是 2^N 倍左右

# 虚拟纹理映射表的数据格式

首先,查表时的输入是一个坐标 (x,y)
每个坐标对应一个虚拟页
实际的坐标 = 虚拟页坐标 / 虚拟页大小 (x/y 分别算) 其次,每个存储的内容格式占用 32bit 整型
1. pageOffsetX : 8 位
2. pageOffsetY : 8 位
3. Mip 等级 : 8 位
4. Debug 信息 : 8 位 pageOffsetX = 物理页 U 坐标 / 物理页大小
pageOffsetY = 物理页 V 坐标 / 物理页大小
Mip: 每个物理页对应的 mipLevel
Debug: 用于 Debug (比如 frame Counter) 其中,通过 pageOffsetX/pageOffsetY 以及 MippageSize 可以确定一个物理页的实际 UV 坐标范围,采样的时候也可以通过这些信息和片元 UV 坐标直接计算出在物理页上的 UV 坐标

# Feedback 回读构建当前帧需要的 pageTable

对于 virtualTexture 一个最重要的问题是,我们该如何找到当前需要读哪些 page, 这里可以这样考虑,在实际的页表生成前,我们先将需要贴图的场景渲染一遍 (比如 Terrain), 在渲染时计算出其 mipmap 和对应的 virtual page

# Feedback 大体流程

这个 Terrain / 物体 / Mesh 等,在渲染时必然会进入对应 Shader 的片元着色器阶段,在这个阶段,我们即可以将每个片元对应的 virtual page Idxmip map Level 求出来
在片元着色器返回时,返回如下即可

1
2
3
4
5
float4 frag(feedback_v2f i) : SV_Target
{
//return rg : pageIdx, b : mipLevel
return float4(page / 255.0f,mip / 255.0f,1.0f);
}

即最终输出的图片的 rgb 通道的意义就被确定了下来

mipmap (b 通道)

page (rg 通道)

三个通道混合一下就是最终的 FeedbackRender 贴图,这个贴图上记录了所有片元的 mipmap Level 和 pageIdx 拆分 mipmapLevel Debug 用的 Texture