Skip to content
PRODUCT GITHUB  ↗

Section Groups & Sections

A Realtime Mesh’s geometry lives in a two-level structure inside each LOD: section groups (buffer sets) own the vertex/index streams, and sections draw ranges of those streams with a material and render settings. This page covers how you address them (keys), how you configure them, and the auto-section behavior that most meshes rely on. For where these sit in the overall object model, see Structure.

The API here is shown against URealtimeMeshSimple; the same keys and config types apply to the other Managed meshes.


Everything is addressed by value-type keys rather than pointers, so the keys are stable, copyable, and safe to store. Keys are scoped to a LOD.

FRealtimeMeshBufferSetKey identifies a buffer set inside a LOD. Create one with:

const FRealtimeMeshLODKey LODKey(0);
// New-style: you supply the slot index explicitly (like a static-mesh material slot).
FRealtimeMeshBufferSetKey GroupA = FRealtimeMeshBufferSetKey::Create(LODKey, 0, FName("Terrain"));
// Name-based: a stable slot index is derived from the name's CRC, so old FName-only
// code keeps working. This is the form used throughout the examples.
FRealtimeMeshBufferSetKey GroupB = FRealtimeMeshBufferSetKey::Create(LODKey, FName("Floor"));
// A guaranteed-unique group when you don't care about the name.
FRealtimeMeshBufferSetKey GroupC = FRealtimeMeshBufferSetKey::CreateUnique(LODKey);

A group key’s identity is the (LODIndex, SlotIndex) pair; the optional name is friendly metadata and does not participate in equality. (A name-based key and an explicit-index key that hashes to the same slot compare equal — that’s intentional, so legacy and new code interoperate on the same buffer set.)

FRealtimeMeshSectionKey identifies a section inside a LOD:

// From a group + name (legacy-style; records the group slot for back-compat lookups).
FRealtimeMeshSectionKey Section = FRealtimeMeshSectionKey::Create(GroupKey, FName("Visible"));
// A unique section within a group.
FRealtimeMeshSectionKey Unique = FRealtimeMeshSectionKey::CreateUnique(GroupKey);
// The key for the auto-created section of poly group N (see below).
FRealtimeMeshSectionKey PolyGroup0 = FRealtimeMeshSectionKey::CreateForPolyGroup(GroupKey, 0);

CreateForPolyGroup is the one you’ll use most, because auto-created sections use exactly these keys — so this is how you reach in and reconfigure them.


On URealtimeMeshSimple you create a group and hand it the geometry as a FRealtimeMeshStreamSet:

FRealtimeMeshStreamSet StreamSet;
// ...build the mesh into StreamSet with a builder (see Key Concepts)...
const FRealtimeMeshBufferSetKey GroupKey =
FRealtimeMeshBufferSetKey::Create(FRealtimeMeshLODKey(0), FName("Box"));
RealtimeMesh->CreateBufferSet(GroupKey, StreamSet);

CreateBufferSet has overloads taking the stream set by const-ref or by rvalue (MoveTemp(StreamSet) when you’re done with it), plus a config argument and a bShouldAutoCreateSectionsForPolyGroups flag (default true). See Public/RealtimeMeshSimple.h.

For building the StreamSet itself — streams, builders, AppendBoxMesh and friends — see Key Concepts. This page assumes you already have one.


FRealtimeMeshBufferSetConfig (in Public/Interface/Core/RealtimeMeshBufferSetConfig.h) controls how the group’s buffers are allocated and updated:

struct FRealtimeMeshBufferSetConfig
{
ERealtimeMeshSectionDrawType DrawType; // Static (default) or Dynamic
bool bComputeWritable; // default false
};

DrawType is the important one:

  • Static — lower rendering overhead, but changing the geometry requires recreating the render proxy for every component using the mesh. Best for geometry that rarely (or never) changes after creation.
  • Dynamic — slightly higher overhead, but the buffers can be updated in place without a proxy recreate. Choose this for geometry you animate or edit frequently — it’s what makes the in-place fast paths eligible (see Updating Mesh Data).

bComputeWritable allocates the group’s GPU buffers with unordered-access (UAV) support so a compute pass can write directly into them. It implies dynamic-like behavior (no proxy recreate on change) since the geometry is mutated on the GPU rather than re-uploaded from the CPU. It’s independent of DrawType.

To make a group updatable in place, pass a Dynamic config at creation:

RealtimeMesh->CreateBufferSet(
GroupKey, StreamSet,
FRealtimeMeshBufferSetConfig(ERealtimeMeshSectionDrawType::Dynamic));

