A .terrain.json says how big the ground is, where its heights come from, how it is chunked, and
which texture layers blend across it. The Terrain component builds one Babylon Lite mesh per
chunk per level of detail, draws them through a single PBR material carrying the terrainSplat
surface shader — so shadows, image-based lighting and fog are the engine's — and answers every
query a game asks of the ground.
// terrain/island.terrain.json{ "format": "ignifx.terrain", "formatVersion": 1, "name": "island", "size": { "width": 512, "depth": 512, "height": 80 }, // metres; height is the full range "resolution": 513, // samples per side, 2^n + 1 "heightmap": { "source": "island.r16" }, // relative to this document "chunks": { "size": 64, "lodLevels": 4, "lodDistance": 96, "skirtDepth": 2 }, "layers": [ { "name": "sand", "albedo": "sand.png", "tiling": 10 }, { "name": "grass", "albedo": "grass.png", "normal": "grass_n.png", "tiling": 8 }, { "name": "rock", "albedo": "rock.png", "tiling": 6, "triplanar": true }, ], "splatRules": [ { "layer": "sand", "height": [0, 6] }, { "layer": "grass", "height": [4, 40] }, { "layer": "rock", "slope": [35, 90] }, ], "material": { "roughness": 0.9, "metallic": 0 },}.r16 — little-endian uint16, row-major, resolution × resolution samples, no header — is the
canonical heightmap, because it is exact in Node and in the browser alike. An 8-bit source loads
with an IGX-1603 warning and terraces visibly: 256 steps over 80 m is a 31 cm stair on every
slope. ignifx import heightmap island.png island.r16 converts one.
terrain() declares the materialPlugins rendering feature for you, and a terrain answers
heightAt/normalAt/raycast from its height field whether or not a collider exists — so
gameplay never has to wait for physics. Omit heightmap and give noise instead for a seeded
procedural island; see walk-on-terrain.
import { Camera, Light, createApp } from "@ignifx/core";import { Terrain, terrain } from "@ignifx/terrain";import type { TerrainAsset } from "@ignifx/terrain";const canvas = document.querySelector("canvas");if (!(canvas instanceof HTMLCanvasElement)) { throw new Error("ignifx renders into a <canvas> element.");}const app = await createApp({ canvas, settings: { assets: { root: "assets" }, rendering: { features: { shadows: true } } }, extensions: [terrain()],});// The document loads its heightmap, its layer textures and its generated shader as dependencies.const island = await app.assets.loadAsync<TerrainAsset>("terrain/island.terrain.json");const eye = app.world.createEntity("Main Camera");eye.transform.localPosition.set(0, 70, -160);eye.transform.lookAt({ x: 0, y: 0, z: 0 });eye.addComponent(Camera, { fov: 60, near: 0.5, far: 2000 });const sun = app.world.createEntity("Sun");sun.transform.lookAt({ x: 1, y: -1.4, z: 0.6 });sun.addComponent(Light, { type: "directional", intensity: 3 }).shadows.enabled = true;// The field is centred on the entity: local X and Z span ±size/2, heights run 0…size.height.const ground = app.world.createEntity("Island");const field = ground.addComponent(Terrain, { definition: island, lodBias: 1, receiveShadows: true });await app.start();// Queries are world-space and allocation-free; a query before the document arrives is IGX-1609.app.log.info("height at the origin:", field.heightAt(0, 0), "slope:", field.slopeAt(0, 0));app.log.info("chunks:", field.visibleChunks, "of", field.chunkCount, "draw calls:", field.drawCalls);Source: examples/recipes/load-a-heightmap-terrain/main.ts