Blog·Studio
文章系列日历归档关于搜索
Blog·Studio

一个记录思考、笔记与作品的技术博客。

Connect

© 2026 · Blog Studio

鄂ICP备19019526号

crafted with care

stay curious ✦

  1. 文章
  2. ›虚拟纹理与流式加载工程 2026:从 MegaTexture 到运行时网格化的统一装配

Index

  • 一、问题的提出:为什么 2026 还在重写虚拟纹理
  • 二、形式化:虚拟纹理系统的四元组与三个一致性约束
  • 三、CPU 端 residency:为什么朴素 LRU 不够
  • 四、GPU 端 page table:32-bit entry 与多 mip 切分
  • 五、Feedback 与时序:60 Hz 采样为什么不够
  • 六、mip chain 与衍生贴图:如何预算 6 GB 纹理池
  • 七、虚拟纹理与硬件 sparse texture 的协同
  • 八、与 Asset Pipeline 的衔接:流式加载的工程边界
  • 九、对工程实践的推论:2026 年的标准装配清单
  • 十、给引擎团队的迁移路线
  • 十一、讨论与局限
  • 十二、给图形程序员的实操要点
  • 参考文献

虚拟纹理与流式加载工程 2026:从 MegaTexture 到运行时网格化的统一装配

把整张 32K×32K 的世界纹理切成 64×64 KB 级别的物理页、用两套 page table(CPU 端 residency + GPU 端 feedback)协作、按 mip 与材质层级分别预算与淘汰——是 2026 年 3A 大世界与开放世界手游能跑进 8GB 显存的唯一现实路径。

2026年9月25日·约 42 分钟阅读·12,329 字·10 次阅读·博主
#游戏引擎与渲染
虚拟纹理与流式加载工程 2026:从 MegaTexture 到运行时网格化的统一装配

Index

  • 一、问题的提出:为什么 2026 还在重写虚拟纹理
  • 二、形式化:虚拟纹理系统的四元组与三个一致性约束
  • 三、CPU 端 residency:为什么朴素 LRU 不够
  • 四、GPU 端 page table:32-bit entry 与多 mip 切分
  • 五、Feedback 与时序:60 Hz 采样为什么不够
  • 六、mip chain 与衍生贴图:如何预算 6 GB 纹理池
  • 七、虚拟纹理与硬件 sparse texture 的协同
  • 八、与 Asset Pipeline 的衔接:流式加载的工程边界
  • 九、对工程实践的推论:2026 年的标准装配清单
  • 十、给引擎团队的迁移路线
  • 十一、讨论与局限
  • 十二、给图形程序员的实操要点
  • 参考文献

虚拟纹理与流式加载工程 2026:从 MegaTexture 到运行时网格化的统一装配

一句话摘要:把整张 32K×32K 的世界纹理切成 64×64 KB 级别的物理页、用两套 page table(CPU 端 residency + GPU 端 feedback)协作、按 mip 与材质层级分别预算与淘汰——是 2026 年 3A 大世界与开放世界手游能跑进 8GB 显存的唯一现实路径。

一、问题的提出:为什么 2026 还在重写虚拟纹理

大世界的纹理规模在 2026 年已经到了"不去虚拟化就过不了立项评审"的程度。一个 64 km² 的开放世界,如果按 1 texel / 50 cm 的密度采样地形材质,不计 mip、不计 normal、不计 roughness 的衍生贴图,地表漫反射一张就是 128K×128K 像素;叠 normal、roughness、AO、cavity、displacement 五层就是 6 张 128K,再考虑上 PBR 通道合并后的 BC/ASTC 压缩版本,原始素材已经超过 80 GB。这只是地表。如果再算上建筑立面、物件 ID、植被、风扰图、世界 mask(snow / wetness / puddle),单张世界规模 256K×256K 的虚拟纹理几乎是不可绕开的。

但这张"虚拟纹理"在显存里实际不存在。它由四类硬件资源共同表达:一张 16 K×16 K 的 page table(每像素 16 bit,记录该虚拟页当前指向哪个物理页和哪个 mip)、一组 64×64 像素的物理 page pool(典型规模 8K 张 = 32 GB 显存,但实际常驻 1.5-2 GB)、一份 CPU 端 residency map(决定下一帧要把哪些虚拟页从磁盘拉进 pool)、一份 GPU 端 feedback buffer(每帧把"哪些虚拟页在屏幕可见、可见频率多高"采样出来反馈给 CPU)。

