DOCUMENTATION

Landscape Authoring

Landscape authoring in MifBridge: create a real Landscape, sculpt and paint it in world units, import and export heightmaps, and diagnose a landscape that looks wrong. Read this before building ground, terrain or roads.

Cooked landscapes are a special case: their source data is gone, so the write endpoints refuse them. See Cooked Projects.

Why use a Landscape for ground

A grid of scaled planes or tiled static meshes fails in predictable ways:

Attempt Symptom Cause
One /Engine/BasicShapes/Plane scaled 16 to 300 times Blurred, smeared corners One UV set stretched over 30,000 units
Several ground materials mixed randomly per tile A more obvious checkerboard Randomizing the material emphasizes the tile boundary
A grid of planes at moderate scale Visible seams Adjacent tiles restart their UVs at the edge
A mesh that is an irregular decal patch used as a tile Gaps showing sky The mesh is not tileable

When several attempts at the same goal each fail differently, a capability is missing. For ground, the capability is a Landscape, and create_landscape makes one.

The endpoints

Endpoint Does
create_landscape Makes a Landscape (flat, rolling or island relief) with an optional layered material
landscape_info Reports every landscape in the editor world: bounds, resolution, scale, material, layers, edit layers
sculpt_landscape Raise, lower, flatten, smooth, erase, noise, terrace, erode, hydro erosion
paint_landscape Paints a weight layer, or cuts holes with the visibility layer
register_landscape_layer Gives a paint layer the layer info it needs before it can be painted
import_landscape_heightmap Writes a whole heightmap, or a layer's weightmap, in one call
export_landscape_heightmap Reads a heightmap or weightmap out as a file or base64
sample_landscape Reads height and every layer's weight at world points
apply_spline_to_landscape Carves and paints along a spline: roads, riverbeds, paths
bind_landscape_rvt Binds runtime virtual textures and creates their volumes
diagnose_landscape, diagnose_landscape_draws Explain a landscape that exists but does not draw

Call describe_endpoint for the exact parameters of each.

Coordinates and heights

Two coordinate spaces exist inside the engine. Vertex space is integer (X, Y) indices into the heightmap. World space is what every other MifBridge endpoint uses. The landscape endpoints take and return world units only: say "flatten a 4,000-unit pad at this point" and the conversion happens inside.

Heights are stored as uint16, where 32768 is the landscape actor's own Z. One step is the actor's Z scale divided by 128 world units, so at the default scale of 100 a step is 0.78125 units and the usable relief is roughly plus or minus 25,600 units. A requested height beyond that is clamped rather than wrapped, so you get a flat clip and not spikes.

Read landscape_info before sculpting or painting. Every world-space argument only makes sense against the bounds it reports.

Creating a landscape

  • quadsPerSection must be 7, 15, 31, 63, 127 or 255. Any other value builds and then renders with cracks between sections, because the level-of-detail chain assumes a power of two minus one. The endpoint rejects other values up front.
  • A layered landscape material with nothing painted renders as its fallback, usually black. create_landscape fills the first layer to full weight by default so the landscape is visible. Pass explicit weight values in layers to override.
  • heightMode is flat, rolling or island; amplitude is in world units of relief.
  • gridSize applies on World Partition maps only: the number of components per streaming proxy. The endpoint refuses it on a map that is not partitioned. Without it on a partitioned map the reply carries a worldPartitionNote: one actor, nothing of it streams.
  • Edit layers are off by default on UE 5.3 to 5.6 and always on from 5.7. Pass editLayers set to true to ask for them at creation. Do not toggle them on an empty landscape afterward: that crashes 5.6.

create_landscape is self-managed (it does not run inside an editor transaction) because undoing the texture creation midway would leave components pointing at freed textures.

Sculpting

sculpt_landscape works in world units: center, radius, amount and targetZ.

  • amount applies to raise and lower only, and targetZ to flatten only. Passing one to the wrong mode is refused, and so is a parameter a mode does not read. An unknown mode is rejected before anything is touched.
  • falloff defaults to 0.5. A value of 0 makes a hard edge, which produces a mesa with vertical walls.
  • strength (0 to 1) scales every mode except raise and lower. It defaults to 1, and to 0.3 for erode and the hydro erosion mode.
  • smooth takes kernel (1 to 31), the radius in vertices of a box average.
  • noise takes amount (the peak in world units), noiseScale (vertices per feature, 1 to 512, default 128), noiseMode (both, add or sub) and seed.
  • terrace takes interval (required, world units between steps) and terraceSmooth (0.0001 sharp to 1).
  • erode takes threshold (0 to 256 height steps, default 64), iterations (1 to 300, default 28), noiseScale (default 60) and noiseMode (both, raise or lower; default lower).
  • Hydro erosion takes rainAmount (1 to 512, default 128), sedimentCapacity (0.1 to 1, default 0.3), iterations (default 75), rainDistribution (both or positive) and noiseScale (default 60).
  • shape is circle (the default), box (center, size, yaw) or ramp (start, end, width; flatten levels the strip to the slope between the two Z values).
  • mask is protect, only_inside or ignore (the default), and uses the edit mask written by set_landscape_edit_mask.

