Skip to content
Work in progress. These docs describe Spawnite at launch, and some parts are still being built.

Ground maps, grass blades and foliage

A grounded forest, a realistic meadow or any scene built from scans needs four things: a ground texture with its relief, pieces lying on the ground, grass shaped to the scene, and leaf cards that read as one lit crown. The ground’s maps, its pieces and foliage are opt-in. The grass draws curved blades in clumps with a colour ramp by default, and a cover’s fields shape them. The ground, its pieces and the grass are set per material, in the game’s src/maps/materials.json or a map’s own materials, as the Landscape page describes. Foliage is a material function for a model.

A material’s texture swaps in a colour texture from the game’s public folder. Every texture path on this page reads from the page’s own folder, a leading / included, as a prop’s model path does, because the games site serves each game under its own folder. Beside it, four maps from the same scan keep its relief and its light:

{
"materials": {
"slope": {
"from": "grass",
"texture": "/assets/forest/ground037.webp",
"normalTexture": "/assets/forest/ground037-normal.webp",
"heightTexture": "/assets/forest/ground037-height.webp",
"roughnessTexture": "/assets/forest/ground037-roughness.webp",
"occlusionTexture": "/assets/forest/ground037-ao.webp"
}
}
}

Each map is optional:

  • normalTexture: a tangent-space normal map in OpenGL’s convention, green up, such as ambientCG’s NormalGL or Poly Haven’s nor_gl. A DirectX map, NormalDX or nor_dx, shades with one axis turned over. bumps scales it, as it scales a built-in material’s.
  • heightTexture: a greyscale height or displacement map. Where two materials meet, the higher texture shows, so stones push through the grass beside them. It moves no geometry. The engine spreads its values round one half, as it does for its own materials.
  • roughnessTexture: a greyscale roughness map, black a mirror and white matte, in place of the material’s one roughness.
  • occlusionTexture: a greyscale ambient occlusion map, white open and black shut. It darkens the sky’s and the bounced light in the crevices, not the sun’s, as glTF’s occlusion does.

The maps go with the texture they were made with, so an entry that gives a map gives its texture too, and the schema refuses one without it. A material that starts from another keeps the other’s texture and maps, until it gives a texture of its own, which drops them all. The maps share the colour’s tiling, its turn per cell and its hexagonal tiling. A map that fails to load leaves its material without it and names the file in the console.

The engine packs the maps into its own textures as they load, so each costs one download and nothing more per frame than a built-in material. A 1024-pixel map in WebP is about 50 to 550 KB.

The ground’s textures, its decals and every model’s maps filter at the quality level’s anisotropy, so ground and bark seen at a grazing angle stay sharp. Three’s glTF loader leaves a model’s maps at 1, and the engine raises them as each file loads.

A texture laid on the ground from above stretches on a slope: on a 50-degree face each texel covers about one and a half times its length down the slope, and a cliff reads as streaks. Above its triplanarAbove, in degrees of slope, a material’s texture is also projected from the side, as triplanar mapping does, so a cliff carries it at its size. The side takes over by 14 degrees steeper, so the default of 38 hands over around 45 degrees, where a projection from above and one from the side stretch alike.

The side reads two planes, facing along x and along z, each by the face’s turn toward it, after Inigo Quilez’s biplanar mapping. A plane with under 2% of the weight is skipped. The colour, the normal map, the height and the roughness all come from the same projections, so where two materials meet on a cliff the taller still shows. The normal map’s tilt is carried from each plane into the face, as Ben Golus’s triplanar UDN does. The per-cell turn applies from above only, so a rock’s strata keep their direction on a cliff; hex tiling and animation apply to every projection.

"cliff": { "from": "rock", "triplanarAbove": 34 },
"sand": { "from": "sand", "triplanarAbove": 90 }

triplanarAbove takes 30 to 90; 90 projects from above alone. Terrain3D’s stable release projects from the side above about 37 degrees by default; Godot’s material and Unity’s and Unreal’s terrains leave it to the material’s author. A flat pixel pays one branch; a steep one reads the material up to three times, and three times that under hex tiling.

The side projection was measured on 2026-10-16 in the forest frame at its spawn camera, which shows few steep pixels and no bough near the lens, 1920 by 1080 on an RTX 4070 SUPER, one run each with the machine 47 to 99% busy. A GPU frame’s median took 15.2 and 14.8 ms with it and 16.2 ms without it at High, and 3.8 and 3.5 ms with it and 3.8 ms without it at Low, inside the spread of the runs.

A painted ground reads as broad masses of colour in the sun and in the distance, and shows its brushwork near the camera and inside a lantern’s pool. A material’s grain draws its texture that way, as the painted surface draws a model’s map:

"needle-duff": {
"from": "dirt",
"texture": "/assets/floor/painted-cover.webp",
"tint": "#8a6538",
"grain": { "contrast": 0.5, "fadeNear": 5, "fadeFar": 18 }
}

The grain is the texture’s fine detail, measured against its own blur about 16 texels across, so the texture’s colour masses stay and only its strokes change. The sun, the moon and the sky show contrast of it, and point, spot and rectangular lights and the light mask’s fill show localLightContrast of it. It flattens from fadeNear to fadeFar metres from the camera. The four fields and their defaults are the painted surface’s.

  • Left out, the material draws its texture as it is, under every light and at every distance; {} takes the defaults, and false turns a parent’s grain off.
  • It merges down the materials a material starts from, field by field, and stays when an entry gives a new texture.
  • It changes the colour alone. A texture with a normal or height map still shades its relief in the sun, so lower bumps for a flat look.
  • A camera that stands far over the ground, as a top-down one does, takes a larger fadeFar, such as 10000 for every distance.
  • A ground with a grain keeps its live paint where the quality level asks for a bake, since the grain changes with the eye’s distance and the kind of light.

Every quality level draws it, Low too. A ground function that multiplies the colour after the paint keeps the lamps’ grain exact; one that lays a colour of its own can mix groundGrainRatio, the lamps’ colour over the day’s, toward 1 where its colour covers. No other engine ties a texture’s detail to the kind of light on it; Unreal’s distance blending and Unity’s Shader Graph fade a detail texture with depth by hand.

A material with a grain reads its texture once more a pixel, at a coarse level, and past fadeFar reads that level alone. It was measured on 2026-10-19 in Last Light’s forest edge at noon, 10 s of the scripted walk, uncapped, two runs each in turn on an RTX 4070 SUPER, the machine 22 to 96% busy. A GPU frame’s median took 11.6 and 13.5 ms with the grain and 17.2 and 15.7 ms without it at High, and 8.0 and 9.0 ms with it and 9.8 and 10.3 ms without it at Low. Past its fade the ground reads one level with no anisotropic taps, so it may cost less than without it; the spread of a busy machine covers the difference. Measure it again on a quiet one before you count on it.

