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
quadsPerSectionmust 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_landscapefills the first layer to full weight by default so the landscape is visible. Pass explicitweightvalues inlayersto override. heightModeisflat,rollingorisland;amplitudeis in world units of relief.gridSizeapplies 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 aworldPartitionNote: 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
editLayersset 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.
amountapplies to raise and lower only, andtargetZto 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.falloffdefaults 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.smoothtakeskernel(1 to 31), the radius in vertices of a box average.noisetakesamount(the peak in world units),noiseScale(vertices per feature, 1 to 512, default 128),noiseMode(both, add or sub) andseed.terracetakesinterval(required, world units between steps) andterraceSmooth(0.0001 sharp to 1).erodetakesthreshold(0 to 256 height steps, default 64),iterations(1 to 300, default 28),noiseScale(default 60) andnoiseMode(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) andnoiseScale(default 60). shapeiscircle(the default),box(center,size,yaw) orramp(start,end,width; flatten levels the strip to the slope between the two Z values).maskis protect, only_inside or ignore (the default), and uses the edit mask written byset_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.
layerInfomust 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.
modeis paint, smooth, flatten or noise. Smooth takesstrengthandkernel; flatten moves towardweightbystrength; noise takesamount(peak weight change, 0 to 1),noiseScale,noiseModeandseed.weightbelongs to paint and flatten only.visibilityset 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) ordata(base64 little-endianuint16, withwidthandheight). - Omit
minZandmaxZfor a straight copy: storage is alreadyuint16, 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'ssurfaceDifferingsays whether the visible surface then matches what you sent. flipXandflipYmirror each tile before the write.tilesset to true withfilenaming 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 ofminZandmaxZ. Every reply carries ageoTransformand, withgeo, reports whether the landscape is at true scale and which scale to set if it is not.layerimports 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 withreadBackMeanErrorandreadBackMaxError.
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
datumis sea (default, true height above sea level) or lowest (height above the area's lowest point, from Z 0). Heights are multiplied byexaggeration(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
suggestedLocationZandsuggestedScaleZ. - X and Y are not changed. The reply's
elevation.quadSpacingCmis the landscape X/Y scale that gives true proportions. - Refused before anything is fetched: an unknown key, no
widthorheight, 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
verticesChangedin 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 thatcreate_landscapeturns them off by default. heightModeabsolute(the default) is refused on any layer above the base, because layers add up: in testing a 408 cm spline height became 808 cm. Usedifference, which keeps only what the spline changed on top of the layer's own heights. The reply'smaxErrorUuis 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:
- The RVT is in the landscape's
RuntimeVirtualTexturesarray: what to draw into. - An
ARuntimeVirtualTextureVolumein 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:
componentsWithoutWeightmapabove 0. Painted layer data never landed, so every blend weight is zero.materialLayersagainst the paintedlayers. Painting a layer the material does not declare changes nothing. The two lists must intersect.runtimeVirtualTexturesempty. A black base color if the material samples an RVT.- 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_landscapehandles this. - Transactions.
sculpt_landscapeandpaint_landscapeare transacted, as the landscape editor is.landscape_info,sample_landscapeandexport_landscape_heightmaprun 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_landscapepass along the corridor fixes it.