← Back to list

Notes on foliage in Unreal 5

How to deal with performance and common issues

Iri Shinsoj · 2024-10-25 14:32 · 53 claps · 10.3 min read
#unreal-engine #optimization
Open on Medium ↗
Wiki topics: 🎮 · Gaming

Notes on foliage in Unreal 5

How to deal with performance and common issues

Nanite

Documentation: https://dev.epicgames.com/documentation/en-us/unreal-engine/nanite-virtualized-geometry-in-unreal-engine#foliage-using-nanite

Here are some nice presentations covering Nanite performance for a start:

[embed]

[embed]

And here are a couple articles on Nanite optimizations and assets authoring:

[embed]Nanite Optimizations in Unreal Engine 5: Diving into Nanite Performance Unlock the full potential of UE5’s rendering system with targeted optimization strategiesmedium.com

[embed]Asset Authoring for Nanite: Building Geometry That Scales How to make assets that play nice with Nanite’s cluster-based system and actually perform.medium.com

Preserve Area

When you enable Nanite on a tree, you may notice that leaves may disappear on a distance:

Nanite (left) vs nonNanite (right)

Nanite (left) vs nonNanite (right)

To maintain the shape of the foliage, enable Preserve Area:

Preserve Area off (left) vs on (right)

Preserve Area off (left) vs on (right)

More details in Foliage Using Nanite section here: https://dev.epicgames.com/documentation/en-us/unreal-engine/nanite-virtualized-geometry-in-unreal-engine#foliageusingnanite

Max Edge Length Factor

Max Edge Length Factor controls how much Nanite can simplify meshes, defining the maximum pixel distance between vertices.

Default is 0 and must be kept at that, unless you have issues caused by over-simplification, like vertex position precision.

shadow proxy mesh example, Max Edge Length Factor 0 vs 0.1

shadow proxy mesh example, Max Edge Length Factor 0 vs 0.1

World Position Offset

In general, you should have subtle WPO effects for leaves movement, otherwise the mesh or shadows might glitch and break apart.

Specifying WPO Disable Distance gives HUGE performance difference for Nanite and VSM. It should be set on all foliage and all Static Meshes that use WPO, it’s very important. Do not skip this step.

Keep an eye on WPO evaluation view mode to spot potential performance eaters:

Here’s an example of how it should look like, notice that all WPO is disabled after certain distance:

Evaluate WPO view mode. Green — WPO evaluation enabled

Evaluate WPO view mode. Green — WPO evaluation enabled

You can find this parameter in Static Mesh details, on Actors, in Foliage Type, in Blueprints and PCG. Remember to set distances in all use cases.

[embed]

Masked materials

Classic lowpoly trees with masked branches material can potentially be more expensive with Nanite than without it, so double check the assets in different setups to find what works best for you.

Pixel Programmable view mode. Red are the plants using masked material

Pixel Programmable view mode. Red are the plants using masked material

If you have highpoly Nanite trees, it might be a good idea to add some more triangles to get rid of masking completely to save performance. This scene was tested on RTX4070 Ti at ~1440p:

Masked

Masked

Notice how Nanite VisBuffer cost dropped from 6.4ms to 4.8ms when I switched master Material to Opaque:

Opaque

Opaque

Artists often have some doubts about conifer trees, but they work just fine with this approach as well. Players would not spend time watching every needle’s detail playing the game, so just focus on the overall shape and look from common gameplay camera views while making the trees:

example from Avowed

example from Avowed

If you make each tiny leaf as a mesh, the trees tend to loose the canopy depth on the distance and even Preserve Area won’t help much. So it’s a matter of trial and error to find the best solution.

[embed]Example of tree asset

If you’re using fading in the foliage material, you have to have masked enabled. Make sure you keep the fading distance short, because the more dithered area you have, the more expensive it is, and you may have a huge and unplayable performance drop when player enters the bushes.

camera fading example — cheap contrast reduces the fading distance and is clamped to a value that gives balanced result

camera fading example — cheap contrast reduces the fading distance and is clamped to a value that gives balanced result

Overdraw

You should keep an eye on overdraw, foliage adds a lot to it. Try to design your levels in a way that you won’t have too much overlapping going on in the scene at once. Don’t stack a lot of foliage, kitbashing would not do well with it.

Read more about aggregate geometry issue here: https://dev.epicgames.com/documentation/en-us/unreal-engine/nanite-virtualized-geometry-in-unreal-engine#aggregategeometry

Overdraw viewmode

Overdraw viewmode

Cull Distance

You can also cull small plants like grass, flowers, bushes and saplings on certain distance, even if they are Nanite. This will reduce the overdraw.

Foliage Cull Distance parameter

