Skip to content
PRODUCT GITHUB  ↗

Runtime Nanite

Nanite is normally an offline, cook-time process: you import a mesh, the editor builds its cluster hierarchy and LOD DAG, and that data ships baked into the asset. RMC’s RealtimeMeshNanite module does the same build at runtime, from geometry you hand it — raw vertex arrays, procedurally generated shapes, or a mesh you just modified. The result is a FRealtimeMeshNaniteResources you apply to a RealtimeMesh, and from there the engine renders it through the standard Nanite pipeline.

All of the public builder API lives under the RealtimeMesh::Nanite namespace. The public headers are thin façades over a shared encoder core, so the type names below are aliases (for example FRealtimeMeshNaniteMesh is an alias into the core FNaniteMesh). Always write the RealtimeMesh::Nanite:: spellings shown here.


Runtime Nanite touches internal engine layouts, so what you get depends on the engine you build against. RMC detects the tier automatically at build time (RealtimeMeshComponent.Build.cs probes the engine’s NaniteResources.h), and the public API is byte-for-byte identical across all three tiers — no caller code changes.

  • Provider fork (best): the TriAxis engine fork exposes a Nanite streaming-provider API. RMC owns its page data and serves streaming pages through the provider path. This is the fully-featured tier.

  • Stock UE 5.7: on the unmodified engine, RMC wraps a real Nanite::FResources and serves its streaming pages from in-memory bulk data through the stock streaming manager. Full runtime Nanite, no engine modifications required.

  • Unavailable: on engines outside that window (for example stock 5.8+ without the fork), the API still compiles, but the builder entry points and Create* calls return null and log a one-time warning:

    Runtime Nanite requires UE 5.7 or the provider engine fork; Nanite resources are unavailable on this engine.

    Components fall back to the normal non-Nanite RMC render path.

Because BuildRealtimeNaniteMesh returns a null resources pointer on the unavailable tier (and on any build failure), always check the result before applying it — the snippets below do.

Build override: setting the environment variable RMC_FORCE_STOCK_NANITE=1 before building forces the stock-engine path even on a fork checkout. This is a build-time validation switch only; it is not a runtime toggle.


Building a runtime Nanite mesh is four steps: create a working mesh from your geometry, build its cluster hierarchy, encode it into resources, then apply the resources to your RealtimeMesh.

FRealtimeMeshNaniteMesh::CreateFromRawMesh ingests raw geometry. UVs are passed as an array of channels (one TConstArrayView<FVector2f> per UV channel); the trailing per-triangle material-index array is optional (omit it and everything is material 0):

#include "RealtimeMeshNaniteBuilder.h"
using namespace RealtimeMesh::Nanite;
// One UV channel.
TArray<TConstArrayView<FVector2f>> UVChannels;
UVChannels.Add(UVs); // TConstArrayView<FVector2f>
FRealtimeMeshNaniteMesh NaniteMesh = FRealtimeMeshNaniteMesh::CreateFromRawMesh(
Vertices, // TConstArrayView<FVector3f>
Normals, // TConstArrayView<FVector3f>
UVChannels, // TConstArrayView<TConstArrayView<FVector2f>>
Colors, // TConstArrayView<FColor> (may be empty)
Triangles); // TConstArrayView<int32> (flat index list)

If you intend to build a full LOD DAG (next section), use CreateFromRawMeshOptimized instead. It runs meshoptimizer to produce spatially-coherent, vertex-cache-friendly LOD0 clusters that the LOD generator expects:

FRealtimeMeshNaniteMesh NaniteMesh = FRealtimeMeshNaniteMesh::CreateFromRawMeshOptimized(
Vertices, Normals, UVChannels, Colors, Triangles);

For a single-LOD mesh, BuildMinimalHierarchy is enough:

FRealtimeMeshNaniteMesh::BuildMinimalHierarchy(NaniteMesh);

For a mesh with generated LODs, call BuildLODAwareHierarchy after generating them — see Hierarchical LODs below.

BuildRealtimeNaniteMesh runs the encoder and returns a FRealtimeMeshNaniteResourcesPtr. Tune the build with FRealtimeMeshNaniteBuildSettings:

FRealtimeMeshNaniteBuildSettings Settings;
Settings.PositionPrecision = 4; // position quantization bits
Settings.NormalBits = 8; // normal quantization bits
FRealtimeMeshNaniteResourcesPtr Resources = BuildRealtimeNaniteMesh(NaniteMesh, Settings);

Other FRealtimeMeshNaniteBuildSettings fields worth knowing:

  • TargetMinimumResidencyInKB (default 256) — the memory floor for always-resident root pages; pages beyond that count spill to on-demand streaming.
  • Residency (ENaniteResidency, default Streamed) — Streamed keeps coarse LODs resident and streams finer ones; FullyResident lays out every page as a root page, so the resource needs no streaming provider at all (TargetMinimumResidencyInKB is ignored in this mode).
  • bEnableParallel / MinClustersForParallel — control parallel cluster encoding.

The resources are applied through the data layer using an update context. This is the canonical pattern from the examples (RealtimeMesh is a URealtimeMeshSimple*):