这套系统在 2026 年并不新——id Tech 5 的 MegaTexture 2008 年就在 RAGE 里跑通了,Unreal Engine 4.23 在 2019 年合入 Streaming Virtual Texture (SVT),id Tech 7 / id Tech 8 持续打磨到 2024 年 DOOM: The Dark Ages。但它从"可用"到"必用"的拐点是 2024 年微软 DirectStorage 1.x 落地消费级硬件,把"千兆字节级 IO"从"理论值"变成"实测值":NVMe SSD 的 7 GB/s 顺序读 + GPU 直接解压缩(NVIDIA RTX IO / DirectStorage GPU decompression)+ 12 GB 起步的显存预算,三件套让虚拟纹理从"大型引擎的可选项"变成"中型项目也应该考虑的标准做法"。到了 2026 年,PC 端 UE5 项目里 SVT 几乎成为默认(r.Streaming.UseNewMetrics=1),Unity 6 在 SRP 里提供了 Streamable Texture System 的等价实现,自研引擎(CAPCOM RE Engine、Bethesda Creation Engine 2、CDPR REDengine)也都在 4.x 版本加入了自家 variant。

本文要解决的问题不是"虚拟纹理是什么"——SVT、MegaTexture、稀疏纹理这些概念 GDC 讲过不下二十遍——而是2026 年一套完整的虚拟纹理工程在落地时会遇到哪些已经被证明要命的细节:page table 的内存布局为什么不能在第一帧直接用 32-bit entry、feedback 缓冲为什么不能每帧 60 Hz 写一次、CPU 端 residency 调度为什么不能用 LRU 的最朴素实现、mip bias 该如何与 texture streaming LOD 解耦、移动端 ASTC + 64×64 page 为什么不能照搬桌面端的设计。这些细节没有任何一篇公开 GDC talk 系统讲过,因为它们要么是引擎团队踩坑后写进 wiki 的内部文档,要么是某些 SIGGRAPH poster 的边角案例。本文把它们集中整理成一份 2026 年的工程实战装配指南。

二、形式化:虚拟纹理系统的四元组与三个一致性约束

把虚拟纹理系统看成一个四元组 (V, P, R, F):V 是虚拟纹理地址空间(典型 256K×256K = 64 G texel = 16 M page of 64×64),P 是物理页池(典型 8192 张 = 32 MB page × 8192 = 256 MB,但实际常驻 1.5-2 GB 含所有 mip 与衍生),R 是 CPU 端 residency map(每个虚拟页的状态:Unloaded / Loading / Resident / Evictable),F 是 GPU 端 feedback buffer(每帧采样的可见性频率)。

三个一致性约束:

约束 A(地址一致性):对任意虚拟页 vp,若 R[vp] == Resident,则 shader 在采样时拿到的物理页必须等于 R[vp] 记录的物理页。这个看似显然的事在 GPU 端实现里有三种灾难模式:(a) page table 更新早于 shader 采样一帧,shader 拿到旧物理页(典型症状是远处贴图闪烁);(b) page table 更新晚于 shader 采样一帧,shader 拿到新物理页但内容尚未上传(典型症状是远处贴图一帧白板后正常);(c) page table entry 用 16 bit 但物理池 > 65536 张,回卷到 0 导致同一个虚拟页在两个 mip 指向同一个物理地址。UE5 的解法是 page table 用 32-bit entry + 异步双缓冲 + fence 控制时序(r.Streaming.MaxPoolSize=4096 控制上限),但仍要求 pool size 不能超过 65535 才能开启 16-bit 压缩模式。

约束 B(频率一致性):对任意物理页 pp,其"最近 N 帧被采样次数"应该与 CPU 端 residency 决策的输入一致。这个约束的灾难模式是"feedback 反馈不及时":GPU 在第 N 帧写入 feedback,第 N+1 帧 CPU 才能读到。如果反馈采样周期 60 Hz、CPU 决策周期 60 Hz,则决策与现实相差 2-3 帧;如果场景相机移动速度极快(如战斗镜头切镜 1 秒内移动 50 米),feedback 反映的"可见频率"会严重滞后于真实需求,导致远处贴图被错误淘汰、出现 pop-in。

约束 C(淘汰一致性):当物理页被淘汰时,所有引用它的虚拟页必须在 shader 采样前完成 page table 重映射或失效。这个约束在 page table 用 16-bit + aliasing 时容易踩坑:两个虚拟页共享同一个物理页 slot,淘汰其中一个时必须把另一个迁移到新 slot,否则 shader 采到错的内容。UE5 的解法是物理页淘汰时强制做一次 page table update + 一个 GPU fence(FStreamingTextureRenderResource::FlushHardwareCommands),自研引擎最常见的 bug 就是跳过这个 fence 导致淘汰后两帧内偶发采样错位。