Foliage Cull Distance parameter

Note that Nanite meshes are not affected by culling fading like it used to be with non-Nanite meshes, but culling works.

Nanite cost — how much is too much?

My rule of thumb is to have Nanite cost at any point of the game below 4ms on rtx3060 with native 1080p resolution at 60FPS. For higher-end platforms, a good reference would be RTX4070 Ti or RTX4080 at 1440p at 60FPS.

Doable, if keeping the UE5 new tech specifics in mind, while designing your game. This also requires lowering certain cvars, adjusting Scalability settings, and validating assets to make sure they follow the rules and fit the budgets. If your game is full of detailed realistic foliage, then it can be a good idea to consider 30fps as a target instead, so the art teams will have some performance budget to work with, without sacrificing visual quality.

Memory and Disc Size

Nanite trees can be quite heavy for memory, because Nanite compression doesn’t work with such geometry as optimally as with solid meshes like rocks, with the same amount of triangles:

This presentation explains the Nanite vs Foliage problem and how to create trees as modular meshes for better compression:

[embed]

This way you’ll have more detailed trees with less disc and memory used.

NEW in 5.7 — procedural vegetation editor and Nanite foliage

https://portal.productboard.com/epicgames/1-unreal-engine-public-roadmap/c/2158-procedural-vegetation-editor

Procedural vegetation editor — new experimental plugin for making PCG foliage assets.

https://portal.productboard.com/epicgames/1-unreal-engine-public-roadmap/c/2219-nanite-foliage-skinning-experimental-

Lumen

Lumen reflections tend to become expensive if there’s a lot of glossy surface on the screen.

Reducing the roughness threshold on the assets can have quite a significant effect. Default value is 0.4:

r.Lumen.Reflections.MaxRoughnessToTrace 0.4

For example, 0.2 can be a good starting point for setting up scalability profile for consoles and PCs with similar performance.

Disabling ray casting reflections on foliage completely, can give a significant performance boost, while it might have zero visual difference:

r.Lumen.Reflections.MaxRoughnessToTraceForFoliage 0

If you need some reference budgets, let’s say, keep reflection cost under 1ms, for 60FPS target on:

  • RTX3060 (~PS5 equivalent) at 1080p
  • RTX4070 at 1440p

This scene was tested on RTX4070 Ti, at ~1440 resolution:

MaxRoughnessToTrace 0.4

MaxRoughnessToTrace 0.4

You can see that we got 1.8ms difference between default Max Roughness to Trace 0.4 and if it's set to 0:

MaxRoughnessToTrace 0

MaxRoughnessToTrace 0

Here’s a presentation where you can see an example with foliage reflections (added a timestamp):

[embed]

Distance Fields

Lumen is using Distance Fields for indirect shadowing and, by default, on Epic scalability it’s using the detailed Mesh Distance Fields (MDF):

Mesh Distance Fields (MDF)

Mesh Distance Fields (MDF)

On High scalability though, by default, it’s using Global Distance Field (GDF), which is very lowres:

Global Distance Field (GDF)

Global Distance Field (GDF)

That’s how DF might look for a tree:

MDF (left) vs GDF (right)

MDF (left) vs GDF (right)

To see the DF view, go to Show > Visualize, there you can toggle Global Distance Fields and Mesh Distance Fields:

Overshadowing is a common issue with foliage because of the DFs:

sg.globalilluminationquality 3 vs 2

sg.globalilluminationquality 3 vs 2

Usually, the two sided DF is the way to go to mitigate the issue:

Sometimes it’s preferable to disable DF on small plants like tiny saplings and grass:

You can do that by setting DF resolution 0 in Static Mesh (don’t forget to apply after changing the setting):

Or disabling Affect Distance Field Lighting on Actor or Foliage instance:

In other cases, it can be a good idea to use a mesh replacement for DF to control it:

Another thing to keep in mind is that Distance Fields will be you asset’s reflections in SWRT (Software Ray Tracing) mode. Make sure that the trees and bushes have some DF. In some cases DF for trunks and big branches might be sufficient.

HWRT vs SWRT. Left tree has Distance Field while right one doesn’t

HWRT vs SWRT. Left tree has Distance Field while right one doesn’t

To enable\disable HWRT you can use this command: r.Lumen.HardwareRayTracing 0. To be able to use HWRT, Support Hardware Ray Tracing should be enabled in the Project Settings:

HWRT is more expensive for performance, so make sure that you test your assets in both modes, if your project has it enabled (discuss this with Technical Art team), to avoid potential visual inconsistency:

example from Avowed — no trees in the SWRT reflection

example from Avowed — no trees in the SWRT reflection

Virtual Shadow Map

Coarse Pages