(Grounded in ARealtimeMeshExample_Simple_FastUpdate, Source/RealtimeMeshExamples/Private/Simple/RealtimeMeshExample_Simple_FastUpdate.cpp.)


By default (bShouldAutoCreateSectionsForPolyGroups == true), you usually don’t create sections by hand at all. When you create or update a Simple section group, RMC inspects the poly-group stream in your StreamSet and automatically creates one section per poly group, keyed by FRealtimeMeshSectionKey::CreateForPolyGroup(GroupKey, PolyGroupIndex). If the mesh has no poly-group stream, a single section covering the whole buffer is created.

By default each auto-created section is configured with its material slot set to its poly-group index. To point poly group 0 at material slot 0, reconfigure that section:

const FRealtimeMeshBufferSetKey GroupKey =
FRealtimeMeshBufferSetKey::Create(0, FName("Box"));
RealtimeMesh->CreateBufferSet(GroupKey, StreamSet);
// Reach the auto-created section for poly group 0 and set its config.
RealtimeMesh->UpdateSectionConfig(
FRealtimeMeshSectionKey::CreateForPolyGroup(GroupKey, 0),
FRealtimeMeshSectionConfig(0)); // material slot 0

(This exact pattern is used in ARealtimeMeshExample_Simple_LODs and ARealtimeMeshExample_Simple_Collision.)

You can toggle the behavior per group:

RealtimeMesh->SetShouldAutoCreateSectionsForPolyGroups(GroupKey, false);
bool bAuto = RealtimeMesh->ShouldAutoCreateSectionsForPolygonGroups(GroupKey);

When auto-creation is off, create sections yourself with the Managed CreateSection API described next.


FRealtimeMeshSectionConfig (in Public/Interface/Core/RealtimeMeshSectionConfig.h) is the per-section render configuration:

struct FRealtimeMeshSectionConfig
{
int32 MaterialSlot; // which material slot this section draws with (default 0)
bool bIsVisible; // default true
bool bCastsShadow; // default true
bool bIsMainPassRenderable; // advanced; default true
bool bForceOpaque; // advanced; default false
};

MaterialSlot chooses which of the mesh’s material slots the section renders with — see Materials. The constructor takes the slot index, so FRealtimeMeshSectionConfig(2) is a visible, shadow-casting section on slot 2.

On any Managed mesh (URealtimeMeshManaged, so Simple/Procedural/Dynamic), you can create a section explicitly against a stream range and config:

RealtimeMesh->CreateSection(
SectionKey,
FRealtimeMeshSectionConfig(0),
StreamRange,
/*bShouldCreateCollision=*/ false);
RealtimeMesh->UpdateSectionConfig(SectionKey, FRealtimeMeshSectionConfig(1), /*bShouldCreateCollision=*/ false);
RealtimeMesh->UpdateSectionRange(SectionKey, NewStreamRange);

The bShouldCreateCollision flag marks the section’s triangles for inclusion in complex (per-triangle) collision — see Collision.

A section draws a sub-range of its group’s buffers, described by FRealtimeMeshStreamRange — a Vertices range and an Indices range, both FInt32Range. Auto-created sections get the full valid range of the buffer set; you only set ranges explicitly when you want a section to draw a subset (for example a shadow-only section over part of the index buffer). Use UpdateSectionRange to change it later.


You can flip visibility and shadow casting without rebuilding the section’s config. These return a TFuture<ERealtimeMeshProxyUpdateStatus> (and have Blueprint overloads that take a completion callback):

RealtimeMesh->SetSectionVisibility(SectionKey, false);
RealtimeMesh->SetSectionCastShadow(SectionKey, true);
bool bVisible = RealtimeMesh->IsSectionVisible(SectionKey);
bool bShadow = RealtimeMesh->IsSectionCastingShadow(SectionKey);
FRealtimeMeshSectionConfig Config = RealtimeMesh->GetSectionConfig(SectionKey);

See Updating Mesh Data for what that TFuture return means.


TArray<FRealtimeMeshBufferSetKey> Groups = RealtimeMesh->GetBufferSets(FRealtimeMeshLODKey(0));
TArray<FRealtimeMeshSectionKey> Sections = RealtimeMesh->GetSectionsInBufferSet(GroupKey);
RealtimeMesh->RemoveSection(SectionKey); // TFuture-returning; also a callback overload
RealtimeMesh->RemoveBufferSet(GroupKey); // removes the group and its streams

GetBufferSets is exactly how the dynamic-update example decides whether to create a group or update the existing one (ARealtimeMeshExample_Simple_DynamicUpdate, Source/RealtimeMeshExamples/Private/Simple/RealtimeMeshExample_Simple_DynamicUpdate.cpp).