A tiled scan holds its detail at a few millimetres a pixel, which the distance blurs away, so a forest path seen from a few metres reads as a smooth smear. The path in a finished game reads because of its pieces: chips, cones, needles and leaves, each a few centimetres across. A material’s decals scatter such pieces over it as flat pictures, from a sheet of them:

{
"materials": {
"trail": {
"from": "dirt",
"decals": {
"texture": "/assets/forest/decal-wood-chips.webp",
"normalTexture": "/assets/forest/decal-wood-chips-normal.webp",
"columns": 4,
"rows": 4,
"perSquareMetre": 18,
"size": { "min": 0.12, "max": 0.22 }
}
}
}
}

The fields:

  • texture: a sheet of pieces on a transparent ground, one piece in each cell of its grid. Keep each piece inside the circle its cell holds, so a turn never cuts it. Its alpha cuts the piece at one half.
  • normalTexture: the sheet’s normal map in OpenGL’s convention, laid out as the sheet. Without it, the pieces lie flat and take the ground’s light.
  • columns and rows: the sheet’s grid, 1 by 1 when left out. Each piece is drawn as often as the others. A map’s sheets hold at most 256 pieces together, the layers a texture array holds on every device, and a sheet two materials share counts once. Past them, the ground refuses the map and names its materials.
  • perSquareMetre: pieces on a square metre of map seen from above, where the material covers the ground whole; 10 when left out, 0 for none. The pieces thin where the material blends into another, decided at each piece’s centre, so a piece is drawn whole or not at all.
  • size: metres across one cell of the sheet on the ground, each piece drawn at random between min and max; 0.08 to 0.16 when left out. A square metre holds at most 1 / (max × max) pieces, 20 at a max of 0.22.
  • contactShadow: how much each piece darkens the ground round its edge, and the ground its shadow falls on away from the sun, so it lies on the ground rather than floating on it; 0 to 1, 0.5 when left out. The shadow is the sun’s alone, from a piece standing a seventh of its size proud, and runs at most a tenth of its size.
  • roughness: the pieces’ own roughness, so dry chips on wet mud stay matte; the material’s when left out.

Each piece takes a random turn, size and brightness. Pieces thin out on slopes steeper than 35 degrees and are gone by 50, where a flat picture would stretch. Unreal’s landscape grass, Unity’s terrain details and Godot’s multimeshes scatter meshes instead; meshes give a piece its silhouette, which a cone standing proud needs and a chip lying flat does not. Use a model for a piece a player sees from the side, and decals for the litter between.

A material that starts from another keeps its decals and changes them field by field. A new texture drops the old sheet’s normalTexture, columns and rows unless the entry gives its own. The ground holds perSquareMetre against size.max once the entries merge, so a child may raise the count over smaller pieces it inherits. A material with no texture of its own, such as the grid, scatters decals too; a game whose set is flat draws none. To lay pieces thicker in one place, such as chips round a stump, make a second material from the first with a higher perSquareMetre and paint it there, as spawnite map terrain paint --material chips --where "near=stump,1.6" does.

The asset library ships two sheets with their normal maps, decal-wood-chips and decal-forest-floor, under textures/decals.

Each piece is sampled from its own layer of a texture array, so the mipmaps never blend one piece into the next. Past the point where a piece covers about 4 pixels, the pieces give way to their mean colour over the share of ground they cover, so the floor keeps its tone and the far ground costs nothing more. At Medium quality and up, a material draws up to four passes of pieces, two texture reads each. At Low it draws two passes that fade out nearer, at 6 pixels, and the rest go to the mean colour.

On the forest edge at 1920 by 1080, on an RTX 4070 SUPER shared with other jobs, the scene pass took 3.58 to 3.96 ms at High with no decals and 3.51 ms with three materials scattering them, a still camera over 12 s, measured 2026-10-14. At Low it took 2.0 to 4.9 ms either way across four alternating runs. Both differences are within this machine’s spread. A camera close over ground that fills the screen costs more, about 8 to 16 reads a pixel. A sheet of 16 pieces at 256 pixels each takes about 11 MB of GPU memory with its normal map and mipmaps.

A material’s cover grows blades the way Ghost of Tsushima grows its grass. Each blade is a curve that leaves the ground near upright and arcs over to its tip. It is lit as its upper face, the side it bows over, and shades round across its width, so one half takes the sun and the other shades. Blades grow in clumps that share a height, a lean and a shade, and splay out from the clump’s middle. A blade takes its root’s colour at its foot, where the field shades itself, its own colour through its body and its tip’s colour at the top. Lit from its far side, light passes through it. Seen edge on, it turns a little toward the eye, so a near blade never thins to a line. Near the camera the blades grow in bunches from one crown, as Grow bunches near the camera says.

Any material grows a cover, a dirt path’s as well as a meadow’s. The Landscape page gives the cover’s density, height, colour and sway, and the scatter brush that thins it cell by cell. The fields are spelled the British way, colour and tipColour. Twenty-six more fields shape the blades, and varieties mixes other kinds of blade in, as Mix kinds of blade says. Unity’s terrain detail sets the same width, texture and dry colour, and Ghost of Tsushima’s grass the same ramp, bend and clumps:

