Virtual Shadow Maps (VSMs) is the new shadow mapping method used to deliver consistent, high-resolution shadowing that works with film-quality assets and large, dynamically lit open worlds using Unreal Engine 5's Nanite Virtualized Geometry, Lumen Global Illumination and Reflections, and World Partition features.
Goals of Virtual Shadow Maps
Virtual Shadow Maps have been developed with the following goals:
Significantly increase shadow resolution to match highly detailed Nanite geometry
Plausible soft shadows with reasonable, controllable performance costs
Provide a simple solution that works by default with limited amounts of adjustment needed
Replace the many Stationary Light shadowing techniques with a single, unified path
Conceptually, virtual shadow maps are just very high-resolution shadow maps. In their current implementation, they have a virtual resolution of 16k x 16k pixels. Clipmaps are used to increase resolution further for Directional Lights. To keep performance high at reasonable memory cost, VSMs split the shadow map into tiles (or Pages) that are 128x128 each. Pages are allocated and rendered only as needed to shade on-screen pixels based on an analysis of the depth buffer. The pages are cached between frames unless they are invalidated by moving objects or light, which further improves performance.
Nanite does not support many of the Stationary Light shadowing techniques, such as pre-shadows and per-object (or Inset) shadows. While some of the more dynamic parts of Stationary Light shadowing may work — such as the Cascaded Shadow Maps near the camera viewer — Nanite and Stationary Lights with conventional shadow maps are not supported. If you are using Nanite in your project, you must either use Movable Lights or use Virtual Shadow Maps.
Enabling Virtual Shadow Maps
In the Project Settings under Engine > Rendering in the Shadows section, you can set what Shadow Map Method your project supports, whether Virtual Shadow Maps or the conventional Shadow Maps that have been used in previous Unreal Engine releases.
Projects created with versions before Unreal Engine 5.0 will need to opt-in using the project setting, or console variable r.Shadow.Virtual.Enable. New projects use Virtual Shadow Maps by default.
Virtual Shadow Maps are designed to be used with Nanite. If your target platform or project does not use Nanite, Virtual Shadow Maps is likely not a good fit.
What happens with existing shadow methods when VSMs are enabled?
When VSMs are enabled, they replace a variety of existing shadow methods in Unreal Engine:
Stationary precomputed shadows, including 2D Distance Field and shadow factor "shadow maps"
Pre-shadows
Per-object / inset shadows
Cascaded Shadow Maps (CSMs)
Movable dynamic shadows for local lights
Fully baked shadows from Static Lights will still work as before (when not using Lumen). Their contributions are entirely represented in the baked lightmaps and there is no runtime lighting evaluated at all. Stationary lights will use the indirect diffuse contribution from any baked lightmaps, but their direct lighting and shadows are evaluated dynamically (the same as Movable lights) when VSMs are enabled.
Distance Field Shadows are not replaced, and can be used in tandem with Virtual Shadow Maps for Directional Lights. Distance Field Shadows take over for non-Nanite geometry beyond the dynamic shadowing distance of Movable Lights — set on a Directional Light using the Cascaded Shadow Maps property Dynamic Shadow Distance Movable Light. Using Distance Field Shadows provides a useful tool to reduce runtime costs in complex scenes, such as those with lots of non-Nanite foliage.
Nanite geometry always renders to the Virtual Shadow Map regardless of distance, since this is the most performant option and provides the highest quality. It is possible to make non-Nanite geometry behave the same way as Nanite by the console variable r.Shadow.Virtual.UseFarShadowCulling 0.
Local lights (Point and Spot Lights) are not affected, and selecting Distance Field shadows for these continues to override the active shadow map method.
Due to VSM's high resolution and accuracy, the screen space Contact Shadow feature controlled with the Contact Shadow Length property is no longer necessary to achieve sharp contact shadows. It still may have value when used to pick up cheaper shadows from objects set to not render into shadow maps but is not recommended otherwise as it will be less accurate than the shadows VSMs will create.
Ray-traced shadows still take precedence over VSMs as they generally provide the highest quality solution.
Soft Shadows with Shadow Map Ray Tracing
Shadow Map Ray Tracing (SMRT) is a sampling algorithm used with virtual shadow maps to produce more plausible soft shadows and contact hardening. Objects that cast shadows farther will have softer shadows than objects casting shadows closer to a shadow-receiving surface. For example, the mesh pictured below is tall and casts its shadow over a long distance. Shadows near the base are sharper than those farther away.
Past methods based on Percentage-Closer Filtering (PCF) would over-blur and reduce the visual impact of high-resolution geometry and shadows.
The SMRT algorithm works by shooting a number of rays towards the light source, but instead of evaluating intersections with geometry — like conventional raytracing does — a number of samples along the ray are projected and tested against the virtual shadow map to achieve soft shadowing and contact hardening. Shadow rays are distributed based on the light using Source Radius on local lights or Source Angle on Directional Lights.
Local lights have no Source Radius by default, compared to Directional Lights which start with a low Source Angle. When either are set with an appropriate value, SMRT produces real-time soft shadowing with contact hardening, like the example below using a Point Light with a Source Radius of 10.
Controlling Penumbra Shadow Quality
The penumbra shadow's softness and quality is set by console variables for both local and Directional Lights. They include their own scalability settings that are generally good for most scenes.
Noise in the penumbra is influenced by the number of rays being used, and both local and Directional Lights use eight rays by default when scalability for Shadows is set to Epic.
Use the console variables r.Shadow.Virtual.SMRT.RayCountLocal and r.Shadow.Virtual.SMRT.RayCountDirectional to adjust the number of rays. Fewer rays show visible noise in the penumbra. Setting the associated console variable to 0 disables SMRT and reverts to single-sample hard shadows.
Furthermore, the number of shadow samples taken along each ray's path can be set for both local and Directional Lights to control the maximum softness. Fewer shadow map samples are cheaper to render, but limit the amount of penumbra softness that can be achieved by the light's shadows.
For finer control than the Shadow scalability settings, and the console variables r.Shadow.Virtual.SMRT.SamplesPerRayLocal and r.Shadow.Virtual.SMRT.SamplesPerRayDirectional to adjust the number of samples used. Using values between 4 and 8 samples works well.
Dragging the slider will show what happens when a Directional Light with a Source Angle of 5.0 uses Shadow Map Samples that number 0, 2, and 8 (default).
Limitations of Shadow Map Ray Tracing
Quality of SMRT is generally good with the default setting, but there are limitations inherent to using the data from a single shadow map projection rather than testing against true geometry.
Penumbra Size Limits
The shadow's penumbra is clamped for local and Directional Lights to avoid sample divergence from the ray origin that can become increasingly "bent" compared to the ideal test along the ray itself. Using reasonable values for Source Radius on local lights and Source Angle on Directional Lights will avoid results that are too extreme, limiting the extent that the ray can diverge in various ways. Values that are too large can both impact performance and cause shadow penumbrae to visually warp as the camera gets near to them.
Local and Directional Lights can use the console variables r.Shadow.Virtual.SMRT.MaxRayAngleFromLight and r.Shadow.Virtual.SMRT.RayLengthScaleDirectional to loosen or tighten the clamped extents.
Inconsistent Penumbra
Since the Virtual Shadow Map only stores the first depth layer where naive iteration misses intersections with any occluders behind the first one, a variety of light leaking artifacts can happen where the occluders overlap. These types of light leaks are resolved using a gap-filling heuristic that extrapolates depths behind the first occluder based on depths seen before the point of occlusion.
Clipmaps for Directional Light
A single virtual shadow map does not provide enough resolution to cover large areas. Directional Lights use a clipmap structure of expanding ranges around the camera, with each clipmap level having its own 16K VSM. Each clipmap level is the same resolution but covers twice the radius of the previous one.
By default, clipmap levels 6 through 22 are allocated virtual shadow map page tables. This means that the default settings have the most detailed clipmap covering 64 cm (2^6 cm) from the camera position, and the broadest clipmap covering about 40 kilometers (2^22 cm). The cost of virtual clipmap levels is insignificant if nothing is present in them, so these defaults work well to cover very large scenes with fairly high resolution near the camera.
The first and last levels can be adjusted using the console variables r.Shadow.Virtual.Clipmap.FirstLevel and r.Shadow.Virtual.Clipmap.LastLevel.
The resolution allocated to a given pixel is a function of the distance away from the clipmap origin — the camera. This is set by Shadow scalability with the console variable r.Shadow.Virtual.ResolutionLodBiasDirectional. A value of 0 picks the resolution required based on the perspective projection of the camera.
Projective aliasing — when a shadow is cast on a surface almost parallel to the light direction — is still possible, even at high resolutions, but it can be reduced partly by biasing the resolution. Like Mip biasing in textures, lowering the value by -1 doubles the resolution of shadows with the associated performance tradeoffs.
Local Lights
Spot Lights use a single 16k VSM with a mip chain rather than clipmaps to handle shadow level of detail. Similarly, Point Lights use a cube map of 16k VSMs, one for each face.
Local lights provide a significant increase in resolution compared to traditional shadow maps. It is possible to run out of virtual resolution with very large local lights, and care should be taken to use Directional Lights where possible in these cases.
Appropriate mip levels are picked by projecting the size of the screen pixels into shadow map space. Like Directional Lights, it's possible to bias the resolution with the Shadow scalability settings, or using the console variable r.Shadow.Virtual.ResolutionLodBiasLocal.
Per-light resolution controls are not available but may be added in a future release.
Caching
Reusing shadow map pages from previous frames is key to maintaining high performance with Virtual Shadow Maps, especially in complex scenes. Caching is enabled by default, but can disabled for debugging purposes using the console variable r.Shadow.Virtual.Cache 0.
Since pages are only rendered for on-screen pixels, changing camera visibility due to movement of disocclusion can reveal new pages that need to be rendered. Generally, as long as camera movement is relatively smooth, it is not a major source of new pages. On the other hand, you should watch out for very fast movement near objects, large disocclusions, and camera cuts.
In most scenes, the largest source of work comes from shadow map pages that need to be redrawn due to changes in scene geometry. Common sources of cache page invalidations include:
Any light movement or rotation will invalidate all cached pages for that light
Geometry that casts shadows moving, or being added or removed from the scene will invalidate any pages that overlap its bounding box from the light's perspective.
This can include objects such as Blueprints setting properties on primitive components that trigger render state invalidation even though it does not actually move.
Geometry using materials that may modify mesh positions, such as World Position Offset (WPO) or Pixel Depth Offset (PDO).
Managing Cache Invalidations
Decreasing cache invalidations is best done by first visualizing the invalidations, then finding and reducing the things causing them. The Cached Page visualization is a good place to start.
In this visualization, pages that are fully cached are shaded green. Pages that are new or invalidated are shaded red. As you move the camera you should expect to see small rings of red near screen edges, clipmap boundaries and disoccluded geometry. With a static camera, most of the new pages will come from cache invalidations.
Once problem areas have been found, it is often additionally useful to inspect the objects and object bounds that are driving the invalidations using the ShadowCasters viewmode. Inspect these objects and bounds to ensure that they are indeed objects that are expected to invalidate shadows, and that their bounds are as tight as possible. Since all pages that an invalidating object could potentially overlap within its bounds must be invalidated, even moderately inflated bounds combined with low light angles can cause many unnecessary invalidations.
Invalidations that are entirely in the shadow of Nanite objects can be skipped, but this is not true for non-Nanite objects. For this reason it is particularly important to ensure that large shadow-casting objects — such as buildings — use Nanite.
You can trigger Virtual Shadow Map invalidation on Nanite LOD streaming changes with the console variable r.Nanite.VSMInvalidateOnLODDelta. Clusters that are not streamed into LOD matching the computed Nanite LOD estimate trigger VSM invalidation such that they are re-rendered when streaming completes. This functionality is experimental. We do not recommend shipping projects with Experimental features and this feature is subject to change.
You can override default engine behavior for shadow invalidations with the per-primitive enumeration class Shadow Cache Invalidation Behavior. This is useful to prevent invalidations from static World Position Offset (WPO). The options include:
Auto: (Default) Invalidates based on World Position Offset material and transform changes.
Always: Always invalidate shadows, can be used to flag a primitive that is using some method of animating that is not known to the system.
Rigid: Suppress invalidations that would otherwise be generated by, for example, World Position Offset.
Static: In addition to the Rigid behavior, also suppress invalidations due to transform changes. Add/Remove still triggers invalidations. If the primitive is moved or animated, the visual result is undefined.
Deformation and Foliage
Geometry that can be deformed using Skeletal animation, or materials using World Position Offset or Pixel Depth Offset always invalidates cached pages every frame. It is extremely important to ensure that these features are used carefully and bounds are kept under control.
In some cases, such as grass and sometimes foliage, using only Contact Shadows is a sufficient substitute for high resolution shadow maps. In cases where high detail shadows are necessary in the foreground, consider the following to help mitigate the performance cost:
Non-Nanite objects still respect the regular shadow CPU culling settings, such as
r.Shadow.RadiusThreshold. Use these to help control the cost of rendering these objects into Virtual Shadow Maps.Use Mesh LODs to switch to materials that do not use WPO/PDO at distances where the effect is no longer obvious. In some cases, it may be possible to switch off dynamic shadow casting for these objects in the distance and rely entirely on screen space Contact Shadows.
For Directional Lights, there are additional options available:
Distance Field Shadows take over for non-Nanite geometry beyond the Dynamic Shadow Distance Movable Light distance set by the Cascaded Shadow Maps section of the Light. Switching to Distance Field Shadows for non-Nanite in the distance can be a large performance improvement since this geometry does not have the fine-grained LOD scaling that Nanite provides.
Disabling Mesh Deformation at a Distance
Techniques such as World Position Offset (WPO), Pixel Depth Offset (PDO), tessellation, and skeletal animation are commonly used to animate or deform objects. Aside from the additional cost of calculating these displacements, they also impact the Virtual Shadow Map cache. Objects that animate need to be redrawn into the shadow map. When an object is far away from the camera, and its on-screen footprint is small, you can disable the animation to avoid these shadow map invalidations.
The World Position Offset Disable Distance property on primitives can be set to limit the distance at which WPO is applied.
The Shadow Cache Invalidation Behavior property on primitives can be used to ignore certain invalidation sources.
Displacement Fading can be used on materials.
When WPO is used, the actual displacement is not known until the object is rendered. This means the Virtual Shadow Map cache system has to assume the object needs to be redrawn, even if the actual displacement has not changed. Replacing WPO usage with skeletal animation or modifying the instance transform avoids this pitfall.
Separate Static Caching
Many scenes consist of a large amount of static geometry that never moves, together with significantly smaller amounts of dynamic (or movable) geometry. If cached together, this means that the relatively cheap dynamic geometry invalidates pages that then cause the expensive static geometry to be re-rendered, just to update the dynamic portion.
To better optimize in these cases, Virtual Shadow Maps stores what is effectively two copies of depth, one for each category of geometry. Thus, when the dynamic geometry moves, there is no need to redraw the static geometry. Instead, the cached static page can be cheaply composited on top. In cases, such as the Valley of the Ancients sample project, it can be a significant performance optimization as the static terrain is very expensive while the dynamic elements are relatively cheap.
What geometry is rendered into which "layer" (static versus dynamic) is determined at runtime, and does not primarily rely on object mobility. Generally, if a mesh is a static mesh and is set to Static mobility, it will be cached as static. If a mesh has Movable mobility but is not updated, it will be moved to the static cache.
The Virtual Shadow Map statistics are a good way to get a high level overview of how well static caching is working. In particular, the number of "invalidated" static pages should be near 0. Finding instances that are invalidating the static cache frequently and switching them to Movable is an important way to ensure the static cache remains valid.
Nanite includes an advanced visualization mode to help determine object mobility in the world, which is also useful for Virtual Shadow Maps.
Moving Lights
Stationary lights are largely cached and, as a result, can use a much higher resolution than moving lights. To account for this difference between stationary and moving lights, movable lights should have a different LOD bias than stationary lights. When a light stops moving, it does a gradual transition to the original bias. You can control the LOD bias for moving lights with the following scalability console variables:
r.Shadow.Virtual.ResolutionLodBiasLocalMovingr.Shadow.Virtual.ResolutionLodBiasDirectionalMoving
Coarse Pages
Depth buffer analysis is used as the primary method of marking pages that are needed to render. There are some systems that need to sample shadows at more arbitrary locations though, such as Volumetric Fog and forward-rendered translucency. Most systems only need low-resolution shadow data that gets filtered and blurred through other data structures.
To accommodate shadowing in these situations, Coarse Pages are marked to ensure that at least low-resolution shadow data is available across the whole domain for sampling. Local lights simply mark the root mip page while Directional Lights can mark a series of pages at various low detail levels to form some coarse clipmaps. Depending on the scene, coarse pages can create performance issues. This is particularly true for non-Nanite dynamic geometry since they are effectively rendering low-resolution shadows over large regions of space, which can result in draw-call bottlenecks.
Local light coarse pages can be toggled off with r.Shadow.Virtual.MarkCoarsePagesLocal, if not needed.
Directional Light coarse pages can be toggled off with r.Shadow.Virtual.MarkCoarsePagesDirectional, or the range of clipmap levels that coarse pages are marked in can be modified with r.Shadow.Virtual.FirstCoarseLevel and r.Shadow.Virtual.LastCoarseLevel.
Visualization
Visualization options for Virtual Shadow Maps are accessible from the Level Viewport using the View Modes drop-down menu and selecting Virtual Shadow Map.
Visualization options include:
| Visualization Name | Description |
|---|---|
Shadow Mask | The final shadow mask that is used by shading. |
Clipmap/Mip Level | The chosen clipmap (for Directional Lights) or mip (for Local Lights) level. |
Virtual Page | Visualization of the virtual page address. |
Cached Page | Cached pages are tinted green, uncached pages are red. Pages where only the static page is cached (dynamic uncached) are blue. |
Nanite Overdraw | Nanite overdraw into mapped pages. |
Shadow Casters | Visualize objects that cast shadows, and which type of shadow map they invalidate. This can be used to find which objects are invalidating the shadow map. |
By default, the Virtual Shadow Map visualizations display results for all lights at the same time. By selecting a light in the Outliner, you can view visualizations for that light.
You can also enable visualization using the console (except in Shipping builds), which is useful for profiling and debugging a live game. If any of these are set in the editor, they override any mode selection made through the editor's user interface.
| Visualization Name | Description |
|---|---|
| When the view mode visualization is set to Virtual Shadow Map through the Level Viewport or the console command, this command specifies which of the channels to display. Use the names below in place of "[mode]" to enable that visualization. Cache and vpage are two common ones used for visualization and none disables vsm visualization.
|
| Enables the Virtual Shadow Map visualization when a visualization mode is specified. |
| Enables the ShadowCasters visualization mode. |
| Choose a layout for the Virtual Shadow Map visualization.
|
| Outputs a list of the current lights with Virtual Shadow Maps to the console. In some build / runtime configurations, the names of the lights may be different from their names in the editor. |
| Specify a light by name, partial or full matches are accepted. To clear a light from being selected using this console variable, specify a light name that won't match any names. Using two quotation marks (") is an acceptable way of resetting it back to no specified light. |
| Select the next light for visualization. This can be useful when the light name is unknown, and you cannot select lights in the outliner, such as builds of the project. This command can also be bound to a key in the editor preferences. |
| Select the previous light for visualization. This can be useful when the light name is unknown, and you cannot select lights in the outliner, such as builds of the project. This command can also be bound to a key in the editor preferences. |
| Show the cached pages for all lights and hide uncached pages. By default, when viewing all lights in the "cache" viewmode, the colorization of cached pages is hidden to make the visualization easier to read. Enabling this option allows you to view cached pages explicitly. |
Enabling visualization modes has a small but measurable effect on the performance of Virtual Shadow Maps. Be sure to disable the visualization before performance profiling.
GPU Profiling and Optimization
Unreal Engine provides several tools that help you check performance in your projects:
The GPU Profiler (or platform-specific tool) is a good starting place to troubleshoot and debug performance issues.
Unreal Insights can also be used to take real-time measurements, both for CPU and GPU performance. Use
trace.enable counters, vsmto see advanced statistics under the Counters tab.VSM statistics can also be recorded and graphed using the CSV profiling system with
CsvCategory VSM enable.
There are two main performance buckets where VSM costs will show up: Shadow Depths and Shadow Projection (under Lights). The tradeoffs in each of their categories are relatively independent from one another.
GPU timings can be unreliable if GPU asynchronous compute is enabled. Disabling asynchronous compute with r.RDG.AsyncCompute 0 will give you a more representative view of the performance of each pass. This is recommended for profiling only, and should never be disabled in shipping builds.
Be aware that commands used to list statistics, such as stat gpu and associated counters, can provide unreliable timings, especially if your project's performance is CPU-bound.
Shadow Depths
The Shadow Depths category refers to the cost of rendering geometry into shadow maps.
RenderVirtualShadowMaps(Nanite) contains all rendering of Nanite geometry into VSMs. All Directional Lights are rendered in a single Nanite pass, and all local lights are done in a second pass.
RenderVirtualShadowMaps(Non-Nanite) handles the rendering of non-Nanite geometry. Each visible light has a separate pass with individual draw calls for various objects and instances, the same as conventional shadow maps rendering.
The cost of the Shadow Depths pass with VSMs is directly related to how many shadow pages need to be rendered, and how much geometry needs to be rendered into them. Non-Nanite geometry is much more expensive to render into VSMs than Nanite geometry. For this reason, it is recommended to enable Nanite on all supported geometry, including low-poly meshes. The only exception is for features not yet supported by Nanite Virtualized Geometry.
Understanding the Number of Pages Being Drawn
The on-screen stats for VSM pages used can give an idea of how many pages are being used, and where to look to resolve potential problems.
Use the following console variables in succession to enable stats:
r.ShaderPrintEnable 1r.Shadow.Virtual.Stats 1(orr.Shadow.Virtual.Stats basicto show only the page statistics)
| Stat Name | Description |
|---|---|
Physical Pages | The maximum number of physical pages that virtual shadow maps can use. |
Allocated | The total number of requested shadow map pages by the current view. It should always be less than Max pages, otherwise corruption can occur. (See the Issues and Limitations section below.) |
Cleared | The number of new pages that were cleared in the current frame. |
Cached | The number of pages that are already in the virtual shadow map page cache, and that do not need to be rendered in the current frame. Cached pages are very low cost and will have almost no impact on performance. When separate static caching is enabled, this is further split by cached Static Pages and Cached Dynamic Pages. |
Invalidated | The number of otherwise-cached pages that were invalidated by dynamic geometry in the previous frame. These pages need to be rendered again because something moved that covers them. When using separate static caching the number of invalidations of static pages should ideally be zero or very close to it. If large numbers of static pages are being invalidated the actor(s) causing the invalidations should be switched to Movable. |
Merged | When separate static caching is enabled, this is the number of pages where the dynamic and static pages were merged (due to one or the other not being cached). |
The total page counts are a function of the average number of lights that affect each pixel on screen. They can be lowered by reducing screen resolution, shadow resolution (using the console variables for resolution LOD bias), light extents, or the number of shadow casting lights.
Poor performance in shadow depths is typically associated with a high number of pages being used and lots of dynamic invalidation happening, which leads to poor caching of VSMs. See the Caching section for more details on diagnosing and reducing cache invalidations.
Improving Non-Nanite Performance
Beyond improving caching, there are a number of ways to improve the performance of non-Nanite shadow rendering.
Consider the following to improve non-Nanite object performance:
Enable Nanite on as much geometry as possible for your project.
Nanite geometry renders far more efficiently into Virtual Shadow Maps and should be preferred in every applicable case, regardless of polygon counts.
Nanite geometry can occlude non-Nanite geometry and avoid spurious cache invalidations. Thus, the only non-Nanite objects should be those that are not supported by Nanite itself.
Non-Nanite objects should have full Mesh LOD hierarchies setup.
It is important that non-Nanite meshes have LODs setup or else they become extremely expensive to render into small pages.
Where possible, it is advisable to disable mesh deformation (WPO/PDO/tessellation) beyond a distance where the effect is obvious. For more information, see the Deformation and Foliage section of this page.
CPU culling console variable are still useful for Non-Nanite meshes rendering into Virtual Shadow Maps
Adjust values of CPU culling on non-Nanite objects rendering into Virtual Shadow Maps with the console variable
r.Shadow.RadiusThreshold. It can help control the cost of small objects in the distance.
Using Distance Field Shadows for distant shadowing of Non-Nanite Objects
For Directional Lights, it is often necessary to switch to Distance Field Shadows beyond some range, same as Cascaded Shadow Maps. With Virtual Shadow Maps, only non-Nanite geometry will switch to using the Distance Field Shadows while Nanite geometry continues to have full-detail shadows.
Disabling non-Nanite geometry in Coarse Pages can increase performance
Disabling non-Nanite geometry in coarse pages can often net a large performance gain, since non-Nanite geometry is generally inefficient to render into large pages.
If a significant amount of cost is going to non-Nanite local lights shadows, try experimenting with the console variable
r.Shadow.Virtual.MarkPixelPagesMipModeLocal. Setting this to 1 or 2 reduces the number of mips allocated for local lights which can help non-Nanite rendering performance.
The Virtual Shadow Maps statistics can give some insight into non-Nanite instance counts:
| Stat Name | Description |
|---|---|
Total | Total number of non-Nanite instances input to the GPU culling. Instances are culled separately for each virtual shadow map mip/clipmap level. For example, a single Static Mesh instance can result in 48 instances (8 mip levels * 6 cubemap faces) for each point light that it intersects, 17 instances for each Directional Light (in default configuration there are 17 clipmap levels) and so on. |
Drawn | Number of instances actually drawn into all virtual shadow maps after culling. |
HZB Culled | Number of instances removed due to being occluded (by Nanite geometry) from the light's perspective. |
Page Mask Culled | Number of instances removed due to not overlapping any required pages. This, for example, represents Static Meshes that are discarded when drawing into already cached areas. |
Empty Rect Culled | Number of instances removed due to not overlapping any required pages. For example, this represents Static Meshes that are discarded when drawing into already cached areas. |
Frustum Culled | Number of instances that were outside the view frustum. |
Depth of Field
Cutscenes often make heavy use of depth of field among other post-processing effects. Parts of the scene that are out-of-focus can request lower resolution shadows, as these regions will be blurred anyway. The r.Shadow.Virtual.MaxDOFResolutionBias variable defines the maximum bias that can be applied to the shadow resolution. The highest gains are expected in views with an expensive background that is heavily out of focus, like a character closeup in front of heavy moving foliage.
Shadow Projection
The Shadow Projection category is the cost of sampling shadow maps using Shadow Map Ray Tracing. These passes are located under the Lights | DirectLighting | UnbatchedLights and there will typically be one VSM projection pass per associated light. The most expensive pass is usually the main SMRT loop in VirtualShadowMapProjection. The rest should be relatively low cost.
Shadow projection is purely a function of the total number of shadow map samples that are taken across the screen, and performance doesn't depend on the number of pages or caching.
When SMRT is being used, it comes down to:
Average lights per pixel
The more lights touching large portions of the screen, the more expensive rendering will be. Lights covering small numbers of on-screen pixels are cheaper, although there are still some fixed costs per light.
Care should be taken to avoid having the majority of pixels on screen taken up by several large lights.
Rays per pixel
Visible softness of shadows affects performance due to the adaptive ray count. Try reducing the Source Radius of local lights or Source Angle of Directional Lights before lowering any of the ray and sample counts.
Samples per ray
These settings are set by the Shadow scalability setting, but can be further adjusted if needed. See the Shadow Map Ray Tracing section of this page for more details.
Generally, shadow projection costs are much easier to control (trade off with quality and noise) than shadow depth costs.
Translucent Surfaces
Virtual Shadow Maps support high quality shadow filtering for translucent surfaces with Substrate and legacy paths. This is a global opt-in feature that you can enable with the console variables r.Shadow.Virtual.TranslucentQuality. This console variable controls the quality of shadows for lit, translucent surfaces. This is applied on all translucent surfaces and has a high performance impact. Settings for r.Shadow.Virtual.TranslucentQuality:
Any value greater than 1: High quality shadows for lit translucent surfaces.
0: (Default) Normal quality shadows for lit translucent surfaces.
Supported Platforms
Virtual Shadow Maps are currently supported on PlayStation 5, Xbox Series S|X, and PCs with graphics cards meeting these specifications, using the latest drivers with DirectX 12:
NVIDIA: Maxwell-generation cards or newer
AMD: GCN-generation cards or newer
All newer versions of Windows 10 (newer than version 1909.1350) and Windows 11 with support for DirectX 12 Agility SDK are supported.
Windows 10 version 1909 — The revision number should exceed or be equal to .1350.
Windows 10 version 2004 and 20H2 — The revision number should exceed or be equal to .789.
DirectX 12 (with Shader Model 6.6 atomics), or Vulkan (VK_KHR_shader_atomic_int64)
Apple Silicon M2 or newer.
Linux with a NVIDIA GeForce 2080 or newer.
Latest Graphics Drivers
For additional information about hardware and software specifications recommended by Epic Games, see Hardware and Software Specifications.
Issues and Limitations
Virtual Shadow Maps are still actively being developed. There are a number of known issues and limitations to using them currently, and they are currently targeted at high-end desktops and next-generation consoles.
Object Bounds
The Virtual Shadow Maps page marking system uses object bounding boxes to mark which parts of the shadow map need to be rendered. If the bounding box of your mesh is set too small, you may see parts of an object having missing or incorrect shadows. However, if the bounding box is too large, it may invalidate more of the shadow map than is strictly necessary, which has a performance penalty. Therefore, the bounding box should fit tightly around the object. If your material uses World Position Offset, you need to set Max World Position Offset Displacement in the material or extend the mesh bounds to account for the maximum displacement. You can inspect bounding boxes with ShowFlag.Bounds or ShowFlag.VisualizeShadowCasters.
Multiple Lights Performance
Performance in scenes with many small local lights is still a work in progress. For the time being, the best strategy is to enable one pass projection and be very careful with invalidations to keep as many of the pages cached as possible. Multiple local lights will perform far better with Nanite geometry than non-Nanite geometry, so aggressively culling or disabling shadow casting on non-Nanite geometry in the distance can help a lot. In some cases it may be desirable to disable dynamic shadow casting on distant lights entirely and rely solely on screen space Contact Shadows.
In the future, better controls will be available for making algorithmic and quality tradeoffs, as well as throttling updates in these situations.
Low Poly Geometry
Low poly geometry with high curvature and smooth normals can exhibit artifacts. This is known as the "shadow terminator problem." It also occurs in ray tracing and other highly accurate visibility queries. The issue stems from the mismatch between the real low poly geometry and the "smooth" shading normals: in the area near the terminator some of these faces are geometrically in shadow, but the non-geometric shading normals are slightly facing the light. It is common to work around this artifact with a normal-based bias to the shadow lookup. This particular artifact can be more noticeable with Virtual Shadow Maps because by default they are set up to deliver highly detailed shadows from Nanite geometry.
The best way to address this is to increase the polygon count in these objects/regions. Limiting the divergence between the geometric and shading normals reduces or eliminates these artifacts without negatively impacting shadow quality elsewhere. With Nanite adding more polygons is straightforward and generally inexpensive. If the offending objects cannot use Nanite, adding a higher detail level of detail (LOD) often works well too, as these artifacts are mostly visible when objects are near the camera.
If it is not possible to add more geometry, the Virtual Shadow Map normal bias can be increased using r.Shadow.Virtual.NormalBias (default 0.5). Note that this should only be considered when content adjustments are not possible as it will negatively impact the quality of shadows throughout the scene, but particularly in areas of fine detail.
In the examples below, artifacts are visible on a low polygon sphere near the camera with high detail geometry in the background. Adding polygons to the sphere improves the artifacts without negatively affecting the detailed landscape in the background.
Adjusting the bias also improves the artifacts, but fine detail is visibly lost in the background geometry.
Overflow of Physical Page Pool
With Virtual Shadow Maps, all of the shadow data in the scene for all lights is stored in a single large texture pool. The default pool size is affected by the Shadow Scalability setting, but it may need to be adjusted in scenes with many lights using high resolution shadows.
Alternatively it may need to be adjusted on lower-end hardware to save video memory.
The page pool size can be adjusted using r.Shadow.Virtual.MaxPhysicalPages. Enabling Virtual Shadow Map stats with r.ShaderPrintEnable 1 and r.Shadow.Virtual.ShowStats 2, in succession, will display statistics about the current page pool use.
If the number of Pages exceeds Max Pages corruption will occur, which sometimes manifests visually as a checkerboard pattern, or corrupt or missing shadows. If the shadow page pool overflows, a warning is displayed on screen and in the log.
If this occurs, increase the size of the page pool or decrease the shadow resolution or number of virtual shadow map casting lights.
Materials
Only simple subsurface materials are supported. Subsurface Profile and transmission are not yet implemented. If a material is using them, that material will be shadowed as though it is opaque.
Shadow Resolution
Virtual Shadow Maps provide significantly increased resolution compared to conventional shadow maps, but shallow light angles (or projective aliasing) and very large local lights can exhaust the available virtual resolution. This may present itself as box-like shadows and bias issues depending on the geometry's surface.
Directional Light clipmaps are much less susceptible to running out of resolution, but very narrow camera fields-of-view can eventually exhaust those as well.
There is no simple solution to solving projective aliasing with shadow maps. Even with virtual shadow maps, some care must be taken to avoid the worst cases and balance resolution with performance.
Unsupported Geometry
While the vast majority of geometry types are supported, there are a few types that are not. By default, these unsupported geometry types will simply not cast shadows.
Setting r.Shadow.Virtual.ForceOnlyVirtualShadowMaps 0 will re-activate the old shadow rendering path for this geometry. This is not recommended, as this can have a significant performance cost. This variable is deprecated and will be removed in a future version of the engine.
Map Check Warnings
Virtual Shadow Maps cause some inaccurate map check warnings to happen:
The Lighting needs to be rebuilt message does not appear when Virtual Shadow Maps is enabled, even though lighting may in fact need to be rebuilt when not using Lumen Global Illumination and Reflections features. While Stationary direct lighting is dynamic with Virtual Shadow Maps enabled, Stationary indirect lighting is still baked.
Warnings regarding preshadows can be ignored as they are not relevant when using Virtual Shadow Maps.