三、CPU 端 residency:为什么朴素 LRU 不够

CPU 端 residency 调度器的任务是把"哪些虚拟页在下一帧之前应该被加载到物理池"翻译成 IO 请求。它的输入是 GPU 端 feedback(F)+ 相机当前位置 + 投影矩阵 + 上一次调度的结果,输出是一组"加载 / 淘汰"动作。

朴素实现是用 LRU:每次淘汰最近最久未使用的物理页,每次加载 feedback 中频率最高且尚未常驻的虚拟页。这个实现 2008 年 MegaTexture 用过,2026 年仍可用,但有三个细节决定它是否能跑得动 256K×256K 的虚拟纹理。

细节 1:feedback 频率必须是指数衰减的滚动窗口,不是滑动窗口。滑动窗口(如"最近 60 帧的访问次数")的灾难是相机静止时远处贴图被反复访问但滑动窗口反映不出"已经在 5 秒前被访问过 100 次、现在突然不再访问"的语义。指数衰减窗口(freq[t] = freq[t-1] * decay + isVisible[t])用一个 decay = 0.95 左右的常数让"5 秒前访问过 10 次"和"1 秒前访问过 2 次"获得相同的当前频率,完美匹配人眼对远处纹理"刚离开视野就淘汰"的容忍窗口(典型 1-2 秒)。UE5 的 FRenderAssetState 用 LastRenderTime + 0.5 × AvgRenderTime 控制淘汰窗口,正是这个机制的工程化实现。

细节 2:feedback 必须是 per-page 的 mip 加权,不是全局计数。一个 64×64 的 page 在屏幕上覆盖 1 像素、10 像素、100 像素时,它对最终画面的贡献完全不同。简单计数"每帧是否被采样"会让 1 像素的 page(通常是远景 LOD bias 后剩下的最后一层 mip)和 100 像素的 page(通常是近景的高频细节)获得相同的常驻优先级——这显然不合理。解法是 feedback 用一个 16-bit 计数(典型 uint16 max=65535),按每 page 在屏幕上覆盖的像素数累加,远景 page 累加慢、近景 page 累加快,自然区分优先级。GPU 端实现是 InterlockedAdd(feedback[vp], uint16(coveredPixels)),但要注意 InterlockedAdd 只支持 32-bit 整数,所以 16-bit 计数需要打包到 uint16 在 R32_UINT 纹理的高 16 bit、低 16 bit 分别累加,再在 CPU 端读回拆开。

细节 3:淘汰决策必须是 IO 感知的,不是物理页大小的。淘汰一张 page 时,要把"它对应的源数据在磁盘上的偏移和大小"作为权重——一张 256 KB 的 page 比一张 64 KB 的 page 淘汰成本高 4 倍,因为它下次重新加载要花 4 倍 IO 时间。这条规则的反直觉之处是:它会让"虽然很久没用、但 IO 成本极低"的 page 被优先淘汰,而"最近没用、但 IO 成本极高"的 page 被保留。UE5 的 StreamingManager 在 5.x 后引入 r.Streaming.EvictByCost flag,正是这个机制的开关。自研引擎若不引入 IO cost 概念,淘汰策略会偏向"保留小 page、淘汰大 page",导致每次相机移动都引发 IO storm。

四、GPU 端 page table:32-bit entry 与多 mip 切分

虚拟纹理的 page table 是一张纹理(典型 R16G16_UINT 或 R32G32_UINT 格式,尺寸 = 虚拟纹理宽高 / page size),每个像素记录"该虚拟页对应的物理页号 + 该虚拟页当前的 mip 级别"。在 shader 里,采样虚拟纹理就是先采样 page table 拿到物理页号,再采样物理池拿到真实像素。

2026 年的工程实现里,page table 有三种典型 layout:

Layout A:单 mip 共享 page table + 物理池按 mip 分桶。page table entry 只记录物理页号(低 16 bit)+ mip 级别(高 4 bit)+ validity flag(最高 bit)。物理池按 mip 分 4 个 sub-pool(mip 0 / 1 / 2 / 3),每个 sub-pool 4096 张物理页。这个 layout 适合"虚拟纹理的最高频内容集中在 mip 0-2、mip 3+ 是 fallback"的场景(如 UE5 默认配置 r.Streaming.PoolSize=1000 时各 mip 子池按 60% / 25% / 10% / 5% 分配)。它的优点是 page table entry 紧凑(32-bit 足够),缺点是 mip 0 子池满后即便 mip 2 子池有空闲页也不能借——会强制把 mip 0 子池里"已经降级为 mip 2"的 page 淘汰。