{
"materials": {
"slope": {
"from": "grass",
"cover": {
"density": 2,
"height": 0.36,
"colour": "#5c8a20",
"tipColour": "#b4c43a",
"width": 0.035,
"bend": 0.6,
"dryColour": "#c4aa5a",
"dryShare": 0.15,
"drySpread": 5
}
}
}
}
  • width: metres across a blade at its root. Left out, it is 0.1875 of the cover’s height, 0.06 m at the default 0.32 m. A slender meadow blade is 0.03 to 0.04 m; a fine grass of 0.02 m takes a higher density to cover the ground. Far blades still widen, so a thinner field covers the ground at a distance.
  • taper: how a blade narrows to its tip, from 0 to 1. 1, the default, comes to a point; 0 keeps its width to the top, as a card for a texture of several blades.
  • texture: an image from the game’s public folder, drawn up each blade with its bottom edge at the root. Its alpha cuts the blade’s outline at 0.5, and its colour multiplies the blade’s, so keep it light. The engine reads it at 128 by 512 pixels, filters it at the quality level’s textureAnisotropy, and scales its alpha up with its mipmap level as it does a leaf’s, so a card of thin blades keeps its blades far off rather than thinning to specks. A card of a few thin blades covers less ground than a plain blade, so give it a wider width, such as 0.09 m, and a higher density.
  • tipColour: the colour a blade turns toward at its tip, as the sun lights it, such as a gold tip over a green blade. Left out, the tip is a lighter, yellower shade of the blade’s colour. Give it as a colour, never a tint: the engine keeps the ramp from one to the other. A tip in the albedo lights in shade as much as in sun, so a cream tip reads as straw in the shade; the sun’s own highlight on a tip is specularIntensity’s.
  • rootColour: the colour a blade turns toward over the lowest 30% of its length, such as a deep green under a yellow-green blade, at every density. The ground between near blades takes it too, as groundBlend says. Left out, the root is 0.6 of colour, and 0.65 for a textured blade, which carries its own shading; how much darker a dense field’s feet stand is the field’s own shade, occlusionIntensity, in their light. The ground between the near blades of a cover with no rootColour takes a fifth of colour deep in a dense field and 0.6 round a sparse blade in the open. Like the tip, the engine keeps the ramp from the blade’s colour, and a root channel the blade lacks, such as the red of a red sheath on a green blade, is added on top.
  • roundness: how round a blade shades across its width, not its outline, which taper sets. 0 shades it flat, 0.4 round and 1 nearly folded down its middle, each edge turning 80 degrees toward its side, so one half takes the sun and the other shades as a keeled blade does. 0.4 when left out, and 0 for a card texture, which carries its own shading. Ghost of Tsushima rounds its blades’ normals across their width the same way. Unity’s detail grass gives its blades the ground’s up-facing normal, Unreal’s foliage bends a card’s normals in its material, and Godot and Roblox expose no such control. Neither field costs a frame anything measurable. On 2026-10-17 the forest edge’s GPU frame took a median of 26.0 and 26.7 ms before them and 26.1 and 26.4 ms after at High, and 5.9 and 6.1 ms against 5.5 and 5.8 ms at Low, alternating runs with the machine 17 to 79% busy.
  • bend: how far a blade arcs over at its tip before the wind, from 0, upright, to 1, bowed toward the ground. 0.3 when left out, a mown meadow; 0.6 a lush one; 0.9 long grass lying over.
  • widthVariation and bendVariation: how far each blade’s width and bend stray from the cover’s, each blade on its own and apart from the other, so no two neighbours match. 0.3 and 0.45 when left out, so blades run from 0.7 to 1.3 of width and 0.55 to 1.45 of bend, and 0 grows every blade alike, as a stylised field may want. A card keeps its picture’s shape and takes no width variation. Unity’s detail grass varies width between minWidth and maxWidth, Unreal’s ScaleX and Godot’s random scale each instance, and none varies the bend, which Ghost of Tsushima varies per blade.
  • clumpSize: metres across a clump. 0 sets every blade on its own. 0.5 when left out, the size of a meadow’s tufts. clumpPull and clumpHeightVariation shape the clumps, as Grow tufts over a short base says.
  • backlight: the share of each colour of sunlight that passes through a blade lit from its far side, times its own colour, as defineFoliage takes it for leaves. The colour is sRGB, so #9c9c9c passes about a third. The grass takes the leaves’ term: Unreal’s two-sided foliage wrap on the side of the blade the eye sees, times a phase that is strongest looking into the light, so a side sun lights a field’s far sides too. Left out, none passes. A backlight costs one term for each light on every grass pixel. It needs the sun’s shadow, so a blade in a tree’s shadow lets none through, and a level with no sun shadow draws none.
  • castShadow: whether the blades cast the sun’s shadow near the camera, true when left out, as Cast shadow pools between tufts says.
  • specularIntensity: how much of the sun and the sky a blade’s waxy face reflects, from 0 to 1, three.js’s name and scale: 1 is a standard surface’s 4% reflection, which Unreal’s Specular and Godot’s specular reach at 0.5. 0.25 when left out, since a dense field seen at a grazing angle reflects far more than one surface. The reflection sits on a blade’s upper half and rises to its tip, and the body below reflects a tenth of it. A blade’s body is soft and matte and its open, waxy tip catches the sun, so a lit lawn shows no sheen band at a grazing angle. A dry blade reflects a fifth of it. It gives a lit lawn its bright tips and its shade some of the sky’s blue, which the blade’s colour alone never carries. Under a canopy the sky’s part fades with the canopy’s depth over the blade.
  • bunchBlades: the blades in each bunch the cover grows near the camera, 8 to 32, or 0 for single blades all the way in. 16 when left out, and 0 when left out for a cover whose own blade is a card.
  • bunchSize: metres across a bunch’s crown at the ground, above 0 to 0.8, as Grow bunches near the camera says.
  • groundBlend: how far the ground under the near blades turns toward the blades’ root colour, from 0 to 1, 0.5 when left out. Between near blades the eye sees their feet and the ground in their shade, so a dark green rootColour reads as shade between broad blades and a pale one pales the gaps. 0 keeps the ground’s own texture at every distance, as on a dirt path, and also draws no far field past the last blade. Above 0, where the quality level thins the blades with distance, the ground the thinned blades leave in view stands in for them, as Keep the field’s value across distance says. A cover with a density below 1 turns its ground less, in proportion. The blend follows the material, not the scatter brush, so a patch the brush clears of blades keeps its material’s blend.
  • dryColour: the colour some blades turn toward, in patches, a clump turning whole. A dry blade is lit as dead grass: its tip bleaches to straw, a paler and warmer shade of dryColour, rather than the living blade’s ramp, and it reflects a fifth of the cover’s specularIntensity. A tip the cover’s tipColour sets for a card keeps its picture’s. A game’s grass shape reads and sets bladeDryness, 0 to 1, so a shape that repaints a dry blade sets it to 0.
  • dryShare and drySpread: how much of the field turns dry, from 0 to 1, 0.5 when left out, and how many metres across a patch is, 6 when left out. Each needs a dryColour. A share of 0.25 turns a quarter of the blades, in patches with a soft edge.
  • bareShare and bareSpread: how much of the cover’s ground grows no blades, in patches, from 0 to 1, 0 when left out, and how many metres across a patch is, 4 when left out. A share of 0.25 removes a quarter of the blades, its patches’ soft margins counted, so the edge reads as a sparse margin onto the soil and less than the share is wholly bare. Every kind of the cover’s blades thins together, and a bunch thins whole. Unity’s detail layers and Unreal’s landscape grass thin by a painted density or weight map, which the scatter brush is here, and Ghost of Tsushima by a density texture; Godot and Roblox ship none. A bare patch shows the material’s own soil, read from the blades’ own patch noise at the same place, so it stays bare past the last blade. A bare blade still runs its vertex shader, so the patches save drawing, not the blades’ count.
  • heightVariation and heightSpread: how far the field’s height strays in patches, from 0 to 0.8, and how many metres across a patch is. A patch stands from 0.9 less the variation to 0.9 plus it times height, so 0.7 grows patches from 0.2 to 1.6 of it. 0.4 and 3 m when left out. Each blade and each clump varies on its own too, by its size and clumpHeightVariation. Unity’s detail grass varies its height between minHeight and maxHeight by a noise of noiseSpread, and Unreal’s ScaleZ and Godot’s random scale vary each instance alone.
  • occlusionIntensity: how dark the field’s own shade makes a blade’s foot, from 0 to 1, 0.5 when left out, and 0 for none. The shade falls from the root to none at 60% of the blade’s length, deeper in a denser field and none at a density of 0.3 or less, where a blade stands in the open. The sky’s and the bounced light take it at every distance. The sun’s light takes it past the blades’ cast shadows, as Cast shadow pools between tufts says, and from the eye on a level or a cover that casts none. A lantern’s or a spot light’s light takes none, as Godot’s AO_LIGHT_AFFECT leaves the direct lights out by default. three.js names the same control aoMapIntensity. Unity’s detail grass, Unreal’s landscape grass and Godot have no grass occlusion field, and Ghost of Tsushima darkens toward the root as occlusion.
  • patchVariation: how strongly the field’s colour varies in broad patches, on its blades and the ground past them, from 0 to 1. 0.35 when left out, and 0 draws none, as Keep the field’s value across distance says.

