Skip to content
PRODUCT GITHUB  ↗

RealtimeMesh Simple

URealtimeMeshSimple is the primary RMC mesh type. You build a StreamSet with the mesh builder, hand it to the mesh, and RMC turns it into GPU buffers, sections, and (optionally) collision. It exposes RMC’s full surface — arbitrary stream layouts, polygroup auto-sectioning, LODs, and the minimum-copy update paths — so reach for it unless one of the other mesh types fits your workflow better.

Header: Source/RealtimeMeshComponent/Public/RealtimeMeshSimple.h.


Initialize the component to a Simple mesh, then keep the returned pointer to drive it:

#include "RealtimeMeshSimple.h"
using namespace RealtimeMesh;
URealtimeMeshSimple* RealtimeMesh = Component->InitializeRealtimeMesh<URealtimeMeshSimple>();
// Material slots are addressed by index from your sections.
RealtimeMesh->SetupMaterialSlot(0, "PrimaryMaterial");

The minimal end-to-end path — a single triangle — is the ARealtimeMeshExample_Simple_HelloTriangle example actor (Source/RealtimeMeshExamples/Private/Simple/RealtimeMeshExample_Simple_HelloTriangle.cpp).


A section group owns a set of shared buffers (positions, tangents, texcoords, triangles, …); the sections inside it reference ranges of those buffers and carry the material/draw config. You address a group with an FRealtimeMeshBufferSetKey:

// Build the geometry into a StreamSet.
FRealtimeMeshStreamSet StreamSet;
TRealtimeMeshBuilderLocal<uint16, FPackedNormal, FVector2DHalf, 1> Builder(StreamSet);
Builder.EnableTangents();
Builder.EnableTexCoords();
Builder.EnableColors();
// ... add vertices and triangles ...
// Key the group into LOD 0 with a name of your choosing.
const FRealtimeMeshBufferSetKey GroupKey = FRealtimeMeshBufferSetKey::Create(0, FName("MyMesh"));
// Hand the StreamSet to the mesh. Returns a TFuture you can wait on if you need to.
RealtimeMesh->CreateBufferSet(GroupKey, StreamSet);

CreateBufferSet returns a TFuture<ERealtimeMeshProxyUpdateStatus> that completes once the render proxy has been updated — you can ignore it for fire-and-forget updates, or chain on it.

Both CreateBufferSet and UpdateBufferSet come in two flavors:

// const& — RMC copies your StreamSet; you keep ownership and can reuse it.
CreateBufferSet(const FRealtimeMeshBufferSetKey&, const FRealtimeMeshStreamSet& MeshData, ...);
// rvalue — RMC takes ownership with no copy. Use MoveTemp when you're done with the StreamSet.
CreateBufferSet(const FRealtimeMeshBufferSetKey&, FRealtimeMeshStreamSet&& MeshData, ...);

Prefer the move overload when you built the StreamSet just for this call — it avoids copying the vertex data:

RealtimeMesh->CreateBufferSet(GroupKey, MoveTemp(StreamSet));

There is also a no-data overload, CreateBufferSet(GroupKey, InConfig, bShouldAutoCreateSectionsForPolyGroups), that creates an empty group you fill in later.


When your StreamSet has polygroups enabled (you tagged each triangle with a group index via the builder), creating the section group automatically creates one section per polygroup — you don’t have to declare sections yourself. This is controlled by the bShouldAutoCreateSectionsForPolyGroups parameter (default true) on CreateBufferSet.

The keys of the auto-created sections are derived from the group key and the polygroup index, so you can address them afterward to point each at a material slot:

Builder.EnablePolyGroups();
Builder.AddTriangle(V0, V1, V2, /*PolyGroup*/ 0); // -> section for poly group 0
Builder.AddTriangle(V2, V1, V0, /*PolyGroup*/ 1); // -> section for poly group 1
RealtimeMesh->CreateBufferSet(GroupKey, StreamSet);
// Point each auto-created section at a material slot.
const FRealtimeMeshSectionKey Section0 = FRealtimeMeshSectionKey::CreateForPolyGroup(GroupKey, 0);
const FRealtimeMeshSectionKey Section1 = FRealtimeMeshSectionKey::CreateForPolyGroup(GroupKey, 1);
RealtimeMesh->UpdateSectionConfig(Section0, FRealtimeMeshSectionConfig(0));
RealtimeMesh->UpdateSectionConfig(Section1, FRealtimeMeshSectionConfig(1));

You can toggle the behavior per group at runtime with SetShouldAutoCreateSectionsForPolyGroups(GroupKey, bNewValue) and query it with ShouldAutoCreateSectionsForPolygonGroups(GroupKey).

The ARealtimeMeshExample_Simple_MultipleSections example (Source/RealtimeMeshExamples/Private/Simple/RealtimeMeshExample_Simple_MultipleSections.cpp) packs several polygroup sections into one buffer set, each on its own material slot.