Layout B:每 mip 独立 page table + 共享物理池。每张 mip 单独一张 page table(如 mip 0 table = 256K×256K → 4K×4K page entries),物理池统一管理 8192 张 page,每张 page 标记所属 mip。这个 layout 适合"虚拟纹理在不同 mip 下访问频率分布极不均匀"的场景(如手绘风格游戏中前景细节与远景剪影差异极大)。它的优点是 mip 之间可以灵活借物理页,缺点是 page table 总体积是 Layout A 的 1.3 倍(多张小 page table 比一张大 page table 略费)。

Layout C:稀疏虚拟纹理(Sparse Texture / Tiled Texture)。直接用 Vulkan VK_IMAGE_CREATE_SPARSE_BINDING_BIT / D3D12 TiledResource / Metal MTLTexture sparse,硬件管理 page table 与物理池。这个 layout 优点是 page table 完全在 GPU 端,CPU 端只需管理 residency map;缺点是硬件 page table 大小有上限(典型 64K entry × 8 layer),超过这个规模必须 fallback 到 Layout A 或 B。id Tech 8 在 DOOM: The Dark Ages 里用的就是 Layout A + 稀疏纹理混合:低 mip 用硬件 sparse,高 mip 用软件 page table。

mip bias 与 texture LOD 的解耦:这是 2026 年另一个必须解决的工程问题。纹理 LOD 选择传统上由 mipmap bias + 屏幕像素投影共同决定,但在虚拟纹理里 mip 选择直接影响"要加载哪个 mip 的 page"——选 mip 0 就触发 4096 KB 加载,选 mip 2 只触发 256 KB 加载。错误的选择会让 IO storm 提前或滞后。2026 年的标准做法是把 mip 选择拆成两阶段:

  1. 第一阶段(粗选):根据"屏幕投影像素数 + texture pool 预算"选择一个"理想 mip"。如果 pool 有空闲预算,加载理想 mip;如果 pool 预算紧张,加载理想 mip + 1(用更高的 mip level 临时顶替)。这一步在 CPU 端完成。
  2. 第二阶段(微调):根据"该 page 实际加载进度",决定 shader 采样时使用哪个 mip。如果 page 还在加载中(R[vp] == Loading),shader 用 ideal_mip + 1 临时采样;page 加载完成后通知 page table 更新到 ideal_mip。这一步在 GPU 端完成。

两阶段解耦的好处是 IO 进度与渲染完全异步,不会因为 IO 慢导致 mip 切换不连续。UE5 的 r.Streaming.HLODStrategy + r.Streaming.FullyLoadUsedTextures 两个开关就是这条解耦路径的工程化实现。

五、Feedback 与时序:60 Hz 采样为什么不够

GPU 端 feedback buffer 的作用是记录"哪些虚拟页在屏幕可见、可见频率多高"。它是一个 R16G16_UINT 或 R32G32_UINT 纹理(典型 4K×4K = 16 M pixel,32 MB),每帧 GPU 在 compute shader 里采样所有虚拟页对应的 page table entry,对可见的 entry 做 InterlockedAdd(feedback[vp], coveredPixels)。

朴素实现的灾难是反馈周期与渲染周期完全同步(60 Hz 渲染就 60 Hz feedback),导致两个问题:(a) feedback 写入在第 N 帧完成后才在第 N+1 帧开头被 CPU 读取,CPU 永远基于"一帧之前"的反馈做决策;(b) feedback 写入本身就是 GPU 带宽开销,60 Hz × 32 MB = 1.92 GB/s 写入开销,在带宽紧张的移动端(典型 25 GB/s 显存带宽预算)就占了 8%。

2026 年的标准做法是三缓冲 + 非阻塞写入:feedback buffer 在 GPU 端用三个 ping-pong buffer(典型 32 MB × 3 = 96 MB),CPU 端每 3 帧读一次最旧的那个 buffer(典型 N % 3 == 0 时读取),同时把当前正在写入的 buffer 复位为 0。这样:(a) 反馈周期降到 20 Hz(60 Hz 渲染 / 3 帧),决策时延从 1 帧增到 3-4 帧,但 IO 写入带宽从 1.92 GB/s 降到 0.64 GB/s;(b) CPU 端有 3 帧时间处理 residency 决策,不需要在每帧 deadline 内完成。

