Lighting Passes

10 minute read

← Back to all passes

Lighting can often be the heaviest part of the frame. This is especially likely if your project relies on dynamic, shadowed light sources.

Direct lighting

ComputeLightGrid

Responsible for:

  • Optimizing lighting in forward shading

Cost affected by:

  • Number of lights
  • Number of reflection capture actors

According to the comment in Unreal’s source code1, this pass “culls local lights to a grid in frustum space”. In other words: it assigns lights to cells in a grid (shaped like a pyramid along camera view). This operation has a cost of its own but it pays off later, making it faster to determine which lights affect which meshes2.

Lights → NonShadowedLights

Responsible for:

  • Lights in deferred rendering that don’t cast shadows

Cost affected by:

  • Rendering resolution
  • Number of movable and stationary lights
  • Radius of lights

This pass renders lighting for the deferred shading – the default method by which Unreal handles lights and materials. Please read how it works – and how is it different from the other mode, forward rendering – in the relevant chapter.

Optimization

To control the cost of non-shadowed lights, use each light’s Attenuation Distance and keep their total number in check. The time it takes to calculate all lighting can be simply derived from the number of pixels affected by each light. Occluded areas (as well as off-screen) do not count, as they’re not visible to the camera. When lights’ radii overlap, some pixels will be processed more than once. It’s called overdraw and can be visualized in the viewport with Optimization Viewmodes → Light Complexity.

Static lights don’t count to the overdraw, because they’re stored as precomputed (aka baked) lightmaps. They are not considered being “lights” anymore. If you’re using baked lighting in your project (and most probably you do), set the mobility of all lights to Static, except for some most important ones.

Shadows

Lights → ShadowedLights

Responsible for:

  • Lights that cast dynamic shadows

Cost affected by:

  • Rendering resolution
  • Number of movable and stationary lights
  • Radius of lights
  • Triangle count of shadow-casting meshes

Description TODO.

ShadowDepths

Responsible for:

  • Generating depth maps for shadow-casting lights

Cost affected by:

  • Number and range of shadow-casting lights
  • Number and triangle count of movable shadow-casting meshes
  • Shadow quality settings
Figure: Shadow-casting spot light

ShadowDepths pass generates depth information for shadow-casting lights. It’s like rendering the scene’s depth from each light’s point of view. The result is a texture, aka depth map.3

Then the engine calculates the distance of each pixel to the light source from the camera point of view – but still in light’s coordinate space. By comparing this value with the depth map, during the ShadowProjection pass, it can test whether a pixel is lit by the given light or is in shadow.

Figure: Left: the scene from light’s point of view. Right: Approximate frustums of spot lights

The cost of it is mostly affected by the number and the range of shadow-casting lights. What also matters is the number and triangle count of movable shadow-casting objects. Depending on the combination, you can have static meshes and static lights – then the cost is zero, because static lights are just textures: they’re precomputed. But you can also have, for example, stationary or movable lights and static or movable objects in their range. Both of these combinations require the engine to generate shadows from such meshes, separately for each light.

Optimization

Shadows are usually one of the most heavy parts of rendering, if care is not taken. The easiest way to control their performance hit is to disable shadows for every light source that doesn’t need them. Very often you’ll find that you can get away with the lack of shadows, without the player noticing it, especially for sources that stay far away from the area of player’s movement. It can be also a good workaround to enable shadow-casting just for a single lamp in a bigger group of lights.

For shadowed lights, the biggest factor is the amount of polygons that will have to be rendered into a depth map. Light source’s Attenuation Radius sets the hard limit for the drawing range. Shadowed spot lights are typically less heavy than point lights, because their volume is a cone, not a full sphere. Their additional setting is Outer Cone Angle – the smaller, the better for rendering speed, because probably less object will fall into this lamp’s volume.

Directional light features a multi-resolution method of rendering shadows, called cascaded shadow maps. It’s needed because of the huge area a directional light (usually the sun) has to cover. Epic’s wiki provides tips for using CSMs. The most significant gains come from Dynamic Shadow Distance parameter. The quality in close distance to the camera can be adujsted with Cascade Distribution Exponent, at the expense of farther planes.

To control the resolution of shadows, use sg.ShadowQuality X in the INI files, where X is a number between 0 and 4. High resolution shadows can reduce visible jagging (aliasing), at the cost of rendering, storing and reading back bigger depth maps.

ShadowProjection

Responsible for:

  • Final rendering of shadows4

Cost affected by:

  • Rendering resolution
  • Number and range of shadow-casting lights (movable and stationary)
  • Translucency lighting volume resolution

Note: In GPU Visualizer it’s shown per light, in Light category. In stat gpu it’s a separate total number.

Shadow projection is the final rendering of shadows. This process reads the depth map and compares it with the scene, to detect which areas lie in shadow.

Optimization

Unlike ShadowDepths, ShadowProjection’s performance is also dependent on the final rendering resolution of the game. All the advice from other shadow passes applies here as well.

Indirect lighting

LightCompositionTasks_PreLighting

Responsible for:

  • Screen-space ambient occlusion
  • Decals (non-DBuffer type)

Cost affected by:

  • Rendering resolution
  • Ambient occlusion radius and fade out distance
  • Number of decals (excluding DBuffer decals)
Figure: Screen-space ambient occlusion

