MegaLights: Stochastic Direct Lighting in Unreal Engine 5
MegaLights: Stochastic Direct Lighting in Unreal Engine 5 - SIGGRAPH 2025
游戏光照面临的挑战与目标
游戏对光照质量的需求不断攀升
- 希望使用 更多的光源,且所有光源都是 全动态的、投射阴影的
- 希望使用 面光源(Area Lights) 并产生漂亮的 软阴影
- 希望使用更复杂的 BRDF 与材质分层(Material Layering)——甚至仅仅是 无阴影的光照计算 本身就可能变得过于昂贵
- 游戏复杂度持续增长,当前阶段 工作流(Workflow)已成为游戏画面的主要瓶颈——美术应该能够自由地、不受限制地工作(例如截图中程序化放置了大量灯光的场景)
核心设计目标
- 这套方案需要成为 基线光照方法(Baseline Lighting Method),即制作团队可以将其作为 默认目标方案,而不是一个"额外的高端选项"
- 最低要求:必须在 主机(Console) 和 中端 PC 上可用
现有直接光照方案的局限性
方案一:传统的前向/延迟光照(Forward / Deferred Lighting)
- 流程:对每盏灯 → 预计算 Shadow Map(或光追阴影 + 降噪得到 Shadow Mask)→ 将阴影项乘以预积分的光照辐照度
- 优点:对 少量光源 效果很好
- 缺点:光源数量增多后 开销线性增长,无法承受
关键问题:大量无用计算
- 演讲者展示了一个像素的例子:
- 该像素处于 50 盏灯的衰减范围 内 → 需要预计算 50 张 Shadow Map
- 但实际上只有 15 盏灯可见(未被遮挡)→ 大量计算被浪费
- 即使只看可见的 15 盏灯,80% 的能量仅来自 1 盏灯 → 完全没必要精确计算全部 50 盏灯
- 启示:只需对最重要的 1 盏灯做全质量计算,其余做 近似 即可
方案二:BRDF 采样(将直接光照当作 GI 问题处理)
- 常见的 UE 工作流 Workaround:美术放置 自发光网格(Emissive Mesh),从主视图中隐藏,用作 软面光源,由 GI 系统免费处理
- 优点:不需要额外开销,因为 GI 本来就要计算
- 缺点:
- 即使有 GI 引导,BRDF 采样仍然很难找到小型或远距离的发光表面
- GI 本身已经很难——即使在离线渲染中,也常用"在窗户内放灯"这种 trick 来加速收敛。而游戏中的 GI 要在主机上 60Hz 运行,再加上直接光照问题会让情况雪上加霜
方案三:随机光线追踪固定数量的光源(Stochastic Approach)
- 核心思路:随机选择一部分光源 → 追踪 固定数量 的光线 → 命中光源则累积能量到单个 Render Target → 追踪完成后对该 Render Target 做 降噪(Denoise)
- 优点:开销与场景光源数量无关,始终是固定工作量,扩展性极好
- 缺点/难点:
- 在主机上每像素可能 只能追踪约 1 条光线,因此 光源选择(Light Selection) 变得极其关键
- 示例:如果 2 条光线都追踪到了被遮挡的光源 → 整个追踪预算被浪费,什么都没计算到
光源选择策略的分析
方法一:光源层级结构(Light Hierarchies)
- 将所有光源放入某种 层级结构 中,尝试找到"最有意义的光源簇(Cluster)"
- 核心问题:不考虑遮挡项(Shadowing Term),而如前所述遮挡信息非常关键
方法二:ReSTIR(Reservoir-based Spatiotemporal Importance Resampling)
- 在高端 PC GPU 上已展示出惊人效果
- 关键问题:能否缩放到主机/中端设备?
ReSTIR 的工作原理
ReSTIR 在随机直接光照管线中注入三个主要模块:
- 重用(Reuse):从 历史帧(Temporal History) 或 空间邻域(Spatial Neighbors) 中抓取样本
- 可见性验证:追踪光线检查这些重用样本的 可见性(Visibility)
- 组合:将重用样本与新的随机候选光源 合并
- 核心理念:如果当前帧随机选的光源很糟糕(都被遮挡了),大概率能从历史帧或邻居像素中 找到更好的样本
ReSTIR 在主机上的问题
-
高常数开销:
- 不仅是重用操作本身的开销
- 最关键的是额外光线追踪的开销——每个重用样本都需要验证可见性
- 要获得合理质量至少需要 每像素 1 个样本,这意味着需要追踪 2-3 条光线/像素
- 但主机上只能承受 约 1 条光线/像素
-
候选采样(Candidate Sampling)问题:
- 理论上可以随机选一盏灯就完事,但实际上需要:
- 按 BRDF 对每盏灯加权
- 至少检查 20% 的光源列表
- 在高端 PC 上这不算什么,但在主机上 → 相当于对场景中 20% 的光源做无阴影光照评估,开销很大
- 更严重的问题:如果存在很强但被遮挡的光源,BRDF 加权会让系统不断往这些光源追踪光线 → 更难找到真正可见的高质量候选样本
- 理论上可以随机选一盏灯就完事,但实际上需要:
-
结论:ReSTIR 已经超出了主机预算,而且仍然需要解决非常相似的光源采样问题
ReSTIR 的质量问题:样本相关性
- 一个关键发现:有时 简单的随机采样(无重用)反而比 ReSTIR 看起来更好
- 原因:ReSTIR 从历史帧重用"好的离散样本",但这会导致 样本相关性(Sample Correlation)
- 输出结果中会出现 样本聚团(Clumps of Samples)——这些聚团对 降噪器来说非常难处理
- 这是一个基本性的质量限制:重用带来了信息增益,但同时破坏了样本的统计独立性
阶段性总结
| 方案 | 扩展性 | 主机可行性 | 核心问题 |
|---|---|---|---|
| 延迟光照 | ❌ 线性增长 | ✅ 少量光源时 | 光源多时大量无用计算 |
| BRDF 采样 (GI) | ✅ | ✅ | 难以找到小/远发光面;给 GI 额外增加负担 |
| 固定光线随机追踪 | ✅ | ✅ 开销固定 | 光源选择极其关键,1 条光线/像素容错率极低 |
| ReSTIR | ✅ | ❌ 超预算 | 额外光线追踪开销 + 候选采样开销 + 样本相关性 |
Epic 的新方案目标:在不使用 ReSTIR 重用机制的前提下,找到一种更适合主机的随机直接光照方法,解决光源选择问题并保持降噪友好的样本分布。
核心系统设计:可见光列表(Visible Light List)
核心思想:每像素维护一个"可见光列表"
理想目标
- 理想情况下,希望为 每个像素 维护一个 可见光列表(Visible Light List)
- 每帧可以从列表中 选取不同的光源,生成漂亮的 交错采样模式(Interleaved Patterns),这种模式对 降噪器非常友好
- 关键约束:列表中必须是 可见光源——朝被遮挡的光源追踪光线是浪费的,它们不会对最终像素产生贡献
- 时间一致性假设:上一帧可见的光源,本帧大概率仍然可见,因此可以利用 历史帧数据 来构建此列表
实际做法:8×8 屏幕空间瓦片(Screen Space Tiles)
- 逐像素存储光列表 数据量太大,不现实
- 折中方案:以 8×8 像素的瓦片 粒度存储可见光列表
- 采样时:将当前像素 重投影(Reproject) 到上一帧,从对应瓦片的列表中 选取光源
瓦片边界不连续性处理
- 8×8 瓦片较大,相邻瓦片之间可能发生 光列表突变,导致视觉上的 不连续性
- 解决方案:使用 随机双线性查找(Stochastic Bilinear Lookup) 来平滑过渡,隐藏瓦片边界
隐藏光源的探索策略
为什么需要探索隐藏光源?
- 场景是 动态的,之前被遮挡的光源可能 随时变为可见
- 如果只采样可见光列表,永远无法发现新的可见光源
20% 预算分配策略
- 将采样预算的 20% 分配给 检查隐藏光源
- 实现方式:
- 分别 对隐藏光列表和可见光列表进行采样
- 在合并之前,将隐藏光源样本的权重 钳制(Clamp) 到总权重的 20%
- 为什么不用降权(Downweighting)? 降权方式下,如果存在一个非常强但被遮挡的光源,可能仍然吸引过多光线。而钳制方式可以 保证 最多只有 20% 的光线追踪预算投向隐藏光源
重投影失败的处理
- 当历史重投影 失败 时(例如重投影后超出屏幕范围,或深度测试不通过):
- 仍然使用最近的可见光列表(大概率包含有用光源)
- 但将隐藏光源的采样比例 提升到 50%,以加速新可见光源的发现
新管线概览
- 整体管线与之前类似,新增一个 橙色模块:负责为每个 8×8 屏幕空间瓦片 构建可见光列表
- 后续采样阶段利用此列表 引导光线方向
- 优势:
- 构建列表 开销很低
- 绝大多数追踪的光线 真正贡献到最终像素
- 光列表可用于生成 降噪器友好的采样模式
光源选择算法:加权水库采样(Weighted Reservoir Sampling)
基本原理
- 一种非常简单的算法:只需遍历所有光源一次,就能根据权重 随机选出一个光源
- 权重函数:每个光源的权重 = BRDF 的亮度值(Volumetric Luminance of BRDF)
- 额外应用 对数变换(Logarithm) 做 感知加权(Perceptual Weighting)
- 原因:如果某盏灯极其强烈,经过 色调映射(Tone Mapping) 后,它对最终像素的实际视觉影响并没有那么大;对数加权能更好地反映人眼感知
蓝噪声优化:STBN(Spatiotemporal Blue Noise)
- 为了生成降噪器友好的采样模式,使用 时空蓝噪声(STBN)
- 问题:STBN 为每像素预计算 一个随机变量,但光源选择的水库采样循环中需要 多个随机变量
随机变量复用技巧
单光线情况:范围回映射(Range Warping)
- 每次水库采样迭代后,将已选范围 重新映射回 [0, 1]
- 这样一个随机变量就可以在整个循环中 反复使用
多光线情况:分段采样(Dithered Sampling / Segment Remapping)
- 如果需要每像素追踪 多条光线,则将随机变量 分割成多个区间段
- 每条光线使用各自的区间
关键收益
- 使用 单个随机变量 完成所有光源采样
- 保持 STBN 的蓝噪声特性 不被破坏
- 获得 显著的质量提升,且无额外计算或光线追踪开销
实际场景中的光源遍历策略
不能真的遍历所有光源
- 真实场景中,一个 Light Grid Cell 内可能有 数百盏灯,逐一遍历不现实
两阶段策略
- 检查可见光列表中的所有光源:它们大概率仍然可见
- 从 Light Grid Cell 中采样部分光源:每个像素检查 不同的子集
- 这种做法 相干性较差(Incoherent),对 GPU 性能不太友好
- 但好处是能 快速发现新的可见光源
计算优化
- 标量化计算:由于只需要 BRDF 的亮度值作为权重,不需要完整的 RGB 计算,可以只做标量运算,节省 VGPR(向量通用寄存器)
- 阈值剔除:权重低于某个阈值的光源 直接丢弃
- 这不是性能优化,而是 质量优化
- 原因:如果一盏灯太弱、无法影响最终像素,那就没必要检查它的可见性,应该把这条光线分配给更重要的光源
降分辨率采样(Lower Resolution Sampling)
依据
- 相邻像素如果 深度和法线相似,那么光源权重通常也 非常相近
- 因此可以 降低采样分辨率
具体做法
- 例如:不再每像素选 1 盏灯,而是 每 4 个像素选 4 盏灯(共享采样结果)
- 采样模式必须精心设计:
- 使用 棋盘格(Checkerboard) 或 叉形模式(Fork Pattern)
- 在 空间和时间 上都施加 抖动(Jitter),最大化重建成功率
重建方法
- 随机双线性上采样(Stochastic Bilinear Upsampling),结合 深度和法线权重 进行加权
- 如果 本帧无法成功重建:直接 复用历史帧数据,不做任何历史矫正(无邻域钳制等操作)
整体效果
- 有时会让阴影 稍微变软
- 但 采样速度提升 4 倍
- 实践中通常是 非常划算的折中
方向光的特殊处理
问题
- 方向光(Directional Light)通常比任何局部光源 强数千倍
- 如果按 BRDF 加权采样,几乎所有光线都会被分配给方向光,局部光源被饿死
常见方案(但不够好)
- 将方向光从随机光照采样中 剥离,单独运行一个 光追阴影 Pass
- 缺点:需要每像素额外追踪一条光线 + 额外的降噪 Pass,开销显著
本方案的做法
- 限制方向光最多占 50% 的采样预算
- 当局部光源 较暗或不存在 时,放松此限制,允许所有光线都追踪方向光
- 这样在 不增加额外 Pass 的前提下,方向光和局部光源可以 共存于同一个随机采样管线 中
面光源的特殊处理
问题
- 面光源(Area Light)可能 体积很大
- 简单的 二值可见性 不够用:光源的一大部分可能被遮挡,如果光线恰好追踪到被遮挡区域,就会浪费预算
解决方案:2×2 位掩码(Bit Mask)
- 为每盏面光源维护一个 2×2 的位掩码,跟踪光源 哪些部分可见
- 此掩码与可见光列表 一起构建
- 采样流程:
- 先为每像素 选择一盏灯
- 再为该灯 选择光源表面上的具体点
- 在选择具体点时,降低被遮挡部分的权重,引导更多光线到 可见部分
- 同样使用 STBN + 样本映射(Sample Warping),但采用适用于 2D 点 的变体
着色阶段(Shading)
流程
- 累积所有可见样本的权重:收集影响给定像素的所有可见光样本的权重之和
- 乘以预积分的光照辐照度(Pre-integrated Light Irradiance)
着色-阴影分离的折中
- 此方法假设 着色(Shading)与阴影(Shadowing)可以分离
- 这是一个 近似,会在某些极端情况下不完全正确
优势
- 减少噪声
- 向后兼容性:随机光照模式下的光源外观与 非随机模式完全一致
- 这对于在现有项目中无缝启用此功能至关重要
- 不是根本性限制,只是一个 特定的工程折中
着色后的降噪与滤波(Denoising)
整体降噪架构
- 即使仅靠 随机光照采样 + 光源选择(SR),画面已经相当不错,但部分区域仍需 专门的降噪处理
- 所有光源的结果合并后,使用 单个降噪 Pass 统一处理
- 降噪分别运行在 漫反射(Diffuse) 和 镜面反射(Specular) 两个独立信号上
- 两个信号都经过 解调(Demodulation):即移除 非随机性的材质属性(如纹理 Albedo),避免降噪器模糊掉材质纹理细节
降噪核心思想:基于时序方差的自适应滤波
- 降噪算法基于 追踪时序方差(Temporal Variance Tracking),该思路源自 SVGF(Spatiotemporal Variance-Guided Filtering)
- 核心逻辑:时序方差告诉我们 某个像素有多"噪",从而决定需要对其施加 多大的空间滤波核
时序滤波(Temporal Filter)
数据存储
- 将历史帧 重投影(Reproject),累积 光照值 和 光照矩(Lighting Moments)
- 光照值存储在 两个 32-bit 格式 中(实际实现为 4×2-bit 的紧凑格式),这得益于 随机浮点量化(Stochastic Float Quantization) 技术
- 额外存储 第一矩和第二矩,但仅针对 漫反射亮度(Luminance of Diffuse) 和 镜面反射亮度(Luminance of Specular)
- 利用这两个矩可以 重建时序方差
邻域钳制(Neighborhood Clamp)
- 使用较宽的 5×5 邻域钳制
- 实现方式:先将数据 加载并打包到 Group Shared Memory 中,再从共享内存执行 方差裁剪(Variance Clipping)
两个关键技巧
-
基于距离的历史权重衰减:
- 如果当前帧新数据 与历史值距离很远(远离邻域边界),则 大幅降低历史权重
- 目的:快速丢弃不可靠的历史数据,消除鬼影(Ghosting)
-
降采样重建像素的宽松钳制:
- 在降采样过程中,部分像素是 重建出来的(而非直接采样)
- 对这些重建样本,适当放宽邻域钳制的范围,避免过度约束
空间滤波(Spatial Filter)
与标准做法的差异
- 不使用 传统的 多 Pass À-Trous 小波滤波(开销太大,不适合此场景)
- 改用 单 Pass + 单个稀疏核(Single Sparse Kernel),该核 逐像素旋转
- 此方法与 TSR(Temporal Super Resolution) 配合良好,TSR 可以进一步清理残留噪声
自适应应用
- 仅在高相对方差的像素上 应用空间滤波核
- 双重收益:
- 提升性能:不必要的像素跳过滤波
- 保持锐度:低噪声像素不被模糊
边缘停止函数(Edge-Stopping Functions)
- 使用标准的 边缘停止函数,防止跨越不相关材质的模糊
遮挡与 Firefly 处理
色调映射空间累积
- 为处理 遮挡导致的能量突变,在 色调映射空间(Tone Map Space) 中进行累积
- 作用:有效 消除萤火虫(Fireflies) 现象(极亮的异常像素点)
自适应衰减策略
- 初始阶段还会 增加空间采样数量
- 随着历史数据累积充足,逐渐淡出 色调映射和额外采样——即系统在收敛后自动减轻滤波强度
降噪与时序上采样的分辨率冲突
问题描述
- 降噪器 在 渲染分辨率(Render Resolution) 下累积历史
- 时序上采样(Temporal Upsampling / TSR) 在 显示分辨率(Display Resolution) 下累积——远高于渲染分辨率
- 这意味着降噪历史中的 一个像素 对应上采样后的 一组像素
- 两难选择:
- 混合不相关像素 → 光照变得 模糊
- 每帧丢弃历史 → 结果充满 噪声
解决方案:基于能量覆盖率的自适应旁路
- 核心观察:如前所述,大部分能量往往来自 1~2 盏光源
- 例如:80% 的能量来自 1 盏灯,且本帧 成功采样到了该光源 → 该像素 其实并不太噪
- 此时可以 直接将信号传递给 TSR,完全跳过降噪
启发式判断方法
- 可见光列表 提供了像素 可接收的总能量
- 当前帧的采样结果提供了 实际成功采样到的能量
- 计算 已采样能量占总能量的比例:
- 比例高 → 减少降噪强度 甚至 完全绕过降噪器,直接交给 TSR
- 比例低 → 正常降噪流程
稳定性处理
- 该启发式判断本身可能 有少许噪声
- 在其上施加一层 时序稳定化(Temporal Stabilization) 即可
- 整体效果非常好:启用此机制后,画面 明显更加锐利(Much Sharper)
可见性计算与光线追踪机制(Visibility & Ray Tracing)
为什么使用硬件光线追踪而非 Shadow Map
- 主要可见性计算方法是 硬件光线追踪(Hardware Ray Tracing)
- 核心优势:避免了为每帧维护和渲染 大量 Shadow Map 的开销——当光源数量庞大时,传统 Shadow Map 方案的开销尤其严重
BVH 表示的挑战
无法直接使用完整细节几何体
- 内存开销:BVH 中每个顶点都有较高的 内存消耗
- Nanite 游戏的特殊问题:使用 Nanite 的游戏倾向于使用 高精度网格,导致 BVH 构建和追踪 过于昂贵
- Kit-Bashing 环境:大量实例 重叠(Instance Overlap) 进一步增加追踪成本
解决方案:分层简化表示
必须使用 简化代理网格(Proxy Meshes)、激进的实例剔除(Instance Culling) 以及 聚合表示(Aggregate Representations) 来降低光追复杂度。
双层光线追踪结构
近场(Near Field):代理网格
- 覆盖范围:玩家摄像机周围 150 米
- 使用 低多边形代理网格,在 性能与精度 之间取得平衡
- 适用于 近距离追踪
远场(Far Field):聚合简化表示
- 当光线未命中近场网格并 超出近场半径 时,继续追踪远场
- 远场表示会 合并实例 并 大幅减少三角形数量
- 存储在 独立的 TLAS(Top-Level Acceleration Structure) 中
- BVH 复杂度更低 → 追踪更快
- 默认使用 Inline Ray Tracing,在当前一代主机上性能更好
代理网格的核心问题:几何不匹配(Geometry Mismatch)
问题描述
- 代理网格与光栅化几何体之间存在 不匹配
- 光线从 光栅化表面 出发,可能 立即与代理网格三角形相交,导致 错误的自阴影(Self-Shadowing)
具体案例
- 曲面细分(Tessellation)网格:墙壁使用曲面细分,光追中无法高效表示 → 墙面出现错误阴影
- 地板不匹配:代理网格与地板光栅化几何体偏差 → 额外自阴影
- 布料模拟网格:玩家角色的模拟布料在光追中表示不佳 → 角色阴影错误
- 动画与 Alpha Mask 几何体:在光追中表示和追踪都 非常昂贵
为什么不能用面剔除解决?
- 背面剔除(Back Face Culling):在某些简单情况下可以防止错误遮挡
- 但实际中:光线可能同时命中 正面(Front Face),此时仍会产生错误阴影
- 额外代价:启用正面或背面剔除会 增加约 10% 的追踪开销,因为阻止了 BVH 遍历中的 提前退出(Early Exit)
解决方案一:屏幕空间光线追踪(Screen Space Ray Tracing)
核心思路
- 先在 屏幕空间 追踪光线(使用光栅化的 深度缓冲区)
- 由于屏幕光线是基于光栅化几何体的,可以 安全地离开表面,不会错误地与代理网格相交
- 然后根据屏幕追踪的距离,偏移世界空间光线的起点 → 避免大部分自阴影问题
- 额外收益:获得 BVH 中未表示的物体的 接触阴影(Contact Shadows)
关键技术细节
HZB 追踪(Hierarchical Z-Buffer Tracing)
- 与典型接触阴影不同,这里需要 像素精确的屏幕追踪,不能用少量固定步进
- 原因:
- 光线长度在 世界空间 中定义,某些情况下需要追踪 整个屏幕宽度
- 固定步进太贵
- 使用 HZB(Hierarchical Z-Buffer)追踪 快速但精确地 跳过空白区域
表面厚度策略
- 默认采用 非常保守的低表面厚度
- 当光线穿过表面背后时,不假设存在交点,而是回退到 硬件光追
- 目的:尽可能避免典型屏幕空间追踪伪影
深度缓冲区别名问题
- 长距离屏幕追踪会因 深度缓冲区走样(Depth Buffer Aliasing) 产生明显伪影
- 实际做法:限制屏幕追踪光线长度
深度缓冲区自阴影处理
- 深度缓冲区追踪本身也会造成自阴影
- 解决方案:组合使用 点采样(Point Sample) 和 双线性采样(Bilinear Sample) 深度值
Stencil 位标记
- 使用 Stencil Bit 标记哪些实例在光追中 有对应表示
- 如果光线穿过 无光追表示的像素背后,则 增大估计厚度,从这些实例获得更多接触阴影
解决方案二:Alpha Mask 几何体处理
问题本质
- 对于 漫反射 GI,可以在 BVH 构建时通过 缩小/剔除三角形 来粗略近似 Alpha Mask 遮挡
- 但对 直接阴影,这些 trick 效果不佳
- 理想方案是固定功能 Alpha Masking,但美术使用 材质图(Material Graph) 定义 Alpha Mask → 不实际
三种处理方式
| 方案 | 描述 | 优缺点 |
|---|---|---|
| 全量 Any-Hit 评估 | 所有光线使用完整的 Any-Hit Shader 评估材质 | ✅ 结果精确;❌ 无法使用 Inline Ray Tracing,主机开销巨大 |
| 延续光线(Continuation Ray) | 先做轻量级 Inline 追踪;命中需要材质评估的实例时,发射延续光线 并启用 Any-Hit | ✅ 仅在需要时付出额外开销;✅ 保留 Inline 追踪的性能优势 |
| 回退到 Virtual Shadow Map | 对特定光源/实例回退使用 VSM | 适用于硬件光追完全不实际的情况 |
解决方案三:Virtual Shadow Map(VSM)回退
适用场景
- Any-Hit Shader 开销过高(如 密集植被,大量重叠三角形)
- 大量实例动画网格,BVH 构建耗费过多内存和时间
VSM 的优势
- 能够生成 可信的软阴影,与其他硬件光追阴影 混合良好
- 在光源采样 Pass 中,可以 标记哪些 Page 被采样,避免不必要的网格渲染到 Shadow Map 中
灵活的混合使用
- 非二元选择:美术可以 手动标记 哪些实例渲染到 Shadow Map,其余实例仍使用硬件光追
- 局限:VSM 回退并未解决 Shadow Map 的 根本扩展性问题
- 实际使用:主要用于 方向光(Directional Light),因为方向光的几何不匹配问题最为显著
完整追踪管线总结
整体管线按以下顺序执行:
1. 屏幕空间追踪(Screen Traces)
→ 避免代理网格自阴影
2. 近场追踪(Near Field Traces)
→ 处理大部分光线
├─ 命中 Alpha Mask 物体?→ 延续光线 + 材质评估
└─ 光线超出近场半径?→ 远场追踪(Far Field Traces)
3. VSM 回退(可选)
→ 对配置了 VSM 的光源,额外采样 Shadow Map
着色器性能优化:瓦片分类(Tile Classification)
问题
- 追踪着色器需要处理 多种材质类型 和 光源特性 → 寄存器压力(Register Pressure) 高,占用率(Occupancy) 低
解决方案
- 基于 材质类型 和 影响每个 Tile 的光源类型 进行 瓦片分类
- 只在 确实需要 的 Tile 上付出最坏情况的 VGPR 使用量
- 矩形光(Rect Light) 和 纹理光(Textured Light) 的支持逻辑开销较大 → 为其创建 专用着色器排列(Shader Permutations)
实际效果
- 在 MegaLights Demo 中,基于光源类型的额外分类使 占用率提升约 20%,而分类本身的额外开销很小
半透明材质与体积雾的照明(Translucency & Volumetric Fog)
UE5 中体积光照的既有架构
两种存储结构
- 体积雾(Volumetric Fog):使用 摄像机对齐的视锥体网格(Camera-Aligned Frustum Grid / Froxel Grid)
- 粒子效果与半透明物体(Translucency):使用 世界空间网格(World Space Grid),每个体素存储 一阶球谐(First Order Spherical Harmonics)
传统更新方式
- 每帧将光源 注入(Inject) 这两个体积结构
- 阴影通常通过 Shadow Map 计算
Mega Lights 带来的问题
- Mega Lights 方案下 没有 Shadow Map
- 注入 大量光源 本身也 非常昂贵
- 因此这两个体积结构必须 升级以适配 Mega Lights
将不透明表面管线扩展到体积
基本方法
- 对体积同样运行 采样 + 光线追踪 管线:
- 以 半分辨率光线 对每个体素进行 光源采样 并追踪 阴影光线
- 对可见样本使用 相位函数(Phase Function)(体积雾)或 漫反射 BRDF(半透明体积)进行着色
- 构建 可见光列表(Visible Light Lists)
- 需要对两个体积 各执行一次
直接套用的问题
- 探针稀疏:尤其是世界空间网格,从邻近体素 收集可见性信息 容易导致在 高频阴影区域引导失效
- 大量重复工作:两个体积的探针覆盖范围 大幅重叠,导致选光和追踪的重复计算
优化方案探索
方案一:统一使用 Froxel 网格(存储球谐)
-
思路:扩展 Froxel 网格结构,使其也存储 球谐数据,用于粒子和半透明物体照明
-
优点:
- 避免两个体积之间的重复工作
- Froxel 更密集 → 引导效果更好
-
缺点 / 实际伪影:
- Froxel 网格结构可见:尤其在摄像机移动时非常明显(世界空间网格虽也有类似问题,但至少是 摄像机无关的、稳定的)
- 时序滤波的重投影问题:会产生无法完全避免的 可见伪影
方案二(采用方案):混合方案——共享采样与追踪
- 核心思路:仅 采样和追踪 在两个体积间共享,着色分别进行
具体流程
- 采样阶段:基于 Froxel 位置(更密集)运行采样逻辑 → 相邻探针可以 共享可见性数据 而不损害引导效果
- 追踪阶段:对这些样本统一追踪阴影光线
- 着色阶段:运行 两个独立的着色 Pass,每个体积从最近的采样探针 随机收集(Stochastically Gather) 样本
半透明采样的特殊挑战
与不透明表面的关键差异
不透明表面的 "80% 能量来自 1 盏灯" 假设在半透明中 不完全成立:
- 光源注入球谐:不知道哪些表面最终会用某个体素着色 → 无法利用法线和材质属性 预先筛选光源
- Froxel 尺寸相对像素仍然很大:从邻近体素 随机上采样 容易导致 不稳定 或需要更宽的滤波
- 双重评估需求:统一体积中必须同时考虑 相位函数 和 半透明 BRDF → 光源权重分布 更均匀,难以集中到少数几盏灯上
结论
需要 每个体素着色更多样本(比不透明表面多得多)
两种增加样本的策略
| 策略 | 方法 | 优点 | 缺点 |
|---|---|---|---|
| 增加每体素样本数 | 直接提升采样数量 | 简单直接 | 管线各阶段 全面变贵 |
| 扩展随机上采样 | 着色时从 多个邻近体素 收集并合并样本(而非仅一个) | 避免额外采样和追踪开销 | 模糊可见性项,损失光照细节 |
后处理稳定化
- 对体积光照结果额外施加 时序滤波 和 空间滤波,提高照明稳定性
高质量半透明表面(如玻璃)
问题描述
- 部分半透明材质(如 玻璃)需要 更高质量的照明,包括 镜面反射(Specular)
方案一:完整管线重跑
- 将前层视为最重要层,对其重新运行 整个 Mega Lights 管线
- 问题:当玩家靠近半透明表面时,直接光照开销可能翻倍——实际中通常太贵
- 还需要支持 多层着色,可扩展性不足
方案二(当前使用):利用球谐近似
- 使用已有的 半透明体积球谐数据
- 从球谐中 提取主方向(Dominant Direction) 来近似镜面反射照明
- 优点:非常廉价
- 缺点:
- 仅支持 一个镜面高光
- 球谐中的光照必须 非常稳定,否则镜面反射会出现 可见的抖动(Jitter)——即使光源是静态的
- 当前状态:作为临时方案使用,改进仍在进行中
运行时性能分析(Runtime Performance)
测试环境
| 参数 | 值 |
|---|---|
| 平台 | PlayStation 5 |
| 渲染分辨率 | 1080p |
| 每像素样本数 | 1 SPP |
| TSR 计算 | 禁用 |
| 屏幕光源数量 | 超过 900 盏 |
| 每像素光源数量 | 约 20~80 盏 |
总体开销
- 总耗时约 5.5 毫秒,涵盖 所有直接光照的阴影和着色 成本
关键性能观察
1. 采样比着色更耗时
- 不透明表面采样 尽管运行在 半分辨率 下,耗时仍然 超过着色
- 原因:
- 采样成本与 光源数量 成比例——此场景光源数量极高
- 大量 矩形光源(Rect Lights),采样开销高且 限制 Occupancy(占用率)
- 着色相对稳定:有每像素着色样本数的 上限,成本主要受 瓦片类型 影响
2. 半透明着色几乎与不透明一样贵
- 尽管 探针数量远少于屏幕像素数
- 原因:如前所述,半透明需要 每个体素着色更多样本 以改善稳定性和降低噪声
美术工作流的实际影响
美术创作模式的转变
- 一旦美术开始使用 Mega Lights,创作方式 迅速发生变化:
- 初期:谨慎地零星放置光源
- 中期:光源数量 大幅增加
- 后期:开始大量使用 矩形光源,甚至利用 程序化光源生成工具(Procedural Light Spawners) 沿样条线自动布置灯光
系统的可扩展性
- 整体系统 扩展性更好、容错性更强
- 但传统的 内容优化手段 仍然有价值:
- 减小衰减范围(Attenuation Range)
- 收窄锥角(Cone Angles)
- 使用挡光板(Barn Doors)
- 这些措施可以有效 降低噪声 和 采样 Pass 的开销
总结与未来工作展望(Conclusion & Future Work)
Mega Lights 系统总结
核心成就
- Mega Lights 利用 实时硬件光线追踪 来计算直接光照,甚至在当前一代主机上 也可运行
- 光源数量不再是主要限制因素——这是该系统最重要的实际收益
现有质量限制
- 网格在光追中的表示质量 仍然是画质的主要瓶颈
- 大多数情况下每像素仅能负担 1 个样本 → 精心的采样策略 和 高质量降噪 至关重要
性能特性
- 性能开销主要取决于两个因素:
- 每像素的光源样本数量
- 光追场景复杂度
- 实践中证明这是一个 良好的性能-质量权衡
未来工作方向
1. 产品化完善
- Mega Lights 仍在 走向生产就绪(Production Ready) 的过程中
- 需要完成大量 优化、修复和缺失功能
2. 可扩展性问题(Scalability)
- 核心挑战:一旦美术使用 Mega Lights 制作内容,很难在不大量返工的情况下降低光源数量
- 不希望 美术被迫手动维护 多套光照配置(例如高端/低端分别一套)
- 这仍是一个 开放问题
3. 降噪与上采样的统一化
- 当前管线:Mega Lights 自有降噪 → TSR 进一步处理
- 机器学习降噪 领域有大量新研究,展示出非常有前景的结果
- 机会点:将降噪和上采样 统一整合,减少总开销并提升最终画质
4. 光追中的网格表示(最大挑战)
- 此前 漫反射 GI 和 粗糙反射 对几何精度要求较低,低精度代理网格 勉强够用
- 但 直接光照的阴影 会轻易 暴露底层几何表示的不足
- 动态几何体 仍是 未解决的难题:
- 大量动画实例会 轻易超出内存和性能预算
- 目前无法精确表示
5. 新一代光追 API 扩展
- 开始出现新的 光追 API 扩展,有望更高效地表示 Nanite 网格
- 但 几何复杂度在持续增长,且新类型的内容(如 Nanite 植被等)在光追中的表示更具挑战性
观众问答精华
Q1:是否考虑过 Light Cuts 等多光源合并技术?
- 回答:有考虑过,即演讲开头提到的 光源层级结构(Light Hierarchies)
- 核心问题:这类技术 不考虑遮挡项,而被遮挡光源的问题正是 Mega Lights 要解决的关键点,因此帮助有限
Q2:如何判断上采样是否对某帧失败?
- 回答:上采样时根据 深度和法线 对每个像素计算权重
- 如果权重为零(例如非常细的线条),则判定 无法上采样,回退到上一帧信息
Q3:多轮样本范围回映射(Sample Warping)是否会出现精度问题?
- 回答:是的,每次回映射会 丢失 1 bit 精度
- 但实际中采样的光源数量 并不多——只采样上一帧可见的光源 + 光源网格单元中的少量光源
- 因此精度位数 不会耗尽
Q4:仅用光追检查遮挡,是否需要高质量光追几何体?对动画 Nanite 网格效果如何?
- 回答:多管齐下的策略——
- 支持 动画代理网格(会有不匹配,但可接受)
- 屏幕空间追踪 在很多情况下效果很好
- 美术在 Mega Lights 下倾向于放置 软阴影光源,天然 隐藏了不匹配问题
- 对于 关键光源(Hero Lights) 或问题严重的实例,可标记使用 Virtual Shadow Map 路径,获得完全精确的阴影
Q5:有无策略减少遮挡检测的延迟?突变亮度变化是否有滤波处理?
- 回答:
- 有 遮挡检测滤波器(Disocclusion Filter):在历史累积不足的帧上施加 额外空间采样
- 着色置信度(Shading Confidence) 机制修复了大部分延迟问题
- 仅在光源数量非常多 时才可能出现累积不够快的问题
Q6:能否用集群加速结构(Cluster Acceleration Structures)替代代理网格?
- 回答:
- 非常感兴趣,但 当前主机上尚不支持
- 即使有新的光追扩展,某些内容(如 曲面细分、Nanite 植被)仍无法很好表示
- 理想目标 是完全消除代理网格和屏幕追踪,但 距离实现还很远
Q7:Mega Lights 对快速移动的光源/摄像机/动画网格表现如何?
- 回答:取决于像素处的光照复杂度
- 如果只有 1-2 盏主导光源 → 表现 很好
- 如果有 20 盏光源需要多帧累积 → 表现 很差
- 本质:随机采样提供了一个 性能与质量之间的滑块——可以根据具体需求调整在时序稳定性和画质之间的平衡点
关键结论
| 维度 | 状态 |
|---|---|
| 光源数量限制 | ✅ 基本解决 |
| 主机可用性 | ✅ 已实现 |
| 产品化就绪 | 🔄 仍在进行中 |
| 可扩展性(低端回退) | ❌ 开放问题 |
| 光追几何表示质量 | ❌ 最大的未来挑战 |
| 动态几何体 | ❌ 未解决 |
| 降噪 + 上采样统一 | 🔄 有前景,待整合 |