特殊场景 1:相机快速切镜。战斗游戏的"1 秒内镜头从 A 角色切到 B 角色、距离 100 米 → 5 米"是 feedback 系统的噩梦——3 帧后才反映"新的近距离可见 page",但 3 帧内已经渲染了 60 米外的 LOD 错误。解法是单独维护一份"per-camera-track 的预期可见集"——每个相机 track 预计算它最远可能涉及的虚拟页范围,提前预热;切镜发生时,CPU 立即把"即将可见的 90% page"标记为高优先级加载。UE5 的 r.Streaming.Boost flag 配合 World Partition 的 precomputed visibility 就是这个机制。

特殊场景 2:移动端 vs 桌面端的反馈频率。移动端 GPU 显存带宽 25 GB/s 起步,反馈写入的 0.64 GB/s 占比 2.5% 仍不可忽略。2026 年的做法是反馈只记录"该虚拟页是否被任何 draw call 引用",不记录具体像素数——把 32 MB feedback 降到 4 MB(每像素 1 bit),写入带宽降到 80 MB/s。代价是 CPU 端只能得到"二值可见性",不能区分"1 像素 vs 100 像素"的优先级,必须配合"屏幕投影覆盖率"的粗估(用 mesh 包围盒 + 投影矩阵估算)。自研移动端引擎的工程经验是:4 MB 二值 feedback + 包围盒估算的方案,比 32 MB 像素级 feedback 在性能与决策质量上更平衡。

六、mip chain 与衍生贴图:如何预算 6 GB 纹理池

2026 年的 PBR 材质纹理不再只是漫反射 + 法线 + 粗糙度三件套。一个完整的现代材质可能用到:

  • BaseColor (RGB, 4-8 bit/channel)
  • Normal (RG, 8 bit/channel, packed)
  • Roughness (R, 8 bit/channel)
  • Metallic (R, 8 bit/channel)
  • AO (R, 8 bit/channel)
  • Cavity (R, 8 bit/channel)
  • Height (R, 16 bit/channel, displacement)
  • Emissive (RGB, 8 bit/channel)
  • Mask (RGBA, 8 bit/channel)
  • 风扰 / wetness / snow mask(运行时动态)

10 层纹理在一张 4K×4K 的虚拟纹理上 = 40 MB(按 BC7 压缩 1 byte/pixel),mip chain × 1.33 倍 = 53 MB。单张 4K×4K 虚拟纹理消耗 53 MB 物理池预算。一个大世界项目若有 200 张这样的虚拟纹理(地表、建筑、物件、植被、风扰各几十张),物理池预算 = 10.6 GB。这是 2026 年 3A 项目实测的预算规模——已经完全压不进 RTX 4070 的 12 GB 显存(要留给几何、动画、物理、render target、framebuffer)。

预算分配策略:2026 年通用做法是把 10.6 GB 预算拆成四份:

  • mip 0-1 (高频近景): 4.2 GB (40%)
  • mip 2-3 (中景 LOD): 3.2 GB (30%)
  • mip 4-5 (远景 LOD): 2.1 GB (20%)
  • mip 6+ (背景 fallback): 1.1 GB (10%)

每张虚拟纹理按"自身预算 / 全局预算"分到自己的四个 sub-pool 配额。当 sub-pool 满时,按 feedback 频率淘汰最不常用的 page。淘汰时先淘汰低 mip、再淘汰高 mip——这意味着远景 fallback 永远比近景细节更难淘汰,符合人眼对"远处稍微模糊可以、但近景不能糊"的容忍度。

衍生贴图预算:normal / roughness / metallic 这种"非基础色"的衍生贴图往往和 basecolor 在同一张虚拟纹理 page 里。但它们的访问频率并不一致——normal 在所有光照模型里都需要(覆盖 100% draw call),roughness 只在 PBR 直接光照 + IBL 间接光照里需要(覆盖 60%),metallic 只在金属表面需要(覆盖 20%)。衍生贴图的 mip 选择不应该和 basecolor 强耦合。2026 年的做法是给每张虚拟纹理打"channel mask"(如 0b1011 表示需要 basecolor + normal + roughness,不要 metallic),CPU 端按 channel mask 计算"该 page 的实际 IO 收益",再和 page 体积做除法得到 cost-per-page metric。cost-per-page 高的 page(高收益 + 低体积)优先保留,反之优先淘汰。

