Light Evaluation Overview — The Three-Tier System光照计算概览 — 三层系统

UE5's deferred renderer evaluates local lights (point, spot, rect) in a layered architecture that separates lights by type, count, and quality requirement. Understanding this is essential for performance work — "too many lights" is almost always a problem in a specific tier, not globally.

UE5 的延迟渲染器以分层架构计算局部灯光(点光、聚光、矩形光), 按类型、数量和质量需求分离灯光。理解这一点对性能工作至关重要—— "灯光太多"几乎总是某个特定层级的问题,而非全局性问题。

UE5 Lighting Evaluation Tiers (executed in this order): Tier 1 — Directional Lights (always deferred, up to 4) └── DeferredLightingCommon.usf → SimpleDynamicLighting path Special-cased: cascaded shadow maps, sky light, atmosphere Tier 2 — Clustered Deferred (all local lights via light grid) └── ClusteredDeferredShadingPass.cpp ├── Light grid injection (LightGridInjection.cpp) │ CPU culls lights → GPU froxel assignment compute └── Shading pass: for each pixel, iterate only lights in its froxel Handles: point, spot, rect lights Shadows: pre-rendered shadowmaps (VSM or conventional) Tier 3 — MegaLights (UE 5.5+ GPU ray-traced many-light, optional) └── MegaLights/MegaLights.cpp Stochastic sampling of a large light pool via 1 ray/pixel/light Enables hundreds of shadow-casting lights at acceptable cost

Light Classification — CPU Culling Before GPU灯光分类 — GPU 前的 CPU 剔除

Before any GPU work happens, the CPU culls lights against the view frustum and sorts them by type. This happens in FDeferredShadingSceneRenderer::GatherLightsAndComputeVisibility(). Each visible light is placed into one of several buckets:

在任何 GPU 工作发生之前,CPU 针对视图锥体剔除灯光并按类型排序。 这在 FDeferredShadingSceneRenderer::GatherLightsAndComputeVisibility() 中发生。 每个可见灯光被放入以下几个桶之一:

Bucket Content内容 Rendered via渲染方式
DirectionalLightsUp to 4 directional lights, Skylight最多 4 个方向光、天空光Full-screen quad per light每个灯光全屏四边形
SimpleLightsCPU particle lights (no shadow)CPU 粒子灯光(无阴影)Injected into light grid, clustered pass注入灯光网格,集群通道
ClusteredLightsAll local lights: point/spot/rect所有局部灯光:点/聚/矩形Clustered deferred pass via froxel grid通过 Froxel 网格的集群延迟通道
ShadowedLightsLocal lights with shadow maps带阴影贴图的局部灯光Shadow rendered first, then combined in clustered pass先渲染阴影,再在集群通道中合并
// FSceneRenderer::PrepareViewStateForCapture (simplified concept)
// Actual: GatherLightsAndComputeVisibility in DeferredShadingRenderer.cpp

for (auto& LightProxy : Scene->Lights)
{
    if (!LightProxy.AffectsBounds(ViewFrustum)) continue; // frustum cull

    if (LightProxy.LightType == LightType_Directional)
        DirectionalLights.Add(LightProxy);
    else if (LightProxy.ShouldRenderWithMegaLights())
        MegaLightsPool.Add(LightProxy);     // UE 5.5+
    else
        ClusteredLights.Add(LightProxy);    // goes into froxel grid
}

Light Grid — Frustum Voxels (Froxels)灯光网格 — 视锥体体素(Froxel)

The light grid is the acceleration structure that prevents the GPU from evaluating every light for every pixel. The camera frustum is divided into a 3D grid of froxels (frustum voxels). For each cell, a compute shader determines which lights overlap it. During shading, each pixel only iterates the lights in its froxel — not all lights in the scene.

灯光网格是防止 GPU 对每个像素计算每个灯光的加速结构。 相机视锥体被划分为Froxel(视锥体体素)的 3D 网格。 对于每个单元格,计算 Shader 确定哪些灯光与之重叠。 着色时,每个像素只迭代其 Froxel 中的灯光——而不是场景中的所有灯光。