The reply reports verticesChanged, heightBefore and heightAfter over the brush, and maxChange. Read them: a brush smaller than one quad can touch zero vertices, and the reply says so with a warning.

A brush call costs a fixed amount of time regardless of its size. For a whole coastline or a large shape, build a heightmap and use import_landscape_heightmap instead.

Collision is cooked from the heightfield separately from the visible surface. The sculpt endpoint refreshes the components' bounds and render state after a stroke so both move together.

Painting

paint_landscape paints a weight layer in world units, which is what makes a road corridor read as dirt while the verge stays grass.

  • layerInfo must be one of the layers the landscape's material declares, and that is enforced. Painting an undeclared layer used to allocate a stray weightmap channel, dim the real layers and then vanish, all while reporting success.
  • Weights normalize across layers, so painting one layer up pushes the others down. For that reason there is no erase mode.
  • mode is paint, smooth, flatten or noise. Smooth takes strength and kernel; flatten moves toward weight by strength; noise takes amount (peak weight change, 0 to 1), noiseScale, noiseMode and seed. weight belongs to paint and flatten only.
  • visibility set to hole or restore paints the visibility layer instead of a weight layer, with a collision rebuild. The landscape material needs a Landscape Visibility Mask node, otherwise the engine skips the write and visibility is refused.

A fresh landscape's material can declare a layer and leave its layer info empty. That is the ordinary state, and the reason paint_landscape refuses it. register_landscape_layer creates and assigns the layer info. The layer name must already be declared by the material; an undeclared name is refused with the list of real ones. The created asset is unsaved. op set to unregister (with confirm) deletes the layer's weights from every edit layer and removes it from the landscape; the layer info asset is kept.

Edit layers

On a landscape with sculpt edit layers, the merged heightmap and weightmaps are an output, regenerated from the layers every time the landscape composites. A write straight into the merged data lasts only until the next composite. So sculpt_landscape, paint_landscape and import_landscape_heightmap take editLayer (default: the first layer), write into that layer and run the composite before returning. export_landscape_heightmap and sample_landscape read the composited surface by default, or one layer by name.

Layers add up: the composite adds each layer's height above its zero onto the layers below. For a procedural layer (splines, a road tool's patch, a Blueprint brush), landscape_info with layerEffect measures what each layer changes. It hides the layer, recomposites, compares the surface, and shows the layer again, leaving nothing changed.

Heightmaps

Import

import_landscape_heightmap writes a whole heightmap in one call. It is seconds where brushing is hours.

  • Pass file (a 16-bit grayscale PNG or a raw .r16) or data (base64 little-endian uint16, with width and height).
  • Omit minZ and maxZ for a straight copy: storage is already uint16, so a round trip is exact. Give both to map 0 to 65535 onto a world Z range.
  • On a landscape with edit layers the heights go into editLayer, and the reply's surfaceDiffering says whether the visible surface then matches what you sent.
  • flipX and flipY mirror each tile before the write.
  • tiles set to true with file naming any tile of a set called <base>_x<N>_y<M> reads every tile beside it and places tile (N, M) side by side, with no shared edge vertices, as the editor's tiled import does. A set with a tile missing is refused.
  • geo (meters per pixel, minimum and maximum elevation in meters) maps the heights to true elevation with sea level at world Z 0, in place of minZ and maxZ. Every reply carries a geoTransform and, with geo, reports whether the landscape is at true scale and which scale to set if it is not.
  • layer imports a registered paint layer's weightmap instead: an 8-bit grayscale PNG or one byte per vertex, where 0 to 255 is weight 0 to 1. weightAdjust (default true) scales the other layers so every vertex still sums to 1; false writes this layer alone. The reply reads the layer back with readBackMeanError and readBackMaxError.

Real-world elevation