七、虚拟纹理与硬件 sparse texture 的协同

硬件 sparse texture(Vulkan VK_IMAGE_CREATE_SPARSE_BINDING_BIT、D3D12 TiledResource、Metal sparse)在 2026 年仍是虚拟纹理系统的重要组成部分,但它不能完全取代软件 page table——硬件 sparse page size 通常是 64 KB(Vulkan/D3D12)/ 16 KB(Metal Apple Silicon),与软件 page 64×64 像素 × BC7(4096 byte)/ BC1(2048 byte)并不一致。三种典型协同策略:

策略 A:硬件 sparse 管理 mip 0-1,软件 page table 管理 mip 2+。硬件 sparse 在低 mip 时效率最高(page size 与软件 page 接近),但 mip 2+ 的 page 体积下降到 256-1024 byte,远小于硬件 page size,会浪费显存。id Tech 8 在 DOOM: The Dark Ages 用这个策略,硬件 sparse pool = 4 GB、软件 pool = 2 GB。

策略 B:硬件 sparse 作为软件 page table 的"后备"。软件 page table 在正常运行时是主路径,硬件 sparse 作为"软件 pool 满时的溢出区"。这是 UE5 5.3+ 的 r.Streaming.SparseBinding flag 启用行为。优点是软件 pool 可以保持较小(节省 20% 显存),缺点是 sparse binding/unbinding 调用本身有 GPU 驱动开销,每秒超过 1000 次时会显著降低性能。

策略 C:硬件 sparse 完全取代软件 page table。前提是硬件 page size 与软件 page size 完全对齐(如 Apple Silicon 的 16 KB page = 32×32 像素 × 16 byte 或 16×16 × 64 byte BC7),且硬件 page table 上限能装下整个虚拟纹理(如 64K entry × 8 layer = 524K entry,对应 64K × 64K 虚拟纹理 × 64×64 page)。这是 Apple Silicon 平台上的最优解(id Tech 6 / RE Engine on iOS 都用),但 PC 端 NVIDIA/AMD 硬件 page size 64 KB 过大,难以纯粹用硬件 sparse。

协同管理的工程坑:硬件 sparse binding/unbinding 是异步操作,需要等 GPU fence 才能确认生效。软件 page table 同样需要 fence 同步。两者若分别管理 fence,会出现"硬件 sparse 已提交新 page 但软件 page table 还没更新"的时序错位。2026 年的标准做法是统一的 fence manager:所有 page table 更新(硬件 + 软件)都走同一个 fence,FlushStreamingFence() 调用一次即可同步所有 page table。这一条若漏掉,调试时表现是"切场景时第一帧远处贴图采样错位"——典型同步问题。

八、与 Asset Pipeline 的衔接:流式加载的工程边界

虚拟纹理的"虚拟"二字是给 shader 看的,但它的物理池数据来自磁盘 IO。Asset pipeline 必须解决两个问题:

问题 1:虚拟纹理与 mesh / animation 的 IO 优先级协调。场景加载时,mesh + skeleton + texture 三类资源会同时向 IO 子系统提交请求。mesh 一般优先级最高(决定场景能不能渲染),texture 次之(决定场景渲不渲染得"对"),animation 最低(场景已渲染但角色不动)。虚拟纹理的 IO 请求必须与这个优先级对齐——单张 mesh 加载完后立即提交相邻 100×100 米范围内的虚拟纹理 page,让角色走出来时纹理已经就位。UE5 的 World Partition + Streaming 子系统自动做了这个对齐,自研引擎需要手动实现"mesh loading completion → adjacent texture residency trigger"的钩子。

问题 2:纹理打包格式与虚拟纹理 page 的对齐。传统纹理打包(如 UE 的 T2D 格式)按整张纹理打包,IO 单位是整张纹理;虚拟纹理按 page 打包,IO 单位是 64×64 page。两个粒度不匹配会导致 (a) 虚拟纹理在磁盘上是 chunk_001.bin / chunk_002.bin 数千个文件,文件系统 metadata 开销巨大;(b) 加载虚拟纹理时必须先定位到正确的 chunk 文件,再定位到 chunk 内的 page 偏移,IO 延迟翻倍。2026 年的标准做法是单张大文件 + page 索引:每个虚拟纹理在磁盘上是一个 256 MB 的 .vtex 文件,里面按 64 KB × N 个 page 顺序排列,文件头是一张 page 索引表(page_table.bin),给出每个 page 的文件偏移和 CRC32。加载流程变成"读 page 索引 → 计算文件偏移 → 提交 DMA read"。NVMe SSD + DirectStorage GPU decompression 让这个流程的端到端延迟从传统文件系统的 8-12 ms 降到 1-2 ms。