A field reads as one surface from the boots to the horizon: the blades thin with distance, the ground takes over where they go, and neither shows as a band. The engine keeps the value the same three ways:

  • The blades keep their own light. Each blade turns its normal a quarter toward the ground’s at every distance, so a far blade takes the light it takes near, whatever distance the level thins it at.
  • The ground stands in for the blades it loses. Where the level thins the blades, the ground they leave in view takes the colour the whole field reads as from afar. That colour is the blades’ bodies with their shaded sides and the gaps between, 0.45 of their colour, while the ground between near blades takes their roots. Under a root darker than that, as the engine’s own is in a dense field, the far ground reads the lighter of the two. It is weighed against the ground the whole cover shows: a fifth of the view looking straight down on a full cover, less as the view slants, and more under a sparse cover. The share falls as a canopy’s gap fraction does: the gap to the power of one over the cosine of the view’s angle from the ground’s normal. From a walker’s eye a full field 30 m off shows no ground between its blades.
  • The field takes the light as blades do. Past the blades the ground takes 0.8 of the sun and 1.5 times the sky’s light, since blades shade each other from the sun and turn their sides to the sky. Light crossing a field passes through or between blades before it leaves, so the field also takes its blades’ colour a second time at the same brightness, and reads greener than one blade lit once.
  • The field’s patches run on. Every cover’s field, its blades and the ground under them, lightens and warms in some patches and deepens in others. The patches take three octaves 3, 9 and 26 m across, about 15% either way at a patch’s heart at the default patchVariation. The blades and the ground read the same patches at the same place, so they run on past the last blade, as Unity’s detail grass varies between its healthy and dry colours by a noise.

The field’s colours are worked out from the cover’s own blades: each kind’s colour, tip and dry patches, counted by the ground its blades cover and, for a card texture, by the texture’s own colour once it loads. A pale colour over a green clover card reads as the card’s green. Past the last blade the ground keeps a little of its texture’s light and dark, and its bumps fade as the blades would cover them. Unity’s terrain details, Unreal’s landscape grass and Godot’s multimeshes leave the far ground to a material’s author; Ghost of Tsushima blends its terrain to an authored far texture where its grass ends.

The far slope across the valley from walk 1’s 60 m point stands 90 m off. On 2026-10-17 it was measured against the same camera with the dense blades drawn to 130 m. Before the gap’s slant and the field’s light, the open slope between its trees read a tan, a mean of 115, 96 and 49 in the frame, at a hue of 42 to 48 degrees. The blades read an olive green, 93, 89 and 43, at 54 to 64 degrees. With them it read 91, 86 and 42, at 53 to 59 degrees. The blades’ finer texture, their dark gaps and lit tips, still reads as a flat field, which a noise at the clumps’ scale would carry.

Walk 1 of the forest edge climbs an open slope where the blades thin from 20 to 50 m at High. On 2026-10-16 each band’s ground pixels were measured against the same camera with the blades drawn to 100 m. Each band read within 5 levels from the walk’s start and within 8 from 10 m on, and within 1 level past the last blade. On 2026-10-17, with the blades lit as their upper face, every 30-pixel band of the frame at walk 1’s 20 m point read within 1 level of the blades drawn to 100 m.

At Low the blades thin from 4 to 12 m and draw no sun shadow. On 2026-10-16, with the blades lit as their underside, the ground past them read 17 levels darker than the blades drawn to 100 m at 10 to 20 m. From 30 m on it read up to 13 levels paler. Low has not been measured since. Low lights the field by other rules than High, so one far colour cannot match both.

The far field costs one distance a ground pixel. It was measured on 2026-10-16 in the forest frame at its spawn camera, which shows few steep pixels and no bough near the lens, one run each with the machine 47 to 99% busy. A GPU frame’s median took 15.2 and 14.8 ms with it and 14.6 ms without it at High, and 3.8 and 3.5 ms with it and 3.3 ms without it at Low, inside the spread of the runs.

The patches cost three value noises a blade and a ground pixel. They were measured on 2026-10-16 at the same camera, one run each, on, off and on again, with the machine 30 to 96% busy. A GPU frame’s median took 18.6 and 18.1 ms with them and 17.7 ms without at High, and 5.6 and 4.9 ms with them and 5.5 ms without at Low, inside the spread of the runs.

Where two materials meet, each blade draws one of them whole, picked by their shares, so a fine grass and a coarse one mix blade by blade rather than averaging into a half of each. A textured blade stands down until its texture loads, and draws plain where its cover’s texture fails, naming the file in the console.

The blades bend in the look’s wind: they lean as its gust fronts roll across the field, and each clump sways together about that lean near its own period, about 1.7 s, so the field never repeats. A field reads painted rather than stranded when its colour varies by clump and by patch, not blade by blade. Keep colour and tipColour near the field’s colours in the light you set, and leave the grade to the look. A card texture of several thin blades reads as strands at any distance; a plain blade with a width reads as grass.

