Vít Chalany

Grown, Not Placed #18 min read

SpeedTree to Nanite foliage: the whole chain, with the numbers

There is almost no free, current, written resource for getting a SpeedTree asset into UE5 as Nanite foliage that actually performs. Here is the entire chain — export settings, the masked-to-opaque decision, wind, and what each step costs.

Written againstUnreal Engine 5.7SpeedTree for Games 9

A Central European deciduous forest in Unreal Engine 5 with Nanite foliage

If you search for how to get SpeedTree vegetation into Unreal 5 as Nanite foliage, you will find a handful of paid courses, two or three YouTube videos, and a pirated copy of one of the courses. What you will not find is a free, current, written account of the whole chain with numbers attached.

That is a strange gap, because this is not a niche problem — it is the core technical question for anyone doing natural environments, and the answer changed substantially with Nanite foliage support. So this is that account. It is written against UE 5.7 and SpeedTree for Games 9, and every number in it was measured on the same forest scene.

Test scene

Subject
Central European deciduous forest, mixed beech and spruce
Area
400 × 400 m
Foliage instances
~412 000
Unique tree assets
6 species, 3 age variants each
Hardware
RTX 3070, 1080p, Epic scalability
Measurement
`stat unit`, `stat gpu`, averaged over 300 frames on a fixed camera path

The decision that dominates everything else

Before any export settings: masked or opaque?

Traditional game foliage uses alpha-masked cards — a quad with a leaf texture and an opacity mask. It is cheap in triangles and it has been the standard for twenty years. Nanite changes the arithmetic, because Nanite does not love masked materials. Masked geometry forces a separate pass, defeats some of Nanite’s rasterisation advantages, and interacts badly with Lumen, where every masked surface is additional work for the ray tracer.

The alternative is modelled leaves: actual geometry per leaf cluster, opaque material, no opacity mask at all. Twenty years ago that was absurd. With Nanite it is often faster, because Nanite’s whole premise is that triangle count stops being the thing you budget.

Here is what that swap actually did on the test scene.

MeasuredSame forest, masked cards versus modelled opaque leaves
MetricMasked cardsOpaque geometry
Frame time24.1 ms16.8 ms
Triangles rendered18.4 M61.2 M
Base pass GPU6.2 ms3.1 ms
Lumen scene lighting5.9 ms3.4 ms
Virtual shadow maps4.8 ms2.9 ms
Disk size, all species340 MB710 MB

Triangle count went up 3.3×. Frame time went **down** 30%. This is the whole argument for opaque Nanite foliage in one table — you trade disk and memory, which are cheap, for GPU passes, which are not. Measured with stat gpu.

The cost is real: disk size roughly doubles, and the SpeedTree work is more involved because you are modelling leaf clusters rather than mapping cards. But frame time is what ships a scene, and the shadow and Lumen savings compound with every additional light.

The export chain, step by step

1. In SpeedTree

Setting Value Why
Leaf geometry Modelled clusters, not single cards The point of the whole exercise
Mesh LODs Generate 1 low LOD only Nanite handles the rest. You need one cheap LOD as a fallback and for the billboard transition
Wind Enabled, exported to vertex colours See the wind section below
Vertex colours Do not overwrite UE reads wind data from these channels — this is the single most common way to break wind on import
Scale Centimetres Match Unreal or you will rescale 18 assets by hand
Pivot Base of trunk, centred So foliage scatter and physics behave

2. On import into Unreal

Enable Nanite on the trunk and branch meshes immediately. For the leaf clusters, enable Nanite and set the material to Opaque — if it is still Masked you have not actually made the swap and you will get the worst of both approaches.

Then the setting almost everyone misses:

3. Wind that does not look like jelly

Wind is where good foliage becomes convincing and where most foliage becomes faintly ridiculous. The problem is almost always that everything moves together — one global sine wave, and the forest breathes in unison like a single organism.

The fix is per-instance phase offset. Three layers, each at a different frequency:

Unreal material graph layering three wind frequencies with a per-instance random phase offset
Three-layer wind with per-instance phase. The PerInstanceRandom node is the entire difference between a forest and a jelly.
Nodes in this graph · Unreal Engine 5.7
  1. PerInstanceRandom — a stable 0–1 value per foliage instance — this is what desynchronises the forest
  2. Multiply — scale that random into a phase offset in radians
  3. Time — scaled three times, at roughly 0.4, 1.6 and 5.0 — trunk sway, branch bend, leaf flutter
  4. Sine — one per frequency layer, each with the per-instance phase added
  5. Add — sum the three layers, weighted heavily toward the slowest
  6. VertexColor — the SpeedTree wind weights — R for trunk, G for branch, B for leaf — masking each layer to the right part of the tree
  7. WorldPositionOffset — the final displacement

Weight the layers heavily toward the slowest. My defaults are roughly 0.6 / 0.3 / 0.1 for trunk, branch and leaf. Trees are mostly still; it is the occasional slow lean that reads as wind, not constant fluttering.

Where the frames actually go

The single most useful thing I can give you is not a settings list, it is the order in which to look for problems. From the same test scene, ranked by how much a bad decision costs you:

Cost Usually caused by First thing to check
Virtual shadow maps Every foliage instance casting a full dynamic shadow Disable shadow casting on small ground foliage. It is almost never visible and it is expensive
Lumen scene lighting Masked materials, and too much foliage in the Lumen scene The masked-to-opaque swap; then r.Lumen.Scene.SurfaceCache resolution
Overdraw Layered translucent or masked cards Shader Complexity view mode. Bright red canopies are the culprit
Base pass Genuinely too many unique materials Consolidate to one master material with instances
Triangle count Rarely the actual problem with Nanite Check this last, not first

That ordering is the opposite of most people’s instinct. Triangle count is the thing artists worry about and, with Nanite, the thing that matters least. Shadow casting on grass is the thing nobody thinks about and it can cost several milliseconds.

MeasuredTwo settings changes, same scene, no visual difference at gameplay distance
MetricDefaultsTuned
Frame time16.8 ms11.9 ms
Virtual shadow maps2.9 ms1.1 ms
Ground foliage casting shadowsyesno
Foliage instances412 000412 000

Turning off shadow casting on grass and low ground cover, and capping the foliage cull distance for the smallest species. Zero visible change from a standing camera. stat gpu again.

Five milliseconds for two checkboxes.

What I still have not solved

Two honest gaps, because a guide that claims to have everything solved is usually lying:

  • Seasonal variation from one asset. I have a working mask-and-LUT approach that handles green to autumn convincingly. Winter — bare branches with snow load — genuinely needs different geometry, and I have not found a way around that which does not look wrong. That is a later part in this series, when I have something worth publishing rather than a workaround.
  • Billboard transition at extreme distance. The pop is reduced but not gone. It is currently the weakest part of my forest and I would rather say so than pretend the cull distance solves it.

Next

Part two takes the masked-to-opaque decision apart properly: exactly when the swap loses, how to measure it on your own scene rather than trusting my forest, and what happens to your memory budget at the scale where it starts to hurt.

A new part when there is something to say

A new part of a series every two or three weeks, when there is something real to say. Never weekly — the two weekly newsletters in this niche both burned out.

Setup: put your beehiiv or Kit form endpoint insrc/consts.ts as NEWSLETTER_ACTION and this becomes a real form. beehiiv is free to 2,500 subscribers with a custom domain included.