UE5.8 MegaLights 区域光阴影:把一千盏动态灯塞进主机帧预算
老陈第一次把赛博朋克酒吧关卡打开给我的时候,屏幕里挤满了霓虹灯管、全息招牌、天花板上的长条荧光灯,还有吧台下方的氛围灯带。少说也有一百多盏灯。他苦笑着说:「你数数,这还没摆完呢。」
那是 UE5.3 时代。我们的做法是灯光师先分出静态、固定、可移动三类,把主体光源烘焙进光照贴图,再用数量有限的 Stationary Light 做动态阴影。每次美术调整完灯的位置,至少要等二十分钟的烘焙。更难受的是,Stationary Light 的阴影缓存会打架,超过四个重叠的阴影投射光源就会开始闪烁。老陈半夜三点在群里发了一张图,吧台角落的阴影在抽搐,配文:「又爆了。」
UE5.8 正式发布之后,MegaLights 从 Experimental 标签底下走出来,变成 Production-Ready。Epic 的 Release Notes 写得直接:支持海量动态阴影区域光,噪声大幅下降,目标是在当代主机上跑 60fps。我当时的反应和老陈一样——先把那套破烘焙流程扔了再说。
从烘焙流程到全动态:到底省了哪些事
过去做这种室内关卡,灯光师不是在打光,而是在排班。哪些灯烘焙、哪些灯走 Stationary、哪些灯只能无阴影,全要靠人工分级。一旦场景改动,分级表就得重写。
MegaLights 的思路很简单:屏幕上看得见的灯才算数。它把传统光照评估里的「每盏灯都算一遍」改成「按视锥体切分成小格子,只在格子里找真正影响像素的光源」。这套切分就是 froxel。
flowchart TD
A[老陈打开关卡] --> B[拖入一百盏 RectLight]
B --> C[勾选 Use MegaLights]
C --> D[引擎按 froxel 剔除光源]
D --> E[只对可见像素采样阴影]
E --> F[画面直接刷新]
F --> G[老陈回去睡觉]这个流程图里没有烘焙、没有 Lightmass、没有 Stationary Light 的数量上限。灯光师的工作重新变成打光,而不是解数独。
Froxel:把视锥体切成小块的剔除魔法
MegaLights 的核心开销控制来自 froxel-based sparse sampling。UE 会把相机视锥体在屏幕空间和深度方向上切成三维网格,每个小格子叫一个 froxel。对每个 froxel,引擎只维护一份「可能照亮这个格子的光源列表」,而不是让每盏灯都参与全屏光照。
具体做法是:先把区域光源按照包围盒投影到屏幕,再和 froxel 网格做相交测试。被 froxel 完全遮挡的光源直接剔除,半遮挡或距离太远的光源降权。最终每个 froxel 拿到的光源数量被限制在一个固定预算内,GPU 的负载就从「光源总数」变成了「每个 froxel 的有效光源数」。
这个机制和传统的 tiled deferred 有点像,但 froxel 多了深度分层,所以对室内外混合、前景遮挡特别有效。老陈的酒吧关卡里,吧台后面的灯通常不会照亮门口的角色,因为中间隔着一堵墙,froxel 会把它们从门口像素的候选列表里踢掉。
稀疏采样的另一面是重要性采样。MegaLights 会根据光源的亮度、距离、可见概率分配样本数。亮且近的灯多吃几口样本,暗且远的灯少吃甚至跳过。这样既保留了视觉上的主导光源,又把总样本数压在预算里。
管线的骨架:GBuffer、Froxel、采样器与阴影
整个渲染管线可以拆成几个大块。GBuffer 提供材质和法线信息;Froxel Culling 负责光源列表;Light Guide 做重要性采样;Ray Tracing BVH 或 VSM Atlas 提供阴影可见性;最后 Deferred Composite 把直接光合成到画面上。
architecture-beta service gbuffer(disk)[GBuffer] service froxel(server)[Froxel Culling] service guide(server)[Light Guide] service rt(cloud)[Ray Tracing BVH] service vsm(database)[VSM Atlas] service comp(server)[Deferred Composite] gbuffer:B --> T:froxel froxel:B --> T:guide guide:B --> T:rt guide:B --> T:vsm rt:B --> T:comp vsm:B --> T:comp
这里 MegaLights 不是单独替换 Lumen。Lumen 仍然负责间接光和环境光,MegaLights 只管直接光的高密度覆盖。两者共享同一份光线追踪场景,所以 BVH 的构建成本不会翻倍。老陈最担心的就是:「开了 MegaLights 是不是 Lumen 就得关?」答案是不用。
区域光阴影:软阴影是默认,不是附加品
传统点光源的阴影是硬边,因为数学上它是个没有面积的无限小点。真实世界里的灯管、灯箱、窗户都有面积,阴影本应是半影和本影混合的软边。MegaLights 默认用光线追踪区域光阴影,每个像素朝光源表面发射多条射线,根据命中比例算可见度。
这个做法直接带来了两个好处。软阴影的宽度由光源大小和距离自动决定,不需要美术手动调阴影模糊半径。同一盏 RectLight 靠近墙面时阴影锐利,远离墙面时阴影柔和,符合物理直觉。
MegaLights 的阴影方法可以在灯光组件上切换。默认是 Ray Tracing,不额外生成 per-light shadow map,性能最好,也能得到正确的区域软阴影。如果某盏灯需要捕捉 Nanite 网格的完整细节,可以切到 Virtual Shadow Map,代价是额外的内存、CPU 和 GPU 开销。我的建议是:除非这盏灯是特写镜头的主角光,否则全部用 Ray Tracing。
用数学语言说,直接光照的蒙特卡洛估计长这样:
其中 是从像素朝光源第 个采样点投射阴影射线得到的可见性, 是光源辐射亮度, 是 BRDF。MegaLights 的聪明之处是 不是常量,而是由 Light Guide 根据当前 froxel 的光源分布动态分配。
一盏灯进入画面的全过程
为了把上面的概念串起来,我画了一个时序图。它描述的是美术拖入一盏 RectLight 之后,引擎内部到底发生了什么。
sequenceDiagram
participant A as 灯光师
participant E as Engine
participant F as FroxelGrid
participant S as Light Guide
participant R as RT Shadows
A->>E: 拖入一盏 RectLight
E->>F: 按视锥体生成 froxel 网格
F->>F: 光源与 froxel 求交并剔除
F->>S: 返回可见光源列表
S->>S: 按亮度距离分配样本
S->>R: 发射区域光阴影射线
R-->>E: 返回可见性与颜色
E-->>A: 画面刷新注意 froxel 求交这一步是每帧都会做的,所以动态移动光源没有额外的 CPU 负担。老陈后来把几盏招牌灯绑在 Sequencer 上做动画,帧时间没抖。放在以前,动态光源每帧都要更新 primitive interaction,CPU 很容易炸。
阴影方案的关系网
MegaLights 里并不是所有光源都必须走同一条阴影路径。下面这张图把几种方案的关系理清楚。
graph LR
A[区域光] --> B[Ray Tracing 阴影]
A --> C[Virtual Shadow Map]
B --> D[正确软阴影]
B --> E[无 per-light shadow map]
C --> F[完整 Nanite 细节]
C --> G[额外内存与 CPU 开销]
H[MegaLights] --> I[Froxel Culling]
H --> J[Light Guide 重要性采样]
H --> K[当代主机 60fps 目标]从这张图能看出,Ray Tracing 和 VSM 不是二选一的对立关系,而是为不同类型的光源服务。大面积填充光用 Ray Tracing 足够,特写关键光才值得开 VSM。
性能预算:当代主机 60fps 不是口号
UE5.8 的官方性能目标是当代主机稳定 60fps。这个「当代主机」主要指 PlayStation 5、Xbox Series X|S,以及有光追能力的 PC。要达到这个目标,单靠 MegaLights 自己是不够的,还需要配合几个调参方向。
内部渲染分辨率对 MegaLights 开销影响最大。同样的光源数量,1440p 和 1080p 的 froxel 数量差了一倍还多。项目如果做性能模式,降到 1080p 渲染是最直接的收益。
光线追踪场景的质量也很关键。MegaLights 会先走一段屏幕空间追踪,超出屏幕或被遮挡后才切到硬件光追。如果 BVH 里堆了大量细小实例,光追开销会涨。我的做法是让场景美术把远景小物件合并成 proxy mesh,减少 BVH 节点数。
每盏灯仍有少量遗留 CPU 开销,用来跟踪 primitive interaction。全部光源走 MegaLights 时,可以在控制台关掉它:
[/Script/Engine.RendererSettings]
r.MegaLights.Allowed=1
r.MegaLights.ForceEnabled=1
r.Visibility.LocalLightPrimitiveInteraction=0
r.RDG.AsyncCompute=0
[SystemSettings]
r.MegaLights.RayTracing=1
r.MegaLights.SoftwareRayTracing=0
r.MegaLights.HardwareRayTracing.EvaluateMaterialMode=0
r.MegaLights.VirtualShadowMaps=0这段配置做了几件事。r.MegaLights.Allowed 和 ForceEnabled 强制开启管线;r.Visibility.LocalLightPrimitiveInteraction 0 去掉每个灯光的 CPU 跟踪;r.RDG.AsyncCompute 0 在 profiling 时关闭异步计算,避免 Stat GPU 的计时失真;其余几条把默认阴影方法锁在硬件光追,关掉材质评估和 VSM,保持最轻量的路径。
调试工具:Light Finder 和 Ray Visualizer
老陈在调酒吧关卡时最烦的问题不是帧率低,而是不知道帧时间花在哪盏灯上。UE5.8 给了两个新工具:Light Finder 和 Ray Visualizer。
Light Finder 会高亮当前 froxel 里实际被采样的光源。打开之后,鼠标点到画面哪个位置,就能看到该区域有哪些灯参与了光照评估。老陈用它发现吧台下方一条灯带被错误地计入了远处走廊的 froxel,原因是包围盒设得太松。把光源范围调小之后,那个 froxel 的样本数直接降了一半。
Ray Visualizer 则把阴影射线画成线框。它适合用来排查噪声来源:如果某个像素只收到一两根射线,软阴影就会斑驳。解决方法是提高该区域的样本预算,或者把影响过大的光源拆小。老陈用 Ray Visualizer 验证了招牌灯的半影宽度,确认 Ray Tracing 的软阴影已经够真实,不需要额外开 VSM。
老陈的周末:从骂街到收工
酒吧关卡最后验收那天,老陈把版本发给我。我打开 Stat GPU,MegaLights pass 稳定在 4.2ms,整帧在 PS5 性能模式下跑到 62fps。他原来预估至少要砍一半灯,结果一盏没删。
这个转变里最值钱的部分不是帧率数字,而是工作流。灯光师可以在编辑器里拖动一百盏灯,实时看到软阴影和半影变化。策划想换个招牌位置,不用等烘焙,改完直接跑。老陈在群里发了一句话:「以后谁再让我分 Stationary Light,我就把这关丢给他做。」
当然,MegaLights 也不是万能药。它不支持定向光,半透明、水体、毛发和次表面散射厚度估计会回退到旧管线。室外以阳光为主的大场景,仍然要靠传统级联阴影和 Lumen。室内、夜景、复杂人工光源,才是它的主场。
我的实际建议很简单:
- 先做一个小关卡试点,量一下同屏能扛多少盏灯。
- 光源分级 still 有必要。关键光走传统动态,中远景和装饰光走 MegaLights,纯发光体不开阴影。
- 把
r.MegaLights.Allowed放进项目默认配置,避免美术忘记勾选。 - profiling 时关掉
r.RDG.AsyncCompute,否则 GPU 计时是假的。 - Ray Visualizer 和 Light Finder 应该写进团队检查清单,比盯着帧率数字更有效。
UE5.8 把 MegaLights 从一个「看起来很炫的实验功能」变成了能放进生产管线的工具。对客户端程序和灯光师来说,这意味着少写很多剔除代码,少调很多烘焙参数。老陈周末终于能睡个好觉了。
采样预算、时域滤波和噪声控制
MegaLights 的噪声问题在 UE5.5 实验阶段经常被吐槽。酒吧关卡里霓虹灯密集,阴影边缘容易出现颗粒感,尤其在镜头缓慢移动时像撒了一把盐。UE5.8 的改进里,噪声下降是最明显的一项。
降噪声的核心是两件事:固定的每像素样本预算和时域累积。每帧发出的射线数量被限制在预算内,只靠空间采样必然不够干净,所以引擎会把前几帧的结果 blend 进来。只要画面运动不大,累积几帧之后软阴影就稳定了。
这个机制有个副作用。角色快速穿过灯光前面,或者镜头大角度甩动时,时域缓存会失效,颗粒感又会冒出来。老陈的处理方式是把动态物体的运动模糊和 TAA 调得更积极一点,让噪点在视觉上被掩盖。静态镜头则完全没有这个问题。
样本预算也不是越高越好。酒吧关卡最初我把每像素样本数拉得很高,帧时间涨了将近两毫秒,噪声改善却并不明显。后来换成先减少单盏大区域光的尺寸,把一盏长条灯拆成三盏小灯,样本分布更均匀,画质反而更干净。这说明 MegaLights 不怕灯多,怕的是单盏灯覆盖的屏幕面积过大。
还有一点常被忽略:Light Function 在 MegaLights 下是支持的,但必须开启光线功能图集。Alpha Masking 也能通过 r.MegaLights.HardwareRayTracing.EvaluateMaterialMode 1 启用,只是这条路的 GPU 开销非常重。老陈的酒吧里有很多铁丝网和镂空招牌,他最后选择把镂空部分做成几何体切片,而不是依赖材质 Alpha。省下来的性能多给了两排背景灯。
团队规范:别让美术放飞自我
工具再好用,也需要规则兜底。我们在项目里定了几条内部规范,写进了灯光文档。
第一,所有室内人工光源默认勾选 Use MegaLights,阳光和月光保持传统动态光。这样室外室内不会互相干扰。
第二,RectLight 的长宽比控制在四比一以内。过于狭长的灯会让 froxel 求交效率变差,阴影质量也不稳定。
第三,单盏灯的半径范围不要覆盖超过屏幕三分之一。影响范围太大的光源会被 Light Guide 强制降权,反而容易出现闪烁。
第四,每周末跑一次 Light Finder 扫描,检查有没有被错误计入 froxel 的灯。这个检查在五分钟内能找出百分之九十的异常光源。
第五,性能预算写死:PS5 性能模式下 MegaLights pass 不能超过 5ms,质量模式不能超过 8ms。超出预算的关卡必须砍灯或拆关卡流。
这些规则听起来很琐碎,但老陈说:「有预算才有自由。以前不是不让放灯,是放了不知道代价。现在至少知道钱花在哪。」
和 Lumen Medium Quality 搭伙
UE5.8 还有一个容易被忽略的改动:Lumen 新增了 Medium Quality 模式。它用辐照场加探针遮蔽取代了一部分全分辨率追踪,速度是 High Quality 的两倍。对 Nintendo Switch 2 这样的掌机,Epic 直接把它当成默认 GI 方案。
酒吧关卡在 PS5 上用的是 High Quality Lumen,因为室内反射比较复杂。后来我们把 Lumen 降到 Medium,把省出来的 GPU 时间全砸给 MegaLights。结果间接光的质量损失很小,直接光的密度却上去了。最后同屏一百四十盏动态阴影灯,帧率稳定在六十一帧左右。
这个组合的关键是分工明确。Lumen 负责间接光和颜色反弹,MegaLights 负责直接光和区域软阴影。不要指望 MegaLights 去补间接光,也不要让 Lumen 去扛高密度点光源。两者各干各的,性能预算才能算清楚。
如果你的项目反射要求极高,比如大面积镜面地面,那 Lumen High Quality 还是值得保留。普通室内、街道、商场这类场景,Medium 加 MegaLights 的组合基本够用了。
写到这里,我想再补一个数字。酒吧关卡的最终版本里有一百四十七盏灯,其中一百一十二盏走 MegaLights,十七盏走传统动态光,剩下十八盏是 no-shadow 装饰光。这个比例不是拍脑袋定的,而是 Light Finder 和 Ray Visualizer 一轮轮筛出来的。工具的价值不是让规则消失,而是让规则有据可查。
如果你正在评估要不要在项目中启用 MegaLights,我的建议是先拿一个灯光最复杂的关卡做试点。把 baseline 帧率、同屏灯数、阴影 pass 时间记下来,再打开 MegaLights 复测一遍。数字会说话,比任何宣传都靠谱。
老陈现在每次看到那关卡的帧率曲线,都会截个图发群里。这大概就是 UE5.8 最让人踏实的地方:炫技的成分少了,能交付的成分多了。