#include "Data/RealtimeMeshData.h"
if (Resources.IsValid()) // null on build failure or an unsupported engine tier
{
RealtimeMesh::FRealtimeMeshUpdateContext UpdateContext(RealtimeMesh->GetMesh()->GetContext());
RealtimeMesh->GetMesh()->SetNaniteResources(UpdateContext, MoveTemp(Resources));
}

SetNaniteResources takes the resources pointer by rvalue, so move it in.

The whole flow is packaged in the shared example helper BuildAndApplyNaniteMesh (see Source/RealtimeMeshNaniteExamples/Private/NaniteExampleHelpers.h), which most of the from-raw-data examples call.


A minimal hierarchy renders at a single detail level. To get Nanite’s signature seamless LOD, generate a full LOD DAG with BuildHierarchicalLODs before building the hierarchy. The pipeline is: CreateFromRawMeshOptimized → BuildHierarchicalLODs → BuildLODAwareHierarchy → BuildRealtimeNaniteMesh.

#include "RealtimeMeshNaniteLODBuilder.h"
using namespace RealtimeMesh::Nanite;
// LOD0 must be the meshopt-optimized variant so the LOD generator can partition it.
FRealtimeMeshNaniteMesh NaniteMesh = FRealtimeMeshNaniteMesh::CreateFromRawMeshOptimized(
Vertices, Normals, UVChannels, Colors, Triangles);
FRealtimeMeshNaniteLODBuildSettings LODSettings;
LODSettings.MaxLODs = 10; // hard ceiling on LOD count
LODSettings.TargetPartitionSize = 8; // clusters per partition
LODSettings.LODReductionFactor = 0.5f; // each LOD targets half the triangles
BuildHierarchicalLODs(NaniteMesh, LODSettings);
FRealtimeMeshNaniteMesh::BuildLODAwareHierarchy(NaniteMesh); // NOT BuildMinimalHierarchy
FRealtimeMeshNaniteBuildSettings BuildSettings;
FRealtimeMeshNaniteResourcesPtr Resources = BuildRealtimeNaniteMesh(NaniteMesh, BuildSettings);
// ...apply as in step 4.

FRealtimeMeshNaniteLODBuildSettings exposes the simplifier controls: LODReductionFactor, MinClustersToContinue, ProgressFloor (stop when a pass stops materially reducing cluster count), PositionWeldEpsilon (cross-cluster seam welding), and the attribute error weights NormalWeight (default 0.5), UVWeight (0.1), and ColorWeight (0.0).

The demonstrating example is ARealtimeMeshExample_Nanite_HierarchicalLODs (Source/RealtimeMeshNaniteExamples/), which runs this exact pipeline on a dense UV sphere and logs a per-LOD pyramid (cluster counts, triangle counts, and the error band per level) so you can inspect the result.


Nanite resources are immutable once built, so a mesh that changes every frame means rebuilding and re-applying the resources each update. There is no in-place edit path — you re-run the build pipeline and call SetNaniteResources again with the new resources. RMC’s resource-sharing and copy-on-write proxy design keeps that from re-cloning data on every proxy recreate, but the encode itself still runs each time, so a full rebuild per frame is only appropriate for modest mesh sizes.

The ARealtimeMeshExample_Nanite_FrequentUpdates example stress-tests this: it rebuilds and re-applies Nanite meshes on tick at several update frequencies and tracks the timing.


For full control you can author clusters directly on a FRealtimeMeshNaniteMesh — emplacing FRealtimeMeshNaniteCluster entries and filling their vertices, normals, colors, UVs, indices, and per-material ranges by hand — instead of going through CreateFromRawMesh. This is an advanced path; see ARealtimeMeshExample_Nanite_ManualClusters for a worked example that builds multi-cluster and multi-material-range meshes.


The shared Nanite builder registers a couple of developer console variables (non-Shipping builds only). These are builder diagnostics, not runtime render toggles:

  • r.TriAxisNanite.RMC.Audits — enable read-only diagnostic/audit passes in the builder (decode round-trip and conservation checks). Purely diagnostic; produces no data consumed by the encode path. Default off.
  • r.TriAxisNanite.RMC.ISPC — enable ISPC-optimized cluster building (where ISPC is compiled in). Default off.

All six live in Source/RealtimeMeshNaniteExamples/ as C++ actors you can drop into a level:

  • ARealtimeMeshExample_Nanite_BasicRawData — the core pipeline: build Nanite meshes from raw vertex/triangle arrays (a low-poly sphere and a high-poly torus) and apply them.
  • ARealtimeMeshExample_Nanite_HierarchicalLODs — the full BuildHierarchicalLODs + BuildLODAwareHierarchy LOD-DAG pipeline, logging the LOD pyramid.
  • ARealtimeMeshExample_Nanite_LODInspector — builds the LOD pyramid and extracts each LOD level into its own mesh side-by-side, for visually inspecting per-level geometry.
  • ARealtimeMeshExample_Nanite_FrequentUpdates — frame-by-frame rebuild-and-reapply at several update frequencies, with timing.
  • ARealtimeMeshExample_Nanite_ManualClusters — hand-authored clusters and material ranges, bypassing CreateFromRawMesh.
  • ARealtimeMeshExample_Nanite_PrebuiltData — copies prebuilt Nanite resources straight off a StaticMesh asset onto a RealtimeMesh (via FRealtimeMeshNaniteResources::CreateFromCopy), bypassing the build pipeline entirely.