Drop a 500,000-triangle generated statue into an Unreal 5 level with Nanite enabled and the frame time barely moves. Drop forty of them and it still barely moves. Then package the build and the pak file has grown by 560 MB, the editor takes ninety seconds longer to load the map, and a physics query against one of them costs 4 ms because somebody left complex collision on.
That is the honest answer to whether polycounts still matter. Rendering cost has largely stopped being the constraint. Disk, memory, collision, and the non-Nanite paths that still exist inside your project have not.
What Nanite actually removes from your budget
Nanite splits a mesh into clusters of roughly 128 triangles, builds a hierarchy over them, and at render time selects the cluster level whose triangles land near one pixel each. Two things follow from that.
Cost scales with screen pixels, not with source triangles. A 2-million-triangle rock filling 300 × 300 pixels resolves to roughly the same work as a 20,000-triangle rock filling the same area. And LOD authoring becomes unnecessary for opaque static geometry, which is a genuine saving of hours per asset.
What Nanite does not remove is its own fixed overhead. Instance culling, cluster culling, the visibility buffer rasterisation and the material resolve pass run every frame regardless of scene contents. On a mid-range desktop GPU at 1440p that block runs about 1.8–3.2 ms whether the scene holds 200 Nanite instances or 4,000.
If your entire scene is 300k triangles of simple modular geometry, enabling Nanite makes it slower. The break-even is somewhere around 2–4 million rendered triangles or a few thousand draw calls, depending on GPU.
Where generated and scanned assets land
A typical AI-generated or photogrammetry-derived model arrives somewhere between 25k and 250k triangles, with one or two materials and a single 2048 or 4096 texture set. That is comfortably inside Nanite’s happy range and you can enable it without thinking hard.
The catch is triangle distribution. Generated meshes tend toward uniform density: the same triangle size on a flat wall panel as on a carved ornament. Nanite renders that fine, but you are paying disk and streaming bandwidth for detail that carries no information.
Numbers from a generated statue I measured last month:
| Version | Triangles | Nanite build on disk | Visual difference at 2 m |
|---|---|---|---|
| As generated | 248,000 | 13.1 MB | baseline |
| Uniform decimate 50% | 124,000 | 6.8 MB | silhouette softening on fingers |
| Curvature-aware decimate | 91,000 | 4.8 MB | none I could find |
| Non-Nanite, 4 LODs | 91,000 + LODs | 3.9 MB | none, plus LOD pops |
A curvature-aware decimate removed 63% of the triangles and 63% of the disk footprint for no visible change, because those triangles were sitting on flat plinth surfaces. Nanite does not make that pass pointless; it makes it a storage optimisation rather than a rendering one.
Decide this at export rather than in the editor. If your source is a generation studio like MeshyFlix, you can set a target polycount before the mesh is built and receive geometry that is already distributed sensibly, which is cheaper than decimating afterwards and rebaking normals to cover the difference. When you do have to reduce an existing mesh, use a quadric-error or curvature-weighted method rather than a uniform one, and rebake the normal map from the original so the silhouette detail you removed comes back as shading.
The five things that still cost you
1. Collision
This is the big one and it catches almost everyone. Nanite has nothing to do with collision. Complex collision uses the full source mesh, so a 250k-triangle Nanite asset with “Use Complex Collision as Simple” is a 250k-triangle physics body. Author simple collision primitives, or a hand-made convex hull under about 200 triangles, on every Nanite asset that anything ever queries against.
The gap is not subtle. A sweep against a 250k-triangle complex collider runs roughly 3–5 ms on the game thread; the same sweep against a six-primitive simple collider runs under 0.05 ms. Ten of those per frame is the difference between a stable 60 and a stutter you will spend two days blaming on the renderer.
2. The fallback mesh
Every Nanite mesh keeps a reduced fallback used for ray tracing (unless you have enabled Nanite ray tracing proxies), for distance field generation, for platforms without Nanite support, and for anything reading mesh data on the CPU. The default fallback percentage is aggressive. If your fallback is 1% of 250k triangles, hardware Lumen reflections will show a noticeably different silhouette than the raster pass. Set Fallback Relative Error explicitly rather than accepting the default on hero assets.
3. Material complexity and masked materials
Nanite’s material pass evaluates per-pixel, so an expensive material graph costs the same as it always did. Masked materials are worse: they force per-pixel evaluation during the depth pass and lose some of the culling benefit. Foliage with heavy world position offset behaves similarly, since WPO invalidates the cached cluster bounds.
The practical rule I use: opaque generated props get Nanite by default; anything masked with more than about 30% transparent area gets tested both ways before it ships.
4. Texture memory, which is now the real budget
With triangles cheap, textures become the dominant cost. A single 4096 base colour plus 4096 normal plus 4096 ORM at BC7/BC5 is 67 MB with mips. Sixty unique assets at that resolution is 4 GB of texture memory, which no consumer GPU is giving you alongside everything else. Virtual Textures help with residency but not with disk size.
If you have a fixed budget and are deciding where to spend it, spend it on texture resolution before triangle count. The player sees texel density long before they see a silhouette facet.
5. Vertex attributes
Nanite stores UVs and vertex colours per vertex inside its cluster data. Each extra UV channel makes clusters larger and streaming heavier. Nanite supports up to four UV sets; use one unless you have a reason, and strip the vertex colour channel if the material does not read it.
Budgets I use
| Asset role | Non-Nanite target | Nanite target | Note |
|---|---|---|---|
| Hero prop, held in hand | 15k–40k | 150k–400k | above this you store sub-pixel detail |
| Environment prop, mid distance | 3k–10k | 40k–150k | decimate flat regions first |
| Scanned or generated rock, statue | 8k + 4 LODs | 100k–600k | Nanite’s best case |
| Modular wall or floor piece | 500–2k | keep non-Nanite | per-instance overhead dominates |
| Foliage card, masked | 60–300 | test both ways | masked overdraw is real |
| Skeletal character | 40k–90k | 40k–90k | skinned Nanite is still maturing |
| Glass, water, anything translucent | 2k–15k | unsupported | Nanite is opaque and masked only |
| Mobile, Quest, WebGL target | 5k–15k | unavailable | budget as if Nanite does not exist |
That last row deserves emphasis. If your project has any non-Nanite shipping target, whether that is a standalone VR build, a mobile port, or a marketing web viewer, your asset budget is set by the weakest target, not the strongest. Building a library at Nanite densities and then discovering you need a Quest build is a very expensive discovery.
Console variables worth knowing
r.Nanite.MaxPixelsPerEdge— default 1. Raising to 2 roughly halves rasterised triangle work for a quality loss most people cannot see in motion. The cheapest single perf win in Nanite.r.Nanite.Streaming.StreamingPoolSize— default 512 MB. If you are streaming large scan libraries, raise it before you blame the disk.r.Nanite.ShowStats 1andNaniteStats— per-pass cluster and triangle counts. Use this rather than guessing which asset is heavy.r.Nanite 0— toggle everything off for a frame to see what Nanite is actually buying you in your scene. Do this before assuming it helps.
Do and don’t
- Do enable Nanite on opaque static meshes above roughly 10k triangles.
- Do author simple collision on every Nanite asset. Complex collision on a dense mesh is the single most expensive mistake available.
- Do run a curvature-aware decimate on generated meshes before import. Nanite renders the extra triangles fine; your build size does not.
- Do profile with
r.Nanite 0before assuming Nanite is helping a light scene. - Don’t enable Nanite on thousands of tiny modular pieces. Per-instance overhead swamps the benefit.
- Don’t accept the default fallback mesh quality on anything that appears in ray-traced reflections.
- Don’t assume Nanite covers translucency. It does not, and it will silently render those meshes through the old path.
- Don’t build your whole asset library at 500k triangles if a mobile or VR target exists anywhere in the plan.
Where to start tomorrow
Open your heaviest map, run r.Nanite.ShowStats 1, and sort by cluster count. The top ten assets are where any optimisation effort belongs, and in my experience two or three of them are dense meshes on flat geometry that decimate by more than half with no visible change.
Then audit collision. Search the content browser for static meshes with complex collision enabled and a triangle count above 20k. That query usually returns a handful of assets, and fixing them is a bigger frame-time win than anything you will do to the render path.