What is WorldScape
WorldScape brings Clipmap-based terrain technology to Unreal Engine. With its 64-bit precision geometry, you can craft infinite flat and spherical worlds for single-player or multiplayer games. Instanced, multithreaded procedural foliage, RGBA-layered noise biomes, custom planetary gravity, heightmap import (8/16-bit), procedural materials and many editor tools come in the box. All sources in C++.
Landscape system, planet-scale exploration, multiplayer survival sandboxes, RTS battlefields, infinite arcade worlds, cinematic real-world recreations.
Feature list
- Create an infinite world playable in single-player and multiplayer, flat or spherical.
- 64-bit geometry precision for surface mesh construction.
- Create infinitely large or tiny flat worlds.
- Create infinitely large or tiny planets.
- Instanced & multithreaded procedural foliage (spawn driven by terrain temperature & humidity).
- Import HeightMaps in 8-bit or 16-bit and apply them to surfaces as decals (additive or merged).
- Editable terrain collision.
- Noise biomes customizable in C++.
- Realistic lighting for planets.
- WorldShifting ("fake 64-bit squared") for stable kilometer-scale gameplay.
- Procedural materials for surfaces.
- Custom MovementComponent BP for planetary gameplay.
- Custom gravity behavior for planets.
Architecture
WorldScape ships as 10 native modules. Each one does one thing:
| Module | Type | Role |
|---|---|---|
WorldScapeCore | Runtime | The Root actor, mesh component, LOD pipeline, FCP collision pool, grid & snap spawners |
WorldScapeNoise | Runtime | Noise classes. Earth, Moon, Rocky, Ice, Selenae, Cube, Warped, Tiny, HeightMap-based |
WorldScapeFoliages | Runtime | Foliage Asset / Collection / Cluster / Blueprint with biome constraints |
WorldScapeVolume | Runtime | HeightMap, Noise, Hole and Foliage Mask volumes |
WorldScapeCommon | Runtime | Double-precision math, FDVector, shared structs |
WorldScapeCompute | Runtime | GPU compute shaders for noise generation |
WorldScapeGPUTerrain | Runtime | Quadtree manager, heightfield manager, custom vertex factory |
WorldScapeEditor | Editor | Toolbar, custom details panels, content browser categories |
WorldScapeFactory | Editor | Asset factories for noise classes, foliage assets, heightmap data |
WorldScapePCG | Editor | Procedural Content Generation nodes & Blueprint library |
Requirements
- Unreal Engine versions supported:
4.26,4.27, and5.0through5.7 - Build platforms: Win64 or Linux (the plugin's
.upluginblacklists Mac, iOS and Android) - Plugins activated automatically:
Niagara,ProceduralMeshComponent,ModelingToolsEditorMode,Landmass,GeometryScripting,PCG,QLevel - For packaging: the Water plugin must be enabled (see Troubleshooting)
- Networking: replication is supported on Dedicated Server and Listen Server
Installation
From a fresh project to a procedural planet in under 10 minutes.
Get the plugin
Buy WorldScape on Fab Marketplace. Install via the Epic Games Launcher into your engine, or copy the WorldScape folder into YourProject/Plugins/.
Enable in your project
Open your UE5 project, go to Edit → Plugins, search for WorldScape, enable it, and restart the editor when prompted.
Apply the required UE5 settings
WorldScape needs two engine settings adjusted for terrain artifacts to disappear. See the next section.
Open the demo map
Inside the plugin's Content/Maps folder, open L_PlanetCore. Hit Play. Walk around. You're on a 6,360 km procedural planet.
Recommended UE5 settings
One render setting fixes a known visual artifact on UE 5.x:
Velocity Pass: Write After Base Pass
Open Project Settings → Rendering → Velocity Pass and set it to Write After Base Pass. This prevents terrain visual artifacts on UE 5.x.
MF_SLM_Layer_Planet in the WorldScape material functions folder, find planet location, right-click → Promote to Double, and set Rotate with Planet to false. (Thanks to zyzy2922 for spotting this.)
r.UseVisibilityOctree 0: earlier docs recommended this engine cvar. It is not registered by the WorldScape plugin and is deprecated in modern UE5 builds, so it has been removed from the setup. The plugin's own runtime cvars are listed in the Console reference.
Your first flat world
Drag a Root
In the Place Actors panel, type "WorldScape" and drag WorldScapeRoot into your level. Add a directional light if your scene doesn't have one.
Set the world type
In the Root's details panel, set Generation Type (the GenerationType property, of type EWorldScapeType) to Flat World.
Position at origin
Make sure the actor's transform location is (0, 0, 0).
Pick a noise & generate
Assign a noise asset to the Root's WorldScapeNoise property (right-click in the content browser → WorldScape tab to create one). See Noise system for the list of built-in noise classes. Generation runs automatically; force a regen with WS_ForceRegenerate() from Blueprint.
Your first planet
Drag a Root
Same as flat world, drag WorldScapeRoot into the level.
Set Generation Type to Planet, enable Generate
In the details panel under the Default category, set GenerationType to Planet. Enable bGenerateWorldScape (DisplayName "Generate") and bOcean (DisplayName "Generate Ocean"). The companion flag bFreezeGeneration (DisplayName "Freeze Generation") pauses regeneration if you ever need to lock the world while editing.
Set the planet scale
The Root's PlanetScale property holds the planet's radius in centimeters. Default value is 637817792 (a classic Earth-radius approximation in cm). Lower it for moons, raise it for gas giants.
PlanetScale = 637817792.0 // default — Earth-likeIf your camera starts inside the planet, lift it up by roughly PlanetScale on Z (legacy maps used a transform offset of Z = -PlanetScale to put the actor's pivot at the planet's center). The plugin no longer requires this convention, placing the Root at (0,0,0) is fine and the demo map does just that.
Pick a planet-class noise
Use one of the built-in noise generators: UTerraNoiseExample, URockyWorldsNoise, USelenaeNoise, UIceWorldNoise, UCubeNoise, UTinyPlanetExample, UTheMoonNoise, or your own subclass. See Built-in noises.
Materials, foliage, atmosphere
Copy materials from the demo map, add foliage collections to the Foliage tab, drop in a Sky Atmosphere and Skylight. Hit play.
TriangleSize ≈ 500-5000 (vertex spacing in cm near the ground), LodResolution ≈ 32-64 (vertices per axis per LOD), MaxLod ≈ 10-12 (lower values cause mountain pop-in), PlanetScale = target radius in cm (Earth-like = 637,817,792).
Demo map tour
The plugin ships a single sample map: WorldScape/Content/Maps/L_PlanetCore.umap. Use it to validate your install and as a reference scene for your own setup. (Older docs and tutorials may reference a "Demonstration_EarthLikePlanet" map, that name is legacy; the current map is L_PlanetCore.)
Core Concepts
Understand these five concepts and the rest of the docs slot in cleanly.
Flat vs Planet
EWorldScapeType picks one of two surface topologies:
| Mode | Math | Use case |
|---|---|---|
| Flat | infinite tiled XY plane | RTS, survival, top-down, square open worlds |
| Planet | cube-mapped sphere with FDVector ECEF coords | space sims, full-planet exploration, real-world heightmaps |
Both share the same Root actor, same noise system, same foliage stack. You can swap between them by changing one enum and the transform.
Root properties, quick reference
Verified directly against WorldScapeRoot.h. Categories below match the names you see in the editor's details panel.
Default
| Property | Type | Default |
|---|---|---|
| GenerationType | EWorldScapeType | (none) |
| bGenerateWorldScape | bool | (none). DisplayName "Generate" |
| bFreezeGeneration | bool | (none). DisplayName "Freeze Generation" |
| bOcean | bool | (none). DisplayName "Generate Ocean" |
| bProjectPosition | bool | (none). DisplayName "Projected Position (Experimental)" |
| IsAllowAutoGetNearest | bool | true, lets the subsystem track this Root |
| UsePostSlateTick | bool | true |
| OverrideMainTick | bool | true |
| StartTickGroup, EndTickGroup | ETickingGroup | TG_PrePhysics |
| PlanetScale | float | 637817792, only shown when GenerationType == Planet |
Lod Parameters
| Property | Type | Purpose |
|---|---|---|
| MaxLod | int32 | How many LODs of doubling-size sectors are generated. |
| LodResolution | int32 | Number of vertices per axis per sector. Higher = more detail, more cost. |
| TriangleSize | float | Distance between each vertex of the terrain when near the ground (cm). |
| HeightAnchor | float | Doubles triangle size above this altitude so distant/high terrain is cheaper. |
| OceanMaxLod, OceanLodResolution, OceanTriangleSize | int32 / float | Same triplet for the ocean clipmap. Hidden when bOcean = false. |
Collision
CollisionTriangleSize (float) is in the Collision category, not Lod Parameters, gated by bGenerateCollision / bGenerateCollisionInEditor.
Noise
| Property | Type | Default |
|---|---|---|
| WorldScapeNoise | UWorldScapeNoiseClass* | nullptr, assign your noise asset here |
| NoiseScale | float | 800 |
| NoiseIntensity | float | 1200000 |
| NoiseOffset | FVector | (0,0,0) |
| Seed | int32 | 10 |
| OceanHeight | float | 0, hidden when bOcean = false |
8 for low-variation worlds, 10-12 for altitude-heavy terrain to prevent mountain pop-in.
64-bit precision (FDVector)
UE5's stock FVector is float-precision. At kilometer scale that's not enough, you get jitter, terrain seams, snapping artifacts. WorldScape uses FDVector (double-precision) internally for all surface positions, then converts to float at the rendering boundary. That's how you get a 100,000 km planet without losing centimeter-level placement at the player's feet.
The Root actor lifecycle
AWorldScapeRoot exposes a small set of Blueprint-assignable delegates, verified directly in WorldScapeRoot.h:
| Delegate | Signature | Fires when |
|---|---|---|
InitializedEnd | FInitializedEnd() | The Root has finished its first initialization pass. |
OnCollisionLodsUpdate | FOnCollisionLodsUpdate(UWorldScapeLod*) | A per-section collision LOD update; provides the affected UWorldScapeLod*. |
WS_Pooling_OnInitialized | FWS_Pooling_OnInitialized() | Volume pooling is fully initialized and ready for Pooling_Take_* calls. |
WS_Grid_LoadCell_Delegate | FWS_Grid_LoadCell_Delegate(const FWS_Grid& Cell) | A grid cell finished loading. |
WS_Grid_UnLoadCell_Delegate | FWS_Grid_UnLoadCell_Delegate(const FWS_Grid& Cell) | A grid cell unloaded. |
WS_Grid_StartPendingLoad | FWS_Grid_StartPendingLoad() | A batch grid-load window opened. |
WS_Grid_EndPendingLoad | FWS_Grid_EndPendingLoad() | A batch grid-load window closed. |
WS_OnStartFoliage, WS_OnEndFoliage, WS_OnStartGrid, WS_OnEndGrid, WS_OnStartTerrain, WS_OnEndTerrain, WS_OnStartCollision, WS_OnEndCollision (between /* and */ in WorldScapeRoot.h around lines 357-386). These delegates are not currently exposed in the build, an earlier audit incorrectly listed them as available.
The Subsystem adds 4 more cross-Root delegates: WS_OnNearestChanged, WS_OnNearestNULL, WS_OnWorldScapeRegistry, FWS_OnWorldScapeUnRegistry.
The Subsystem Beta
Class name: UWorldscapeSubsystem. Auto-created per UWorld.
Cross-Root delegates
FWS_OnWorldScapeRegistry(AWorldScapeRoot*), a Root just registeredFWS_OnWorldScapeUnRegistry(AWorldScapeRoot*), a Root unregisteredFWS_OnNearestChanged(AWorldScapeRoot*), the player got within range of a different RootFWS_OnNearestNULL(), no Root is nearby anymore
Key functions (BlueprintCallable)
| Function | Returns | Purpose |
|---|---|---|
| Get_NearWorldScapeFromLocation | AWorldScapeRoot* | Find the active Root nearest to a location, with optional valid/exclude tags. |
| WS_Start_AutoGetNearest | void | Start auto-tracking the nearest world for the player. |
| WS_Stop_AutoGetNearest | void | Stop the auto-tracking loop. |
| WS_GetValidNearest | bool | True if a tracked Root is currently valid. |
| WS_GetCurrentNearestRoot | AWorldScapeRoot* | Currently tracked Root (null if none). |
| WS_GetCurrentSurfaceUpVector | FVector | Up-vector at the player's current surface point. |
| WS_GetActualGravityFromLocation | FVector | Gravity vector + direction + magnitude at any location. |
Auto-Get-Nearest tuning (subsystem settings)
| Property | Default | Purpose |
|---|---|---|
| AutoGetNearest_AutoEnabled | true | Master switch for auto-tracking. |
| AutoGetNearest_Update_Interval | 1.0 s | How often to re-pick the nearest Root. |
| AutoGetNearest_PlanetaryRadiusScaleLimit | 1.5 | Multiplier on PlanetScale beyond which the Root is considered out of range. |
| AutoGetNearest_FlatDistanceToGroundLimit | 20,000,000 cm | Max distance for flat worlds. |
| AutoGetNearest_Use_ValidTag | false | Require the valid tag for tracking. Defaults OFF, flip on if you want tag-filtered tracking. |
| AutoGetNearest_ValidTag | WS_Valid | Tag a Root must carry to be tracked (when Use_ValidTag is on). |
| AutoGetNearest_Use_InvalidTag | false | Apply the exclude tag. Defaults OFF. |
| AutoGetNearest_invalidTag | WS_Invalid | Tag that excludes a Root from tracking. |
Material Parameter Collection auto-sync
The subsystem owns a UMaterialParameterCollection* reference and updates it every frame with the current surface up-vector (Update_SurfaceVector()) and other planet/flat-world parameters (Update_MPC_Value()). Reference that MPC in any material or shader to get gravity-aware effects without calling functions from Blueprint.
Noise system
Every WorldScape vertex is the output of a noise function. The noise drives both shape (height) and biome (temperature, humidity, water mask). Customize it and you customize everything downstream.
FNoiseData, the contract
Every noise function returns an FNoiseData struct. Seven fields, registered in vertex color and used everywhere:
| Field | Type | Description |
|---|---|---|
| HeightNormalize | double | Height normalized 0-1, also stored in vertex color Red. |
| Height | double | Height in Unreal units, displaces terrain from planet center (or up for flat). |
| Temperature | float | Stored in vertex color Green. Drives material biomes & foliage masking. |
| Humidity | float | Stored in vertex color Blue. Drives material biomes & foliage masking. |
| WaterMask | float | Foliage masking + flag for HeightMap Influencer when ocean is higher than terrain. |
| FoliageMask | float | Direct foliage masking channel. |
| Hole | bool | Per-vertex flag that punches a hole in the terrain (used by the Hole Volume). |
Two functions must exist on every noise class:
FNoiseData GetHeight(..), terrain noise.FNoiseData GetOceanHeight(..), ocean / river / lake noise.
Built-in noise classes
Two-tier system. Generators (subclasses of UWorldScapeNoiseClass) produce FNoiseData per vertex. Most generators consume a parameter object (subclass of UNoiseParameter) holding the tunable values. Right-click in the content browser → WorldScape tab → create either.
Generators (assign to the Root's WorldScapeNoise property)
| Generator class | Pairs with parameter | Best for |
|---|---|---|
UWorldScapeCustomNoise | UCustomNoiseParameter | Earth or Moon (with crater mode). The most general-purpose generator. |
UTerraNoiseExample | UPlanetTerraNoise | Generic planetary terrain preset. |
URockyWorldsNoise | URockyWorlds | Rocky, low-vegetation planets. |
USelenaeNoise | USelenae | Luna-like moons. |
UIceWorldNoise | UIceWorld | Ice-covered worlds. |
UCubeNoise | UCubeNoiseParameter | Cube-mapped noise for cubic / weird planets. |
UwarpedNoiseeExample | UWarpedNoise | Domain-warped noise for alien terrain. |
UTinyPlanetExample | UTinyPlanet | Small procedural worlds (asteroids, marble planets). |
UHeightMapBasedV2 | UHeightMapV2 | Heightmap-driven instead of pure procedural. |
UYourOwnNoiseExample | UYourNoiseParameter | Template / starting point for custom subclasses. |
Additional QANGA / Earth noise generators (under CustomWorldScapeNoise/)
UEarthNoiseFuntion,UEarthNoise2Example, alternate Earth-style presetsUearthTerra, legacy Earth-Terra blendUTheMoonNoise. Luna preset with cratersUBarenWorldsNoise, barren / desert planetsUQangaV2Noise,UQangaNoise. QANGA-game-tuned presetsUHeightMapBased, legacy heightmap-driven generator (use V2 for new projects)
UwarpedNoiseeExample, UEarthNoiseFuntion, UearthTerra). The plugin still ships them as-is; use them with their literal name.
Custom noise (C++)
WorldScape was designed for noise editing in C++. Subclass UWorldScapeNoiseClass, implement GetHeight() and GetOceanHeight(), expose your own UPROPERTY parameters. The plugin includes UYourOwnNoiseExample as a barebones starting point.
youtu.be/HpMExX-JcTY.
Key parameters available on UCustomNoiseParameter:
Ocean
OceanFloorHeight (0.015), OceanFloorBaseHeight (-0.1), OceanFloorScale (1.0), OceanFloorOctave (5), OceanFloorLacunarity (2.5), OceanFloorPersistence (0.4), OceanFloorWeight (2.0), OceanFloorWeightOctave (10), OceanFloorWeightScale (0.5), OceanFloorWeightPower (256.0).
Mountains
MountainHeight (0.5), MountainHeightPower (1.2), MountainScale (0.15), MountainOctave (8), MountainLacunarity (1.5), MountainPersistence (0.7), MountainWeight (1.5), MountainWeightScale (0.0025), MountainWeightPower (10), MountainTemperatureReduction (0.35).
Hills
HillHeight (0.1), HillScale (0.5), HillOctave (8), HillLacunarity (2.3), HillPersistence (0.4).
Beaches
BeachMask (8), BeachMaskOffset (3.75), BeachMaskWeight (1), BeachHeight (0.001), BeachScale (1).
Climate
TemperatureOffset, TemperatureHeightReduction (2), TemperatureHeightReductionPower (1.2), HumidityOffset, HumidityScale (1).
Warp
WarpIntensity (0.01), WarpScale (10), Warp2Scale (100), bIgnorePlanetaryHeightmap.
Moon-specific (when EWorldGenerationType::Moon)
CraterFrequency (1), CraterOctave (8), CraterLacunarity (1.25), CraterPersistence (0.75), CraterHeightMultiplier (1), CraterSize (8), CraterRimHeight (15), CraterRimOctaveLimit (3), CraterRimPower (4), CraterWarp (1), CraterWarpFrequency (1), CraterWarpOctave (4).
Noise Volume
To add procedural noise overrides in a specific region:
- Right-click in content browser → Blueprint Class → search "Noise Volume".
- Drag the volume into your level. Its details panel mirrors the Root's noise tab.
- Open the Root, go to Volumes Tab, click + next to the Noise Volume list and assign your volume.
28000 or 5000000 on all axes for it to affect a large area."
GPU compute noise New
The WorldScapeCompute module ships GPU compute shaders that generate noise on the graphics card. Use this when CPU noise generation becomes a bottleneck (very high-detail planets, real-time terrain deformation, kilometer-radius streaming).
Heightmap system
Heightmap volumes apply external textures to your terrain as decals, localized via AHeightMapVolume, or wrapping the entire planet via the Root's planetary heightmap. The textures live in UHeightMapVolumeData data assets that can be reused by multiple volumes.
Channel layout
A UHeightMapVolumeData asset holds two texture references and exposes six addressable mask channels:
| Texture slot | R | G | B | A |
|---|---|---|---|---|
HeightMap | Height | Temperature | Humidity | Alpha (mask for R/G/B) |
HeightMapOcean | Ocean Height | , | , | Ocean Alpha |
EWSHeightMask (six values) addresses each channel: Height, Temperature, Humidity, Alpha, OceanHeight, OceanAlpha.
Sampling & tiling
EHMSamplingMethod (set per-volume in SamplingMethod):
Nearest, pixel-perfect, fastBilinear, smooth interpolation (default)Bicubic, smoothest, costs more samples
EHMTilingMethod is configured on the data asset, separately for X and Y, separately for the main and ocean textures (HeightXTiling, HeightYTiling, OceanXTiling, OceanYTiling, default Wrap):
Wrap, tile beyond UV 0-1 (good for repeating patterns / planetary wrap)Clamp, clamp to edge pixel beyond UV 0-1 (good for one-shot decals)Mirror, mirrored repetition
The UHeightMapVolumeData data asset
Right-click in content browser → WorldScape tab → HeightMap Volume Data. The asset is split into three property categories:
Category Texture
| Property | Type | Purpose |
|---|---|---|
| HeightMap | TSoftObjectPtr<UTexture2D> | Main texture (R/G/B = height/temp/humidity, A = mask). |
| HeightMapOcean | TSoftObjectPtr<UTexture2D> | Optional second texture (R = ocean height, A = ocean mask). |
Category TextureInfo
| Property | Default | Purpose |
|---|---|---|
| HeightXTiling, HeightYTiling | Wrap | Tiling mode for the main texture, X and Y axis independently. |
| OceanXTiling, OceanYTiling | Wrap | Tiling mode for the ocean texture. |
| bIgnoreHeightMap | false | Skip the height channel even if the texture is present. |
| bIgnoreTemperature | false | Skip the temperature channel. |
| bIgnoreHumidity | false | Skip the humidity channel. |
| bIgnoreAlpha | false | Skip the main alpha mask. |
| bIgnoreOceanHeightMap | false | Skip the ocean-height channel. |
| bIgnoreOceanAlpha | false | Skip the ocean alpha mask. |
Category TextureInfo · CustomResolution
Per-channel maximum sample resolution caps (in pixels). 0 = no limit (use the full texture resolution).
| Property | Default | Purpose |
|---|---|---|
| MaxHeightResolution | 0 | Cap the height-channel sampling resolution. |
| MaxTemperatureResolution | 0 | Cap temperature. |
| MaxHumidityResolution | 0 | Cap humidity. |
| MaxAlphaResolution | 0 | Cap the main alpha. |
| MaxOceanHeightResolution | 0 | Cap ocean height. |
| MaxOceanAlphaResolution | 0 | Cap ocean alpha. |
Functions on the asset
| Function | Editor only | Purpose |
|---|---|---|
Generate() | yes | The main button. Auto-configures the source textures and rebuilds all internal sample arrays, see workflow below. |
CleanData() | yes | Empty all StoredHeightMapData / StoredTemperatureData / etc. Use before re-Generate to force a clean rebuild. |
BuildTexture(UTexture2D*) | no | Sample a specific texture into the main channel arrays. |
BuildOceanTexture(UTexture2D*) | no | Sample into ocean channel arrays. |
GetStoredValue(int Index, EWSHeightMask) | no | Direct read of the sampled uint16 array. |
IsValidIndex(int, EWSHeightMask) | no | Bounds check. |
MaskSize(EWSHeightMask) | no | Resolution of the sampled array for that channel. |
Width(EWSHeightMask) / Height(EWSHeightMask) | no | Per-channel sampled width/height. |
Texture import workflow
The Generate() function does the heavy lifting automatically. You don't need to manually adjust the texture's compression / mip / sRGB settings, Generate() sets them for you, samples the pixels into typed arrays, then restores the original texture configuration.
Generate() changes on the source texture (briefly, then restores):
PowerOfTwoMode = PadToPowerOfTwo, non-power-of-two textures are padded automatically.CompressionSettings = TC_HDR, raw 16-bit-float pixel access.MipGenSettings = NoMipmaps, sample from the full-res mip.SRGB = false, linear data.
Step-by-step
- Import your texture (PNG 16-bit recommended for height; 8-bit acceptable for masks).
- Right-click content browser → WorldScape tab → HeightMap Volume Data → name it (e.g.
HMD_Crater). - Open the data asset. Drop your main texture into
HeightMap; optionally a second texture intoHeightMapOcean. - Set tiling per axis if you don't want defaults (
Wrapfor planetary wraps,Clampfor one-shot decals). - Toggle
bIgnore*flags to skip channels you don't need (saves memory). - Click Generate. The asset's internal
uint16arrays fill with sampled values. - Save the asset.
Source-specific tips
- WorldMachine: export PNG 16-bit. Never use the (+1) offset option, it shifts the value range and breaks height interpretation.
- NASA elevation tiles: usable directly as planetary heightmaps. Map the elevation range with
PlanetaryMapHeight+PlanetaryAltitudeon the Root. - Existing UE landscapes: use the WS Tools Landscape Export feature to convert.
- Power of 2 isn't required any more,
Generate()auto-pads viaPadToPowerOfTwo. (However, power-of-2 sources still produce slightly cleaner sampling.)
Apply via a HeightMap Volume (localized)
- Create the data asset (previous section).
- Drag a
HeightMapVolumeblueprint into your level (right-click content browser → Blueprint Class → type "HeightMap"). Position and scale it where you want the decal. - On the volume, set
HeightMapDatato your data asset. - Toggle the channels you want active:
Height,Temperature,Humidity,OceanHeight, plus optionalHeightAlpha/OceanHeightAlphamasks. - Configure per-channel ranges:
- Height:
MapHeight(texture value => cm),Altitude(base offset). - Temperature / Humidity:
Min*&Max*(0-1). - Ocean:
MaxOceanHeight&MinOceanHeight.
- Height:
- Set
OverrideHeight, true (default) replaces the underlying terrain height; false adds/subtracts on top of the existing noise. - Optionally enable
UseBlending+EdgeFalloffto soft-fade the volume's edges into the surrounding terrain. - Open your
WorldScapeRoot. In Category Volumes →HeightMapVolumeList, click + and assign your volume. - Make sure
bEnableVolumes = trueon the Root (master toggle for ALL volume types).
Generate again, then restart the level (the Root caches volume data on init).
Planetary heightmap (the entire planet)
Distinct from local volumes: the Root has a built-in planetary heightmap that wraps the entire planet. All properties live in Category Noise · PlanetaryHeightmap and only show when GenerationType = Planet.
Master toggles
| Property | Default | Purpose |
|---|---|---|
| bUsePlanetaryHeightMap | false | Master toggle. Without this, all the rest is ignored. |
| PlanetaryHeightMap | nullptr | The UHeightMapVolumeData data asset wrapping the planet (typically a single equirectangular texture). |
| SamplingMethod | Bilinear | Sampler used for planet-wide reads. |
| PlanetaryHeightMapBlend | Addition | EPlanetaryHeightMapBlendType: Replace (DisplayName "Normal", replace procedural noise), Addition (DisplayName "Additional", add on top), Subtract. |
| PlanetaryBlendAlpha | 1 | Master blend amount (0-1) between procedural noise and the heightmap result. |
| bPlanetaryInvertMapBlend | false | Invert the blend direction. |
Height channel
| Property | Default | Purpose |
|---|---|---|
| bPlanetaryHeight | false | Toggle the height channel from the planetary texture. |
| PlanetaryMapHeight | 800,000 (8 km) | Texture value => world-space height. Default range is 8 km of vertical relief. |
| PlanetaryAltitude | -1,000,000 (-10 km) | Base altitude offset added on top of the sampled height. Default places the "zero" of the texture 10 km below sea level. |
| HeightBlendAlpha | 1 | Per-channel blend weight (0-1) for the height contribution. |
Temperature channel
| Property | Default | Purpose |
|---|---|---|
| bPlanetaryTemperature | false | Toggle. |
| PlanetaryMaxTemperature | 1 | Upper bound (0-1). |
| PlanetaryMinTemperature | 0 | Lower bound (0-1). |
| TemperatureBlendAlpha | 1 | Per-channel blend (0-1). |
Humidity channel
| Property | Default | Purpose |
|---|---|---|
| bPlanetaryHumidity | false | Toggle. |
| PlanetaryMaxHumidity | 1 | Upper bound (0-1). |
| PlanetaryMinHumidity | 0 | Lower bound (0-1). |
| HumidityBlendAlpha | 1 | Per-channel blend. |
Alpha & ocean
| Property | Default | Purpose |
|---|---|---|
| bPlanetaryHeightAlpha | false | Use the main texture's alpha as a mask (only shown when at least one of bPlanetaryHeight/Temperature/Humidity is on). |
| bPlanetaryOceanHeight | false | Use the second texture's R channel for ocean height. |
| PlanetaryMaxOceanHeight | 0 | Upper bound for ocean height remap. |
| PlanetaryMinOceanHeight | 0 | Lower bound. |
| bPlanetaryOceanHeightAlpha | false | Use the second texture's alpha as ocean mask. |
Step-by-step setup
- Build a
UHeightMapVolumeDataasset (see Texture import workflow) using an equirectangular projection of your planet. - On the Root, set
GenerationType = Planetand confirm the planetary section appears. - Enable
bUsePlanetaryHeightMap. - Assign the data asset to
PlanetaryHeightMap. - Choose the blend mode:
Replacefor pure heightmap (no procedural noise),Additionto add to procedural,Subtractfor inverted. - Toggle the channels you need (
bPlanetaryHeight,bPlanetaryTemperature, etc.). - Tune
PlanetaryMapHeight(texture range in cm) andPlanetaryAltitude(base offset). Defaults are 8 km of relief, anchored 10 km below sea level, adjust for your planet's geology. - Use
PlanetaryBlendAlphafor global heightmap-vs-noise mixing, and per-channel*BlendAlphafor fine control.
Blueprint API
GetPlanetaryDataFromPosition(FVector Position) → float, sample the planetary height at a 3D location (BlueprintCallable, Category "Info").GetPlanetaryCoordinate(DVector Position) → FDVector2D. ECEF → UV (for custom sampling code).
PlanetaryHeightMapBlend = Addition, lower PlanetaryBlendAlpha to 0.5-0.7 to let the noise show through. Or use per-channel: keep HeightBlendAlpha = 0 to disable the heightmap's contribution to height while still using its temperature/humidity/ocean channels for biome control.
HeightMapVolume actors layer on top of the planetary heightmap (e.g. a global Earth texture + a hand-painted Mt. Everest volume).
Heightmap Editor Beta
The WS Tools toolbar exposes an in-engine heightmap editor, sculpt textures with a brush, apply filters (blur, noise, erosion), resize, save, view in 3D. See WS Toolbar for the full feature list.
Volumes
Volumes are localized modifiers that override the Root's terrain in their bounding region. All inherit from AVolumeInterface and are managed via the Root's Volumes tab.
Shared base, AVolumeInterface
Every volume listed below inherits these properties (Category Defaults):
| Property | Type | Default | Purpose |
|---|---|---|---|
| IsEnabled | bool | true | Toggle the volume on/off without deleting it. |
| Priority | int32 | 0 | Sorting priority when multiple volumes overlap. |
| AlignToPlanetSurface | bool | false | For planets, align the volume's transform to the surface tangent at its location. |
| SurfaceAlignmentRotationOffset | FRotator | (0,0,0) | Extra rotation applied after surface alignment (gated by the flag above). |
Plus the WS_ForceRefresh() BlueprintCallable function that flags the volume as dirty for re-application.
HeightMap Volume (AHeightMapVolume)
Apply an external heightmap as a localized decal on the terrain. Workflow covered in Heightmap · Apply via volume; full property list:
| Property | Type | Default | Purpose |
|---|---|---|---|
| HeightMapData | UHeightMapVolumeData* | nullptr | The data asset holding the textures + channel config. |
| SamplingMethod | EHMSamplingMethod | Bilinear | Per-volume sampler (Nearest / Bilinear / Bicubic). |
| OverrideHeight | bool | true | Replace the underlying height (default), instead of adding/subtracting. |
| EdgeFalloff | float (0-0.5) | 0 | Soft edge falloff, how much of the volume's edge fades into the surrounding terrain. |
| UseBlending | bool | false | Enable curve-based blending. |
| Blending | FWSHeight_Blend | (default) | Blend curve config when UseBlending is on. |
| Height | bool | false | Toggle the height channel (Red). |
| Temperature | bool | false | Toggle the temperature channel (Green). |
| Humidity | bool | false | Toggle the humidity channel (Blue). |
| HeightAlpha | bool | false | Use the texture's alpha as a mask for the three above. Hidden until at least one channel is on. |
| OceanHeight | bool | false | Toggle the ocean-height channel (second texture, Red). |
| OceanHeightAlpha | bool | false | Use second texture's alpha as ocean mask. |
| MapHeight | float | 10000 | Texture value => world-space height in cm. Hidden when Height is off. |
| Altitude | float | 0 | Base altitude offset added on top of the sampled height. |
| MaxTemperature, MinTemperature | float (0-1) | 0 | Range remap for the temperature channel. |
| MaxHumidity, MinHumidity | float (0-1) | 0 | Range remap for the humidity channel. |
| MaxOceanHeight, MinOceanHeight | float | 0 | Range remap for the ocean-height channel. |
BlueprintCallable methods on the volume: CanSample(), CanSampleMask(EWSHeightMask), IsHeightDataValid(), GetData(FVector2D UV, EWSHeightMask) → float.
Noise Volume (ANoiseVolume)
Adds procedural noise generation in a region, blended with the global noise. Properties (Category Noise):
| Property | Type | Purpose |
|---|---|---|
| WorldScapeNoise | UWorldScapeNoiseClass* | The noise generator to apply locally (subclass of UWorldScapeNoiseClass). |
| NoiseScale | float | Wider value = wider details (same semantics as the Root's NoiseScale). |
| NoiseIntensity | float | Intensity in cm, higher value = higher mountains. |
| NoiseHeightOffset | float | Height offset added on top of the noise output. |
| NoiseOffset | FVector | 3D offset applied to the noise sampling space. |
| Seed | int32 | Random seed for this volume's noise (independent of the Root's Seed). |
| EdgeFalloff | float | Soft falloff at the volume's edge. |
28000 or 5000000 on all axes for it to affect a large area."
Hole Volume (ATerrainHoleVolume)
Cuts holes in the terrain, for caves, cliffs, hand-built openings. Inherits the base AVolumeInterface properties only; data lives in its UTerrainHoleVolumeData data asset.
- Right-click content browser → Blueprint Class → type "hole".
- Apply under the Root's Volumes tab.
- A Foliage Mask Volume can overlap to remove foliage from inside the hole.
Foliage Mask Volume (AFoliageMaskVolume)
Mask specific areas to clear or allow foliage. Properties (Category Defaults):
| Property | Type | Default | Purpose |
|---|---|---|---|
| UseBlending | bool | true | Soft-blend the mask edge instead of a hard cut. |
| BlendFalloff | float (0-1) | 0.33 | How wide the soft-blend region is. |
| MaskAll | bool | false | Apply to every foliage entry (ignore FoliageLayerMask). |
| FoliageLayerMask | TArray<int> | [] | Layer integers this volume affects. Must match foliage's FoliageLayer. |
| SpawnVolume | bool | false | Inverted mode: when true, foliage with the right layer + SpawnOnlyInVolume set is allowed to spawn here (instead of being masked out). |
See also Foliage layer masking for the integer-matching rules.
Collision Invoker Volume Beta
Generates isolated collision for WorldScape terrain in a region, via a collision actor that auto-attaches to the Root.
| Parameter | Type | Purpose |
|---|---|---|
| Max Resolution | int | Limits triangle count to avoid editor performance hits. |
| Quad Size Target | float | Controls collision mesh resolution based on volume size. |
| Quad Size Auto Correction | bool | Adjusts target resolution if the volume is too large to maintain reasonable performance. |
| Triangle Size | float | Triangle size in WorldScape mesh generation. |
Place the volume, click Make Collision, ensure assignment to the Root.
Volume Pooling Beta
Foliage
WorldScape foliage is multithreaded, instanced, biome-aware. You don't paint anything, you describe what should grow where, and the plugin places it during terrain generation.
Foliage Asset
UWorldScapeFoliagesAsset is the basic unit. Right-click → WorldScape tab → Foliage Asset.
Visuals
StaticMesh, the mesh to instance.OverrideMaterialarray, replace the mesh's materials.Is_NaniteMesh, toggle Nanite for high-poly foliage.bCastShadows,bReceiveDecal,bCastFarShadow,bAffectDynamicIndirectLighting,bAffectDistanceFieldLighting,bNeverDistanceCull,bCastDistanceFieldIndirectShadow,bSelfShadowOnly.
Placement
Offset(FVector), position offset.MinScale,MaxScale, size variation (defaults 0.8-1.2).bAlignedToGround, snap to terrain.bRandomRotation, rotation variation.GroundRotationInfluenceMin/Max, slope alignment (defaults 0.35-0.6).
Spacing & density
FoliageSectorSize(default 2000), generation sector size.bUsePoissonDisc, use Poisson distribution.PoissonDiscDensity(1-200). Poisson density.FoliagesCount(1-30000), spawn count per sector.FoliageCullDistanceMultiplier(default 0.5x), cull distance.
Behavior
bSpawnActorInstead+ObjectToSpawn, spawn full actors. Marked obsolete in source (DisplayName ends with "(Obselete)" sic), prefer Foliage Blueprint for scripted behavior.bRequiresDeterminism, consistent placement (use for trees that gameplay refers to). Disable for grass/rubble to enable parallelization.SpawnInWater, theEFoliageWaterSpawnenum:Ignore,OutsideWater,InWater.FoliageLayer(-1 = use collection default; ≥ 0 = override).SpawnOnlyInVolume,bGenerateOnServer(defaults to true),bUseFoliageNoiseMask.
bUsePoissonDisc property exists but is hidden from the editor in the current build (EditCondition = "false"). The default placement uses sector-based randomization with the FoliagesCount count.
Foliage Collection
Right-click → WorldScape tab → WorldScapeFoliageCollection. A collection groups multiple foliage assets that share constraints.
- Open the collection. Add foliage assets to the foliage list.
- Set shared constraints (elevation, temperature, humidity).
- In the Root's Foliage tab, click + to add the collection to the spawn list.
Foliage Cluster
Spawn dense clumped objects (grass, rocks) with minimal perf impact, resembles UE's Hierarchical Instanced Static Mesh.
- Right-click → WorldScape tab → Foliage Cluster.
- Open the cluster, click + on Foliage Cluster Unit List.
- Sector size + count dictate spawn area + density. With dense meshes, start with smaller foliage counts.
Biome constraints
Temperature & Humidity influence generation through noise-derived climate data.
Spawn in Water: if true, force foliage to spawn inside water bodies (when ocean height > land height); if false, force outside.
Align to Surface + Ground Influence: aligns foliage to ground normal. Ground Influence customizes how much, the value is randomized between min and max.
Note: random rotation is not compatible with align to surface."
Per-foliage constraints (when bOverrideCollectionConstraint is true):
| Constraint | Type | Range |
|---|---|---|
| Elevation | FWorldScapeFoliagesContraint | height min/max in cm |
| Temperature | FWorldScapeFoliagesContraintNormalized | 0-1 |
| Humidity | FWorldScapeFoliagesContraintNormalized | 0-1 |
| Slope | FWorldScapeFoliagesContraintNormalized | 0-1 |
Layer masking
Three mechanisms to control where foliage appears:
- Define Layers Foliage: set a
FoliageLayerinteger on a collection or foliage. - Exclude Layers Foliage: place an influencer in your level, specify the layer numbers to exclude in the influencer menu in the Root.
- Noise Mask: the Use Noise Mask parameter spawns foliage inside a procedurally generated mask.
Foliage Baker Beta
Converts procedurally-generated WorldScape foliage into standard Unreal Foliage actors or HISM components, useful for shipping or for handing terrain over to artists.
| Parameter | Type | Purpose |
|---|---|---|
| WorldScape Root Actor | dropdown | Source of procedural foliage |
| WS Foliage Baker Bound Volume | volume | Defines the bake region |
| Tile Capture | slider | Horizontal/vertical tile divisions, more = more localized sections |
| Process Tile Delay | float | Delay between tile bakes |
Workflow: launch from the WS Tools toolbar → position over procedural foliage → Launch Foliage Baker Tools → adjust tile capture → Start Capture → don't move the camera → pick HISM or Foliage output.
Foliage Collision Pooling (FCP) Beta
Configure on each foliage asset:
| Property | Type | Purpose |
|---|---|---|
| CollisionPooling_ComputeType | EWS_FCP_TypeCompute | Auto / Near / Far, controls when collision is computed for a foliage cell relative to the player. |
| CollisionPooling_Data | FWS_FCP_PoolingData | Contains BasePoolSize (pool capacity, default 100), MinimalScale (skip collision for foliage scaled below this), and a full BodyInstance for collision channels & responses. |
Materials (5.0+)
Most material controls live on the Root. Five color layers + surface noise drive the entire planet appearance, blended by the Layer tab.
Color layers
| Layer | Role |
|---|---|
| Bottom | Coastline aspects of the landscape. |
| Mid Color Layer 1 (humid) | Colors 1-3: temperate grass. Color 4: cold tundra near snow edge. Color 5: savanna + desert transition. |
| Mid Color Layer 2 (dry) | Colors 1-2: sand. Color 3: rock on mountains/dry. Color 4: sand tint. Color 5: no effect. |
| Top | Snow caps / overall snow color. |
| Surface noise | Detail textures visible from space. |
Layer tab controls the height and sharpness transition for bottom/mid/top primary layers.
Legacy 5-layer materials
For UE 4.26-4.27 builds and older WorldScape versions:
- Sand, deserts & when height < 0.
- Grass, default layer.
- Stone, high height (mountains).
- Cliff, high slopes; tunable via Automask parameters.
- Snow, low temperature + low slope, or very low temperature.
Each layer needs three textures: Color (RGB albedo), Normal, and OMRH. Red=Occlusion, Green=Metal, Blue=Roughness, Alpha=Height.
Material fix, floating point errors
If you see floating-point artifacts on the surface:
- Open the WorldScape material functions folder.
- Find
MF_SLM_Layer_Planet. - Locate the planet location parameter and the Rotate with Planet parameter.
- Right-click planet location → Promote to Double.
- Set Rotate with Planet to
false.
Grid & Snap spawning Experimental
Two systems for placing pre-built content (POIs, buildings, roads, props) on procedural surfaces.
Grid Spawning
The grid system has its own dedicated chapter with full workflows, the 6 spawn-type modes, placement parameters, constraints, LOD proxies, exclusion zones and 5 worked recipes:
→ Grid spawning & streaming, complete guide
That chapter covers both entry points (Root's global Grid_BaseSpawnData and the localized AWorldScape_GridSpawnerVolume), the UWorldScape_GridDataAsset structure, the FWS_Grid_SpawnParams constraints, and dedicated-server tuning.
Snap Spawner
Key parameters
| Property | Type | Purpose |
|---|---|---|
| DataType | EWSSnapDataType | ManualData, SnapData, or GridData (3 values, not 2, ManualData uses the inline arrays in the actor). |
| SnapDataAsset | UWorldScape_SnapDataAsset | Building / road collection data |
| Extent | FVector | Spawn volume (default 1000,1000,1000) |
| ComputeCulling | bool | LOD culling |
| WS_ValidTag | FName | Tag the Snap Spawner looks for on Roots (default WS_Planetary) |
| AutoGet_NearWS | bool | Auto-find the nearest Root |
| Use_PendingSpawn | bool | Async/streamed spawning |
| PendingSpawnActor_PerTick | int | Throttle (default 10) |
| PendingSpawnStaticMesh_PerTick | int | Throttle (default 2) |
Editor functions (when WITH_EDITOR): ViewFlat() for debug visualization, Simulate() to preview, Clear() to reset.
Physics & Movement
Standard UE characters can't walk on a sphere. WorldScape ships a custom MovementComponent and a gravity API to fix that.
Gravity Value Beta
Use the subsystem call:
FVector GravityDir;
double Force;
FVector G = Subsystem->WS_GetActualGravityFromLocation(
Location, GravityDir, Force);WorldScapePlayer
The plugin provides a ready-to-test player Blueprint at WorldScape/Content/Ressources/Mesh/Player/WorldScapePlayer.uasset (note: "Ressources" with two s's, literal folder spelling in the plugin). Use it as-is to walk on a planet, or as a reference for your own custom character.
Custom character & MovementComponent
Recipe:
- Subclass
UCharacterMovementComponent. - Override
PhysicsRotationto align the character's up-vector toWS_GetCurrentSurfaceUpVector(). - Override gravity application: each tick, query
WS_GetActualGravityFromLocationand apply the resulting force as your acceleration. - In your Character class, set the new MovementComponent class via the
CharacterMovementComponentClassoverride in the constructor. - For multiplayer, ensure replication of the up-vector or recompute it server-side via the same subsystem call.
Multiplayer
WorldScape is officially marketed as "Network Replicated: Yes (DedicatedServer & ListenServer)". Read the next paragraph carefully, that statement is true but means something different from what most UE developers expect.
UPROPERTY(Replicated), zero UFUNCTION(Server|Client|NetMulticast) RPCs, and zero bReplicates = true actors. The plugin's "multiplayer" is deterministic local generation from a shared Seed. Each peer (server and clients) regenerates the same terrain, foliage and grid spawns independently, in lock-step, by reading the identical Seed + identical noise asset + identical world parameters. No vertex data, no foliage instance, no spawned mesh ever crosses the wire.
This is by design and matches a comment found in the source: // Pourquoi répliquer l'actor ? ("Why replicate the actor?") in WorldScapeRoot_Main.cpp. The plugin is netmode-aware everywhere, exposes per-side knobs, and knows the difference between dedicated server, listen server, client and standalone, but it does not push state across the wire.
What you must replicate yourself
Anything that varies at runtime must be replicated by your own code, then applied to AWorldScapeRoot on each peer. Typical replicated state in a WorldScape MP game:
Seed, the single most important value. If your design has a "world seed" that changes between matches, replicate it via yourAGameStateand apply it to each Root inOnRep_Seed.- Player-built / persistent terrain modifications (custom volumes, dynamically spawned holes, height edits).
- Any custom GameMode flags that drive WorldScape regeneration (season, time of day affecting biomes, etc.).
Determinism
WorldScape's "multiplayer" works because every peer runs the same procedural code path on the same inputs. There is no syncing of vertex positions or foliage instances over the wire. Understanding which inputs matter is the key to a stable MP build.
The chain of derivations
Determinism flows in one direction:
Root.Seed + noise asset + per-Root noise parameters
Terrain noise (height + temperature + humidity + watermask + foliagemask)
The LOD sector pyramid & sector positions in world space
Foliage seed = FMath::Fmod(DotProduct(Sector.Position, FVector(Sector.Size + 100×CollectionID, 2000×FoliageID, 50000×FLID)), 2×109)
Per-instance position, scale, rotation
Grid streaming uses an analogous chain, cell coords + per-asset SeedOffset drive the spawn seed (Root.Seed doesn't directly appear in the grid seed math either).
Root.Seed doesn't appear in the foliage or grid seed formulas at all. Both depend on position, which transitively depends on the terrain noise. Because the terrain noise depends on Root.Seed, the chain is preserved, but if you ever change foliage or grid in code expecting Root.Seed to feed in directly, it doesn't.
What MUST match across peers
| Input | Why |
|---|---|
Root.Seed (int32, default 10) | Drives noise → terrain shape → transitively, sector positions → foliage / grid placement. |
| Noise asset reference + parameters on the Root | Different noise = different terrain shape, even with same seed. |
Foliage Collections array (order matters) | The foliage seed includes FoliageCollectionID and FoliageID, reordering collections shifts every foliage placement. |
Grid DataAssets + per-asset SeedOffset | Same reasoning, the grid seed includes the asset's SeedOffset. |
| Volumes placed in the level (HeightMap, Hole, etc.) | Level-loaded volumes are identical on every peer "for free" because every peer loads the level. Dynamic volumes you spawn at runtime must be spawned on every peer. |
PlanetScale, NoiseScale, NoiseIntensity, NoiseOffset, LodResolution, MaxLod, TriangleSize, HeightAnchor | All Root parameters that affect terrain or LOD geometry. If your design flips them at runtime, replicate them. |
Replicating Root.Seed in your own code
The Root's Seed is a plain non-replicated UPROPERTY. If your design requires changing it at runtime (per-match seeds, server-controlled regen), replicate it via your AGameState or your own actor:
// In your GameState header
UPROPERTY(ReplicatedUsing = OnRep_WorldSeed)
int32 WorldSeed = 10;
UFUNCTION()
void OnRep_WorldSeed() {
if (AWorldScapeRoot* Root = ..) {
Root->Seed = WorldSeed;
Root->WS_ForceRegenerate();
}
}About the bRequiresDeterminism foliage flag
The flag (WorldScapeFoliagesInterface.h:69, default false) selects which generation pipeline runs for that foliage:
- True →
GenerateVegetationForSector_Batched. Sequential. Source comment: "Batched Noise Version. Only batch MAIN point noise, keep everything else IDENTICAL to original". - False →
GenerateVegetationForSector_FullParallel. UsesParallelForacross points. Random values are pre-drawn sequentially before parallelization (RandomValues[p] = Stream.FRand();in a single-threaded loop, indexed read inside the lambda).
The author's verbatim comment, directly above the property declaration:
Take that at face value: follow the author's recommendation and set bRequiresDeterminism = true for any foliage where position must match across regenerations or peers (trees, large rocks, anything gameplay-significant). For purely cosmetic grass and small rubble where exact position is invisible, leave it false for the parallel speed-up.
What CAN go wrong (sources of cross-peer divergence)
When two peers desync visually or physically, the cause is almost always one of:
- Different
Root.Seedon each side. The single most common cause. Verify in PIE with Play As Listen Server + Play As Client by logging the seed on both. - Different noise asset reference, e.g. one side has a redirector, the other has the renamed asset.
- Foliage collection order differs, e.g. you added an entry to
Foliages[]at index 0 on the server but appended on the client. EveryFoliageCollectionIDdownstream shifts. - One side missing a placed volume, usually because of streaming-level differences. Volumes are seen "for free" only if both peers loaded the same sub-level.
- Dynamically spawned volumes / actors that aren't replicated. If you spawn a HeightMapVolume at runtime on the server but not on the client, the client doesn't see the terrain modification.
- Different
PlanetScaleor noise parameters after a runtime tweak that wasn't replicated.
Grid-specific dedup: Grid_CheckSpawnBySeed
Default true. Maintains a per-side TSet<int64> of already-spawned instance seeds to deduplicate items at cell boundaries (where two adjacent cells could try to spawn the same instance). Leave on for MP, turning it off can cause ghost / double spawns at cell seams that may differ between peers.
Dedicated server behavior
Auto-disabled paths (always off on DS, no flag to flip)
- GPU noise, the server has no RHI. Even if
bUseGPUNoise = true, the server falls back to the CPU path silently. - Indirect instanced terrain renderer, never used on DS.
- Foliage Collision Pooling (FCP), the dynamic per-pawn collision swap-in. Use
bCollision = true+bGenerateOnServer = trueon foliage instead. - Grid SMProxy visualization, the LOD swap-mesh layer is no-op on DS.
- Subsystem auto-tracking,
WS_Start_AutoGetNearest()'s timer never starts on DS (or listen). - Material Parameter Collection sync, the auto-update of
WS_Surface_UpVector,WS_Planet_Origin,WS_Planet_Size,WS_Planetary,WS_Valid,WS_Flat_Originis never run on DS.
Auto-enabled paths on DS
- All-player collision generation, collision is built around every connected player's pawn (not just the local one).
- All-player grid streaming, grid loads cells around every connected player's pawn.
- Multiplied volume pools, pre-allocated pool sizes are
3×larger on DS to service many players' regions concurrently.
Per-side performance knobs (on AWorldScapeRoot)
| Property | Default | Purpose |
|---|---|---|
| DisableFoliageDedicatedServer | false | Master kill switch for ALL foliage on DS. Skip every foliage entirely on dedicated. |
| Grid_MaxSpawnPerSecond | 512 | Spawn throttle for client / listen / standalone. |
| Grid_MaxSpawnPerSecond_OnDedicated | 1024 | Spawn throttle for dedicated server (higher because no rendering competition). |
| Grid_MaxDestroyPerSecond | 1024 | Despawn throttle, non-DS. |
| Grid_MaxDestroyPerSecond_OnDedicated | 2048 | Despawn throttle, DS. |
| Grid_SkipStaticMeshOnDedicated | false | Skip ALL StaticMesh/CollectionStaticMesh/Foliage grid types on DS (keeps actor types). |
| Grid_SkipAdjacentCellOnDedicated | false | Shrink loaded grid neighborhood from 9 cells to 1 on DS. |
| Grid_SimulateDedicated | false | Pretend you're DS even on a client (debugging). |
| VolumePooling_Enabled | false | Master switch for client/listen/standalone pooling. |
| VolumePooling_Enabled_OnDedicated | true | Independent switch for DS pooling. |
| Heightmap_Pooling_Dedicated_Multiplier | 3.0 | DS pool size multiplier (heightmap volumes). |
| HFoliage_Pooling_Dedicated_Multiplier | 3.0 | DS pool size multiplier (foliage mask volumes). |
| Hole_Pooling_Dedicated_Multiplier | 3.0 | DS pool size multiplier (hole volumes). |
| bGenerateCollisionForAllPlayer | false | Force the all-player collision branch even on a pure client (useful for split-screen-like setups). |
Per-asset DS opt-in / opt-out
UWorldScape_GridDataAsset.SpawnableOnDedicated(defaulttrue), per grid data asset. Set tofalsefor purely-cosmetic items.AWorldScape_SnapSpawnerActor.SpawnOnDedicated(defaultfalse!), you must opt in for snap spawns to exist server-side.UWorldScapeFoliagesInterface.bGenerateOnServer(defaulttrue, per foliage), turn off for visual-only grass.
Cooking implications
The plugin uses IsRunningDedicatedServer() guards around several SoftClass.LoadSynchronous() calls, meaning soft-class assets that aren't already loaded won't be force-loaded on DS. If your gameplay depends on certain actor classes existing on the server, ensure they are cooked into the server build via Project Settings → Asset Manager or +ServerAdditionalAssetDirectoriesToCook. ProxyMesh and AdditionalComponentClass paths are skipped on DS entirely, their assets can be omitted from the server cook.
Listen server quirks
- Collision: all-player branch, ✅ same as DS
- Foliage Collision Pooling: ✅ runs on listen (unlike DS)
- Subsystem auto-tracking: ❌ does NOT run on listen (gated to
NM_Client || NM_Standalone) - Material Parameter Collection sync: ❌ does NOT run on listen
- Grid streaming tick: ⚠ neither the DS branch nor the client branch matches
NM_ListenServer, grid streaming may not fire on the listen-server host. Workaround: callSubsystem->WS_Start_AutoGetNearest()manually after BeginPlay, and audit the result. If grid items are missing for the host, this is the cause.
Per-side feature matrix
| System | Dedicated | Listen | Client | Standalone |
|---|---|---|---|---|
| Terrain mesh (CPU noise) | ✅ | ✅ | ✅ | ✅ |
| Terrain mesh (GPU noise) | ❌ | ✅ | ✅ | ✅ |
| Indirect instanced terrain | ❌ | ✅ | ✅ | ✅ |
| Foliage (default) | ✅ if bGenerateOnServer | ✅ | ✅ | ✅ |
| Foliage Collision Pooling (FCP) | ❌ | ✅ | ✅ | ✅ |
| Grid items | ✅ per-asset SpawnableOnDedicated | ⚠ may not stream | ✅ | ✅ |
| Grid SMProxy visualization | ❌ | ✅ | ✅ | ✅ |
| Volume pooling | ✅ (3× pool) | ✅ | ✅ | ✅ |
| Snap spawner | only if SpawnOnDedicated=true | ✅ | ✅ | ✅ |
| Subsystem auto-tracking | ❌ | ❌ | ✅ | ✅ |
| MPC parameter sync | ❌ | ❌ | ✅ | ✅ |
Foliage in MP
bGenerateOnServer(per foliage, defaulttrue), keep ON for trees with collision, turn OFF for purely-visual grass to save DS CPU.DisableFoliageDedicatedServer(Root), nuclear option, kills all foliage on DS.bRequiresDeterminism(per foliage, defaultfalse), per the author's source comment, set totruefor trees/large rocks where consistent placement matters across regenerations / peers. See Determinism.bCollision(per foliage) +bGenerateOnServer = true, required combination for server-authoritative shooting / movement against foliage trunks.- FCP is client-only, do not rely on it for server-authoritative gameplay against foliage.
Root.Seed, the noise asset, the noise parameters, and the foliage collection ordering. The bRequiresDeterminism flag is an additional safety net the author recommends for gameplay-significant foliage, per the source comment, set it to true for trees and large rocks.
Grid spawning in MP
The grid system is the most server-aware of all systems. Both the server and clients independently compute the same grid items at the same locations from the shared seed, then each side decides whether to spawn them based on SpawnableOnDedicated + Grid_Skip* flags.
Watch out: if you make your spawned grid actor bReplicates = true in your own code, you'll end up with two copies on each client, one from the local grid spawn, one replicated by the server. The standard fix:
- For cosmetic grid actors (rocks, grass clumps, billboards): leave
bReplicates = falseand let each peer spawn locally. Default behavior. - For interactable / persistent grid actors (lootable chests, NPCs): set
SpawnableOnDedicated = true, mark the actorbReplicates = true, AND setbNetLoadOnClient = falseon the actor so the client doesn't double-spawn. Server spawns + replicates as canon; clients receive the replicated copy and skip the grid path.
Collision invokers in MP
The UWorldscapeCollisionInvoker component is local-only (not replicated). It registers itself with the per-world UWorldscapeSubsystem in BeginPlay. In MP:
- Each replicated pawn carries a component instance on each side where the pawn exists.
- Server: every player's pawn drives collision generation around it (because all replicated pawns exist on the server).
- Client: by default, only the local pawn exists in full (other players' pawns may be culled by relevancy). Use
bGenerateCollisionForAllPlayer = trueif you need clients to generate terrain collision around other players too.
Subsystem in MP
UWorldscapeSubsystem per-side caveats:
WS_Start_AutoGetNearest()only schedules its 1Hz tick onNM_ClientorNM_Standalone. It is silently a no-op on dedicated and listen servers.- Delegates (
FWS_OnNearestChanged,FWS_OnNearestNULL,FWS_OnWorldScapeRegistry,FWS_OnWorldScapeUnRegistry) are local Blueprint events, not RPCs. Get_NearWorldScapeFromLocation()is netmode-agnostic, you can call it on the server.- For server-authoritative gameplay that needs gravity / surface up vector, do NOT rely on the MPC. Call
WorldScapeRoot->WS_GetGravityFromLocation(..)or the subsystem'sWS_GetActualGravityFromLocation(..)directly.
Sample multiplayer setup (recipe)
- Create a UE Third-Person template project, enable WorldScape.
- Drop an
AWorldScapeRootin your level. SetGenerationType = Planet, pick a noise asset, setSeedto your design's value. - Open
BP_ThirdPersonCharacter, add aUWorldscapeCollisionInvokercomponent on the root. - In your
AGameState(orAGameModeif you don't have a custom GameState), add aUPROPERTY(ReplicatedUsing=OnRep_WorldSeed) int32 WorldSeed;and propagate it to the Root inOnRep_WorldSeed(). - For every replicated foliage you can interact with: open the foliage asset, set
bRequiresDeterminism = true,bCollision = true,bGenerateOnServer = true. - For purely-cosmetic foliage:
bGenerateOnServer = falseto save DS CPU. - Test in PIE with Number of Players = 2 and switch between Play As Listen Server and Play As Client.
- For a real dedicated-server build, package your project with the Server target and verify foliage / grid items appear correctly on both sides.
Common MP bugs & fixes
| Symptom | Fix |
|---|---|
| Grass visible on client but not on server | bGenerateOnServer = true on the foliage interface (default), and DisableFoliageDedicatedServer = false on the Root. |
| Tree appears in different positions on each peer | Audit the four cross-peer inputs: same Root.Seed? Same noise asset reference (no redirectors)? Same foliage collection order in Foliages[]? Same noise parameters (NoiseScale, NoiseIntensity, NoiseOffset, PlanetScale)? Identical on both sides → identical placement. |
| Server-side projectile passes through trees | Foliage needs bCollision = true AND bGenerateOnServer = true. FCP doesn't help because it's client-only. |
| Grid items missing for listen-server host | Listen-server gap (M13). Call Subsystem->WS_Start_AutoGetNearest() manually post-BeginPlay; check that grid update tick is being reached. |
| Material parameter X reads 0 on server | MPC sync doesn't run on DS / listen. Query the Root directly for any gameplay-relevant data; reserve MPC for client-side rendering. |
| Two copies of the same actor on a client (one from grid, one from server) | On the actor, set bNetLoadOnClient = false so only the server-replicated copy exists. |
| Terrain visible but no collision around remote players (on a client) | Either accept the design (clients only collide around local pawn), or set bGenerateCollisionForAllPlayer = true on the Root. |
| Soft-referenced asset missing on dedicated server | Add it to Project Settings → Asset Manager directories or +ServerAdditionalAssetDirectoriesToCook. |
| Server and client see different terrain after seed change | Replicate Seed via your GameState and call WS_ForceRegenerate() on each peer in OnRep_Seed. |
When NOT to use the local-determinism model
WorldScape's seed-driven local generation is great for same-on-every-peer procedural worlds. It is a poor fit for:
- Persistent player-modified terrain across sessions (e.g. trenches the players dug stay dug after a server restart).
- Server-authoritative procedural decorations placed by a separate system (raid spawns, GM-placed items).
- Cross-session deterministic events ("the chest spawned on Tuesday is still there").
For those cases, layer your own replicated state on top: a USaveGame or database that persists the deltas, replicated via your GameState, applied to WorldScape's runtime API (volumes, custom FNoiseData overrides, dynamic spawners).
Net driver compatibility
WorldScape works with any UE net driver, default, Steam Sockets, EOS, Iris, because it never touches the network stack. The plugin compiles identically regardless of which OnlineSubsystem is active.
PCG integration New
The WorldScapePCG module exposes WorldScape data to Unreal's Procedural Content Generation graph, query terrain heights, sample biomes, drive your PCG outputs from the active Root.
Blueprint library
| Function | Returns | Purpose |
|---|---|---|
| IsWorldScapePCGAvailable | bool | Check before any PCG-side WorldScape call |
| GetAllWorldScapeRoots | TArray<AWorldScapeRoot*> | Every active Root in the world |
| CreateWorldScapeTestPCGNode | UWorldScapeTestPCGNodeSettings* | Programmatically create a node settings object |
| GetWorldScapePCGVersion | FString | Returns the PCG module version string |
Recipe: PCG-driven foliage placement
- Create a PCG Graph asset.
- Add a WorldScape PCG node and connect it to your sample / spawn nodes.
- Use the Root's height / temperature / humidity samples to filter PCG points before spawning.
GPU Terrain New
When CPU clipmap LOD isn't enough, massive view distances, real-time deformation, ultra-high resolution, switch to the GPU terrain pipeline.
Components
WSGPUTerrainRenderer, main scene proxy.WSGPUTerrainVertexFactory, custom vertex layout.WSHeightfieldManager. GPU heightfield textures.WSQuadtreeManager+WSQuadtreeNode, quadtree LOD on the GPU.
When to use GPU vs CPU
| Scenario | Recommended |
|---|---|
| Standard kilometer-scale world | CPU clipmap |
| Real-time terrain deformation | GPU |
| Ultra-far view distances | GPU |
| Servers (no GPU) | CPU only |
| Mobile platforms | not supported (Win64/Linux only) |
WS Toolbar
A user-friendly toolbar for planetary and terrain work. Some features are still beta, flagged below.
How to launch
- Open the WorldScape plugin folder in the content browser.
- Navigate to
Content/Tools/WS_ToolsBar/and run theWS_ToolsBarEditor Utility Widget. - Position the WS Tools window where you like.
- Explore the available options.
The 23 toolbar features
- WS selector, select the WS actor in the current scene.
- Select from Outliner, focus on the currently selected WS actor in the outliner.
- Pick Actor from Scene, eyedropper tool to select a WS actor in the scene.
- Reset to Default, resets the selected Root.
- Freeze and Unfreeze Landscape, pauses landscape generation for the selected actor.
- View Collision, shows the collision; you may need editor collision view enabled.
- Custom Details Panel, opens a curated details panel for the current WS actor.
- View POI Editor, shows available points of interest in the scene.
- Go to Camera POI, focus travel to a POI on the selected actor.
- Add POI at Camera Position, adds a new POI at the camera's location.
- Add Volume Specified to Worldscape, adds a Foliage / Foliage Baker / Collision Invoker / Hole / Noise / Heightmap Volume at the camera.
- Attach Actor to Root, attaches an actor to the selected Root.
- Rotate Selected Actor, rotates the selected actor to the tangent of the WS actor.
- Snap Actor to Worldscape, snaps the selected actor to the surface.
- Move Camera Actor, moves the camera to the selected actor.
- Auto Attach Actor to Root, auto-attaches on drop.
- Auto Rotate Selected Actor, auto-rotates on drop.
- Auto Snap Actor to Worldscape, auto-snaps on drop.
- Auto Move Camera Actor, auto-moves the camera on drop.
- Open Heightmap Editor, opens the heightmap editor menu.
- Reset Camera Roll, resets camera rotation for worldspace.
- Camera Selector Mode, pick from 6 modes (see below).
- Online Documentation, opens the docs at the current location you're referring to.
Camera Selector Modes
| Mode | Behavior |
|---|---|
| Normal | Standard editor camera. |
| Automatique | Auto-switches between flat and planetary modes. |
| Unlock | Free-form camera, no constraints. |
| Unlock with Planetary Correction | Free movement but corrects orientation to planet up-vector. |
| Tangent | Camera locked to surface tangent, ideal for surface-walking previews. |
| Planetary Explore | High-altitude planetary navigation. |
Heightmap Editor (in toolbar)
- Edit with Brush, paint directly on the heightmap.
- Brush Parametric Controls, use materials/textures as brushes.
- Channel Selection. Heightmap, Temperature, Humidity, additional channels.
- 3D View, 3D displacement display of the current texture.
- Filter, apply blur, noise, or erosion simulation.
- Resize, resize the heightmap texture.
- Save / Save As + Key (control reference).
Heightmap Tools Volume Beta
Advanced terrain modification toolkit for planet creators. Generates heightmap volumes scaled to planets, creates landscapes for in-engine editing, bakes landscapes to Nanite meshes. Overlays additional heightmap info on placed volumes (does not replace existing data).
Landscape Export Beta
Convert UE terrain into standardized heightmap files, either a Heightmap Volume Data asset or a heightmap texture file.
Blueprint API
All public Blueprint nodes split across the function library (UWS_FunctionLibrary) and methods on AWorldScapeRoot itself. Right-click in any Blueprint graph and search "WorldScape" to find them.
Pawn-relative queries (on the Root)
For per-frame character/vehicle logic on planets:
| Function | Returns | Description |
|---|---|---|
GetPawnNormal(Location) | FVector | Surface up-vector at the given location. |
GetPawnSnappedNormal(Location) | FVector | Up-vector snapped to the nearest configured angle. |
GetPawnTangent(Location) | FVector | Surface tangent. |
GetPawnBiTangent(Location) | FVector | Surface bi-tangent. |
GetPawnAltitude(Location, bWaterNoiseHeight) | float | Altitude above sea level. |
GetPawnDistanceFromGround(Location, ECEFCoordinate) | float | Vertical distance to the ground; pass ECEFCoordinate=true if Location is already in planet-centered coordinates. |
GetHeight(Location, Water) | float | Raw noise height at a 3D position. |
GetGroundHeight(Location, Water) | float | Noise height projected onto the ground. |
GetGroundHeight_HOnly(Location) | double | Ground height as a single double (cheaper). |
GetGroundHeight_HOnly_Multi(Locations[]) | TArray<double> | Batch version, ~10× faster than looping single calls. |
GetPlanetaryDataFromPosition(Position) | float | Single-float "planet height" for material / AI use. |
Force-regenerate & cleanup
WS_ForceRegenerate(), full terrain regen.WS_ForceRegenerate_WS(bool), partial WS regen.WS_ForceRegenerate_Foliage(), foliage-only regen.WS_ForceResetCollision(), reset collision sectors.WS_ForceUpdatePotion(), force update of internal positioning.WS_CleanFoliage(), clean & tag the regen reason for debug logs.
Volume queries
GetVolumeAtPosition(Location, out NoiseVolume[], out HeightMapVolume[], out TerrainHole[]), fills three output arrays with the overlapping volumes at Location.GetLocationInSelectedVolume(Location, HeightMapVolume)→bool.GetVolumeCanSample(HeightMapVolume)→bool.GetLocationIsInWSVolume(Location, FTransform VolumeTransform, FVector VolumeBoxExtend)→bool(generic AABB).GetGroundNoiseWithVolume(Location, NoiseVolumes[], HeightMapVolumes[], TerrainHoles[])→double(height value, not the full FNoiseData).
Material runtime swap
UpdateTerrainMaterial(FWSMaterialLodArray), swap terrain materials at runtime (no regen).UpdateOceanMaterial(FWSMaterialLodArray), swap ocean materials.
Function library, surface sampling
WS_SampleSurface(Root, Location, Size, StepSize, HeightThreshold) → boolWS_SampleSlopeSurface(Root, Location, Size, Step, SlopeMin, SlopeMax) → bool, RelativeNormalWS_SampleSurfaceH(Root, Center, Forward, Right, Up, Size, StepSize) → HDifference, Median, AverageWS_SampleSlope(Root, Center, Forward, Right, Up, Size) → Normal1, Normal2
Function library, grid sampling
WS_Sample_2DGrid(Root, Center, Rotation, Extend, Step) → TArray<FVector>WS_Sample_2DGrid_Async(Index, Delegate, Root, Center, Rotation, Extend, Step), non-blocking version withFWS_SampleReturncallback.WS_GetGridCoord(Location, BaseLocation, Depth, Radius) → FWS_GridWS_GetAdjacentGridCoord(Coord, BaseLocation, Radius) → TArray<FWS_Grid>
Function library, projection & alignment
WS_ProjectGroundLocation_Multi(Root, Locations[]) → TArray<FTransform>WS_ProjectGroundLocationWithNormal_Multi(Root, Locations, Offset), also returns ground normals.WS_ProjectActorToGround(Root, Actor, Offset)WS_ProjectTransformToGround(Root, Transform, Offset) → FTransform, snaps + rotates.WS_SingleProject(Root, Location) → FVectorWS_SingleProject_Transform(Root, Location, Size, YawBase, Z_Offset) → FTransformWS_GetTerrainNormalTriTest(Root, Location, Offset) → FVector
Function library, data queries
WS_GetHeightValueAtLocation(Root, Location) → doubleWS_GetTemperatureValueAtLocation(Root, Location) → doubleWS_GetHumidityValueAtLocation(Root, Location) → doubleWS_GetGroundInWater(Root, Location) → boolWS_FindMinimalGroundH(Root, Points[]) → doubleWS_OverrideLocationH(Root, Location, NewH) → FVectorWS_GetAltitudeInLocation(Root, Location, Offset) → doubleWS_GetAllFoliageIndexMask(Root) → TArray<int>
Function library, math utilities
WS_CubicBezierY(A, B, C, D, T) → FVectorWS_GetSplineOffset(A, B, C, D, T) → FVectorWS_SphereToCube(Location, CenterLocation, MinRadius) → FVectorWS_RelativeToWorld(WorldTransform, RelativeLocation, RelativeRotation) → FTransformGrid_SerializeActor_Direct(Actor) → TArray<uint8>
Editor function library
From WorldScapeEditor_FunctionLib:
WS_CreateLandscape(Transform, SectionSize, SectionsPerComponent, ComponentCountX, ComponentCountY), spawn a UE Landscape actor procedurally.WS_GetLandscapeInfo(),WS_GetSectionSize(), query landscape properties.
Legacy "Picture-style" nodes
| Node | Description |
|---|---|
Get Height | Get the noise height at the set position. |
Get Height Normalize | Get the normalized height at the set position. |
Get Noise | Get the full FNoiseData at a position. |
Get Ground Height | Same as Get Height but projected to the ground. |
Get Ground Height Normalize | Same as Get Height Normalize but projected to ground. |
Get Ground Noise | Same as Get Noise but projected to ground. |
Rebase events (world origin shifting)
When the engine rebases the world origin (large-world streaming), the Root forwards two events:
OnBeginRebase(), world rebase started.OnFinishedRebase(), world rebase finished.
Bind to these to freeze gameplay/physics during the shift.
Console reference
Console variables and commands registered by the WorldScape plugin. Type into the editor's ~ console at runtime, or set under [ConsoleVariables] in DefaultEngine.ini.
| Name | Type | Default | Purpose |
|---|---|---|---|
WorldScape.Foliage.Debug | command | , | Toggle the WorldScape foliage debug overlay and verbose logging. Equivalent to WSFoliageDebugToggle in older builds. |
r.RayTracing.Geometry.WorldScapeMesh | int32 | 1 | Include WorldScape meshes in ray-tracing effects (Lumen, RT shadows). 0 disables. |
ws.GPUTerrain.Indirect.HeightfieldTickBudgetMs | float | 2.0 | Render-thread budget (ms) for indirect GPU terrain heightfield generation. 0 = unlimited. |
ws.GPUTerrain.Indirect.MeshTickBudgetMs | float | 2.0 | Render-thread budget (ms) for indirect GPU terrain mesh generation. 0 = unlimited. |
ws.GPUTerrain.Indirect.ForceVertexColorView | int32 | 0 | Debug overlay on indirect GPU terrain. 0=off, 1=color, 2=red, 3=green, 4=blue, 5=alpha. |
ws.GPUTerrain.Indirect.LogBoundMaterials | int32 | 0 | Logs bound materials on indirect GPU terrain (throttled to 1/sec per proxy). |
ws.GPUNoise | int32 | -1 | Override bUseGPUNoise at runtime. -1=use actor setting, 0=force off, 1=force on. |
ws.IndirectInstancing | int32 | -1 | Override bUseIndirectInstancedNoise at runtime. -1/0/1. |
WS_Valid (auto-tracking accept), WS_Invalid (auto-tracking exclude), WS_Planetary (Snap Spawner default valid tag), WS_Exclude (Snap Spawner default exclude tag). Add these as actor tags to filter which Roots the subsystem considers.
Volume pooling Beta
Take and release volumes from a pre-allocated pool instead of spawning/destroying them at runtime. Avoids garbage-collection hitches during procedural editing or runtime worldgen.
API on AWorldScapeRoot
| Function | Purpose |
|---|---|
Pooling_Init() | Initialize the pool (auto-called on BeginPlay, but can be re-run). |
Pooling_Clear() | Empty the pool and destroy all pooled volumes. |
WS_Pooling_IsInitialized() | Returns true once WS_Pooling_OnInitialized has fired. |
Pooling_Take_HeightmapVolume(out Volume) | Get a HeightMap Volume from the pool. Returns false if none available. |
Pooling_Release_HeightmapVolume(Volume) | Return a HeightMap Volume to the pool. |
Pooling_Release_HeightmapVolume_Multi(Volumes[]) | Batch release. |
Pooling_Take_FoliageMaskVolume(out Volume) | Same for FoliageMask Volumes. |
Pooling_Release_FoliageMaskVolume(Volume) | Return to pool. |
Pooling_Release_FoliageMaskVolume_Multi(Volumes[]) | Batch. |
Pooling_Take_HoleVolume(out Volume) | Same for Hole Volumes. |
Pooling_Release_HoleVolume(Volume) | Return to pool. |
Pooling_Release_HoleVolume_Multi(Volumes[]) | Batch. |
Bind to the WS_Pooling_OnInitialized delegate (on the Root) to know when the pool is ready, before issuing any Take.
Grid spawning & streaming Experimental
A cell-based streaming system that spawns and despawns content (rocks, props, buildings, foliage, anything) around the player based on a deterministic seed. Loads as the player approaches, unloads when they leave. Replicates "for free" across server & clients via shared-seed determinism.
The two grid layers
WorldScape exposes two entry points into the same grid system. Pick whichever fits your design:
| Entry point | Scope | When to use |
|---|---|---|
Root's Grid_BaseSpawnData | Global, applies everywhere the streaming radius reaches | Scattered props or biome content covering the entire world (rocks all over the desert, debris everywhere on a moon). |
AWorldScape_GridSpawnerVolume | Localized, only within the volume's sphere | Region-specific content (a forest only in this zone, buildings only in this hand-placed area). |
Both feed the same execution engine: a UWorldScape_GridDataAsset describes what to spawn and how, and the Root's runtime takes care of streaming, throttling, snapping, and per-side replication.
Quick start (5 steps)
- Enable the grid: select your
WorldScapeRoot, setEnabledGrid = true. - Create a Grid Data Asset: right-click in content browser → WorldScape tab → Grid Data Asset. Name it (e.g.
GDA_Rocks_Mountain). - Open the asset, set
SpawnType = StaticMesh, add at least one entry toStaticMeshDatas(set itsStaticMeshfield to a rock mesh). - Configure constraints in
SpawnParams(e.g.MaxSlope = 0.3,SpawnableInWater = false). - Add the asset to the Root's
Grid_BaseSpawnDataarray. Hit Play. Walk around. Rocks appear withinGrid_DistanceToLoad(default 50 km).
WorldScape_GridSpawnerVolume into the level, scale its sphere, link your Grid Data Asset in SpawnDataAsset, link the Root in Linked_WS_Root.
The 6 spawn types
The SpawnType enum on a Grid Data Asset selects which list is used and what gets spawned.
| SpawnType | Lists used | Result per cell |
|---|---|---|
Actor | ActorsDatas (TArray<FWS_Grid_Spawner_Actor>) | One actor instance, picked by weighted SpawnableChance. |
StaticMesh | StaticMeshDatas | One ISM-instanced static mesh, picked by weighted chance. |
Collection | Collection_ActorsDatas + Collection_StaticMeshDatas + Collection_ProxyData | A pre-arranged group of actors and meshes spawned together (e.g. a "small camp" = tent + crates + barrels). Each entry has its own placement. |
CollectionStaticMesh | Collection_StaticMeshDatas | Same as Collection but static-mesh-only (no actor classes, cheaper). |
Foliage | StaticMeshDatas | Foliage-style high-density spawn using FoliageSectorSize (default 32 km) instead of the per-cell loop. |
DataAsset | Spawners (TArray<FWS_Grid_Spawner_DataAsset>) | Cascade, spawns from nested Grid Data Assets, each with its own scale multiplier. Use to compose modular sets. |
The SamplingType enum is exposed in the editor with only one usable value, CircleRND. (The PoissonDisk and Grid values are present in source but flagged UMETA(Hidden).)
Grid Data Asset properties
Full breakdown of UWorldScape_GridDataAsset:
| Property | Type | Default | Purpose |
|---|---|---|---|
| Enabled | bool | true | Master toggle. |
| SpawnableOnDedicated | bool | true | Spawn on dedicated server (set false for cosmetic-only items to save DS CPU). |
| SpawnType | EWS_Grid_SpawnType | Actor | Which list / mode to use (see above). |
| SamplingType | EWS_Grid_SamplingType | CircleRND | How candidate positions are sampled in the cell. |
| Max_TestToSpawn | int32 | 200 | How many candidate positions to sample per cell before giving up. |
| Max_Spawn | int32 | 20 | Max actual instances spawned per cell (after constraints filter candidates). |
| Z_Offset | double | 0.0 | Global Z offset added to every spawn from this asset. |
| SeedOffset | int32 | 0 | Per-asset seed offset, tweak to shift placements without changing the global Seed. |
| SpawnParams | FWS_Grid_SpawnParams | (see below) | Constraints (elevation, slope, climate, water, flatness). |
| Spawners | TArray<FWS_Grid_Spawner_DataAsset> | [] | For DataAsset mode, cascading list of nested data assets. |
| ActorsDatas | TArray<FWS_Grid_Spawner_Actor> | [] | For Actor mode, weighted actor classes. |
| StaticMeshDatas | TArray<FWS_Grid_StaticMesh_Data> | [] | For StaticMesh / Foliage modes. |
| Collection_ProxyData | FWS_Grid_Proxy | (default) | For Collection modes. LOD proxy mesh + switch distance. |
| Collection_ActorsDatas | TArray<FWS_Grid_Actor_Data> | [] | Collection mode, the actors that compose the group. |
| Collection_StaticMeshDatas | TArray<FWS_Grid_StaticMesh_Data> | [] | Collection mode, the static meshes that compose the group. |
| Extent | FVector | (1000,1000,1000) | Bounding extent for collection items. |
| FoliageSectorSize | double | 3,200,000 | For Foliage mode, sector size in cm (~32 km). |
Spawn constraints (FWS_Grid_SpawnParams)
Every Grid Data Asset has a SpawnParams struct that filters candidate positions before spawning:
| Field | Default | Purpose |
|---|---|---|
| MinElevation | -200000 | Skip the spawn if terrain height is below this (cm). |
| MaxElevation | 200000 | Upper height bound. |
| MinTemperature | -100 | Lower temperature bound (raw noise output range, not 0-1). |
| MaxTemperature | 100 | Upper temperature bound. |
| MinHumidity | 0.5 | Lower humidity bound (0-1). |
| MaxHumidity | 1 | Upper humidity bound. |
| MaxSlope | 0.1 | Reject if slope exceeds this (0 = flat, 1 = vertical). Lower → flatter required. |
| CheckDifference | true | Verify local terrain flatness, sample neighbors and reject if elevation varies too much. |
| Max_ElevationDifference | 650 | Max allowed elevation variance under the spawn (cm). Tightens to "must be flat". |
| SpawnableInWater | false | If false, reject candidates where ocean height is above terrain height. |
| RotateToGround | true | Auto-orient the spawn to surface normal. |
| InvalidSize | 85000 | Exclusion radius around each successful spawn (cm), prevents two items from this asset from spawning closer than this. |
| SpawnSize | 450 | Effective area around the spawn point. |
Max_ElevationDifference to 100-200. For props that can sit on slopes (rocks, bushes), raise it to 2000+ or set CheckDifference = false.
Placement (FWS_Grid_Placement_Data)
Inside FWS_Grid_Actor_Data and FWS_Grid_StaticMesh_Data, every entry carries a Placement struct that fine-tunes how that specific item is positioned:
| Field | Default | Purpose |
|---|---|---|
| Transform | identity | Base transform applied before the snap-to-ground step. |
| SnapGround | true | Project the spawn down to the terrain surface. |
| RotateToGround | true | Align the spawn's up-vector to surface normal. |
| RND_Yaw | false | Apply random yaw rotation. |
| Yaw_Rotate | (1°, 360°) | Yaw range used by RND_Yaw. |
| Z_Offset | 0 | Per-item Z offset on top of the asset's global Z_Offset. |
| Use_DiskRND | true | Apply a random offset within a disk (for collection items, scatter them around the base point). |
| DiskRND_Size | 400 | Disk radius for the random offset (cm). |
| RND_LinearScale | false | Random uniform scale. |
| LinearScale | (0.5, 1.5) | Scale range used when RND_LinearScale = true. |
LOD proxies (FWS_Grid_Proxy)
Each Spawner_Actor, Spawner_StaticMesh, and the collection's Collection_ProxyData can carry an optional LOD proxy mesh that's swapped in at distance to save draw calls:
| Field | Default | Purpose |
|---|---|---|
| UseProxy | false | Master toggle for the proxy. |
| ProxyMesh | null | Soft reference to the simplified mesh (loaded async on client). |
| ProxySwitchDistance | 100,000 | Distance at which the real spawn is replaced by the proxy (default 1 km). |
| Use_AdditionalComponent | false | Spawn an additional component class on the proxy (e.g. for VFX, light marker). |
| AdditionalComponentClass | null | Soft reference to the additional component. |
Root-level streaming knobs
All under Category Grid on AWorldScapeRoot:
Master switches
| Property | Default | Purpose |
|---|---|---|
| EnabledGrid | false | Master toggle. |
| Grid_BaseSpawnData | [] | Global Grid Data Assets active everywhere. |
| Grid_InvalidVolume | [] | Array of AWS_Grid_InvalidSphere exclusion zones. |
| Grid_Debug | false | Draw debug visualization of the loaded grid. |
| Grid_CheckSpawnBySeed | true | Per-side dedup by instance seed (leave on for MP, prevents ghost spawns at cell seams). |
| Grid_DestroyAllOnOutDistance | false | Force despawn all out-of-range items immediately (default keeps them for the unload window). |
Streaming distance & cell size
| Property | Default | Purpose |
|---|---|---|
| Grid_DistanceToLoad | 5,000,000 | Streaming radius (cm). Default = 50 km. Cells inside this radius load, others unload. |
| Grid_Size | 64 | Internal grid index resolution. Combined with Grid_Data_Size determines the per-cell spatial extent. |
| Grid_Data_Size | 8 | Per-cell data resolution. |
| Grid_Size_Scale | 1.1 | Multiplier on the cell area (slight overlap to avoid seams). |
| Grid_BaseSpawnScale | 1.0 | Base scale on the per-cell Max_Spawn count. |
| Grid_GlobalSpawnScale | 1.0 | Global multiplier across all data assets. |
| Grid_RNDCircle_TestLimite | 600 | Hard cap on candidate-position tests per cell (safety). |
Throttling (per-side)
| Property | Default | Purpose |
|---|---|---|
| Grid_UsePendingSpawn | true | Queue spawns instead of doing them all in one frame. |
| Grid_MaxSpawnPerSecond | 512 | Spawn throttle for client / listen / standalone. |
| Grid_MaxSpawnPerSecond_OnDedicated | 1024 | Spawn throttle for dedicated server. |
| Grid_UsePendingDestroy | true | Queue destroys. |
| Grid_MaxDestroyPerSecond | 1024 | Despawn throttle, non-DS. |
| Grid_MaxDestroyPerSecond_OnDedicated | 2048 | Despawn throttle, DS. |
| Grid_FoliageBlockSend | 16 | Foliage-batch granularity. |
Dedicated-server tweaks
| Property | Default | Purpose |
|---|---|---|
| Grid_SimulateDedicated | false | Pretend you're DS even on a client (debug DS behavior in PIE). |
| Grid_SkipStaticMeshOnDedicated | false | Skip ALL StaticMesh/CollectionStaticMesh/Foliage grid types on DS. |
| Grid_SkipAdjacentCellOnDedicated | false | Shrink loaded neighborhood from 9 cells to 1 on DS. |
Live info (read-only, exposed for debug)
| Property | Purpose |
|---|---|
| NumberOfCellLoaded | Current count of loaded cells around all known players. |
| NumberOfThread | Active grid worker threads. |
| OnPendingAsyncCompute | Pending async compute jobs. |
| TempQueueLength | Length of the spawn queue. |
Exclusion zones (AWS_Grid_InvalidSphere)
Drop one or more AWS_Grid_InvalidSphere actors in your level, register them in the Root's Grid_InvalidVolume array, and the grid will refuse to load any cell whose center falls inside the sphere. Useful for:
- Clearing space around hand-placed structures (no rocks inside your village).
- "No buildings in the river valley" type designer overrides.
- Hand-tuning regions around POIs.
The actor exposes a single function WS_ZoneGetSize() that returns the scaled sphere radius.
Localized: AWorldScape_GridSpawnerVolume
Place a Grid Spawner Volume in your level for region-specific spawn rules. Properties (Category Spawner):
| Property | Type | Purpose |
|---|---|---|
| SpawnDataAsset | TArray<TSoftObjectPtr<UWorldScape_GridDataAsset>> | One or more Grid Data Assets active inside this sphere. |
| SpawnScale | float | Multiplier on each asset's Max_Spawn within this zone. |
| Linked_WS_Root | AWorldScapeRoot* | Which Root to spawn against. Auto-set if the volume is a child of a Root. |
| SphereComponent | USphereComponent | Defines the zone radius. Scale the actor in-editor to resize. |
The volume's items still pass through the same streaming pipeline, they'll only spawn inside cells overlapping the sphere AND within Grid_DistanceToLoad of the player.
Per-cell & phase delegates
Bind to these BlueprintAssignable delegates on the Root:
WS_Grid_LoadCell_Delegate(FWS_Grid), fires for every cell that finishes loading. Useful for spawning custom per-cell content (NPCs, AI zones).WS_Grid_UnLoadCell_Delegate(FWS_Grid), cell unloaded.WS_Grid_StartPendingLoad/WS_Grid_EndPendingLoad, batch-load window opened / closed (good for hiding HUD during heavy streaming).OnStartGrid/OnEndGrid, the entire grid pass starts / ends.
Common recipes
Recipe 1. Scattered rocks all over the planet
- Create
GDA_Rocks_Global:SpawnType = StaticMesh. - Add 5-10 rock meshes to
StaticMeshDatas, varySpawnableChance(e.g. small rocks weight 5, big rocks weight 1). - Set each entry's
Placement.RND_Yaw = true,Placement.RND_LinearScale = true,Placement.LinearScale = (0.6, 1.4). SpawnParams:MaxSlope = 0.6,SpawnableInWater = false,CheckDifference = false,Max_Spawn = 30.- Add the asset to Root's
Grid_BaseSpawnData.
Recipe 2. Buildings in a single zone with flat terrain check
- Create
GDA_Village_Buildings:SpawnType = Actor, fillActorsDataswith house/tower/well actor classes. SpawnParams:MaxSlope = 0.05,CheckDifference = true,Max_ElevationDifference = 100,InvalidSize = 200000(200 m between buildings).Max_Spawn = 5(per cell).- Drop a
WorldScape_GridSpawnerVolumein your level over the village area, scale the sphere, link the asset. - Optionally add an
AWS_Grid_InvalidSphereat the village center to keep the middle clear.
Recipe 3. Camp collection (tent + crates + barrels)
- Create
GDA_Camp_Collection:SpawnType = Collection. - Add the tent class to
Collection_ActorsDataswithPlacement.Use_DiskRND = false(it stays at the base point). - Add 3-5 crate / barrel meshes to
Collection_StaticMeshDataswithPlacement.Use_DiskRND = true,Placement.DiskRND_Size = 600(scatter around the tent). - Set
Collection_ProxyData.UseProxy = true, assign a low-poly silhouette mesh, setProxySwitchDistance = 50000(50 m). SpawnParams: tighten flatness check;Max_Spawn = 2.
Recipe 4. Cascading data assets (modular biome)
- Create three small assets:
GDA_Biome_Trees,GDA_Biome_Rocks,GDA_Biome_Mushrooms. - Create a master
GDA_Biome_ForestwithSpawnType = DataAsset. - Add the three child assets to
Spawnerswith their ownSpawnScalemultipliers. - Add only
GDA_Biome_Forestto the Root or volume, it will cascade into the children.
Recipe 5. Server-stripped cosmetic decorations
- Create a Grid Data Asset for cosmetic-only items (low grass, particles).
- Set
SpawnableOnDedicated = false. - The dedicated server will skip this asset entirely; clients will still see it.
Grid in multiplayer
The grid system is not replicated, each peer regenerates the same items locally from the shared Seed + per-asset SeedOffset. See Grid spawning in MP for the full multiplayer model.
Key MP knobs:
- Per-asset
SpawnableOnDedicated, cosmetic items off, gameplay items on. - Per-asset
SeedOffset, lets you shift placements without changing the global Seed. - Root
Grid_CheckSpawnBySeed, dedup by seed across cell boundaries (default on, leave on). - Root
Grid_SkipStaticMeshOnDedicated/Grid_SkipAdjacentCellOnDedicated, coarse DS perf knobs. - For replicated actors: set the actor's
bNetLoadOnClient = falseand let the server replicate, otherwise you'll get two copies on each client (one local, one replicated).
Troubleshooting
| Symptom | Likely cause & fix |
|---|---|
| Nothing spawns at all | EnabledGrid = false (default). Or your Grid Data Asset is empty / has Enabled = false. Or no list matches the SpawnType (e.g. SpawnType=StaticMesh but StaticMeshDatas is empty). |
| Items appear far from the player but not nearby | Grid_DistanceToLoad too low, or the player has no UWorldscapeCollisionInvoker component (its presence drives some grid systems too). |
| Items spawn but are sunk into terrain | Placement.SnapGround = false, or Placement.Z_Offset negative. |
| Items spawn floating above terrain | Placement.Z_Offset too high, or Placement.SnapGround = false. |
| Far too few items spawn | Constraints too strict: MaxSlope too low, Max_ElevationDifference too tight. Or Max_TestToSpawn too low (raise to 400-600). |
| Items overlap / bunch up | Raise InvalidSize to enforce minimum spacing. |
| Frame hitches when moving fast | Lower Grid_MaxSpawnPerSecond, enable Grid_UsePendingSpawn (default on), or reduce Max_Spawn per asset. |
| Items appear in different places on server vs client | Check the determinism contract, verify Seed matches, asset list ordering matches, noise asset matches. |
| Items inside an exclusion zone | Check that the AWS_Grid_InvalidSphere is registered in Grid_InvalidVolume and that its sphere is large enough. |
| Items missing on dedicated server | Per-asset SpawnableOnDedicated = true, Root Grid_SkipStaticMeshOnDedicated = false, and ensure soft-class assets are in the server cook (Asset Manager). |
Collision Invoker (Component) Beta
The plugin exposes two collision-invocation mechanisms, don't confuse them.
- Component (
UWorldscapeCollisionInvoker): attach to any actor that needs collision around it (player pawn, vehicle, AI). Auto-registers with the subsystem. - Volume (Collision Invoker Volume): a placed volume that generates an isolated collision actor in a fixed region (cliffs, structures). Usually used for editor placement, not for moving actors.
For multiplayer: every replicated pawn that needs server-side collision queries should carry a UWorldscapeCollisionInvoker component.
Foliage debug widget
A runtime widget that surfaces every counter the foliage system tracks, the fastest way to understand why your frame budget is being eaten by foliage.
Toggle it via the console:
WorldScape.Foliage.DebugWhat it shows
- Per-foliage active sector count, total instances, actors spawned
- "Ghost sectors", sectors marked active but never filled (memory leak indicator)
- Generation state and regen-reason string (so you know why regen happens, e.g.
"WS_CleanFoliage") - Thread pool alive/done state
- Pending spawn queue size
The widget is a plain Slate widget (SWSFoliageDebugWidget) so it draws on top of any game.
Performance & best practices
A few tunings make the difference between 30 fps and 120 fps at planet scale.
Terrain LOD
- Higher
TerrainResolution_Depth= more detail but more memory. - Sweet spot:
10-12for altitude-heavy worlds,8-10for mostly-flat worlds. - Each level doubles the previous sector's coverage.
Foliage
FoliageSectorSize: 2000= 2,000-cm sectors.FoliagesCount: 100= ~100 instances per sector.- Use
Is_NaniteMesh: truefor high-poly foliage. FoliageCullDistanceMultiplierreduces draw calls in the distance.
Collision
bGenerateTerrainCollision: trueadds significant cost, only enable if needed.- Use
EWS_FCP_TypeCompute::GPUfor foliage collision pooling on heavy worlds. bUseAsyncCooking: truemoves physics cooking off the game thread.
Multiplayer
bGenerateOnServer: true(default) for dedicated servers.- Subsystem auto-tracks the nearest Root per player.
- Collision invokers manage movement-based updates, one per significant moving actor.
Troubleshooting
Common symptoms, fixes, and the FAQ from the original docs.
Stair-step heightmap artifacts
Use a 16-bit texture and enable High Resolution on the volume data asset.
Random heightmap values
Caused by non-power-of-two textures or 8-bit textures imported as 16-bit. Re-export at a power of 2, set compression to HDR.
Mountain pop-in
Increase MaxLOD to 10-12.
Heightmap Influencer behaving inconsistently
Restart the level / project to re-apply the data fresh.
Black screen in the demo level
Enable the Volumetric plugin, or disable CloudVolumetric in the level.
Failed packaging
Enable the Water plugin in your project's plugin list.
Hole Volume not cutting
"Edit the master material for the WorldScape manager and multiply the alpha vertex color at the end of the opacity mask lerp."
Floating-point surface artifacts
See Material fix.
Terrain visual artifacts on UE 5.x
Set Velocity Pass to Write After Base Pass, run r.UseVisibilityOctree 0.
FAQ
Can I walk on a planet's surface?
Yes, use the WorldScapePlayer or build a custom MovementComponent (see Custom character).
Can I use Landscape materials on the clipmap?
No. UE Landscape materials are built for LandscapeComponent. The plugin offers a triplanar example as an alternative.
Can I sculpt the terrain in the editor?
No direct sculpt, create heightmaps in external software (WorldMachine, Gaea, etc.), import via Heightmap Influencer. The WS Tools heightmap editor offers in-engine brush sculpting.
Why does my heightmap have artifacts?
Stair-step = precision; use 16-bit + High Quality. Random values = non power-of-two or 8-bit-imported-as-16-bit.
Why do mountains pop in/out?
MaxLOD too low. Recommended 10-12 for altitude-heavy terrain.
Support & community
Stuck? Found a bug? Want to share what you built?
- Discord: discord.gg/eWuFnPcskQ, join the support channel by sharing your Fab purchase receipt.
- Fab: marketplace listing for ratings, reviews, version history.
- Bug reports: via Discord with a repro project (or a clear video).
- Feature requests: Discord
#feature-requestschannel.
Roadmap snapshot
Currently in beta or experimental and being polished:
- Subsystem (registry, nearest tracking, MPC sync) Beta
- Gravity Value Beta
- Volume Pooling Beta
- Foliage Collision Pooling Beta
- Foliage Baker Beta
- Heightmap Editor & Heightmap Tools Volume Beta
- Collision Invoker Volume Beta
- Grid Spawner / Snap Actor / Planetary Grid Data Experimental