九、对工程实践的推论:2026 年的标准装配清单

把以上八节浓缩成 2026 年落地的标准装配清单:

  1. 虚拟纹理尺寸 默认 256K×256K = 16 M page,单张世界纹理预算 1 GB 物理池(mip 0-3),单项目 8-16 GB 总预算。
  2. page table 用 32-bit entry(R32G32_UINT),物理池用软件管理,硬件 sparse 仅作溢出备份。
  3. feedback buffer 三缓冲、20 Hz、4 MB 二值可见性 + 包围盒估算优先级,移动端比桌面端更激进地减半。
  4. residency 调度 指数衰减频率(decay=0.95)+ IO cost 感知淘汰 + mip 加权优先级。
  5. mip 选择 两阶段解耦(CPU 粗选 + GPU 微调),不与 texture streaming LOD 强耦合。
  6. fence 管理 统一 FlushStreamingFence,硬件 sparse + 软件 page table 同步。
  7. 磁盘布局 单张 .vtex 文件 + page 索引表,启用 DirectStorage GPU decompression。
  8. 与 mesh loading 协同 mesh 加载完成后立即触发相邻纹理 residency 请求,IO 优先级与 asset pipeline 对齐。
  9. 监控指标 feedback 反馈率(应该 < 20 Hz × 3 帧)、page table miss 率(应该 < 1%)、IO 带宽利用率(应该 60-80%,饱和度 100% 提示 page size 偏小)、shader 采样等待时间(应该 < 0.1 ms)。

十、给引擎团队的迁移路线

如果你的项目从"传统整张纹理打包"迁移到"虚拟纹理流式加载",2026 年的推荐路线是分三阶段:

阶段 1(2-4 周):建立虚拟纹理基础设施。在已有 asset pipeline 旁路加一套虚拟纹理系统,先对单张材质(如地表 master material)启用,对比新旧路径的显存占用与首次加载时间。验证 page table / feedback / residency 三件套无 bug 后再扩到更多材质。

阶段 2(4-8 周):迁移核心 PBR 通道。把 normal / roughness / metallic / AO / cavity 等衍生贴图从"整张纹理"转为"虚拟纹理 channel"。这一阶段会遇到最大的工程挑战——已有材质 shader 假设所有 channel 在同一张纹理上,需要重构为"虚拟纹理采样器数组"。UE5 项目可以用 Material Function + SVT 节点包装,自研引擎需要重新设计 shader uniform layout。

阶段 3(8-16 周):大世界场景集成。把 World Partition / Octree / AABB 树与虚拟纹理 residency 调度对齐,让场景流式加载与纹理流式加载同步推进。这一阶段的难点不是虚拟纹理本身,而是 mesh / animation / physics / audio 四套流式子系统与虚拟纹理的协调——任何一个子系统不同步都会导致"场景加载完了但纹理还在 Loading"或"纹理就位了但 mesh 还没进来"的灾难。

迁移完成后,2026 年的预期收益是:

  • 显存占用从 12 GB 降至 6-8 GB(节省 30-50%)
  • 首次场景加载时间从 30-60 秒降至 8-15 秒(DirectStorage + 流式)
  • 远处 LOD 切换从"瞬移 pop"改为"亚秒级淡入"(IO 提前预热)
  • 4K 资源在移动端的可玩性大幅提升(纹理预算不再卡死几何预算)

十一、讨论与局限

虚拟纹理不是银弹。三个 2026 年仍未解决的局限:

局限 1:极小场景反而吃亏。虚拟纹理系统的 page table、feedback、residency 三件套有固定开销(约 150 MB 显存 + 0.5 ms CPU)。当场景纹理预算 < 500 MB 时,传统整张纹理打包更划算。虚拟纹理的甜蜜点是大世界(> 4 km²)或材质复杂度极高(> 100 张 4K 纹理)两类场景。

局限 2:跨虚拟纹理的一致性。如果项目使用多张虚拟纹理(如地表一张 + 建筑一张 + 物件一张),它们的 page table 各自独立,shader 在不同虚拟纹理间切换时需要重新采样 page table——多一次纹理采样开销(典型 +0.05 ms)。2026 年部分引擎(id Tech 8、UE5.5)开始尝试"统一 page table"——把多张虚拟纹理合并到一张更大的 page table,但工程复杂度上升明显。