To replace a group’s geometry, build a new StreamSet and call UpdateBufferSet (same two overloads):

if (RealtimeMesh->GetBufferSets(FRealtimeMeshLODKey(0)).Contains(GroupKey))
{
RealtimeMesh->UpdateBufferSet(GroupKey, StreamSet); // or MoveTemp(StreamSet)
}
else
{
RealtimeMesh->CreateBufferSet(GroupKey, StreamSet);
}

That create-or-update pattern (build a fresh StreamSet each frame) is what ARealtimeMeshExample_Simple_DynamicUpdate (Source/RealtimeMeshExamples/Private/Simple/RealtimeMeshExample_Simple_DynamicUpdate.cpp) does to ripple a grid every tick.

For per-frame vertex animation, the whole-StreamSet rebuild is heavier than it needs to be — see the in-place fast paths below.


When only vertex attributes change and topology stays fixed, you can edit the group’s CPU streams in place and upload just the affected buffers, with no reallocation and no new proxy version:

RealtimeMesh->EditMeshInPlace(GroupKey,
[](FRealtimeMeshStreamSet& Streams) -> TSet<FRealtimeMeshStreamKey>
{
FRealtimeMeshStream* Positions = Streams.Find(FRealtimeMeshStreams::Position);
// ... mutate Positions in place ...
return { FRealtimeMeshStreams::Position }; // report which streams you changed
});

EditMeshInPlaceRanged goes further: your callback returns each changed stream mapped to the half-open element range [lower, upper) it touched, and RMC uploads only that sub-region of the GPU buffer.

Both fast paths require the section group to use Dynamic draw type (so its buffers are allocated as BUF_Dynamic) — create it with FRealtimeMeshBufferSetConfig(ERealtimeMeshSectionDrawType::Dynamic). Streams or ranges that aren’t eligible fall back to a full update.

The ARealtimeMeshExample_Simple_FastUpdate example (Source/RealtimeMeshExamples/Private/Simple/RealtimeMeshExample_Simple_FastUpdate.cpp) demonstrates both. For the full picture of update strategies and when to use each, see Updating Mesh Data.


ProcessMesh invokes your callback with a group’s StreamSet while the accessor lock is held, so you can inspect the data without it escaping the lock:

RealtimeMesh->ProcessMesh(GroupKey, [](const FRealtimeMeshStreamSet& Streams)
{
// Read positions, indices, etc. Do not stash the reference.
});

If the requested group doesn’t exist the callback simply isn’t called. There is a matching ProcessBufferSet that hands you the group’s buffer set instead of just the StreamSet.


The StreamSet build is pure CPU work with no UObject access, so it is safe to run on a worker thread and commit on the game thread. RMC ships helpers for exactly this pattern:

// 1) Build the heavy StreamSet off the game thread.
TFuture<FRealtimeMeshStreamSet> BuildFuture = DoOnAsyncThread([]()
{
return BuildGridStreamSet(); // your pure-CPU builder function
});
// 2) Hop back to the game thread to commit it.
ContinueOnGameThread(MoveTemp(BuildFuture),
[WeakMesh](TFuture<FRealtimeMeshStreamSet>&& Result)
{
if (URealtimeMeshSimple* Mesh = WeakMesh.Get())
{
Mesh->CreateBufferSet(FRealtimeMeshBufferSetKey::Create(0, FName("AsyncMesh")), Result.Get());
}
});

These helpers come from Core/RealtimeMeshFuture.h and are used by the ARealtimeMeshExample_Simple_AsyncBuild example (Source/RealtimeMeshExamples/Private/Simple/RealtimeMeshExample_Simple_AsyncBuild.cpp).


Simple is Blueprintable. Initialize with the static InitializeRealtimeMeshSimple(Owner) node, then work with a URealtimeMeshStreamSet UObject that wraps the same stream data for Blueprint graphs.

The Blueprint CreateBufferSet / UpdateBufferSet nodes take the URealtimeMeshStreamSet object plus an OnComplete completion callback (FRealtimeMeshManagedCompletionCallback) that fires when the update lands:

// C++ signatures of the Blueprint nodes:
void CreateBufferSet(const FRealtimeMeshBufferSetKey& SectionGroupKey,
URealtimeMeshStreamSet* MeshData,
const FRealtimeMeshManagedCompletionCallback& OnComplete);
// Generates a fresh unique key for you and returns it:
FRealtimeMeshBufferSetKey CreateBufferSetUnique(const FRealtimeMeshLODKey& LODKey,
URealtimeMeshStreamSet* MeshData,
const FRealtimeMeshManagedCompletionCallback& OnComplete);
void UpdateBufferSet(const FRealtimeMeshBufferSetKey& SectionGroupKey,
URealtimeMeshStreamSet* MeshData,
const FRealtimeMeshManagedCompletionCallback& OnComplete);

SetShouldAutoCreateSectionsForPolyGroups and ShouldAutoCreateSectionsForPolygonGroups are also exposed to Blueprint.