Quick answer: Procedural Foliage Volume actors are editor-only and stripped from cooked builds. Only the FoliageInstancedStaticMeshComponents baked by Resimulate ship in the package. Run Resimulate, save the level, ensure the baked instances are on an Always Loaded World Partition data layer, and add foliage meshes to the Always Cook list to guarantee they ship.
Here is how to fix Unreal foliage that shows in editor but is missing in a packaged build. Your forest looks gorgeous in PIE: ten thousand trees, dense undergrowth, scattered rocks, all driven by a Procedural Foliage Volume that places everything based on rules. You package the build, launch it, and the entire forest is gone. Bare landscape, nothing scattered, no warnings in the log. The volume is right there in the level, the static meshes are referenced, the foliage type asset exists — but the cooker shipped a level with no foliage instances because nothing baked the procedural placement.
The Symptom
A level that displays correctly placed procedural foliage in the Unreal Editor renders no foliage at all when packaged and launched as a standalone build. The static meshes are present in the cooked content, the level loads without errors, and the Procedural Foliage Volume actor is visible in development logs — but no instances render.
Editor PIE shows everything. Hit Play in editor and the trees are scattered, the grass is dense, the rocks are placed. Standalone Game launched from the editor still shows the foliage. The problem only appears in a fully packaged build.
Packaged build is bare. The cooked level loads, the landscape is correct, lighting is correct, but where the trees should be there is empty terrain. No drawcalls for the foliage meshes, no instances in stat memory, no log warnings.
World Partition variants. In a World Partition map, the foliage may render at the spawn point but disappear as the player moves, or vice versa — the symptom depends on which cells contain the baked instances and which cells the player ends up in.
Static foliage paints survives. Foliage placed by hand with the Foliage Painter tool renders correctly. Only procedurally placed foliage is missing. This is the diagnostic clue: the painter creates instances directly, while the volume only places instances after Resimulate runs.
What Causes This
ProceduralFoliageVolume is editor-only. The actor that defines where procedural placement should occur exists only in the editor. The cooker strips it from the package because its HasAnyFlags(RF_HasExternalPackage) and editor-only metadata mark it as non-shipping. Without it, no procedural placement runs at runtime — placement is intended to happen at edit time and bake into instances.
Instances exist only after Resimulate. The volume defines rules but does not place instances by itself. Clicking Resimulate runs the placement algorithm and creates FoliageInstancedStaticMeshComponent entries on the level’s InstancedFoliageActor. Until you Resimulate, the level contains the volume but zero baked instances. The cooker ships zero instances, you see zero foliage.
Resimulate output not saved. Even after running Resimulate, the baked instances live in memory only. If you do not save the level (or, in World Partition, the affected cells) before cooking, the bake is discarded and the cooker sees the unsaved state.
HISM components stripped by partition. In World Partition maps, baked foliage instances are distributed across cells. If the foliage is in a cell whose data layer is set to Dynamically Loaded with no runtime trigger, the cell never streams in and the instances never render. The cooker ships them, but the streaming subsystem never loads them.
Source mesh not in cook dependency. If the foliage type asset references a static mesh that no other level references, the cooker may exclude the mesh from the package depending on your reference path settings. The instances exist but their mesh asset is missing, so the renderer skips them silently.
The Fix
Step 1: Resimulate every Procedural Foliage Volume and save. Select the volume in the level, open Details, and click Resimulate. Watch the editor briefly hitch as instances bake. Open the World Outliner and confirm InstancedFoliageActor_X entries appear with non-zero instance counts. Save the level immediately. In a World Partition map, also save any affected external actor packages.
// Editor utility to bake all PFVs in the open level
void UFoliageBakeUtility::ResimulateAllInLevel(UWorld* World)
{
if (!World) return;
int32 Baked = 0;
for (TActorIterator<AProceduralFoliageVolume> It(World);
It; ++It)
{
AProceduralFoliageVolume* Volume = *It;
if (Volume && Volume->ProceduralComponent)
{
Volume->ProceduralComponent->ResimulateProceduralFoliage(
[&Baked](const TArray<FDesiredFoliageInstance>& Instances)
{
Baked += Instances.Num();
});
}
}
UE_LOG(LogFoliage, Log,
TEXT("Baked %d foliage instances across level"),
Baked);
}
Hooking Resimulate into a build-time editor script is the only reliable way to ensure procedural foliage stays current. Manual Resimulate is easy to forget — an automated step in your packaging pipeline catches every level before cook.
Step 2: Verify cooked instances and Always Loaded layers. Open Project Settings » Packaging and add your foliage mesh directory to Additional Asset Directories To Cook. In World Partition maps, open the Data Layers panel and assign the foliage actor to a layer marked Always Loaded, not Dynamically Loaded.
// Runtime diagnostic to confirm foliage is in memory
void AFoliageDiagnostic::LogFoliageInstanceCounts()
{
UWorld* World = GetWorld();
int32 TotalInstances = 0;
for (TActorIterator<AInstancedFoliageActor> It(World);
It; ++It)
{
AInstancedFoliageActor* IFA = *It;
TArray<UFoliageInstancedStaticMeshComponent*> Comps;
IFA->GetComponents(Comps);
for (UFoliageInstancedStaticMeshComponent* Comp : Comps)
{
int32 Count = Comp->GetInstanceCount();
TotalInstances += Count;
UE_LOG(LogTemp, Log, TEXT("%s: %d instances"),
*Comp->GetName(), Count);
}
}
UE_LOG(LogTemp, Log, TEXT("Total foliage: %d"),
TotalInstances);
}
Bind LogFoliageInstanceCounts to a console command and run it in your packaged build. If the count is zero, the bake never made it into the cook. If the count is non-zero but you see no rendering, the meshes themselves were stripped — check the cook log for missing references and add the mesh folder to the Always Cook list.
World Partition Specific Steps
World Partition maps split foliage instances across cell actors named FoliageInstancedStaticMeshComponent_X_Cell_Y. Each cell follows the streaming rules of its data layer. If your foliage is split across cells that fall into different data layers, behaviour can be inconsistent — one part of the forest streams in, another does not.
The simplest reliable setup is a single Foliage data layer marked Always Loaded, with every Procedural Foliage Volume assigned to it. Bake the foliage, save the level, and the cooker packages the cell actors as part of the always-loaded set. They are present in memory the entire time the level is loaded, which matches the editor behaviour.
Verifying the Fix
After making changes, do not trust a quick Play-In-Editor test — PIE bypasses the cooking step and uses live editor data. Always run a full Package Project, launch the resulting executable from outside the editor, and confirm foliage is present. If you have continuous integration, add a smoke test that loads the cooked map, queries instance counts, and fails the build if any count is unexpectedly zero.
“Procedural foliage is a build step, not a runtime feature. The volume is your recipe; Resimulate is the oven; the baked instances are the cake. Ship cake, not recipes.”
Related Issues
If your foliage renders but causes severe hitches when streaming in, see Foliage Stream Hitching for HISM streaming and LOD setup. If foliage shadows are missing in the packaged build only, check Foliage Shadows Missing After Cook for shadow distance and per-mesh settings.
If you see the volume but no trees in the cook, the bake never happened — Resimulate, save, package.