第十四章:前沿与展望
一句话概括:RT 的形态在变,但"中间画布"的思想永远不会消失。
生活类比:从纸质草稿到 iPad 画板——载体换了,但"先画草稿再定稿"的工作流没变。
⏱ 30 秒概览
RT 的形态正在快速演变。Visibility Buffer把 G-Buffer 从 4~6 张 MRT 压缩到 1~2 张(只存 Triangle/Instance ID,Lighting 时反查材质),UE5 Nanite 已采用。VRS让 RT 同尺寸但不同区域着色率不同,需额外着色率控制 RT。光线追踪的输出走 UAV 而非 ROP,UAV 成为与 RT Attachment 并列的一等公民。UE5 三大件(Nanite + VSM + Lumen)一帧可用 60~80 张 RT,Frame Graph 成为刚需。Bindless让 RT 作为纹理读取不再受 Slot 限制。Neural Rendering可能带来 Tensor 格式的 RT。XR需要 Texture2DArray 双目渲染 + VRS Foveated + 跨帧 RT 缓存做 Timewarp。不变的本质:"先画到中间画布,再加工输出"的思想永远不会消失。
14.1 Visibility Buffer——G-Buffer 的挑战者
传统延迟渲染的 G-Buffer 方阵——4~6 张 MRT 存储材质属性——正在被一种更轻量的方案挑战:Visibility Buffer。
核心思想
Visibility Buffer 只存两样东西:
| RT | 格式 | 内容 |
|---|---|---|
| Vis Buffer | R32_UINT 或 RG32_UINT | Triangle ID + Instance ID |
| Depth | D32F | 硬件深度 |
就这两张 RT。不存颜色、法线、粗糙度——什么材质属性都不存。
Lighting Pass 时,对每个像素:
- 从 Vis Buffer 读取 Triangle ID + Instance ID
- 用 Triangle ID 反查顶点数据
- 用重心坐标插值得到 UV、法线等属性
- 采样贴图、计算材质参数
- 做光照
对 RT 的影响
| 维度 | G-Buffer | Visibility Buffer |
|---|---|---|
| MRT 数量 | 4~6 | 1~2 |
| G-Buffer 带宽 | 极大(第 8.5 节) | 极小 |
| Lighting Pass 复杂度 | 简单(数据在 RT 中) | 复杂(需要重新获取材质数据) |
| 三角形过小时 | Quad Overdraw 浪费严重 | 影响小(只写 ID) |
Visibility Buffer 大幅减少了 MRT 的数量和带宽,但把计算压力转移到了 Lighting Pass。
UE5 的 Nanite 就使用了类似 Visibility Buffer 的方案——结合硬件光栅化和软件光栅化,输出的是 Visibility 信息而非完整的 G-Buffer。The Matrix Awakens(2021 技术演示)首次展示了 Nanite 驱动的城市级场景,FortniteChapter 4(2022)和黑神话:悟空(2024)是最早大规模上线的 Nanite 游戏。
RT 视角的趋势
G-Buffer 的"宽而浅"(多张 RT,每张存一种属性)→ Visibility Buffer 的"窄而深"(极少 RT,计算时再查)。这是 RT 历史上的一次范式转移。
14.2 Variable Rate Shading(VRS)与 RT
VRS 允许 GPU 对屏幕不同区域以不同的着色频率渲染——中心区域每像素着色,边缘区域可能 2×2 像素共用一次着色。
VRS 对 RT 的影响
传统 RT:每个像素独立。VRS 下:一个 2×2 区域可能只有一个着色值,然后广播到 4 个像素。
┌──┬──┬──┬──┐ │ │ │ │ │ ← 每像素着色(1×1) ├──┼──┼──┼──┤ │ │ │ ← 2×2 组共用一次着色 ├─────┼─────┤ │ │ ← 4×4 组共用一次着色 └───────────┘RT 本身的分辨率不变——还是每像素一个值。但着色器执行次数减少了。
VRS 需要一张额外的"着色率控制图":
Shading Rate Image: 格式:R8_UINT 尺寸:通常是屏幕尺寸 / 16(每 16×16 像素一个着色率值) 值:0=1×1, 1=1×2, 2=2×1, 3=2×2, 4=2×4, ...这张图本身也是一种 RT——需要生成、绑定、被 GPU 读取。
VRS 的两种模式
- Per-Draw VRS:整个 Draw Call 用同一个着色率——不需要额外 RT
- Per-Tile VRS:通过 Shading Rate Image 控制每个 Tile 的着色率——需要额外的 R8 RT
VRS 的工业落地早于很多人印象中:Wolfenstein II(2018)和Gears 5(2019)是最早应用 Tier 2 VRS 的 3A 游戏。VR 场景中 Foveated VRS(眼动追踪+边缘降频)已是标准配置,在 Meta Quest Pro(2022)上省了约 25% 的片段着色开销。
14.3 Ray Tracing 的输出:UAV 代替传统 RT
硬件光线追踪(DXR / Vulkan Ray Tracing)改变了 RT 的生产方式。
传统光栅化
三角形 → 光栅化 → Fragment Shader → ROP → RT光线追踪
Ray Generation Shader → TraceRay() → Hit/Miss Shader → UAV Write光线追踪没有 ROP。输出直接通过RWTexture2D(UAV)写入:
RWTexture2D<float4> outputRT : register(u0); [shader("raygeneration")] void RayGen() { uint2 pixel = DispatchRaysIndex().xy; RayDesc ray = generateCameraRay(pixel); TraceRay(accelerationStructure, ...); // 直接写入 UAV outputRT[pixel] = finalColor; }对 RT 的影响
| 维度 | 光栅化 | 光线追踪 |
|---|---|---|
| 输出方式 | ROP → RT Attachment | UAV Write → Storage Image |
| 混合(Blending) | 硬件 ROP 免费 | 手动实现 |
| MSAA | 硬件支持 | 不支持(需要用其他 AA 方案) |
| 随机写入 | ❌ 只能写当前像素 | ✅ 可以写任意位置 |
| 管线绑定 | Render Pass + RT | 只需要 UAV 绑定 |
趋势:光线追踪的普及让UAV(Storage Image)成为和传统 RT Attachment 同等重要的输出目标。
混合管线
现实中多数游戏采用混合管线——光栅化产生 G-Buffer,光线追踪做反射/阴影/GI:
光栅化 → G-Buffer RT(MRT) 光线追踪 → Reflection RT(UAV) 光线追踪 → Shadow RT(UAV) 光线追踪 → GI RT(UAV) 全屏 Pass → 合成所有 RT → Final ColorRT 的"身份"越来越模糊——有些是 ROP 写的,有些是 UAV 写的,有些是 Compute 写的,但在数据流中它们的角色完全一样。
14.4 Nanite / Virtual Shadow Map / Lumen 中的 RT 使用
UE5 三大件——Nanite、Virtual Shadow Map(VSM)、Lumen——对 RT 的使用都有创新。
Nanite
Nanite 的核心创新是软件光栅化+ Visibility Buffer:
- 小三角形用 Compute Shader 直接写 Visibility Buffer(Atomic 写入
R64_UINT) - 大三角形用硬件光栅化输出到同一张 Visibility Buffer
- 最终结果:一张 Visibility Buffer RT + Depth RT
这是 RT 使用的新范式——混合软硬件光栅化,统一输出到同一张 RT。
Virtual Shadow Map
传统 Shadow Map 是固定分辨率的。VSM 使用虚拟纹理思想——一张巨大的虚拟 Shadow Map(如 16K×16K),但只实际分配有内容的区域(Page)。
RT 视角:
- 物理存储是一张 Shadow Map Atlas(多个物理 Page 拼接)
- 渲染时只渲染发生变化的 Page(增量更新)
- 每帧实际写入的 Page 数量远小于全部
这是 RT 管理中按需分配思想的极致体现。
Lumen
UE5 的全局光照系统 Lumen 使用了大量 RT:
- Surface Cache:存储场景表面的低分辨率光照数据
- Radiance Cache:3D 纹理存储空间中的辐射度
- Screen Probe:屏幕空间的探针 RT,用于最终 GI 采样
- Temporal History:多帧累积降噪
Lumen 一帧的 RT 数量可以达到 60~80 张——Frame Graph(RDG)的自动管理成为刚需。
14.5 Bindless Rendering 对 RT 工作流的影响
传统渲染中,每个 Draw Call 需要绑定当前使用的纹理/RT 到特定的 Slot:
// 传统:每次 Draw 前绑定context->PSSetShaderResources(0,1,&shadowMapSRV);context->PSSetShaderResources(1,1,&gbuffer0SRV);Bindless Rendering 让所有纹理放在一个大的 Descriptor 数组中,Shader 通过索引访问任意纹理:
// Bindless:所有纹理在一个数组中 Texture2D allTextures[] : register(t0, space0); // 无界数组 // Shader 中通过索引访问 float4 shadowValue = allTextures[shadowMapIndex].Sample(sampler, uv);对 RT 的影响
- RT 作为纹理读取变得更灵活——不再受 Slot 数量限制(DX11 最多 128 个 SRV Slot)
- Pass 合并更容易——一个 Pass 可以读取"任意多张" RT(通过索引访问 Bindless 数组)
- Frame Graph 可以更激进地合并 Pass——不再因为"Slot 不够"而拆分 Pass
14.6 Neural Rendering:从 RT 到 Tensor
AI 渲染正在从"后处理辅助"走向"直接参与渲染管线"。
当前阶段:RT 作为 AI 的输入/输出
传统管线降分辨率渲染 → [低分辨率 RT] → DLSS / FSR 2 / XeSS(AI 网络) → [高分辨率 RT]这里 RT 只是 AI 网络的输入输出容器。
新兴阶段:NeRF / 3D Gaussian Splatting 与 RT
NeRF(Neural Radiance Fields)和3D Gaussian Splatting代表了一类全新的渲染范式——不再手动建模几何体,而是用神经网络或点云表示场景。
从 RT 视角看:
- NeRF:每条光线通过 MLP 网络查询颜色和密度,最终逐像素写入 UAV。没有三角形、没有光栅化——输出方式和 Ray Tracing 一样是 Compute + UAV。
- 3D Gaussian Splatting:用可微光栅化器把高斯椭球 Splat 到屏幕上,输出到传统 RT。但排序和 Alpha 混合的实现在 Compute Shader 中完成,涉及 UAV Atomic 操作(类似第 9.7 节的 OIT 链表)。
- 共同点:输出的 RT 格式仍然是 RGBA16F 或 RGBA32F——但数据含义可能不再是简单的"颜色",而是特征向量(Latent Feature),需要后续的解码网络转换为最终颜色。
3D Gaussian Splatting 数据流: 高斯点云 → Compute 排序 → 光栅化/Splat → [Color RT + Depth RT] ↓ 后续合成管线未来阶段:Tensor 替代部分 RT?
实验性技术(如 Neural Radiance Cache、Neural Texture Compression)正在探索:
- 用神经网络推理结果直接作为 Lighting 数据——替代传统的 GI RT
- 用Tensor Core 做去噪——输入和输出不一定是标准 RT 格式
但目前这些技术还在研究阶段。可以预见的是:
- RT 的格式可能不再只局限于传统的 RGBA 像素——可能出现Tensor 格式的 RT(如 NV12 用于视频,未来可能有 Latent Feature 格式)
- GPU 的 Tensor Core / NPU 可能直接参与 RT 的读写
- Frame Graph 需要能编排"Tensor 操作"和"传统光栅化操作"的混合流
- RT 的本质不会变——它仍然是"中间结果的持久化"——但"中间结果"不再只是像素颜色,而可能是高维特征向量
14.7 XR 渲染中的 RenderTarget——双目、Foveated、Timewarp
VR/AR 设备对 RT 的使用有几个独特需求。
双眼 RT:每眼一张 vs Multiview 单 Pass 写两层
VR 需要为左眼和右眼各渲染一张图像。两种方案:
方案 A:两遍渲染
Pass 1: Camera=左眼 → RT_Left (单独的 2D RT) Pass 2: Camera=右眼 → RT_Right (单独的 2D RT)Draw Call 数量翻倍,CPU/GPU 开销大。
方案 B:Multiview / Instanced Stereo
单 Pass: Camera=两眼 → RT_Stereo (Texture2DArray, 2 层) Vertex Shader 中用 gl_ViewIndex / SV_ViewID 区分左右眼 GPU 一次 Draw Call 输出两层RT 角度:Texture2DArray代替两张独立Texture2D。Draw Call 数减少一半,顶点处理共享。
所有现代 VR API(OpenXR)都推荐 Multiview。
Foveated Rendering:中心高分辨率 + 边缘低分辨率的变尺寸 RT
人眼中央视觉敏锐,边缘模糊。Foveated Rendering 利用这一点,中心区域全分辨率渲染,边缘区域降分辨率。
Fixed Foveated Rendering(FFR):
不追踪眼球,假设用户看中心。用 VRS(第 14.2 节)实现:
- 中心区域:1×1 着色率
- 边缘区域:4×4 着色率
RT 大小不变,但带宽大幅降低(边缘像素着色次数少)。
Eye-tracked Foveated Rendering:
用眼球追踪硬件确定注视点,动态调整着色率区域。需要每帧更新 VRS Shading Rate Image。
Foveated Rendering 变种——变尺寸 RT:
更激进的方案:边缘区域用独立的低分辨率 RT 渲染,最终拼接。
中心 RT: 1440×1440(全分辨率) 左 RT: 360×1440(1/4 宽度) 右 RT: 360×1440 上 RT: 1440×360 下 RT: 1440×360 ↓ 拼接合成 → 最终输出这种方案需要管理 5 张不同尺寸的 RT,Pass 也要执行多次——复杂度高但带宽节省显著。
Timewarp / Spacewarp 的 RT 缓存需求
VR 的关键挑战是延迟。如果渲染跟不上 90 FPS,用户会感到眩晕。Timewarp / ASW(Asynchronous SpaceWarp)通过复用上一帧的 RT来弥补:
帧 N 渲染完成 → RT_N 存储 帧 N+1 来不及渲染 → 取 RT_N + Depth_N + 当前头部姿态 → 做 Reprojection(根据新姿态重投影旧图像) → 输出到 Back Buffer 下一帧: 帧 N+2 渲染完成 → RT_N+2 存储RT 需求:
- 必须保留上一帧的 Color RT + Depth RT——不能被 Pool 回收
- Reprojection 可能产生"空洞"——被遮挡区域会露出,需要填充
- Motion Vector RT辅助 SpaceWarp 做运动补偿
这和 TAA 的历史帧需求类似(第 11.10 节),但要求更严格——VR 的 Reprojection 对延迟极其敏感。
本章小结
| 技术趋势 | 对 RT 的影响 |
|---|---|
| Visibility Buffer | MRT 从 4~6 张降到 1~2 张 |
| VRS | RT 大小不变,但着色率可变;需要额外的着色率控制 RT |
| 光线追踪 | UAV 成为和 RT Attachment 并列的输出目标 |
| Nanite / VSM / Lumen | 更多 RT(60~80 张/帧),Frame Graph 成为刚需 |
| Bindless | RT 作为纹理读取时不再受 Slot 限制 |
| Neural Rendering | 未来可能出现 Tensor 格式的 RT |
| XR | Texture2DArray 双目;VRS Foveated;跨帧缓存 RT 用于 Timewarp |
不变的本质:不管技术怎么演进,"先画到中间画布,再加工输出"的思想从 1996 年到 2035 年——始终不变。RenderTarget 的名字可能会变,但这个抽象不会消失。
📘精通篇回顾:第十二到第十四章完成了从"怎么用"到"怎么精通"的最后一程——性能优化(第 12 章)教你把带宽砍到平衡点,调试诊断(第 13 章)教你在 RT 出错时系统化地找到病因,前沿展望(第 14 章)让你看到 RT 在 Visibility Buffer、光线追踪、Neural Rendering 等新范式中的形态演变。
📕全书回望:四篇十四章,我们走过了一条完整的路径——从"RT 是什么"(认知篇)到"GPU 怎么写、API 怎么说"(原理篇),再到"MRT/Compute/Frame Graph/经典效果"(实践篇),最后抵达"优化/调试/未来"(精通篇)。回头看,贯穿全书的只有一个不变量:流水线的中间产物需要一个地方暂存,这个地方就是 RenderTarget。载体会变,但需求不会消失。如果你内化了这一点,任何新 API、新 GPU、新范式,都只是"老朋友换了件衣服"。
设计哲学:从 RenderTarget 看技术演进的不变量
💡 回顾全书十四章,RenderTarget 经历了从"一块固定的显存"到"声明式资源图中的虚拟节点"的演变。但剥去所有 API 差异和硬件优化,RT 的本质始终是同一件事:流水线中间阶段的持久化。
编译器领域有个类似的观察:从 Fortran 到 LLVM,语言和后端不断变化,但SSA(Static Single Assignment)这种中间表示的理念从未消失——它只是换了名字、换了载体。RT 亦然:从 GL Renderbuffer 到 Vulkan Image,从 UAV 到 Tensor,从单张纹理到 Frame Graph 中的虚拟句柄——载体在变,但"把计算结果暂存下来、供后续阶段使用"的需求永远存在。
如果你掌握了这个不变量,那么不管未来出现什么新 API、新 GPU 架构、新渲染范式,你都能快速定位 RT 在其中扮演的角色。真正的技术深度,不在于记住每个 API 的调用方式,而在于看穿表面差异背后的统一模式。
🤔 思考题
- 如果未来出现一种全新的 GPU 架构,完全没有光栅化管线(纯计算+光线追踪),RT 的概念还会存在吗?会以什么形态存在?
- 随着 AI 渲染(Neural Rendering)的发展,"像素"是否还会是 RT 的基本单位?如果 RT 的内容是 Latent Feature 而非颜色值,格式和色彩空间的讨论会怎样变化?
- Frame Graph 的声明式范式已经成为现代引擎的标配。下一代的"Frame Graph"会长什么样?可能吸收哪些来自编译器优化、函数式编程的思想?
写在最后
2024 年回头看,RenderTarget 已经从 DirectX 7 时代那张"离屏 Surface",长成了一个包含格式、状态机、Tile Buffer、Subpass、Frame Graph、UAV、Visibility Buffer 的庞大知识体系。但当你合上这本书、打开 RenderDoc 抓下一帧的时候,你会发现——不管管线有多复杂,每一帧的故事仍然是同一个:
数据从一张 RT 流向另一张 RT,直到抵达屏幕。
如果你在读完十四章之后,看到一张新的 RT 时能条件反射地问出三个问题——“它的格式是什么?谁写的、谁读的?生命周期到哪里结束?”——那这本书就完成了它的使命。
技术会迭代,API 会更替,但**"中间画布"这个抽象不会消失**。它可能叫 RenderTarget,可能叫 Storage Image,可能叫 Tensor Buffer——名字不重要。重要的是你已经掌握了看穿它的眼睛。
祝你写的每个像素都落在正确的地方。
恭喜你读到这里。附录中提供了各 API 的代码对照和配套 Demo,帮助你从理论走向实践。