Notes on foliage in Unreal 5
How to deal with performance and common issues
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:
Preserve Area
When you enable Nanite on a tree, you may notice that leaves may disappear on a distance:

Nanite (left) vs nonNanite (right)
To maintain the shape of the foliage, enable Preserve Area:


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
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
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
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
Notice how Nanite VisBuffer cost dropped from 6.4ms to 4.8ms when I switched master Material to 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
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
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
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
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
Procedural vegetation editor — new experimental plugin for making PCG foliage assets.

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
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
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)
On High scalability though, by default, it’s using Global Distance Field (GDF), which is very lowres:

Global Distance Field (GDF)
That’s how DF might look for a tree:


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
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
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

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
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.

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
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
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
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
Setting lower Nanite LOD Bias may help, default value is 1:
r.Shadow.NaniteLODBias 0

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
메타데이터
- 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