Files
UnrealPrototyping/RawContent/World/README.md
T

8.9 KiB

The world's terrain source

This document describes the pipeline as it is built today. Where it is going — a Go core, stream-power erosion, plates and faults, a Generated edit layer, and a canvas of 7141 vertices at 200 cm — is settled in ../../Docs/Terrain.md (D-47). Nothing here is wrong yet; several things in it are scheduled to be replaced, and that document says which and by what.

L_World is a product of three inputs, none of them hand-edited: this folder's World.json (the manifest), the PNGs in Heightmaps/ that Scripts/Authoring/generate_heightmap.py writes from it, and Scripts/Authoring/create_world.py, which imports them into the level and dresses it with Elite_RockyMeadows' kit. Change an input, rerun the two scripts, and the level is rebuilt from scratch.

D:/UE_5.8/Engine/Binaries/ThirdParty/Python3/Win64/python.exe Scripts/Authoring/generate_heightmap.py
D:/UE_5.8/Engine/Binaries/Win64/UnrealEditor-Cmd.exe Salty.uproject -run=pythonscript -script=Scripts/Authoring/create_world.py -AllowCommandletRendering

The first needs numpy in Scripts/Authoring/.pylib (bootstrap-pylib.sh, once per machine) and takes about five minutes at 4081, most of it erosion (40 s without). The second takes a minute or two (the landscape's textures are built through the derived-data cache) and must not be killed part way: run it detached, not under a tool with a timeout. It can run while the editor is open, as long as the editor does not have L_World loaded at that moment. An existing level is loaded and emptied rather than deleted, because an editor that has had L_World open keeps its two HLOD layer assets locked and a recreation would fail to save; the previous build's proxy packages are swept after the save.

The manifest

Key Meaning
vertices_per_side Heightmap resolution. 4081 gives 16x16 landscape components of 255 quads, one streaming proxy each; see the layout note below before changing it
quad_cm Metres between vertices, in centimetres. 350 makes 4081 vertices 14.28 km, 204 km²
elevation_m.min / .max What heightmap values 0 and 65535 mean, in metres. The landscape's Z scale and the actor's Z offset follow from these, so that elevation 0 m is world Z 0
sea_level_m Where the sea plane sits and what the noise source builds its continent around. Keep it 0 unless there is a reason
spawn_pad_m Radius of the flat disc blended into the centre of the map for the player starts
streaming_grid_components Landscape components per world-partition streaming proxy, per side
source Where the height comes from; see below
layers The paint-layer rules: rock by slope (rise over run), high rock by altitude (metres), and a noise break-up so boundaries are not contour lines

Erosion

Whatever the source, the height then goes through heightmap_erosion.py before the layers are derived, because fractal noise alone gives pillowy hills and no drainage. The erosion block of the manifest drives it; every key has a default in heightmap_erosion.DEFAULTS, and "enabled": false skips the whole stage (for a real DEM, which is already eroded).

Key Meaning
coarse_factor, coarse_droplets, coarse_lifetime The first hydraulic pass runs on the map downsampled by the factor, with long-lived droplets (one cell per step, so 120 on 14 m cells is a 1.7 km path): this carves the valleys. Its result is applied to the full map as a delta, so the fine detail survives
fine_droplets, fine_lifetime The second pass at full resolution, short-lived droplets: gullies and rills
thermal_passes, talus_deg Thermal weathering with an angle of repose: where a cell stands above a neighbour by more than the angle allows, half of the largest excess slides down, shared among the lower neighbours. Mass is conserved, so cliffs keep a face and scree builds at their foot
strata_period_m, strata_contrast Rock hardness as horizontal bands with a slow tilt, scaling the hydraulic erosion: hard bands hold shelves and ledges. Contrast 0 is uniform rock
inertia, capacity, min_slope, deposit_rate, erode_rate, evaporation, gravity The droplet constants, in cell units (a slope of 1 is 45°), so they mean the same at both resolutions
max_change, max_speed, max_load Brakes. Droplets step in vectorised batches that share cells; without a cap on what one droplet may cut or fill per step, a crowd in one cell runs away to infinity. The load cap bounds the mound a droplet can leave where it stops
min_erode_slope, fine_scale Below min_erode_slope (rise over run) water deposits but barely cuts, so lowland soil holds and meadows stay smooth; fine_scale runs the full-resolution pass at a fraction of the cutting rate, so it leaves gullies rather than trenches

Each droplet cuts through a 3x3 brush around its cell, not a single cell: a one-cell footprint leaves every path as a rill one cell wide, which reads as brush strokes across the lowlands. Deposits land on the droplet's own cell, so a pit fills to the brim and the droplets move on; spread through the brush, a pit's rim rises faster than its floor and every droplet draining into it adds to a mound.

The pass also writes four derivative maps next to the weightmaps: L_World_Flow.png (water passed, log scaled), L_World_Wear.png (bedrock scraped), L_World_Deposit.png (sediment laid down) and L_World_Curvature.png (128 flat, brighter convex, darker concave). The layer rules use them: scraped bedrock and convex ridges read as rock, sediment fans and basins read as meadow. Nothing in the landscape material samples them yet; they are there for the material that will.

Swapping the noise for a real heightmap

The source block decides. Today it is noise:

"source": { "kind": "noise", "seed": 7 }

To use a real heightmap, point it at the file and say what its value range means in metres:

"source": { "kind": "file", "path": "RawContent/World/Sources/my_area.png", "elevation_m": { "min": 0, "max": 2400 } }

Then rerun the two scripts. What happens to the file: it is read (16-bit greyscale PNG, or raw 16-bit little-endian .r16/.raw with "width" given when it is not square; 8-bit PNGs are accepted and widened), optionally flipped with "flip_y": true, cropped to a centred square, converted to metres with its own elevation_m, resampled onto vertices_per_side (box-filtered when shrinking, bilinear otherwise; add "smooth_passes": 2 to soften a coarse DEM that was scaled up), and re-encoded into the world's elevation_m range, clipping and reporting anything outside it. The paint layers are derived from the finished height by the same rules as for noise, so a real heightmap needs no weightmaps of its own, and the spawn pad is blended in at the centre either way. Widen the world's elevation_m if the file's range does not fit; the range costs nothing but height precision (65535 steps over the span: 4 cm at 2560 m).

Any DEM tool that writes 16-bit PNG or r16 works: QGIS (gdal_translate -ot UInt16 -scale), World Machine, Gaea, terrain.party, the engine's own landscape export. A 30 m DEM of a 14 km area is only about 470 samples across; it will be smooth after resampling, which is what smooth_passes and the layer break-up are for.

--source-file and --source-elevation on generate_heightmap.py try a file for one run without editing the manifest; --seed does the same for noise.

The component layout

create_world.py hands the PNG to the engine's own importer, which picks the section size the way the editor's Import button does: the largest of 255, 127, 63, 31, 15, 7 quads that divides vertices_per_side - 1 exactly, preferring one section per component. 4081 - 1 = 16 x 255, so 16x16 components of 255 quads. The count matters more than the size: every component is a draw call and carries its own height and weight textures, all built through the derived-data cache on import. Epic's own recommended 4033 would divide only by 63, giving 64x64 components and a build four times as long for no visible gain. If you change the resolution, pick 255 x N + 1 (or 127 x N + 1) with N at most 32.

Rocky Meadows' part

The pack contributes the landscape material (M_Landscape_Main_Inst_RockyMeadows02) and its three layer infos, which is why the weightmaps carry the pack's names. Those names mislead: its Base_Layer samples the rock textures, Layer_02 the grass, Layer_03 the high rock, so the meadow weightmap is L_World_Layer_02.png; the sun with the pack's cloud-shadow light function; its skybox dome and sky light; its height fog and post-process grade. The numbers are copied from the pack's Rocky_Meadows_01 demo map as Scripts/Authoring/dump_level.py read them, and live at the top of create_world.py. The sea is a plane with the engine's water material, World_Sea_Proto, until a water body replaces it.