A cover’s varieties, Unreal’s word, are up to four other kinds of blade its material grows among its own, each taking a share of its blades:

{
"materials": {
"meadow": {
"from": "grass",
"cover": {
"density": 2,
"height": 0.11,
"colour": "#a4ac3a",
"tipColour": "#f2e070",
"varieties": [
{
"share": 0.22,
"height": 0.2,
"width": 0.045,
"bend": 0.6,
"colour": "#94a438",
"clumping": 0.75
},
{
"share": 0.035,
"height": 0.22,
"width": 0.055,
"taper": 0,
"bend": 0.1,
"texture": "/assets/flower-daisy.webp",
"colour": "#ffffff",
"tipColour": "#ffffff",
"clumping": 0.3
}
]
}
}
}
}
  • share: the share of the cover’s blades the variety takes, above 0 to 1. The shares together are at most 1, and the cover’s own blade takes the rest. A variety adds no blades, so the field costs what its density sets. For more flowers without fewer blades, raise the density too. A variety’s blades a square metre are about 220 × the cell’s density × the level’s grass share × share.
  • height, width, taper, texture, colour, tipColour, rootColour, bend, roundness, clumpPull, specularIntensity and castShadow: the cover’s fields for this kind, each the cover’s own when left out. A variety with its own colour takes, with no tipColour or rootColour, the engine’s lighter tip and darker root of its own colour. A variety with its own texture takes no dry patches, so white flowers stay white, and a roundness of 0 unless it sets one, since a card carries its own shading; a plain blade of its own colour dries with the cover’s patches.
  • clumping: how often a blade takes its clump’s variety, from 0 to 1. 1 grows the variety in whole tufts, 0 blade by blade; 0.7 when left out. A variety’s share holds at any clumping. A clump that picks a variety holds about clumping of its blades in it, so give a sparse flower a low clumping, such as 0.3, or its clumps read as beds. A variety with a clumpPull holds clumping of its tuft’s blades, the inner part of the clump, and its share’s rest stands as single blades in the base.

The cover’s density, sway, clumpSize, clumpHeightVariation, bunchBlades, bunchSize, groundBlend, dryShare, drySpread, patchVariation and backlight stay the field’s, and a variety refuses them. A later entry’s varieties replaces the list, and [] clears it. A flower is a card: a texture of a stem and a head on a clear ground, taper 0 and a low bend. A card keeps its picture’s shape, growing as wide as it grows tall. The library ships two, flower-daisy and flower-buttercup, under textures/grass. A variety’s texture that fails draws its blades as the cover’s own and names the file in the console. A game’s grass shape reads bladeVariety, 0 for the cover’s blade and 1 to 4 for its varieties, to recolour one kind.

A cover’s clumps share a height, a lean and a shade, but their blades spread as evenly as the field’s. clumpPull gathers a clump’s blades toward its middle into a tuft, as Ghost of Tsushima’s clumps do, and the blades past the tuft stay spread as the field’s base, so the tufts stand out of one continuous cover with no ground between them. Set it on a variety rather than on the whole cover. The cover’s own blade and its other varieties then fill the base, and the variety’s taller blades stand out of it in tufts:

{
"materials": {
"lawn": {
"from": "grass",
"cover": {
"density": 8,
"height": 0.1,
"width": 0.012,
"clumpSize": 0.4,
"clumpHeightVariation": 0.5,
"bunchSize": 0.12,
"varieties": [
{
"share": 0.3,
"height": 0.2,
"width": 0.0144,
"bend": 0.6,
"clumping": 1,
"clumpPull": 0.85
}
]
}
}
}
}
  • clumpPull: how tightly a clump’s tuft packs toward its middle, from 0, an even spread and the default, to 1, packed tight. The tuft takes the blades within 0.4 of a clump of its middle, about 45% of the clump’s blades on the clumps’ grid. The pull is strongest at the middle and none at the tuft’s edge, so the tuft thins into the base and leaves no bare ring. Only the clump’s own kind gathers, and only its tuft’s blades share the clump’s height, lean and shade: the base between tufts is one field with no clump’s outline in it. A blade of another kind that strays into the tuft stays where it stands, so give a tufted variety a clumping of 1. A variety with no pull of its own takes the cover’s.
  • clumpHeightVariation: how far each clump’s height strays from the cover’s height, from 0 to 0.9. 0.6 grows clumps from 0.4 to 1.6 times the height, and 0.25, the default, from 0.75 to 1.25. Each blade varies on its own beside it, so 0 does not make an even lawn. A variety’s own height scales on top of it.

A pulled blade keeps its seed’s look, its material, height and colour, and reads the ground where it lands. It stands down on ground its cover does not grow on, such as a path or a cell the scatter brush cleared, so a pulled cover’s edges follow its clumps. A cover whose clumpSize is 0 grows no clumps, so the grass refuses its pull or height variation as it draws, naming the material. Unreal’s and Unity’s grass vary a single blade’s height between a minimum and a maximum and pull nothing together.

Every clump’s centre stands anywhere in its cell of a grid a clump across, and the grid is warped by noise 2.5 and 8 clumps across, a quarter of a clump each way, so walked from 3 to 50 m the tufts sit in no rows or lattice. Its cost on the forest edge’s spawn camera, measured on 2026-10-17 on a busy machine, sat inside the runs’ own spread, about 6 ms at High and 1 ms at Low.

A pull widens the culling bounds only of the patches its cover grows in, and only by how far its tufts reach, at most 0.06 of a clump.

Near the camera the grass grows in bunches: many blades from one crown, the outer ones shorter and leaning out, as Unreal’s landscape grass draws clump meshes near the camera. A ring of bunches holds three times the blades a square metre that single blades hold at the same density, so the grass a player stands in covers the soil, and the field past the ring costs what it did. A thin cover grows fewer bunches, with soil between them.

The bunches and the single blades are one field. A bunch’s blades take the cover’s colour, its varieties, its dry patches, its wind, its backlight and a game’s grass shape exactly as the single blades past it do, and a bunch belongs to the same clumpSize clump as the single blades there. Over the last 30% of the ring the bunches give way blade by blade as the single blades grow in, so the swap draws no line.

A cover shapes its bunches with bunchBlades and bunchSize. 8 blades grow many small bunches and 32 a few large ones, with the same blades a square metre either way. bunchBlades: 0 draws single blades all the way in, as a lawn or a mown verge would. A bunch tile draws only as many blades of each bunch as the largest bunch growing under it holds. A lawn of 8 then pays for 8 where only the lawn grows, even beside a cover of 16. A level sets the ring’s reach in grassBunchFar, and a game turns the ring off at a level by setting it to 0, as the Graphics quality page says.

