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.
Initializing
Section titled “Initializing”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).
Creating a section group
Section titled “Creating a section group”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.
rvalue vs const-ref StreamSet overloads
Section titled “rvalue vs const-ref StreamSet overloads”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.
Auto-sections for polygroups
Section titled “Auto-sections for polygroups”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 0Builder.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.
Updating a section group
Section titled “Updating a section group”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.
Editing in place (fast paths)
Section titled “Editing in place (fast paths)”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.
Reading data back
Section titled “Reading data back”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.
Building asynchronously
Section titled “Building asynchronously”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).
Blueprint API
Section titled “Blueprint API”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.