Skip to content
PRODUCT GITHUB  ↗

Lumen Cards

Lumen builds its lighting from a surface cache, and it populates that cache from a mesh’s card representation — a small set of oriented cards (per-object planes) that approximate the surface. Static meshes get their cards at cook time. A RealtimeMesh has no card representation until you generate one at runtime, and without it Lumen has nothing to capture: the mesh will be missing or wrong under Lumen GI.

RMC generates cards with the standalone RealtimeMeshSDF module and applies them through RealtimeMeshExt, mirroring the distance-field path. In practice you almost always want both a distance field and cards — Lumen depends on the distance field too — so the recommended path generates them together.


  • Lumen GI enabled (Project Settings → Engine → Rendering → Global Illumination → Lumen).
  • Generate Mesh Distance Fields enabled — Lumen consumes both the distance field and the cards. A card representation alone, with no distance field, is not enough for Lumen to light the mesh.
  • The GPU card path needs a real RHI (it does not run under -NullRHI); a CPU path is available for background use.

URealtimeMeshSDFLibrary (in RealtimeMeshExt, header RealtimeMeshSDFLibrary.h) generates a card representation on the GPU and applies it to the mesh so Lumen’s surface cache uses it. It operates on a section group (FRealtimeMeshBufferSetKey); every entry point has a C++ Async form returning a TFuture and a Blueprint-callable form with a completion callback.

#include "RealtimeMeshSDFLibrary.h"
// Mesh is a URealtimeMeshSimple*; Key identifies the section group.
URealtimeMeshSDFLibrary::GenerateAndApplyCardsAsync(Mesh, Key, /*MaxCards*/ 12, /*VolumeResolutionScale*/ 1.0f)
.Next([](ERealtimeMeshProxyUpdateStatus Status)
{
UE_LOG(LogTemp, Log, TEXT("Lumen cards applied (status %d)."), static_cast<int32>(Status));
});

GenerateAndApplyCardsFromResidentAsync builds the cards from the render proxy’s already-resident GPU buffers (no geometry re-upload) — the same resident fast path as the distance-field library.

Generate everything Lumen needs in one call

Section titled “Generate everything Lumen needs in one call”

Because Lumen wants both representations, GenerateAndApplyLumenFromResidentAsync builds and applies the distance field and the cards together, from resident GPU buffers, completing once both are applied:

URealtimeMeshSDFLibrary::GenerateAndApplyLumenFromResidentAsync(
Mesh, Key, /*ResolutionScale*/ 1.0f, /*MaxCards*/ 12)
.Next([](ERealtimeMeshProxyUpdateStatus Status)
{
UE_LOG(LogTemp, Log, TEXT("Full Lumen (DF + cards) applied (status %d)."), static_cast<int32>(Status));
});

This is the go-to call for making a dynamic mesh light correctly under Lumen: one call, the SDF volume built once and shared between the distance field and the cards. Each of these also has a Blueprint-callable / non-Async form reporting completion through a FRealtimeMeshManagedCompletionCallback.


If you already have a card representation, apply it directly. On URealtimeMeshSimple:

FRealtimeMeshCardRepresentation Cards(CardData); // from an FCardRepresentationData
Mesh->SetCardRepresentation(MoveTemp(Cards));
// ...or Mesh->ClearCardRepresentation(); to remove it.

FRealtimeMeshCardRepresentation (header Mesh/RealtimeMeshCardRepresentation.h) constructs from the engine’s FCardRepresentationData. SetCardRepresentation returns a TFuture<ERealtimeMeshProxyUpdateStatus> (there is also a callback overload), with a matching ClearCardRepresentation.


The RealtimeMeshSDF module generates the raw card representation, decoupled from any RealtimeMesh. As with the distance-field API, the public headers (RealtimeMeshSDFCards.h, …) are façades aliasing the shared core into RealtimeMesh::SDF — always write the RealtimeMesh::SDF:: spelling.

#include "RealtimeMeshSDFCards.h"
using namespace RealtimeMesh;
SDF::FSdfCardBuildParams Params;
Params.MaxCards = 12;
Params.Source = SDF::ESdfCardSource::RayCast; // precise; needs the triangle mesh
SDF::FSdfMeshInputCPU Input;
Input.Positions = Positions; // TArrayView<const FVector3f>
Input.Indices = Indices; // TArrayView<const uint32>
// CPU (synchronous, RHI-free) — call from a worker thread:
SDF::FSdfCardRepresentation Rep = SDF::GenerateCardRepresentationCPU(Input, Params);
// GPU (hybrid: SDF + surfels on the GPU, cluster on the CPU) — returns a future:
SDF::GenerateCardRepresentationGPU(Input, Params).Next([](SDF::FSdfCardRepresentation Rep) { /* ... */ });

The card entry points (in RealtimeMesh::SDF):

  • GenerateCardRepresentationCPU(FSdfMeshInputCPU, FSdfCardBuildParams) — synchronous, RHI-free; GenerateCardRepresentationCPUAsync wraps it on the thread pool.
  • GenerateCardRepresentationGPU(...) — hybrid GPU build, returns a TFuture<FSdfCardRepresentation>; there is a resident-GPU-buffers overload too.
  • GenerateCardRepresentationFromVolume(FSdfVolume, FSdfCardBuildParams) — derive cards from an existing SDF volume, so one distance field feeds both Lumen representations.

The result is a FSdfCardRepresentation (check IsValid()); ToMeshCardsBuildData() converts it into the engine FMeshCardsBuildData that a card representation expects.

  • MaxCards (default 12) — maximum cards emitted across all six directions.
  • Source (ESdfCardSource) — how the surface samples that drive card placement are produced:
    • RayCast (default) — casts rays through the mesh BVH for true triangle hits and a visibility test. Precise, mirrors the engine’s own card build; needs the triangle mesh. Use it for quality.
    • DistanceField — derives samples from a signed distance field (zero-crossings + gradient normals). Much faster and needs only the volume, but softer on thin features. Good for runtime/streaming.
  • VolumeResolutionScale — for the mesh overloads that build a volume internally, the DistanceFieldResolutionScale used to size it.
  • bMultiThreaded, MaxVoxelsPerDim, TargetVoxelSize, TargetNumSurfels, NumSurfelSamples, NumVisibilityRays — grid and sampling tuning; the defaults mirror the engine.

Note: the GPU and resident-buffer paths (GenerateCardRepresentationGPU) always derive cards from the distance field on the GPU, so Source and the ray-cast sampling params are ignored there.


The old URealtimeMeshCardRepresentationGenerator (RealtimeMeshCardRepresentationGenerator.h) has been removed. Use URealtimeMeshSDFLibrary or RealtimeMesh::SDF::GenerateCardRepresentationCPU. The output is engine-equivalent to the old generator but not bit-identical; the core builds its own LBVH and derives bounds from the mesh.


  • ARealtimeMeshExample_Simple_LumenSupport — the go-to end-to-end example: generates a distance field and a card representation and applies both via URealtimeMeshSDFLibrary, so Lumen lights the mesh. Start here if your goal is a dynamic mesh that looks correct under Lumen GI.
  • ARealtimeMeshExample_Simple_CardProfile (Source/RealtimeMeshExamples/Private/Simple/) — profiles card generation, comparing the RayCast and DistanceField sources and the CPU vs GPU paths via SDF::GenerateCardRepresentationCPU / GenerateCardRepresentationGPU.
  • ARealtimeMeshExample_Simple_DistanceField — also exercises the full Lumen path: with cards and resident buffers enabled it calls GenerateAndApplyLumenFromResidentAsync.