Why "froxels" and not a uniform grid? A uniform 3D world grid wastes resolution near the camera (where you need the most detail) and lacks resolution far away. Froxels are frustum-aligned: they are small near the camera and larger in the distance, matching the perspective projection and giving better light assignment granularity where it matters most. 为什么是"Froxel"而不是均匀网格?均匀 3D 世界网格在相机附近浪费分辨率(这里你需要最多细节),在远处又缺乏分辨率。Froxel 与视锥体对齐:在相机附近小,在远处大,与透视投影匹配,在最重要的地方提供更好的灯光分配粒度。
Light Grid Structure (LightGridInjection.cpp): Froxel grid dimensions (default): 16 × 16 × 32 cells ┌──────────────────────────────────────────────────┐ │ Screen X: 16 tiles (each ~120px at 1920 wide) │ │ Screen Y: 16 tiles │ │ Depth Z : 32 slices (logarithmic distribution) │ │ — Slice 0: near plane (0.1m) │ │ — Slice 31: far plane (e.g., 10km) │ │ — Each slice 2× deeper than previous │ └──────────────────────────────────────────────────┘ Per-froxel: list of light indices (up to 32 lights/cell) Stored in: RWStructuredBuffer LightGrid (on GPU) Step 1: CullLightsCS — for each light sphere/cone, determine which froxels it overlaps (AABB test) Step 2: BuildLightListCS — write light indices into froxel lists Step 3: Shading pixel reads: uint Start, Count = LightGrid[FroxelIndex]; for (i in [Start, Start+Count]) evaluate Light[i]
// LightGridInjection.cpp — ComputeLightGrid() overview

// Pass 1: Clear the light grid (per-froxel count buffer)
AddClearUAVPass(GraphBuilder, LightGridBuffer, 0);

// Pass 2: CullLightsCS — determine which lights touch which froxels
// Each thread = one froxel. Tests each light's AABB/sphere vs. froxel.
TShaderMapRef<FCullLightsCS> CullShader(...);
GraphBuilder.AddPass(RDG_EVENT_NAME("LightGridCull"), Params,
    ERDGPassFlags::Compute, [=](auto& RHICmdList) {
        FComputeShaderUtils::Dispatch(RHICmdList, CullShader, *Params,
            FIntVector(GridX, GridY, GridZ)); // one thread per froxel
    });

// Pass 3: Build light lists — scatter light indices into froxel list buffer
TShaderMapRef<FBuildLightListCS> BuildShader(...);
// Output: LightGrid[froxelIdx] = (offset, count) into LightList[]

Clustered Deferred Shading Pass集群延迟着色通道

Once the light grid is built, the clustered deferred shading pass iterates every pixel of the GBuffer and applies all lights in that pixel's froxel. This is a single compute dispatch that replaces the old approach of rendering a sphere/cone mesh for every light.

一旦灯光网格建立,集群延迟着色通道遍历 GBuffer 的每个像素, 并应用该像素 Froxel 中的所有灯光。这是一次单一的计算调度, 取代了以前为每个灯光渲染球体/锥体网格的旧方法。

// ClusteredDeferredShadingPass.cpp — RenderClusteredDeferredLighting()

void FDeferredShadingSceneRenderer::RenderClusteredDeferredLighting(
    FRDGBuilder& GraphBuilder,
    FSceneTextures& SceneTextures,
    FRDGBufferRef LightGridBuffer, FRDGBufferRef LightList)
{
    auto* PassParams = GraphBuilder.AllocParameters<FClusteredShadingCS::FParameters>();
    PassParams->GBufferA       = SceneTextures.GBufferA;   // normal + shading model
    PassParams->GBufferB       = SceneTextures.GBufferB;   // roughness, metallic
    PassParams->SceneDepth     = SceneTextures.Depth;
    PassParams->LightGrid      = GraphBuilder.CreateSRV(LightGridBuffer);
    PassParams->LightDataBuffer= GraphBuilder.CreateSRV(LightDataBuffer);
    PassParams->OutDiffuseColor= GraphBuilder.CreateUAV(SceneColor);

    // One thread per pixel, grouped 8×8
    FComputeShaderUtils::AddFullscreenPass(
        GraphBuilder, ShaderMap,
        RDG_EVENT_NAME("ClusteredDeferredLighting"),
        Shader, PassParams,
        FIntVector(FMath::DivideAndRoundUp(Width, 8),
                   FMath::DivideAndRoundUp(Height, 8), 1));
}

// Inside the shader (ClusteredDeferredShadingCommon.usf):
// uint FroxelIdx = ComputeFroxelIndex(PixelPos, Depth);
// uint LightOffset, LightCount = LightGrid[FroxelIdx];
// for (uint i = 0; i < LightCount; ++i) {
//     FDeferredLightData Light = LightDataBuffer[LightList[LightOffset + i]];
//     Lighting += EvaluateLight(GBuffer, Light, ShadowMap);
// }

MegaLights — GPU Ray-Traced Many-Light System (UE 5.5+)MegaLights — GPU 光追多灯光系统(UE 5.5+)

MegaLights is a new system introduced in UE 5.5 that extends the lighting architecture to support hundreds or thousands of shadow-casting local lights in a scene — something previously impossible without unacceptable frame time. It uses stochastic sampling: instead of evaluating all N lights per pixel, each pixel traces 1 ray toward a probabilistically sampled light, and the result is denoised over multiple frames.