Passing elevation (lat, lon, sizeM, optional zoom, exaggeration, datum) instead of file or data writes the heights of a real place, with width and height set to the landscape's vertex counts. This is the only call that makes an outbound request: it downloads public AWS Terrain Tiles over HTTPS, at most 64 tiles per call, and caches nothing. It happens only when you pass elevation.

  • The datum is sea (default, true height above sea level) or lowest (height above the area's lowest point, from Z 0). Heights are multiplied by exaggeration (default 1).
  • A landscape holds only about plus or minus 256 m around its own Z at Z scale 100 (Z scale times 2.56 m either way). A mountain needs the actor moved up and its Z scale raised. The endpoint refuses a range it cannot hold, changes nothing, and answers suggestedLocationZ and suggestedScaleZ.
  • X and Y are not changed. The reply's elevation.quadSpacingCm is the landscape X/Y scale that gives true proportions.
  • Refused before anything is fetched: an unknown key, no width or height, a latitude past 85 degrees.

Measured: 2 km around Mount Rainier at 65 by 65 samples peaks at 4386.9 m against the 4392 m summit.

Export

export_landscape_heightmap reads a heightmap out as a .png or .r16, or as base64 with asData. It is the read-back for an import: re-importing an exported file with no minZ and maxZ reproduces the terrain exactly. editLayer exports one edit layer. layer exports a paint layer's weightmap, one byte a vertex, and the answer adds paintedSamples, weightMin and weightMax. An existing file is replaced by default and the answer says so; overwrite set to false refuses the path.

Reading the landscape back

sample_landscape reads height and every paint layer's weight at up to 1024 world points. Values are at the nearest vertex, which each sample reports; they are not interpolated. It is the read-back for sculpting and painting. trace_ground hits collision, which is a separate cook of the same data, so the two can differ.

Each sample carries normal, slopeDeg, holeWeight and hole, and the proxy and component holding the vertex. A point over a World Partition streaming proxy that is not loaded reports proxyLoaded false, and notLoaded counts them. The endpoint also offers a slope step and a flat-area search over a rectangle, which finds connected regions flat enough to build on, biggest first; describe_endpoint lists the options.

A slope is the rise over the distance asked, so a bump smaller than the step is smoothed away. Choose the step for the size of what will stand there.

Splines

apply_spline_to_landscape carves and paints along a spline in one call: a road, a riverbed or a path. Brushes are circles, so a 400 m road with brushes means dozens to hundreds of calls whose overlapping circles never give a clean corridor.

  • Read verticesChanged in the reply, always. The engine's spline call returns nothing, so the count is measured by sampling heights before and after.
  • On a landscape with no edit layers the spline changed no vertices at all in testing on 5.3.2, and 5.7 refuses that case outright. Enable edit layers and pass editLayer. Note that create_landscape turns them off by default.
  • heightMode absolute (the default) is refused on any layer above the base, because layers add up: in testing a 408 cm spline height became 808 cm. Use difference, which keeps only what the spline changed on top of the layer's own heights. The reply's maxErrorUu is the largest gap between the composite and the spline where it wrote.
  • A cooked landscape is refused. The engine call dereferences landscape info without a null check and would crash the editor.

Runtime virtual textures: the black-terrain trap

A landscape material that samples a runtime virtual texture (RVT) for its base color needs two things, and missing either renders the terrain black:

  1. The RVT is in the landscape's RuntimeVirtualTextures array: what to draw into.
  2. An ARuntimeVirtualTextureVolume in the level bounds the region: where it applies.

bind_landscape_rvt does both. The editor's Create Volumes button is only UI and a property it sets does nothing, so the endpoint does the real work. On a World Partition landscape the RVT is bound on the landscape and on every streaming proxy that shares its GUID, and the reply lists them. actors binds any primitive that writes the RVT (roads, decals, meshes), with mainPass set to never, fromVirtualTexture or always. Verify with landscape_info.

Reading the symptom

Look Means
Magenta or bright pink Shader compile error
Checkerboard gray Missing material
Flat lavender Collision view mode, not a material problem
Black with white speckle Base color missing but detail and grass still draw

The speckle is the tell: geometry is fine and detail elements render, only the base albedo is missing.

Do not reach for bind_landscape_rvt on this symptom. Binding an RVT that has no valid pages does not restore the terrain, and it turns every other RVT-sampling material (buildings, roads) blown-out white. In a scratch level, use a landscape material that does not sample an RVT; set_property on LandscapeMaterial applies in place without a rebuild.

Fastest check for any wrong-color scene: spawn /Engine/BasicShapes/Cube with BasicShapeMaterial next to the object. If the cube looks right, exposure, lighting and capture are fine and the problem is that object's material.

Diagnosing a wrong-looking landscape

Call landscape_info first and read it in this order:

  1. componentsWithoutWeightmap above 0. Painted layer data never landed, so every blend weight is zero.
  2. materialLayers against the painted layers. Painting a layer the material does not declare changes nothing. The two lists must intersect.
  3. runtimeVirtualTextures empty. A black base color if the material samples an RVT.
  4. Only then look at pixels.

If a landscape passes all of that and still does not draw, diagnose_landscape lists every proxy and component with its scene proxy, registration, visibility, bounds, heightmap and weightmap residency, material resource and shader map. badBounds counts components with zero radius or extent, which are culled before anything else is tested. diagnose_landscape_draws is the follow-up: per-component cached draw-command counts for the base and depth passes, plus level-of-detail screen sizes.

Other details

  • Info must exist after import. The landscape needs its landscape info created and layer map updated after Import. Without them the landscape renders but every later sculpt or paint reports "no landscape". create_landscape handles this.
  • Transactions. sculpt_landscape and paint_landscape are transacted, as the landscape editor is. landscape_info, sample_landscape and export_landscape_heightmap run outside any transaction and do not dirty the level or leave an undo entry.
  • Paint dirt under a road corridor. Road meshes on unpainted grass look as if they float on the wrong surface; a paint_landscape pass along the corridor fixes it.