Skip to content
PRODUCT GITHUB  ↗

Distance Fields

A mesh distance field (MDF) is a signed distance field baked into a sparse volume texture. The engine uses it for Lumen global illumination, distance-field soft shadows, and distance-field ambient occlusion (DFAO). Static meshes get theirs at cook time; a RealtimeMesh has to generate one at runtime, from whatever geometry it currently holds.

RMC generates distance fields with the standalone RealtimeMeshSDF module (a self-contained GPU/CPU mesh→SDF generator) and applies them through RealtimeMeshExt. There are three entry points, from highest-level to lowest.


  • The project setting Generate Mesh Distance Fields must be enabled (Project Settings → Engine → Rendering). Without it the engine allocates no distance-field atlas and an applied field is ignored.
  • The GPU generation path needs a real RHI — it does not run under -NullRHI. A CPU path is available for headless or background use.

URealtimeMeshSDFLibrary (in RealtimeMeshExt, header RealtimeMeshSDFLibrary.h) is the one-call bridge: it reads a section group’s geometry, generates the field on the GPU, packs it into the engine’s sparse-brick format, and applies it to the mesh via the standard SetDistanceField path — so Lumen / DF shadows / DFAO consume it. Generation is explicit and on-demand.

It operates on a section group, identified by a FRealtimeMeshBufferSetKey. Every entry point is available as a C++ Async function returning a TFuture, and as a Blueprint-callable function reporting completion through a callback.

#include "RealtimeMeshSDFLibrary.h"
// Mesh is a URealtimeMeshSimple*; Key identifies the section group to convert.
URealtimeMeshSDFLibrary::GenerateAndApplyDistanceFieldAsync(Mesh, Key, /*ResolutionScale*/ 1.0f)
.Next([](ERealtimeMeshProxyUpdateStatus Status)
{
UE_LOG(LogTemp, Log, TEXT("Distance field applied (status %d)."), static_cast<int32>(Status));
});

ResolutionScale is the engine’s DistanceFieldResolutionScale (1 = default detail, higher = finer). The generated field is sized the UE way and gets the exterior band added automatically, so it matches a baked static-mesh distance field at the same scale.

If the mesh has already rendered, the ...FromResident variants skip the CPU read-back and geometry re-upload entirely — the field builds straight from the vertex/index buffers already resident on the GPU:

URealtimeMeshSDFLibrary::GenerateAndApplyDistanceFieldFromResidentAsync(Mesh, Key, 1.0f);

This requires the mesh’s render proxy to exist (it must have been rendered) and manual-vertex-fetch support (the position SRV). It uses the mesh’s local bounds plus BoundsPadding.

Both variants also have Blueprint-callable and non-Async forms (GenerateAndApplyDistanceField, GenerateAndApplyDistanceFieldFromResident) that report completion through a FRealtimeMeshManagedCompletionCallback.


If you already have a distance-field volume (from your own pipeline, or from the low-level generator below), apply it directly. On URealtimeMeshSimple:

FRealtimeMeshDistanceField DistanceField(VolumeData); // from an FDistanceFieldVolumeData
Mesh->SetDistanceField(MoveTemp(DistanceField));
// ...or Mesh->ClearDistanceField(); to remove it.

FRealtimeMeshDistanceField (header Mesh/RealtimeMeshDistanceField.h) is RMC’s serializable wrapper around the engine’s sparse distance-field volume; it constructs from an FDistanceFieldVolumeData and can hand back rendering data. URealtimeMeshSimple::SetDistanceField returns a TFuture<ERealtimeMeshProxyUpdateStatus> (there is also an overload taking a completion callback), and there is a matching ClearDistanceField.


The RealtimeMeshSDF module generates the raw volume, decoupled from any RealtimeMesh. Its public headers (RealtimeMeshSDFGenerator.h, RealtimeMeshSDFGenerate.h, …) are façades that alias the shared core into the RealtimeMesh::SDF namespace — always write the RealtimeMesh::SDF:: spelling (examples do using namespace RealtimeMesh; then SDF::).

You feed it a FSdfMeshInputCPU (positions + a flat uint32 index list) and a FSdfBuildParams, and get back a FSdfVolume:

#include "RealtimeMeshSDFGenerator.h"
using namespace RealtimeMesh;
SDF::FSdfBuildParams Params;
Params.ResolutionMode = SDF::ESdfResolutionMode::Auto; // engine-matching sizing + exterior band
Params.ResolutionScale = 1.0f; // Auto mode: the DistanceFieldResolutionScale
Params.bComputeSign = true; // false => unsigned distance field
Params.Precision = SDF::ESdfPrecision::Float32;
SDF::FSdfMeshInputCPU Input;
Input.Positions = Positions; // TArrayView<const FVector3f>
Input.Indices = Indices; // TArrayView<const uint32>, 3 per triangle
// GPU path: returns immediately, future fulfilled after readback.
SDF::GenerateDistanceField(Input, Params).Next([](SDF::FSdfVolume Volume)
{
if (Volume.IsValidVolume()) { /* pack + apply, or inspect */ }
});

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

  • GenerateDistanceField(FSdfMeshInputCPU, FSdfBuildParams) — GPU path; returns a TFuture<FSdfVolume>. The portable choice for any mesh source.
  • GenerateDistanceFieldCPU(...) — runs entirely on the CPU (no RHI), synchronous; call it from a worker thread for background/asset-load use. GenerateDistanceFieldCPUAsync(...) wraps it on the thread pool.
  • GenerateDistanceField(FSdfMeshInputGPU, ...) — build from already-resident GPU buffers, no re-upload.
  • Generate(FSdfMeshInputCPU, FSdfGenerateRequest) (header RealtimeMeshSDFGenerate.h) — the unified request: ask for a distance field and/or cards in one call, choosing CPU or GPU; when both are requested the SDF volume is built once and shared.

On failure every variant still completes, but with an invalid volume — check FSdfVolume::IsValidVolume().

  • ResolutionMode — Auto (default) derives resolution from mesh size the UE way and adds the exterior band; Explicit uses your exact Resolution and Bounds.
  • ResolutionScale / VoxelDensity / MaxResolution — the Auto-mode sizing knobs (ResolutionScale is the engine’s DistanceFieldResolutionScale).
  • Resolution — the exact voxel grid dimensions, used only in Explicit mode.
  • Bounds / BoundsPadding — supply explicit world-space bounds, or leave Bounds invalid to auto-fit and pad.
  • bComputeSign — set false for an unsigned field.
  • Precision — Float32 (default) or Float16 storage.

Once you have a volume, pack it into engine data with SDF::PackToDistanceFieldVolumeData(Volume, MeshBounds, OutData) (header RealtimeMeshSDFPacker.h), wrap it in a FRealtimeMeshDistanceField, and apply it via the direct data path above. The URealtimeMeshSDFLibrary calls do all of this for you.

  • GPU (GenerateDistanceField) is the fastest for interactive/runtime use, but needs a real RHI and can’t run under -NullRHI.
  • CPU (GenerateDistanceFieldCPU) is self-contained and RHI-free — ideal for background threads and asset loading. It builds an LBVH and uses a band-limited Fast Winding Number for the sign; it is far faster than a naive per-voxel generator, but still heavier than the GPU path for large volumes.

The old URealtimeMeshDistanceFieldGeneration API (RealtimeMeshDistanceFieldGenerator.h) has been removed. Use URealtimeMeshSDFLibrary::GenerateAndApplyDistanceField[Async], or RealtimeMesh::SDF::GenerateDistanceFieldCPU + PackToDistanceFieldVolumeData if you need the raw path. The output is engine-equivalent to the old generator but not bit-identical.


  • ARealtimeMeshExample_Simple_DistanceField (Source/RealtimeMeshExamples/Private/Simple/) — builds a box, generates a GPU distance field with the low-level SDF::GenerateDistanceField, draws a color-coded slice (red inside, green outside), and can apply the field / cards / full Lumen path to the mesh via URealtimeMeshSDFLibrary.
  • ARealtimeMeshExample_Simple_DistanceFieldProfile — sweeps grid resolutions and profiles CPU vs GPU generation time using SDF::GenerateDistanceFieldCPU and SDF::GenerateDistanceField.