If foliage is not Nanite, it’s recommended to **disable their rendering into Coarse Pages,** this can give a huge performance boost with a lot of foliage on the scene:

r.Shadow.Virtual.NonNanite.IncludeInCoarsePages 0

Cache Invalidation

Virtual Shadow Maps are reusing shadow map pages from previous frames. If there’s any movement, the pages will be invalidated to update the shadow. WPO used for foliage movement is invalidating VSM constantly.

Cached Page view mode

Cached Page view mode

Keep an eye on cache invalidations with Cached Page view mode, the more green you have the better:

Shadow Cache Invalidation Behavior

Very useful parameter for managing VSM performance is **Shadow Cache Invalidation Behavior. Rigid **is useful for actors that can be moved but you want to make sure the cache is not invalidated while it’s static.

Setting it to Static on static assets by default is a good starting point for projects targeting 9gen consoles and PCs of similar performance, since it gives a huge performance boost, and prevents potential invalidations from different reasons. For example, enabling RVT on the assets made them count as wpo-enabled, dropping performance on a whole test map significantly, and using Static mode fixed the problem.

https://dev.epicgames.com/documentation/en-us/unreal-engine/virtual-shadow-maps-in-unreal-engine#managingcacheinvalidations

https://dev.epicgames.com/documentation/en-us/unreal-engine/virtual-shadow-maps-in-unreal-engine#managingcacheinvalidations

Keep in mind that since the shadows are not updating, you can use this trick only with foliage that has very subtle or no movement, otherwise you’ll have obvious self-shadow glitches.

This scene was tested on RTX4070 Ti at 4k:

Shadow Cache Invalidation Behavior = Auto

Shadow Cache Invalidation Behavior = Auto

You can see how the Shadow Depths cost dropped from 11ms to 1.4ms when I switched all foliage Invalidation Behavior to Static:

Shadow Cache Invalidation Behavior = Static

Shadow Cache Invalidation Behavior = Static

Contact Shadow

Disabling dynamic shadows on tiny foliage like grass and saplings is another good practice in maintaining performance. You can use Contact Shadow instead:

Contact shadow has an intensity parameter, and lowering it gives more subtle foliage shadowing:

Here’s a screenshot from Epic’s blog post about VSM in Fortnite:

https://www.unrealengine.com/en-US/tech-blog/virtual-shadow-maps-in-fortnite-battle-royale-chapter-4

https://www.unrealengine.com/en-US/tech-blog/virtual-shadow-maps-in-fortnite-battle-royale-chapter-4

That’s how it looks like in Hellblade 2 as an example:

[embed]

Make sure you’re not setting the distance too high, because it will create glitches.

Nanite LOD Bias

Thin details shadow can look quite broken on the distance, especially when you’re lowering the LOD bias for Directional and\or Local lights, it would pop up on shorter distance:

r.Shadow.Virtual.ResolutionLodBiasDirectional 0 vs 1

r.Shadow.Virtual.ResolutionLodBiasDirectional 0 vs 1

Setting lower Nanite LOD Bias may help, default value is 1:

r.Shadow.NaniteLODBias 0

NaniteLODBias 0 and r.Shadow.Virtual.ResolutionLodBiasDirectional 1

NaniteLODBias 0 and r.Shadow.Virtual.ResolutionLodBiasDirectional 1

Shadow proxy

If you have highpoly trees in your project, it would be a good idea to use proxy mesh for shadow, to make shadow pass cheaper. For example, having a 200–300k tris tree, you could have 30k tris mesh casting shadow instead.

Fortnite already used a “shadow proxy” system before Chapter 4, and early on, we were unsure if we would be able to hit our shadow performance budget with the full 300k+ polygon tree meshes in the shadow pass, since shadows are typically allocated a smaller frame budget than the primary view despite rendering similar (or in some cases more) pixel counts. Shadow proxies are implemented by having two static mesh components on a tree actor: one for the main view with “cast shadow” disabled and a hidden one for the shadow proxy with “cast hidden shadow” enabled. [link]

https://www.unrealengine.com/en-US/tech-blog/virtual-shadow-maps-in-fortnite-battle-royale-chapter-4

https://www.unrealengine.com/en-US/tech-blog/virtual-shadow-maps-in-fortnite-battle-royale-chapter-4


메타데이터
post_id
3522b6eb159f
slug
notes-on-foliage-in-unreal-5-3522b6eb159f
url
https://medium.com/@shinsoj/notes-on-foliage-in-unreal-5-3522b6eb159f
canonical_url
https://medium.com/@shinsoj/notes-on-foliage-in-unreal-5-3522b6eb159f
author_url
https://medium.com/@shinsoj
status
ok
fetched_at
2026-06-15 22:55:51