Skip to content
PRODUCT GITHUB  ↗

Materials

Materials on a Realtime Mesh work in two layers. The URealtimeMesh object owns a list of material slots (name + material), and each section references one slot by index. On top of that, because URealtimeMeshComponent is a UMeshComponent, the standard component-level material overrides still apply. This page covers both and how they resolve.


A material slot is an FRealtimeMeshMaterialSlot — a SlotName (FName) and a Material (UMaterialInterface*). Sections don’t hold a material directly; they store the index of the slot they draw with (FRealtimeMeshSectionConfig::MaterialSlot).

Set up slots on the mesh:

URealtimeMeshSimple* RealtimeMesh =
GetRealtimeMeshComponent()->InitializeRealtimeMesh<URealtimeMeshSimple>();
// Slot index 0, named "PrimaryMaterial", with an optional material asset.
RealtimeMesh->SetupMaterialSlot(0, "PrimaryMaterial", SomeMaterialInterface);

SetupMaterialSlot(int32 MaterialSlot, FName SlotName, UMaterialInterface* InMaterial = nullptr) creates or replaces the slot at that index. The material argument is optional — the examples routinely register a named slot with no material (SetupMaterialSlot(0, "DefaultMaterial")) and rely on assignment elsewhere or on the engine default material.

To size the slot list directly:

RealtimeMesh->SetNumMaterialSlots(3);

Growing adds empty (unnamed) slots; shrinking trims the trailing slots and drops their names from the name lookup. Note a deliberate design choice: if you shrink below a slot index that a live section still references, that section falls back to the engine default material rather than erroring — the appearance degrades gracefully instead of breaking rendering.

The mesh exposes the full slot API (all BlueprintCallable, see Public/RealtimeMesh.h):

int32 Count = RealtimeMesh->GetNumMaterials();
int32 Index = RealtimeMesh->GetMaterialIndex(FName("PrimaryMaterial")); // INDEX_NONE if missing
FName Name = RealtimeMesh->GetMaterialSlotName(0);
bool bValid = RealtimeMesh->IsMaterialSlotNameValid(FName("PrimaryMaterial"));
FRealtimeMeshMaterialSlot Slot = RealtimeMesh->GetMaterialSlot(0);
UMaterialInterface* Mat = RealtimeMesh->GetMaterial(0); // nullptr if the slot index is invalid
TArray<FName> Names = RealtimeMesh->GetMaterialSlotNames();
TArray<FRealtimeMeshMaterialSlot> Slots = RealtimeMesh->GetMaterialSlots();

GetMaterialIndex is what lets you address slots by name — resolve the name to an index once, then use that index in your section configs.


A section renders with FRealtimeMeshSectionConfig::MaterialSlot, which is an index into the mesh’s slot list. Recall from Section Groups & Sections that auto-created poly-group sections default their material slot to their poly-group index, and you set it explicitly with UpdateSectionConfig:

// Point the first auto-created section at material slot 0.
RealtimeMesh->UpdateSectionConfig(
FRealtimeMeshSectionKey::CreateForPolyGroup(GroupKey, 0),
FRealtimeMeshSectionConfig(0));

So the full path from triangle to material is: a section’s MaterialSlot index → the mesh’s slot at that index → that slot’s Material (or, if the slot is empty or out of range, the engine default material).


URealtimeMeshComponent is a UMeshComponent, so it participates in the engine’s standard per-component material override system. SetMaterial(index, material) (inherited from UPrimitiveComponent) stores an override in the component’s override-materials array, exactly as it does on a static mesh component.

Resolution is layered — the component prefers its own override and falls back to the mesh’s slot (from Public/RealtimeMeshComponent.h / RealtimeMeshComponent.cpp):

// URealtimeMeshComponent::GetMaterial(ElementIndex):
// 1. If the component has an override material at ElementIndex, use it.
// 2. Otherwise, fall back to the mesh's material slot: RealtimeMesh->GetMaterial(ElementIndex).
// 3. Otherwise nullptr.

GetNumMaterials() on the component returns the larger of the component’s override count and the mesh’s slot count, so overrides can extend past the mesh’s declared slots. The component also forwards the slot-name lookups (GetMaterialIndex, GetMaterialSlotName, GetMaterialSlotNames, IsMaterialSlotNameValid) so name-based material access works through the component and integrates with the details panel.

Practical upshot: define your slots (and their default materials) on the mesh, and reserve Component->SetMaterial(...) for per-instance overrides — e.g. two components that share one mesh but want different materials. An override on one component doesn’t affect the shared mesh or any other component.