Version 1.0, coming to Fab. Orrery Terrain 1.0 is not on sale yet; this guide can still change before it is.
8. Tutorial: grassExperimental
The terrain can grow grass, flowers and stones on its paint layers. The grass follows edits: raise a hill and it grows on the new ground, paint a layer and the plants appear.
The grass system is Experimental: its C++ class (FTerrainGrassSystem) and the Is Grass Idle query can change in a minor version, and the changelog lists each change (versioning). The grass entries of a layer set and the terrain actor’s Layer Set property are stable API. It is a client feature: a dedicated server never loads it.
Behaviour
Section titled “Behaviour ”- You describe grass with the engine’s own Landscape Grass Type assets. Each grass type holds one or more varieties (mesh, density, scale, cull distance).
- You attach grass types to the entries of a Terrain Layer Set. A paint layer with a grass entry grows that grass.
- The layer’s weight at a point is the painted weight plus the material’s automatic rules. So grass grows on the auto-grass ground of the default material without anyone painting it.
- The grass is drawn as instanced meshes around each viewer. Distant sub-cells are released.
Step 1: a grass type
Section titled “Step 1: a grass type”- In the Content Browser, Add > Landscape > Landscape Grass Type (or search “Grass Type”). Name it
GT_Meadow. - Open it. Under Grass Varieties add an element. Set Grass Mesh to a static mesh (a small blade or flower mesh), and a Grass Density and End Cull Distance. These are the engine’s own settings.
- The terrain honours density, scaling, rotation, alignment to slope, jitter, and the cull distances of each variety. It ignores the lightmap setting, because the terrain has no lightmap.
The same from Python
Section titled “The same from Python”A grass type needs no Landscape actor. The asset factory and the variety struct are enough. This is the shape our sample maps use.
import unreal
tools = unreal.AssetToolsHelpers.get_asset_tools()mesh = unreal.load_asset("/Game/Grass/SM_Tuft") # your static mesh
gt = tools.create_asset("GT_Meadow", "/Game/Grass", unreal.LandscapeGrassType, unreal.LandscapeGrassTypeFactory())v = unreal.GrassVariety()v.set_editor_property("grass_mesh", mesh)v.set_editor_property("grass_density", unreal.PerPlatformFloat(default=90.0))v.set_editor_property("start_cull_distance", unreal.PerPlatformInt(default=2000))v.set_editor_property("end_cull_distance", unreal.PerPlatformInt(default=4000))v.set_editor_property("scaling", unreal.GrassScaling.UNIFORM)v.set_editor_property("scale_x", unreal.FloatInterval(min=0.7, max=1.4))v.set_editor_property("random_rotation", True)v.set_editor_property("align_to_surface", True)gt.set_editor_property("grass_varieties", [v])
g = unreal.OrreryTerrainLayerGrass() # FOrreryTerrainLayerGrassg.set_editor_property("grass_type", gt)g.set_editor_property("slope_range_deg", unreal.Vector2D(0.0, 32.0))entry = unreal.OrreryTerrainLayerEntry()entry.set_editor_property("name", "Grass")entry.set_editor_property("grass", [g])
f = unreal.DataAssetFactory()f.set_editor_property("data_asset_class", unreal.OrreryTerrainLayerSet)ls = tools.create_asset("LS_Terrain", "/Game/Terrain", unreal.OrreryTerrainLayerSet, f)ls.set_editor_property("layers", [entry])# terrain_actor.set_editor_property("layer_set", ls)Save the assets (unreal.EditorAssetLibrary.save_loaded_asset). In an engine build where unreal.LandscapeGrassTypeFactory is not exposed to Python, the sample falls back to a DataAssetFactory with data_asset_class set to LandscapeGrassType.
Step 2: attach it to a layer set
Section titled “Step 2: attach it to a layer set”- Open
LS_Terrain(see Materials and layers). - On the entry for the layer where the grass should grow, add one element under Grass.
- Set Grass Type to
GT_Meadow. - Optional rules per grass entry:
| Setting | Meaning |
|---|---|
| Density Scale | Multiplies every variety’s density. 0 turns the entry off. |
| Slope Range (deg), Slope Fade | Grows only between these slopes. |
| Altitude Range (m), Altitude Fade | Grows only between these heights. |
| Ground Class Mask | Grows only on these ground classes. |
| Keep Clear Margin (m) | Stays clear of rail, road and canal footprints by this much. |
| On Plates | Also grows on plates (pads). Off by default. |
Step 3: turn it on
Section titled “Step 3: turn it on”Select the terrain actor and set its Layer Set (Terrain category) to your layer set. The terrain is re-created with it, in the editor and when play starts, and the grass grows. Clearing the property clears the grass. Undo and redo work like any other property.
Quick test: a console command (development builds)
Section titled “Quick test: a console command (development builds)”- Press Play.
- Open the console (
`) and type:
Orrery.Terrain.Grass.LayerSet /Game/Path/LS_Terrain.LS_Terrain- The log (
LogOrreryTerrain) printsOrrery.Terrain.Grass.LayerSet: <path>.Typing the command with no argument clears the grass.
The command does not exist in Shipping builds.
From C++: switch the layer set at run time
Section titled “From C++: switch the layer set at run time”A game that changes the grass while it runs (a season, a biome) can set it on the grass system directly. Add OrreryTerrainRenderer and OrreryTerrainCore to your module’s Build.cs. Then, once the terrain exists:
#include "OrreryTerrainRenderSubsystem.h"#include "Grass/TerrainGrassSystem.h"
void AMyGameMode::SetGrass(UOrreryTerrainLayerSet* LayerSet){ if (UOrreryTerrainRenderSubsystem* Render = GetWorld()->GetSubsystem<UOrreryTerrainRenderSubsystem>()) { if (FTerrainGrassSystem* Grass = Render->GetGrassSystem()) { Grass->SetLayerSet(LayerSet); } }}A layer set given this way stays until the terrain is re-created with one of its own: an actor that names no layer set leaves it alone. GetGrassSystem() is null where grass never runs: a dedicated server, a commandlet, or -nullrhi. Check it. The header marks it internal, not part of the stable API, so it can change between versions.
Step 4: tune it
Section titled “Step 4: tune it”| Console variable | Effect |
|---|---|
r.OrreryTerrain.Grass |
1 grows grass, 0 releases all grass. |
r.OrreryTerrain.Grass.DensityScale |
0 to 1 multiplier on all density. |
r.OrreryTerrain.Grass.CullDistanceScale |
Scales every cull distance. A change rebuilds the grass. |
r.OrreryTerrain.Grass.MaxTasks |
How many build tasks run at once (1 to 64). |
r.OrreryTerrain.Grass.AcceptBudgetMs |
Game-thread time per tick for showing finished grass, in milliseconds. |
r.OrreryTerrain.Grass.DuringStroke |
1 refreshes grass as the stroke’s ground lands: a brush stroke reaches the grass four times a second, a road or other spline edit every tick. 0 waits until the stroke ends. |
r.OrreryTerrain.Grass.Debug |
1 shows grass counts on screen (not in Shipping). |
Grass after an edit
Section titled “Grass after an edit”When an edit changes the ground under grass that is already built (a brush stroke, a road laid live, painting a layer), the grass of each edited chunk is rebuilt on its own and shown whole: every grass variety and every changed patch of that chunk appears in the same tick, no earlier than the tick after the ground changed. The grass edge therefore follows the painted edge, not a patch grid.
Two project settings, under Project Settings, Plugins, Orrery Terrain, Grass, limit this rebuild:
| Setting | Default | Effect |
|---|---|---|
| Grass Edit Budget Ms | 4 | Game-thread time per tick for showing edited chunks’ grass. One chunk always lands per tick, so 0 means one chunk a tick. |
| Grass Edit Chunks | 8 | Edited chunks whose grass may be rebuilding at once (1 to 64). |
The cost: a long edit that re-commits every tick, such as a road built live, rebuilds all varieties of the chunks it crosses each time. In one measured road-building clip that raised the grass share of the game thread to about 1.3 ms in the busiest 1 % of ticks (up to 2.8 ms), and the worker threads’ grass time over the clip from 0.8 s to 17.5 s. A game that builds roads live and needs the tick shorter can lower the budget or the chunk cap: the edit then lands over more ticks, the nearest chunks first, and each chunk’s grass still lands whole. Brush strokes cost far less, because their ground reaches the grass only four times a second.
The engine’s grass.densityScale and sg.FoliageQuality also apply. Changing sg.FoliageQuality rebuilds the grass.
Check it worked
Section titled “Check it worked”- Play with a layer set whose layer 0 has a grass entry. Grass appears on flat ground near you and not on steep rock, if you also set a slope range.
- Sculpt a hill with the brush tool. Grass grows on the new ground a moment after the stroke (or about four times a second while you hold, with
DuringStrokeat 1). - Run
r.OrreryTerrain.Grass 0. The grass vanishes. Runr.OrreryTerrain.Grass 1. It comes back.
Placed foliage that follows terrain edits is a separate system. See Foliage that follows edits, and PCG.