MegaLights 是 UE 5.5 中引入的新系统,将光照架构扩展为支持场景中 数百甚至数千个投影局部灯光——这在之前以可接受的帧时间是不可能实现的。 它使用随机采样:不是对每个像素计算所有 N 个灯光, 而是每个像素向一个概率采样的灯光追踪 1 条光线,结果在多帧上降噪。

MegaLights Pipeline (MegaLights/MegaLights.cpp): 1. Build candidate light list per pixel (importance sampling): - Sample N candidate lights based on luminance × solid angle - Use ReSTIR: Reservoir-based Spatio-Temporal Resampling → keeps statistically good samples across frames 2. Trace shadow ray for chosen candidate (1 ray/pixel): - Hardware RT: trace against TLAS (most accurate) - Software: stochastic shadow map lookup (fallback) 3. Accumulate in reservoir buffer (temporal history) - Spatial reuse: share reservoirs with 4 neighbors - Temporal reuse: reproject previous frame reservoirs 4. Denoise → integrate into scene color - SVGF-style spatiotemporal filter - Result: stable, low-noise many-light illumination

MegaLights integrates with Virtual Shadow Maps: each MegaLight automatically gets a VSM page allocation so that shadows are correct and cached. The key enabling technology is that VSM already caches static geometry shadows, so MegaLights only needs to re-evaluate light pages when something moves.

MegaLights 与虚拟阴影贴图集成:每个 MegaLight 自动获得 VSM 页面分配以使阴影正确并被缓存。 关键使能技术是 VSM 已经缓存了静态几何体阴影,所以 MegaLights 只需在有物体移动时重新计算灯光页面。

// Enabling MegaLights (Project Settings or per-light):

// Project Settings → Rendering → MegaLights
r.MegaLights=1                       // Enable MegaLights globally
r.MegaLights.ShadowsEnabled=1        // Enable shadow casting for MegaLights
r.MegaLights.SamplesPerPixel=1       // Rays per pixel (1 = default, 2 = higher quality)
r.MegaLights.UseHardwareRayTracing=1 // Use HWRT for shadow rays (more accurate)

// Per-light in Blueprint/details panel:
// PointLight → Details → "Mega Lights" → Enable for this light
// Only lights marked as MegaLights participate in the many-light pool

// stat MegaLights — shows light pool size, rays/frame, denoiser cost
MegaLights vs. Lumen: MegaLights is a direct lighting system — it evaluates the primary illumination from many lights. Lumen is an indirect lighting system — it computes the light that has bounced. They are complementary: MegaLights handles the "many direct lights" case; Lumen handles the "indirect bounced light" case. Both can be active simultaneously. MegaLights 与 Lumen 的区别:MegaLights 是直接光照系统——它计算来自多个灯光的主要照明。Lumen 是间接光照系统——它计算已经反弹的光线。它们是互补的:MegaLights 处理"多个直接灯光"的情况;Lumen 处理"间接反弹光"的情况。两者可以同时激活。

Light Functions & IES Profiles灯光函数与 IES 配置文件

Light Functions let you mask or modulate a light's intensity using a Material — think of a stained-glass window casting colored shadows, a flickering fire light, or a flashlight with a non-circular beam. IES Profiles are measured real-world light distribution curves that define how light falls off in different directions.

灯光函数让你用材质遮罩或调制灯光强度——想象彩色玻璃窗投射的彩色阴影、 闪烁的火焰灯光,或具有非圆形光束的手电筒。 IES 配置文件是测量的真实世界灯光分布曲线,定义灯光在不同方向上的衰减方式。

Light Function Atlas (UE 5.2+)灯光函数图集(UE 5.2+)

Prior to the Light Function Atlas, each light function material required a separate full-screen render pass. With the atlas system (LightFunctionAtlas.h/.cpp), all light functions are rendered into a shared 2D texture atlas, and the clustered shading pass reads them in a single indirection. This enables dozens of lights with unique light functions at nearly zero per-light overhead.

在灯光函数图集之前,每个灯光函数材质需要单独的全屏渲染通道。 有了图集系统(LightFunctionAtlas.h/.cpp),所有灯光函数被渲染到共享的 2D 纹理图集中, 集群着色通道通过单次间接访问读取它们。这使得具有独特灯光函数的数十个灯光的每灯光开销几乎为零。

// LightFunctionRendering.cpp — RenderLightFunctionForMaterial()
// Each unique light function material → rendered into an atlas tile
// Atlas tile UV is stored in the light data buffer

// IES profile → stored as a 1D texture (luminous intensity vs angle)
// IESTextureManager.cpp: manages the IES texture array (up to 64 profiles)
// Clustered shading reads: LightData.IESTextureIndex → sample IES texture