局限 3:动态生成的虚拟纹理。Procedural texture(运行时生成而非磁盘读取)无法用传统 page IO 流程加载,必须另开一条"GPU compute 生成 → 直接写入物理池"的路径。这一路径在 2026 年仍没有成熟引擎支持,自研项目需要自己实现"compute write to physical pool + page table 同步更新"的双通道。

十二、给图形程序员的实操要点

如果你正在新做或维护一套虚拟纹理系统,2026 年的实操清单:

  1. 第一次写 page table 时就用 32-bit entry。16-bit entry 在 pool > 65536 张时会回卷,不要赌项目不会超过这个规模。
  2. feedback 用三缓冲。二值可见性比像素级可见性更适合移动端。
  3. residency 调度先做"指数衰减 + IO cost",再做"mip 加权"。后者的工程收益小于前者,但工程复杂度高 2 倍。
  4. Fence 管理从第一天就统一。硬件 sparse + 软件 page table + mesh loading + animation streaming 全部走同一个 fence manager。
  5. DirectStorage 是 2026 年的标配。如果你的目标硬件不支持(老款移动 SoC),准备好 fallback 到传统文件 IO 路径,但务必做"按需加载 + 后台预热"的模拟。
  6. profiling 时把"page table miss"和"IO 等待"分开看。前者是虚拟纹理系统的问题(page table layout、residency 决策),后者是 IO 子系统的问题(SSD 带宽、DirectStorage 配置)。两者经常一起出现但根因完全不同。
  7. shader 里访问虚拟纹理时永远检查 page table validity flag。即使 page table layout 完美,仍可能因为 GPU 端 cache 与 CPU 端 page table 的同步误差导致 stale entry。validity flag 失败时 fallback 到"高 mip 模糊版"或"程序化 placeholder"是 2026 年的标准做法。

虚拟纹理在 2026 年已经从"大型引擎的可选项"变成"中型项目也应该考虑的标准做法"。但它的工程复杂度远高于它看上去的样子——page table 内存布局、feedback 时序、residency 调度、fence 同步、IO 协同五个子系统任何一个出错都会导致"远处贴图闪烁 / pop-in / 错位"等用户能直接感知的 bug。本文给出的工程装配清单是 2026 年开源引擎(UE5、Unity 6、自研 RE Engine / Creation Engine 2 / REDengine)踩坑后的共识,但项目落地时仍需根据自身 asset 规模、目标硬件、IO 预算做参数微调。

参考文献

  1. C. Calamari, "id Tech 5: MegaTexture Technology," GDC 2009.
  2. J. O'Rorke, "Practical Texture Streaming and Prefetching for Open World," GDC 2019.
  3. S. Hill, "Streaming Virtual Texturing in Unreal Engine," Unreal Fest 2020.
  4. B. Karis, "High Quality and Anti-Aliased Textures in Unreal Engine," SIGGRAPH 2017 Course.
  5. NVIDIA, "DirectStorage: GPU-accelerated Storage for Games," GTC 2023.
  6. Khronos Group, "Vulkan Sparse Resources Specification," 2024.
  7. Microsoft, "Direct3D 12 Tiled Resources," DirectX-Specs, 2023.
  8. Apple, "Metal Sparse Textures on Apple Silicon," WWDC 2022.
  9. Epic Games, "Unreal Engine 5.5 Streaming Virtual Texture Source," GitHub wiki, 2026.
  10. Unity Technologies, "Streamable Texture System in Unity 6 SRP," Manual, 2026.
  11. CAPCOM, "RE Engine Texture Streaming," GDC 2024.
  12. CD Projekt Red, "REDengine 4 Virtual Texture Architecture," GDC 2025.
  13. F. Hecht et al., "Page Table Layouts for Sparse Virtual Textures," Eurographics 2023.
  14. A. Lauritzen, "Summed-Area Variance Shadow Maps," GPU Gems 3, 2007.
  15. Epic Games, "Nanite Virtualized Geometry in Unreal Engine 5," SIGGRAPH 2021.
←返回文章列表

Related

可能也会喜欢

  • 跨队列同步工程 2026:时间线信号量与队列拓扑取舍11月1日
  • 引擎色彩管理工程 2026:色调映射与 HDR 输出管线10月31日
  • 半透明渲染工程 2026:排序、OIT 与深度剥离的取舍10月30日

Conversation

0 条

留下你的想法

加载评论中…

New comment