Ground material

createGroundMaterial(options) returns a portable engine THREE.MeshStandardMaterial with generated turf, soil, rock, sand, and snow shading plus optional authored albedo inputs. This is a foundation for graphics approaching photorealism, not a declaration that the visual target is complete. The engine has no dependency on an object package or external asset. Dispose the material when its terrain is removed; its generated textures and any URL-loaded albedo resources are released. Further asset inputs are specified in VEGETATION_ASSET_INPUTS.md.

const landscape = api.generateLandscape();
const ground = landscape.terrain.createMesh({
  material: api.createGroundMaterial({
    waterLevel: landscape.seaLevel,
    size: landscape.terrain.size,
    riverRise: 105,
    beach: landscape.beach
  })
});
scene.add(ground);

Configuration

OptionDefaultMeaning
waterLevel4Lake/sea level in metres.
size8000World width in metres.
snowLine640Beginning of the altitude-dependent snow transition.
riverRise0Optional river height rise per world width. Zero disables the river gradient.
riverEndZsize * .18Z coordinate where the optional river gradient reaches lake level.
beachnullExplicit designated beach footprint, using the generated landscape's {x,z,width,depth} metadata.
textures{}Optional grass, soil, rock, and/or sand albedo inputs: loaded 2D THREE.Texture, URL string, or URL.
albedoBlend1Authored-color blend from 0 (procedural color) to 1 (authored color). Height detail is unaffected.
albedoScale1Positive authored UV multiplier, either one number or a per-surface mapping. Omitted mapping entries use 1. Procedural height scale is unaffected.

The water-height estimate is waterLevel + max(0, (riverEndZ - z) / size) * riverRise. A generic material therefore has no implicit river. The generated landscape uses riverRise:105; its river geometry and material then share the same height convention. Other river profiles need caller-supplied material parameters or a different height model.

The beach footprint is centred on (beach.x, beach.z). The current generated-landscape convention uses depth along X and width along Z. The material feathers that footprint and grades its upper edge by height. The designated beach retains a sandy surface; ordinary lake/river banks use a narrower damp soil, gravel, and grass mixture with small sand patches. Wet ground has slightly lower roughness. This affects material appearance only: water geometry and terrain heights remain independent.

Optional authored color and readiness

The application supplies asset URLs; the engine never imports an object package. In an async scene factory, register material ownership before awaiting its image load:

import {GROUND_TEXTURES} from './objects/vegetation/index.js';

const material = scope.own(api.createGroundMaterial({
  waterLevel: landscape.seaLevel,
  size: landscape.terrain.size,
  riverRise: 105,
  beach: landscape.beach,
  textures: GROUND_TEXTURES,
  albedoBlend: .55,
  albedoScale: {grass: .35}
}));
const cancelGround = () => material.dispose();
if (signal?.aborted) cancelGround();
signal?.addEventListener('abort', cancelGround, {once: true});
try {
  await material.userData.ready;
} catch (error) {
  if (signal?.aborted) return {};
  throw error;
} finally {
  signal?.removeEventListener('abort', cancelGround);
}
if (signal?.aborted) return {};
scene.add(landscape.terrain.createMesh({material}));

material.userData.ready resolves to the material after all requested URL images decode. With no URLs, the material is immediately ready. readyState reports loading, ready, error, or disposed; albedos contains per-surface source/ownership/status metadata. Readiness covers asset loading, not subsequent GPU shader compilation.

URL loads use native fetch and createImageBitmap. Their textures are configured for sRGB color, repeat wrapping, linear mipmap filtering, and anisotropy 8. The material owns both the texture and decoded bitmap. Disposing during loading aborts requests and rejects readiness with AbortError; a late decoded bitmap is closed rather than reviving the material. A failed URL batch rejects readiness, cancels remaining requests, releases already decoded URL resources, and resets those URL overrides to procedural fallback. Await or handle the readiness promise to observe failure; readyError retains its diagnostic.

Supplied THREE.Texture objects are borrowed, used directly, and neither modified nor disposed. They must already contain loaded 2D image data. The caller sets their color space, wrapping, and filtering and remains responsible for their lifetime. Custom ground sampling does not apply Texture.matrix; use albedoScale for the authored UV scale.

Overrides replace RGB only. Their alpha is not treated as height or transparency. The original generated alpha channels, normal perturbation, broad material variation, riverbank/beach masks, and snow remain in use. Optional sampler code is compiled only for supplied surfaces.

material.userData.albedoBlend and material.userData.albedoScale can be changed after construction without reloading images or recompiling the material. They support same-camera comparisons and visual tuning. A scale mapping assignment replaces the mapping, with omitted entries reset to 1.

The vegetation package currently exports one project-local generated meadow texture. It was compared against procedural-only color at unchanged ground-level and woodland cameras with time frozen. Blend .55 and grass scale .35 are useful visually tuned starting values; the asset is synthetic, not a photograph or scan. See its provenance, checksum, and measured cost.

Material detail

groundTexture(kind, size=256) also remains exported. Its supported surface kinds are grass, soil, rock, and sand; field is the packed variation map. Surface-map alpha carries height rather than transparency, so consumers using those maps outside this material should account for that convention.

Verification and limitations

The associated vegetation fixture supports actual 8 km terrain and 24,000 trees through tests/vegetation-object.html?world=1, including woodland, rock, ground-level, transition, and flight-height inspection views and a current-view profiler. Add &albedo=1 to start with the optional generated meadow albedo enabled. Its checkbox, blend/scale controls, and frozen time support direct before/after comparison. The full assembled scene is checked through tests/flight-integration.html.

The material was directly inspected on the actual terrain and in the assembled flight, woodland, mountain, and beach views. The narrow mixed riverbanks replace the former continuous bright sand belt while the designated beach stays sandy. No shader warnings or errors were observed in the inspected combined scene. Parameter checks verify that the default river gradient is zero, that the generated-landscape gradient matches its river height function, and that all five generated maps are disposed.

The final combined view checks and measured profile are recorded in ground-vegetation-visual4.json, with actual module URLs and limits on interpreting the timing data.

The focused tests/ground-albedo.test.mjs suite verifies borrowed texture preservation, independent procedural heights, URL readiness/configuration, HTTP failure, pending-fetch cancellation, late-decode cleanup, failed-batch cleanup, and idempotent disposal. The real local PNG was also decoded and rendered in Chrome without shader errors.

In a frozen ground view with 101 near and 1,216 middle trees, enabling the tuned generated color left 47 draw calls and 7,714,934 submitted triangles unchanged. Median GPU time changed from 43.00 to 45.57 ms over 40 sampled frames per setting; forest update remained zero at timer precision. These timings describe a dense close view at a 2293 × 1262 drawing buffer, not general flight performance. See the asset comparison record for scope and settings. A source review also verified the flight and beach factories' abort-before-await listener registration, listener cleanup, cancellation guard, and propagation of real asset-load failures.

The subsequent alpine material comparison used the actual Mountains POI camera [1200,1200,-400] at the same high-quality drawing-buffer size, with 40 frozen frames per version. The terrain/vegetation fixture retained 33 draws and 336,064 triangles; GPU median changed from 3.265 to 4.336 ms. The alpine record separates this focused profile from assembled-shell visual verification. No terrain geometry or forest behavior was changed by the alpine material pass.

This is procedural terrain shading. It does not add displacement geometry or alter the terrain silhouette. Fine texture detail naturally disappears at long viewing distances; the coarse rock layer maintains larger material variation there. Perceived cliff shape, crest aliasing, and geometric shoreline accuracy remain terrain/water concerns. Shadows and postprocessing are owned by their engine modules.