// Usage in Blueprint/Editor:
// PointLight → Details → Light → Light Function Material (any Material)
// PointLight → Details → Light → IES Texture (import .ies file)

// Console: r.LightFunctionAtlas=1 (default enabled in 5.2+)

Forward Light Loop (Desktop & Mobile)前向灯光循环(桌面端与移动端)

For forward-rendered objects (translucent materials, forward renderer on mobile), the light loop works differently. Instead of a deferred pass, the surface shader itself loops over lights. UE5 uses the same froxel grid to supply lights to the forward loop, so complexity is still O(lights-in-froxel) not O(all-lights).

对于前向渲染的对象(半透明材质、移动端前向渲染器),灯光循环的工作方式不同。 不是延迟通道,而是表面 Shader 本身循环遍历灯光。 UE5 使用相同的 Froxel 网格为前向循环提供灯光, 因此复杂度仍然是 O(froxel 中的灯光数量),而不是 O(所有灯光)。

// ForwardLightingCommon.ush — Desktop forward lighting loop

float3 GetForwardDirectLighting(FMaterialPixelParameters Params, ...)
{
    float3 Color = 0;

    // Get froxel index for this pixel
    uint FroxelIndex = ComputeFroxelIndex(Params.SvPosition, Params.ScreenPosition.w);

    // Read light count and offset from the grid
    uint LightDataOffset, LocalLightCount;
    GetLocalLightCountAndOffset(FroxelIndex, LightDataOffset, LocalLightCount);

    // Evaluate each local light in this froxel
    LOOP for (uint i = 0; i < LocalLightCount && i < MAX_LIGHTS_PER_PIXEL; ++i)
    {
        FDeferredLightData LightData = LoadLocalLightData(LightDataOffset + i);
        Color += EvaluateLocalLight(Params, LightData);
    }

    // Also evaluate directional lights (always applied)
    Color += EvaluateDirectionalLight(Params, DirectionalLightData);
    return Color;
}

Stochastic Lighting — Dynamic Indirect Many-Light随机光照 — 动态间接多灯光

Stochastic Lighting (StochasticLighting/) is a complementary system to MegaLights focused on indirect many-light illumination. Where Lumen provides physically-based GI from surfaces, Stochastic Lighting uses a ReSTIR-based approach to evaluate indirect illumination from a large pool of dynamic lights, including emissive materials. This handles scenarios like a scene lit mostly by Niagara particle emitters or dense light grids.

随机光照StochasticLighting/)是 MegaLights 的补充系统, 专注于间接多灯光照明。Lumen 从表面提供基于物理的 GI, 而随机光照使用基于 ReSTIR 的方法,从大量动态灯光池(包括自发光材质)计算间接照明。 这处理了主要由 Niagara 粒子发射器或密集灯光网格照亮的场景。

Performance Tuning性能调优

CVarEffect效果
r.TiledDeferredShading=1Enable tiled/clustered deferred (should always be 1)启用分块/集群延迟(应始终为 1)
r.TiledDeferredShading.MinimumCountMin lights to switch from per-light pass to clustered (default 80)从逐灯光通道切换到集群的最小灯光数(默认 80)
r.LightFunctionAtlasUse light function atlas (1 = enabled, reduces render passes)使用灯光函数图集(1 = 启用,减少渲染通道数)
r.MegaLightsEnable MegaLights for many shadow-casting lights为大量投影灯光启用 MegaLights
r.MegaLights.SamplesPerPixel1 = fastest, 2 = better quality but 2× cost1 = 最快,2 = 质量更好但成本翻倍
r.CapsuleShadowsEnable capsule contact shadows from skeletal meshes从骨骼网格启用胶囊体接触阴影
showflag.LightGrid=1Visualize the froxel light grid in viewport在视口中可视化 Froxel 灯光网格
Practical tip — "Why is my scene slow with 100 lights?" Use stat SceneRendering to identify whether the bottleneck is in LightGrid injection, the clustered shading pass, or shadow map rendering. Often the real bottleneck is shadow maps, not the shading math — consider using MegaLights + VSM caching for scenes with many shadow-casting lights. 实用技巧 — "为什么 100 个灯光让我的场景变慢?"使用 stat SceneRendering 识别瓶颈是在灯光网格注入、集群着色通道还是阴影贴图渲染中。通常真正的瓶颈是阴影贴图而不是着色数学——对于有很多投影灯光的场景,考虑使用 MegaLights + VSM 缓存。

Source Navigation源码导航

All paths relative to Engine/Source/Runtime/Renderer/Private/

所有路径相对于 Engine/Source/Runtime/Renderer/Private/

Unreal Engine 5.6 · Engine/Source/Runtime/Renderer/Private/
Source analysis · For learning purposes