A bunch’s crown is 0.4 of the cover’s height across when the cover sets no bunchSize, and 4 cm at the least. A lawn of 0.10 m blades then grows crowns of 4 cm, which read as single sprouts in rows. bunchSize sets the crown in metres, such as 0.12 for that lawn. A cover that grows no bunches refuses it. A pulled bunch moves with its clump and fans out of it as the single blades past it do.

A fine lawn takes many thin blades: a blade of 0.10 m by 1.2 cm covers about a fifth of the ground a 0.24 m by 2.4 cm blade does, so it needs about five times the density for the same cover. A cover’s density reaches 16, 3,520 blades a square metre, and the schema refuses a map or a game’s materials past it as they load.

Every level draws a cover up to a density of 2 across its whole field, as it always has. A cover past 2 draws the rest only inside the level’s dense ring, out to grassDenseFar: whole to half of it, then thinning to 2 at its edge, so every cover thins over the same band. The ring reaches 8 m at Medium and 12 m at High; Low and Minimum draw none, so a dense lawn at Low draws the blades of a cover of 2 and is never refused. Ghost of Tsushima thins its grass with distance the same way, holding a tile’s blades as its tiles grow outward.

A bunch counts every blade it holds, so a bunched cover thickens to a third of 16, about 5.3; set bunchBlades: 0 for a lawn of single blades. The extra blades cost what they draw: on the forest edge at High, a lawn at 8 against 2 drew 16.9 against 11.1 million triangles, and 12.3 against 9.7 ms of GPU time on a desktop card. A camera with a long lens sees its near ground past 12 m, so a game that frames the player from afar sets a wider ring for its levels, such as grassDenseFar: 20.

Past 5 times its tallest blade from the eye, a dense blade draws from every other row of its strip and its tip: 5 triangles for 9 on a pointed blade, 6 for 10 on a squared one. That is about 3 m for a 0.2 m lawn at the default clump height variation, whose blades read the same from 3 rows as from 5. A taller cover keeps its whole strip farther out. Ghost of Tsushima drops a far blade from 15 vertices to 7.

The blades cast the sun’s shadow near the camera, so a taller tuft shades the short blades and the ground behind it, and a lawn shows small dark pools between its tufts rather than one even light. Each blade casts the shape it draws, with its clump’s pull, its bend, the wind and a game’s grass shape, so the shadow moves with the blade.

The blades draw into the sun’s near cascade only, the one whose texels are about a blade wide, out to the level’s grassShadowFar, as the Graphics quality page lists. The shadow thins out over the last 2 m of that reach, and it stops where the near cascade starts to fade, about 12.6 m with the default shadowSplit of 14 m, whichever comes first. A spot light, a point light and the far cascade draw no blade. Past the reach the field still shades its own feet in the sun’s light, by the cover’s occlusionIntensity, which eases in over the same last 2 m as the cast shadow thins, so a lawn’s light reads the same at 5 m and 20 m.

A cover’s castShadow: false turns its blades off, and a variety’s turns off its own kind, such as a lawn’s flowers. Unreal’s grass varieties and Godot’s MultiMesh cast by default, as the cover here does, and the quality level carries the cost.

To keep that cost down, the blades cast on the far dense blade’s strip of every other row, which reads the same at a shadow map’s texel. A cell denser than 4 casts the first-ranked blades of a density of 4, which spread over the ground, and a sparser cell beside it casts all of its own. The lawn of the forest edge at a density of 8 casts half its blades, which reads the same as all of them. On the forest edge at High, that lawn cast for about 1.6 ms more of the shadow draw and about 2 ms more of a desktop card’s frame, measured with other work on the machine. At Low with a 1024 cascade and 6 m, the sparser field cost nothing measurable.

Each level draws a share of the blades out to a distance, as the Graphics quality page lists them, and Low’s field stands still. Every level draws the clumps, the ramp and the varieties; a level with no sun shadow draws no backlight, and Low draws no bunches. From the level’s near fade the blades thin and widen blade by blade until the last goes, so the field has no hard edge, and the ground turns to the field’s colour over the same distances, as groundBlend says. A level that draws no grass, by a grassFar or a grassShare of 0, draws the field’s colour from the eye, and a Landscape with grass={false} keeps each material’s near blend.

The ground blends its materials, turns their textures, projects them from the side and scatters decals for every pixel, which costs more than any other draw of a scene. Most of that gives the same answer each frame, so a game can have the engine paint the ground once a texel into textures round the camera and draw the ground from them, in a few reads a pixel. Unreal’s Runtime Virtual Texture caches a landscape the same way, Unity’s terrain draws a base map past its Basemap Distance, and O3DE bakes its terrain into clipmaps.

The bake is off at every quality level until a game sets groundBakeResolution, as Unity’s and Unreal’s are. It pays where the camera stands high over the ground, as a tycoon’s or a strategy game’s does: Juice Island’s frame at High on an M2 Pro took 25 ms in place of 36 ms at 3840x2160, and 11.5 ms in place of 16.5 ms at 1920x1080. A camera low over the ground, as a character’s, bakes the strips it uncovers as it walks: Holdfast’s median frame stayed the same, and its slowest twentieth took about 3 ms more.

The bake is a clipmap: camera levels of groundBakeResolution texels a side, the finest 16 m across and each after twice the last, and a base over the whole map with its mips. Each pixel reads the finest level that holds it at its own footprint and turns to the next over the level’s outer eighth, so no ring shows. As the camera moves, a level bakes only the strip it moved into, in steps of 16 texels.

Some ground keeps its live paint:

  • The ground within groundLiveDistance of the camera, 12 m by default, where the bake holds about half the density of the materials’ own textures and a path’s decals would smear at 4K. The live triangles are sorted again each time the camera moves 2 m. A game whose camera stands high over the ground, as a tycoon’s does, sets it to 0 and draws all of it from the bake.
  • A triangle steeper than 50°, where a bake from above lays half the texels a flat metre gets, so a cliff keeps its side projection sharp.
  • A ground whose materials scroll, flip frames, ripple in the rain or draw grid lines, all of it, since each changes with the frame or the pixel.
  • A ground whose materials show a grain, all of it, since the grain changes with the eye’s distance and the kind of light.
  • A Landscape whose ground function dresses the ground, all of it, since the bake cannot know what the function reads.
  • The grass’s thinning: the bake holds the ground under a cover at both ends of the thinning, and each pixel mixes them by its distance.

A stroke, a fill or a height edit bakes its cells in the same frame. A new texture, a decal sheet or the sun turning a degree over decals bakes the ground again over a few frames, the old texels drawn meanwhile. Until the base has baked once, the ground draws live.