Ambient occlusion is a post process operation (with optional static, precomputed part). It takes the information about the scene from the G-Buffer and the hierarchical Z-Buffer. Thanks to that, it can perform all calculations in screen space, avoiding the need for querying any 3D geometry. However, it’s not listed in the PostProcessing category. Instead, you can find it in LightCompositionTasks. The full names of its sub-passes in the profiler – something similar to AmbientOcclusionPS (2880x1620) Upsample=1 – reveal additional information. The half-resolution (1440x810 Upsample=0) is used for doing all the math, for performance reasons. Then the result is simply upscaled to the full resolution.

The LightCompositionTasks_PreLighting pass has also to work on decals. The greater the number of decals (of standard type, not DBuffer decals), the longer it takes to compute it.

Optimization

The cost of this pass is mostly affected by the rendering resolution. You can also control ambient occlusion radius and fade out distance. The radius can be overriden by using a Postprocess Volume and changing its settings. Ambient occlusion’s intensity doesn’t have any influence on performance, but the radius does. And in the volume’s Advanced drop-down category you can also set Fade Out Distance, changing its maximum range from the camera. Keeping it short matters for performance of LightCompositionTasks.

CompositionAfterLighting

Responsible for:

  • Subsurface scattering (SSS) of Subsurface Profile type.

Cost affected by:

  • Rendering resolution
  • Screen area covered by materials with SSS

Note: In stat gpu it’s called CompositionPostLighting.

There are two types of subsurface scattering in Unreal’s materials. The older one is a very simple trick of softening the diffuse part of lighting. The newer one, called Subsurface Profile, is much more sophisticated. The time shown in CompositionAfterLighting refers to the latter. This kind of SSS accounts for the thickness of objects using a separate buffer. It comes with a cost of approximating the thickness of geometry in real-time, then of using the buffer in shaders.

Optimization

To reduce the cost, you have to limit the amount of objects using the new SSS and keep their total screen-space area in check. You can also use the fake method or disable SSS in lower levels of detail (LOD).

Translucency and its lighting

Translucency

Responsible for:

  • Rendering translucent materials
  • Lighting of materials that use Surface ForwardShading.

Cost affected by:

  • Rendering resolution
  • Total pixel area of translucent polygons
  • Overdraw
  • If Surface ForwardShading enabled in material: Number and radius of lights

Note: In stat gpu there are only two categories: Translucency and Translucent Lighting. In GPU Visualizer (and in text logs) the work on translucency is split into more fine-grained statistics.

Description TODO. This is basically the base pass for translucent materials. Most of the advice regaring the base pass applies here as well.

Translucent Lighting

Responsible for:

  • Creating a global volumetric texture used to simplify lighting of translucent meshes

Cost affected by:

  • Translucency lighting volume resolution
  • Number of lights with Affect Translucent Lighting enabled

Lighting of translucent objects is not computed directly. Instead, a pyramid-shaped grid is calculated from the view of the camera. It’s done to speed up rendering of lit translucency. This kind of caching makes use of the assumption that translucent materials are usually used for particles, volumetric effects – and as such don’t require precise lighting.

You can see the effect when changing a material’s Blend Mode to Translucent. The object will now have more simplified lighting. Much more simplified, actually, compared to opaque materials. The volumetric cache can be avoided by using a more costly shading mode, Surface ForwardShading.

The resolution of the volume can be controlled with r.TranslucencyLightingVolumeDim xx. Reasonable values seem to fit between 16 and 128, with 64 being a default (as of UE 4.17). It can be also disabled entirely with r.TranslucentLightingVolume 0

In GPU Visualizer, the statistic is split into ClearTranslucentVolumeLighting and FilterTranslucentVolume. The latter performs filtering (smoothing out) of volume5, probably to prevent potential aliasing issues. The behavior can be disabled for performance with r.TranslucencyVolumeBlur 0 (default is 1).

You can also exclude individual lights from being rendered into the volume. It’s done by disabling Affect Translucent Lighting setting in light’s properties. The actual cost of a single light can be found in GPU Visualizer’s Light pass, in every light’s drop-down list, under names like InjectNonShadowedTranslucentLighting.

Fog, ExponentialHeightFog

Responsible for:

  • Rendering the fog of Exponential Height Fog type6

Cost affected by:

  • Rendering resolution
Figure: Bottom to top: 1. Fog with volumetric lighting enabled. 2. Standard exponential fog. 3. No fog.

Description TODO.

Reflections

Figure: Results of ReflectionEnvironment and ScreenSpaceReflections passes, combined

ReflectionEnvironment

Responsible for:

  • Reading and blending reflection capture actors’ results into a full-screen reflection buffer

Cost affected by:

  • Number and radius of reflection capture actors
  • Rendering resolution

Reads reflection maps from Sphere and Box Reflection Capture actors and blends them into a full-screen reflection buffer.

All reflection capture probes are 128x128 pixel images. You can change this dimension in Project Settings. They are stored as a single big array of textures. That’s the reason you can have only 341 such probes loaded in the world at once7.

ScreenSpaceReflections

Responsible for:

  • Real-time dynamic reflections
  • Done in post process using a screen-space ray tracing technique

Cost affected by:

  • Rendering resolution
  • Quality settings

Description TODO.

Optimization

The general quality setting is r.SSR.Quality n, where n is a number between 0 and 4.

Their use can be limited to surfaces having roughness below certain threshold. This helps with performance, because big roughness causes the rays to spread over wider angle, increasing the cost. To set the threshold, use r.SSR.MaxRoughness x, with x being a float number between 0.0 and 1.0.

Footnotes