Each camera level takes 8 bytes a texel, 14 under a cover that turns the ground toward its field, and a map adds a level each time its size doubles past 16 m. A game sets groundBakeResolution per level in its quality.levels, a multiple of 256 up to 4096, and 0 paints the ground live, as the Graphics quality page says. Juice Island sets 2048 at High, 1536 at Medium and 1024 at Low, sizes that follow each level’s pixel ratio, and its 224 m map takes four levels and the base: 171 MiB, 96 MiB and 43 MiB.

Where a quality level draws no screen-space ambient occlusion, as Low does, the engine seats each of a map’s model, shape and object props on the ground on its own. Each prop is an ellipsoid over its bounds: a model’s from its manifest, and a map object’s from the bounds its build returns. The ground and the grass round its foot take its occlusion of the sky’s light, worked out exactly for the ellipsoid. They also take a soft shadow from the sun in the band round its foot, which a shadow map’s texels miss, as Unreal’s capsule shadows cast. The 16 props nearest the camera count each frame. A model with no bounds in the game’s manifest casts none. Where the occlusion pass draws, it seats the props instead, so the two never darken one foot twice.

defineTopLayer makes a material function that lays a layer over each face of a model turned toward the world’s up, such as moss on a rock’s top, snow or dust, so one rock reads weathered in place. Unreal’s WorldAlignedBlend gives the same mask. Register a model with it, registerModel("rock", { ...models.rock, material: defineTopLayer({ color: "#8e9634" }) }), or pass it to an entity’s or a detail level’s material. Its options:

  • color: the layer’s colour.
  • maxSlopeRadians: how far from up a face may turn and still gather the layer: π / 4, the default, tops and gentle slopes; π / 2 every face turned up at all.
  • blendRadians: the width of the layer’s edge either side of that angle, 0.12 when left out.
  • breakup: metres across the patches by which the edge wanders, 0.3 when left out; 0 follows the model’s contour.
  • roughness, 0.9, and showThrough, 0.3: the layer’s roughness, and how much of the surface’s own light and dark shows through it.
  • up: the direction the layer gathers on, such as [0, 0, -1] for moss on a north face.

The mask reads the surface’s own normal with a little of its normal map. A fine speckle and broad patches vary the colour, and the layer softens the bumps under it. The layer is fixed to the world, so a model that moves carries none of it. Give the model a texture with no moss painted on it, or its sides stay mossy. It dresses three’s standard and physical materials. A plant’s leaves take it first, foliage(topLayer(original)). Two layers stack, such as moss under snow. Under a WebGPURenderer it is off, and the console names it once.

A tree built from alpha-cut cards lights each card by its own normal, so its crown reads as a ladder of flat cards: no lit top, no shaded underside, and the back faces of a double-sided card go black under a high sun. foliage is a material function for InstancedModel and an entity’s material that draws a model’s leaves as the other engines draw foliage:

  • Both faces lit alike. Each face of a card takes its front’s normal, as Unity’s HDRP double-sided normal mode “None” does. A face seen from behind takes no specular, which would sparkle white on a backlit crown.
  • Crown normals. Each leaf’s normal turns toward its crown’s, so a crown has a lit top, a sunward side and a shaded underside, as SpeedTree’s baked crown normals give. Where the model carries no crown normals, the engine fits one to the leaf mesh’s bounds.
  • Light through the leaf. A leaf lit from behind lets light through, gathered where the eye looks toward the light, as Unreal’s two-sided foliage does. The sky’s light behind a leaf passes through too, so a crown’s shaded side takes the sky rather than going brown-black.
  • Interior darkness. A crown’s heart takes less of the sky’s light than its rim, from the model’s baked darkness or the engine’s estimate from the bounds.
  • Per-copy colour. Each copy of a model moves a little toward a dry colour and differs a little in brightness, as Unreal’s PerInstanceRandom lets a material do, so a forest of one model does not read as stamps.
  • Card edges. A card seen nearly edge-on thins and is cut away, so it shows no hard line. A far card keeps its cover as its mipmaps average its alpha down, after Ben Golus’s method.
  • Wind. The whole plant leans and swings in the look’s wind, its boughs dip and its leaves flutter, bark and leaves together, and its shadow moves with it, as Move in the wind says.
  • The camera stops at it. A bough between the follow camera and its character pulls the eye in front of it, as The camera stops at plants says.

Only cut leaves, with an alphaTest, take the leaf shading: a model’s bark wears the same function, keeps its own shading and moves with its leaves. A blended leaf turns into a cut one at half its alpha that writes depth, so it sorts and takes ambient occlusion like the rest. A material with no texture alpha keeps its own settings.

Pass it as material={foliage}. To combine it with a change of the game’s own, wrap the game’s change inside it, (original) => foliage(warm(original)), and foliage keeps that change’s shader hook. The clones it makes live as long as the page. Under a WebGPURenderer the leaf shading is off, and the console names it once.

defineFoliage makes a material function like foliage with options of its own. foliage is defineFoliage(), every option at its default:

  • crownNormalBlend: how far each leaf’s normal turns toward its crown’s, from 0, the card’s own, to 1, the crown’s. 1 by default.
  • backlight: the share of each colour of light a leaf lit from behind lets through, multiplied by the leaf’s own colour, as Godot’s backlight and Unreal’s subsurface colour are. White by default, as Unreal’s two-sided foliage passes when no colour is set; false lets none through.
  • variation: how each copy differs. color is the colour a copy’s leaves move toward, keeping their brightness, a warm yellow-brown by default. share is the most a copy moves toward it, 0.15 by default. brightness is the most a copy differs in brightness either way, 0.08 by default. false draws every copy alike.
  • wind: how the plant moves in the look’s wind, as Move in the wind says. false holds it still in any wind.

The light through a leaf peaks looking straight toward the light, at about 2.6 times what the leaf’s front reflects for white; averaged over every view, white passes two thirds of it. Every direct light takes it, the sun, a point light and a spot light, though not a rectangular area light, through that light’s shadow. Inside the sun’s shadow a crown’s heart stays dark and its rim glows. Past the shadow’s reach, or on a quality level with no sun shadow, the interior darkness keeps a whole crown from glowing.

A model whose leaves should not take the shading picks per material: (original) => original.name === "needles" ? sunlitNeedles(original) : original. In development, a leaf on a material that draws no direct light, such as a MeshBasicMaterial, throws, naming it, and a crownNormalBlend outside 0 to 1 throws.

Define it once, at module scope. The engine’s tests mount this file:

packages/engine/test/outside/SunlitFirs.tsx
import {
defineFoliage,
InstancedModel,
type InstancePlacement,
} from "@spawnite/engine";
// A row of firs whose needles glow yellow-green where the sun stands
// behind them, some trees a little drier than others. Defined once, at
// module scope: InstancedModel asks the function once for each of the
// file's materials.
const sunlitNeedles = defineFoliage({
backlight: "#d8e880",
variation: { color: "#b89a48", share: 0.25 },
});
const firs: InstancePlacement[] = Array.from({ length: 20 }, (_, index) => ({
position: [index * 4, 0, -12],
turn: index,
}));
export function SunlitFirs() {
return (
<InstancedModel
model="fir"
placements={firs}
material={sunlitNeedles}
/>
);
}

Plants stay opaque near the camera and between it and the character it follows. Instead the follow camera’s eye stops in front of a plant on its line to the character, as it stops at a solid, and eases back out once the line clears. Unreal’s spring arm and Cinemachine’s Deoccluder pull in the same way, at the colliders on their camera’s channel or layers. A plant here has no collider, so the eye reads its cards.

The copies of an InstancedModel that wears a defineFoliage material and stands taller than the knee, 0.5 m, stop the eye; a fern at the ankle does not. cameraCollision on the InstancedModel says otherwise for its copies, as the camera’s settle says. A character standing inside a bush keeps the eye outside it, since the sweep passes through what it starts in.

Every plant foliage dresses moves in the look’s one wind, the same wind the grass bends in, so a gust that crosses the grass crosses the trees. Three motions make it, as SpeedTree’s global, branch and leaf wind and Crytek’s vegetation bending do:

  • Sway: the plant leans downwind about its foot, its top most and the plant below it less, by the square of its height share, and sways about that lean, with a smaller sway across the wind.
  • Bend: a bough’s tip is pushed downwind and toward the ground at its own period, faster than its plant’s, by the model’s _WIND bough weight, or by the engine’s from the distance to the plant’s axis.
  • Flutter: a leaf’s free edge shakes along its card about 3 times a second, by millimetres, by the model’s _WIND flutter weight. A model with no _WIND does not flutter, since a shake without its weights would tear a card’s corners apart.

Each sway is a sine whose phase wanders, so each swing is a little longer or shorter than the last and a stand never repeats, as a tree in turbulence moves near its own period. The gust at the plant’s foot drives every motion: as a gust front rolls down the wind, each plant in its path leans further and sways wider, and its leaves flutter mostly then. A tall tree follows only the large fronts, which it is slow enough to lean into.

The wind option’s fields set the sway’s angle and period and the bend and flutter in metres. Their defaults scale with the plant’s height. A 21 m spruce sways once in 5.3 s and its top reaches about 0.15 m in the breeze, and its boughs move over 2.4 s by about 3 cm. By default no plant sways faster than once in 1.6 s or moves a bough faster than once in 1.2 s. These are slower than measured firs, which sway once in 4 to 4.4 s at 28 to 35 m.

Each copy sways at its own phase and each bough at its _WIND phase, so a stand of one model does not move in step. Every mesh of a model and every detail level moves by the nearest level’s size, so a copy moves as one plant across a cross-fade. The motion is in the model’s own space, so a copy scaled up moves farther with it, and the engine grows each mesh’s bounds by the most the wind can carry it, so a swaying top is never culled while it shows. Each mesh turns in its own space, so every node of a moving model scales evenly: a node that stretches one way would part its mesh from the rest, and development refuses it, naming the mesh. Apply the scale in the modelling tool, as Blender’s Apply Scale does.

Plants move as the grass does, by the player’s Plant sway setting. A shadow moves with its plant in InstancedModel, the scatter and an entity that stands still, except a leaf’s flutter, which is under a shadow map’s texel. An entity animated by its nodes sways each mesh as a plant of its own, with a still shadow. A skinned entity’s batch prepares no foliage yet, so a plant that moves stands in InstancedModel or on an entity that stands still.

A model built for the engine can carry what the engine otherwise estimates, as glTF attributes on its leaf primitives. Blender’s glTF exporter writes an attribute whose name starts with an underscore when its Attributes option is on:

  • _CROWN_NORMAL: a unit normal pointing out of the spray, tier or clump the leaf belongs to. The card keeps its own NORMAL, so a needle normal map still tilts it.
  • _OCCLUSION: the crown’s baked darkness, 1 open and 0 shut. It dims the sky’s light and the light through the leaf, and leaves the sun’s to the shadow map.
  • _WIND, on leaves and bark alike, the weights the wind moves a vertex by:
    • x: a bough’s weight, 0 where it leaves the trunk and 1 at its tip, which scales how far the wind pushes it downwind and down.
    • y: the bough’s phase, 0 to 1, one value for the whole bough.
    • z: the flutter weight, 0 where a leaf attaches and 1 at its free edge.
    • w: the stiffness, 0 limp and 1 rigid, which scales bend and flutter by one minus it.

Keep one plant to a primitive. The engine’s estimate fits one crown to each leaf primitive’s bounds, so a primitive holding several plants gets one crown for all of them.

The leaf shading costs a few shader operations a pixel and three vertex attributes, 28 bytes a vertex, written once for a leaf mesh that lacks them. It was measured on 2026-10-13 in a forest of about 5 million triangles at High, 1920 by 1080, on an RTX 4070 SUPER, over four alternating runs each. A GPU frame took 12.2 ms with every option on and 10.9 ms with crown normals, light through the leaf and per-copy colour off, inside the spread of the runs. Expect it to cost most where cut leaves cover most of the screen.

The wind costs a few vertex operations and three attributes, 44 bytes a vertex, on every mesh foliage dresses, bark included. It was measured on 2026-10-14 in the forest frame on the same card, over three alternating runs each, with the machine 21 to 59% busy with other work. A GPU frame took 7.9 ms with the wind and 8.0 ms without it at High, and 4.9 ms and 5.2 ms at Low, inside the spread of the runs. The gusting sway of the plants and the grass was measured again on 2026-10-16 on the same card, over two alternating runs each, with the machine 42 to 90% busy. A GPU frame’s median took 12.0 and 12.1 ms with the wind, and 11.5 and 11.7 ms with every plant and blade held still: about 0.45 ms, most of it the grass. Its shadow materials add three or four shader programs to the loading screen, which lifted about 0.5 s later, with its longest frame 0.3 s longer.

foliage does not turn on alpha to coverage. Under the post-processing stack, the ambient occlusion pass draws a white fringe round each edge it smooths, so a cut edge reads cleaner today.