# Rendering

How a render looks: backgrounds, lighting, and the sky behind a scene. These apply wherever a model renders, through the render functions and the low-level scene pipeline alike.

## Background

The `background` option sets the clear color behind the rendered model. Supports several formats:

```js
// Transparent (default)
background: undefined

// Hex strings (3/4/6/8 digit)
background: "#ffffff"
background: "#ffffff80"

// CSS color strings
background: "rgb(255, 255, 255)"
background: "rgba(255, 255, 255, 0.5)"
background: "hsl(210, 50%, 40%)"
background: "hsla(210, 50%, 40%, 0.5)"
background: "rebeccapurple"

// Number (0xRRGGBB), fully opaque
background: 0xffffff

// Array or object, components 0 to 1
background: [1, 1, 1, 0.5]
background: { r: 1, g: 1, b: 1, a: 0.5 }

// A THREE.Color instance, fully opaque
background: new THREE.Color(0xffffff)
```

## Lighting modes

The `lighting` option picks how faces are shaded:

| Value | Material | Behavior |
|---|---|---|
| `"item"` (default) | custom shader | The built-in Minecraft item shading, picking the flat (gui) or 3d (inventory) light config from the model's `gui_light` like vanilla. Lights are world-fixed, so faces stay consistently lit as the camera orbits. Matches the snapshot renderers |
| `"world"`, or an object | custom shader | Minecraft's in-world lighting: the game's lightmap driven by dimension, time of day, and brightness, plus its flat per-face shade constants. The right mode for blocks placed in world orientation, like structures and dioramas. An object configures it (below); the bare string is world mode with every default |
| `"scene"` | `MeshStandardMaterial` | Reacts to lights you add to the scene (`roughness: 1`, `metalness: 0`, cutout `alphaTest`, sRGB texture). Renders black until you add lights |
| `"off"` | `MeshBasicMaterial` | Unlit and flat: the texture at full brightness, ignoring all lighting |

Tints apply as per-vertex colors multiplied with the texture in the shader, like the game, in every mode; tinted and untinted faces of the same texture share one material and one atlas entry. The end portal keeps its own emissive shader.

The model element fields `shade: false` (legacy, versions before 26.3) and `shade_direction_override` (26.3 onward) redirect an element's shading: it lights as if it faced that direction (`shade: false` forces up). In `"world"` mode that means the direction's cardinal shade constant; in `"item"` mode the directional lights evaluate against the override instead of the element's real face normals. They apply to block-type models only; item-type models ignore both, like the game.

### World lighting

World mode reproduces the game's lightmap shader. Passing an object as `lighting` selects world mode and configures it:

```js
lighting: {
  dimension: "overworld",  // "the_nether", "the_end", or an override object
  daytime: "noon",         // overworld only; the other dimensions have fixed time
  brightness: 0.5,         // the in-game brightness slider: 0 Moody to 1 Bright
  light: sceneLight        // a computeSceneLight volume, or false to skip one
}
```

| Option | Default | Description |
|---|---|---|
| `dimension` | `"overworld"` | The dimension's lighting environment, matching the game's dimension type attributes (see below) |
| `daytime` | `"noon"` | Sky brightness through the day/night cycle: a tick `0`-`23999`, or a name (`"day"` 1000, `"noon"` 6000, `"sunset"` 12000, `"night"` 13000, `"midnight"` 18000, `"sunrise"` 23000). Only the overworld cycles; the nether and end have fixed time, so it has no effect there |
| `brightness` | `0.5` | The in-game brightness setting, `0` (Moody) to `1` (Bright), applied with the game's exact curve. `0.5` is the game's default |
| `light` | | A [`computeSceneLight`](#scene-lighting) volume for per-block light levels, or `false` for none. Without one, faces get full sky light and only `emission` feeds block light |
| `fog` | | The game's fog, off unless set: a render distance in chunks, or `{ distance, color, anchor }`. See [Fog](#fog) |
| `rotateShade` | `true` | The shade rotates along with the model: each face keeps its own axis shade under any [display transform](models.md#display-transforms) rotation, so a rotated single-block render shades like the block does in the world, north and south brighter than east and west. `false` leaves the shade field fixed in place instead, blending the constants across faces the display turns between axes |

Each dimension carries the game's lightmap attributes; pass an object as `dimension` to override any of them, with missing fields defaulting to the overworld's. To tweak another dimension instead, spread its preset: `dimension: { ...LIGHT_DIMENSIONS.the_nether, ambientColor: 0x000000 }`.

| Field | overworld | the_nether | the_end | Description |
|---|---|---|---|---|
| `skyLightFactor` | `"overworld"` | `0` | `0` | Sky light strength: a constant `0`-`1`, or `"overworld"` for the day/night timeline (`1.0` by day, `0.24` by night, with the game's dusk/dawn ramps) |
| `skyLightColor` | `#7A7AFF` | `#7A7AFF` | `#AC60CD` | The sky light tint. On the timeline this is the night color (days are white); as a constant factor it applies as-is |
| `ambientColor` | `#0A0A0A` | `#302821` | `#3F473F` | The additive ambient floor: this is what keeps the nether warm-dark and the end green-dark with no light around |
| `blockLightTint` | `#FFD88C` | `#FFD88C` | `#FFD88C` | The warm torchlight tint on block light |
| `cardinalLight` | `"default"` | `"nether"` | `"default"` | The per-face shade constants. `"default"` is down 0.5, up 1.0, n/s 0.8, w/e 0.6; `"nether"` raises down/up to 0.9. An object with any of `down`/`up`/`north`/`south`/`west`/`east` customizes them |
| `hasSkyLight` | `true` | `false` | `true` | Whether [`computeSceneLight`](#scene-lighting) seeds sky light from above (it takes the same `dimension` option) |
| `fogColor` | `#C0D8FF` | `#330808` | `#181318` | The base fog colour, what `fog` fades terrain into. The overworld's follows the day/night curve and the sunrise glow, like the sky's |
| `skyColor` | `#78A7FF` | `#000000` | `#000000` | The base sky colour, mixed into the fog colour at short render distances as the game does |

Colors can be a hex number, a `"#rrggbb"` string, an `[r, g, b]` array of `0`-`1` floats, or a `THREE.Color`. The presets are exported as `LIGHT_DIMENSIONS` if you need the values. The day/night curve runs in the shader from a shared uniform exposed as `scene.userData.daytime`, so a live cycle is just `scene.userData.daytime.value = tick` per frame, with no rebuild.

### Fog

Set `fog` on the world lighting config to turn on the game's fog. A number is the render distance in chunks:

```js
await createScene(assets, blocks, { lighting: { fog: 12 } })
```

An object also picks the colour and where distances are measured from:

```js
await createScene(assets, blocks, { lighting: { fog: { distance: 12, color: "#c0d8ff", anchor: [0, 64, 0] } } })
```

| Option | Default | Description |
|---|---|---|
| `distance` | | The render distance in chunks. `0` turns the fog off |
| `color` | the dimension's `fogColor` | The base fog colour. It follows the day/night curve and the sunrise glow like the sky's, and takes on the sky colour at short distances as in the game |
| `anchor` | the rendering camera | Where distances are measured from: a fixed position in world units (`[x, y, z]`, `{ x, y, z }` or a `Vector3`), or an object to follow |
| `sunriseGlow` | the camera's direction | A fixed `0` to `1` strength for the sunrise and sunset glow in the fog colour, in place of the game's, which depends on how directly the camera faces the sun |

Terrain fades into the fog colour over the last stretch before the render distance, with the overworld's faint distance haze on top, matching the game's two fog terms.

The built scene keeps a fog handle, at `group.userData.fog` on a `createScene` result and `scene.userData.fog` after `loadModel`. The same three settings are assignable on it at any time with no rebuild, and `update()` re-reads an object anchor's position after it moves. `fog` also accepts a handle, so one made by [`createSky`](#createskyassets-args) can be shared with the blocks, and `createFog(config, dimension?)` makes one on its own for scenes built elsewhere (a worker's scene rebinds to it by uniform name, like `daytime`):

```js
const fog = handle.group.userData.fog
fog.distance = 8
fog.anchor = camera
```

An element's `light_emission` (0-15) is the light level it emits: the element feeds the lightmap's block-light channel, holding bright while the rest of the model darkens, with vanilla's warm torchlight tint at partial levels. It shows wherever the model can be darker than full: in `"world"` mode at a dim `daytime` (a `light_emission: 15` face stays lit at midnight while its neighbours fall to moonlight), and in `"scene"` mode as self-illumination even with no scene lights. In the full-bright `"item"` and `"off"` modes there is nothing to stand out against, like the game's inventory, so it has no visible effect.

Blocks that glow in game without their models using `light_emission` (glowstone, lanterns, lava) get it automatically: when the renderer knows the block being rendered (`renderBlock`, or `loadModel` with `args.block`), every element's emission is floored at the block's own in-game light level, including state-dependent ones like a lit furnace or candle counts (see [`getLightEmission`](models.md#getlightemissionid-properties-resolvedefault)). So a glowstone stays bright at midnight with no model changes.

A model or blockstate can set its own floor with a top-level [`light_emission`](extending.md#model-json), which stacks with the automatic block level rather than replacing it: the glow item frame uses it on its blockstate, so the frame renders fullbright while emitting no light into the world, and packs can remodel it freely.

The render functions and `loadModel` also take an `emission` option (0-15) that floors every element the same way, and when present it replaces both the automatic block level and any model floor. `emission: 15` keeps a model bright at any `daytime` or light level without the flat look of `lighting: "off"`, covering renders the game draws at full lightmap while keeping their normal face shading, like the contents of a glow item frame; `emission: 0` turns a glowing block's automatic glow off.

Emission alone keeps a glowing block bright; it doesn't light anything around it. For scenes where torches should light up their surroundings, compute a light volume with [`computeSceneLight`](#scene-lighting) and pass it as `lighting: { light }`.

A light volume also enables the game's smooth-lighting ambient occlusion: faces darken toward corners where full-cube blocks crowd them, with vanilla's exact neighbor rules and falloff, evaluated per fragment so it stays correct across merged geometry. Matching the game, it skips emitting elements, fluids, and models with `ambientocclusion: false`; without a volume there is no occlusion data, so plain `"world"` lighting renders without it.

```js
const group = new THREE.Group()
for (const model of await parseBlockstate(assets, "stone")) {
  await loadModel(group, assets, await resolveModelData(assets, model), { lighting: "scene" })
}
scene.add(group)
scene.add(new THREE.AmbientLight(0xffffff, 0.6))
scene.add(new THREE.DirectionalLight(0xffffff, 1))
```

## Scene lighting

`"world"` lighting on its own shades every face as if it stood under open sky, with `daytime` scaling the whole scene evenly. [`computeSceneLight`](#scene-lighting) adds real per-block light: torches and other emitters glow, light falls off with distance and wraps around corners, and interiors and overhangs darken because the sky can't reach them. It runs Minecraft's flood fill over the scene's block grid and packs the result into a light volume texture the `"world"` shader samples per fragment, so the gradients are smooth and merged geometry from [`optimizeScene`](optimization.md#scene-optimization) is lit correctly with no extra draw calls.

### `computeSceneLight(blocks, options)`

| Option | Default | Description |
|---|---|---|
| `blocks` | required | The scene's blocks, each `{ id, properties?, pos: [x, y, z] }` (`{ x, y, z }` fields work too), or a flat `{ palette, raw }` run as [`createScene`](scenes.md#createsceneassets-blocks-args) takes. Cell coordinates, as in [`optimizeScene`](optimization.md#scene-optimization) placements |
| `options.assets` | required | The assets source |
| `options.version` | | Minecraft version, as in [`renderBlock`](standard-api.md#renderblockargs) |
| `options.defaults` | | Which [default blockstates](extending.md#default-blockstates) fill properties a block doesn't set, as in [`renderBlock`](standard-api.md#renderblockargs) |
| `options.dimension` | `"overworld"` | The dimension, as in [world lighting](#world-lighting): dimensions without sky light (the nether) skip the sky seeding, so their volumes carry block light only |
| `options.externalOcclusion` | | `(x, y, z) => boolean` over cell coordinates, as on [`createScene`](scenes.md#createsceneassets-blocks-args). Empty cells it reports as occluded count as full opaque blocks in the volume, so dropped buried blocks don't read as dark air at the corners around them |
| `options.onProgress` | | `(done, total)` while the scene's blocks are processed, for progress bars. The flood fill after the last call is quick |
| `options.release` | `false` | Drops `blockLight` and `skyLight`, and the texture's CPU copy once it's uploaded, as on [`optimizeScene`](optimization.md#releasing-cpu-copies). `lightAt` then throws |

Pass the result to every [`loadModel`](scenes.md#loadmodelscene-assets-model-args) call in the scene through the world lighting config:

```js
import { computeSceneLight, loadModel } from "block-model-renderer"

const blocks = [
  { id: "stone", pos: [0, 0, 0] },
  { id: "torch", pos: [0, 1, 0] }
]
const light = await computeSceneLight(blocks, { assets })
for (const block of blocks) {
  // build as usual, passing the same light to each block
  await loadModel(scene, assets, resolved, { lighting: { light } })
}
```

Light propagates accurately to the game: block light from emitters (via [`getLightEmission`](models.md#getlightemissionid-properties-resolvedefault)) and sky light from above, both spreading one level per block and blocked by the block shapes read from the models, so a slab roof shadows the room while light wraps through the open half. The game's non-model attenuation is applied on top from extracted data (`lightDampening` in [`lighting.json`](extending.md#block-data-and-colors)): leaves, fluids, and waterlogged blocks dim light one level per block, so tree canopies darken toward the trunk and deep water darkens with depth, and tinted glass blocks light outright.

Shading uses the vanilla lightmap: the dimension's ambient floor, sky and block light adding on top, the warm torchlight tint, and the brightness setting (all [configurable](#world-lighting)). In the overworld at the default full-bright `noon` most of the scene reads as lit, so emitters mainly show indoors; use a darker `daytime` (or the nether or end) to see them everywhere.

The result:

| Field | Description |
|---|---|
| `origin`, `size` | The volume's min cell corner and dimensions in cells (the scene bounds plus a one-cell border) |
| `blockLight`, `skyLight` | The raw levels (0-15), one `Uint8Array` cell each, x fastest then y then z |
| `lightAt(x, y, z)` | `{ block, sky }` levels at a cell, for your own use |
| `setOffset(position)` | Call with the world offset you move the built scene by (a `Vector3`, array, or `x, y, z` numbers), e.g. the centering translation on [`optimizeScene`](optimization.md#scene-optimization)'s group, so the shader keeps sampling the right cells. Rotation and scaling aren't supported |
| `dispose()` | Frees the light texture. Call it when you discard the scene |

The volume uploads as 2D textures of stacked slices with trilinear filtering done in the shader, so it behaves identically on the web and on Node's WebGL1 context. In browsers with WebGL2 it takes 3 bytes a cell, as a two-channel light texture and a one-channel ambient occlusion texture, so the renderer there must be WebGL2 too. Node packs both into one 4 byte texture. Lighting is static: it's computed once from the block list, so moving or removing emitters means computing a fresh volume and rebuilding the scene.

## Sky

`createSky` builds the game's sky as a three.js group: the sky gradient, the sun and moon textures from the pack, the star field, and the sunrise and sunset glow. It sits behind the scene, follows the camera, and reads the same time of day as [world lighting](#world-lighting), so one value moves the sun and relights the blocks together.

```js
import { createScene, createSky } from "block-model-renderer"

const handle = await createScene(assets, blocks, { lighting: { daytime: 13000 } })
scene.add(handle.group)

const sky = await createSky(assets, { daytime: handle.group.userData.daytime })
scene.add(sky.group)
```

### `createSky(assets, args)`

| Option | Default | Description |
|---|---|---|
| `assets` | required | The assets source, as in [`renderBlock`](standard-api.md#renderblockargs). The sky textures come from the pack stack |
| `args.dimension` | `"overworld"` | `"overworld"` for the day/night sky, `"the_end"` for the End's tiled skybox, `"the_nether"` for its fog with no sky. An object overrides the parts (`skybox`, `skyColor`, `fogColor`) |
| `args.daytime` | `"noon"` | The time of day, as in [world lighting](#world-lighting). Pass a scene's `userData.daytime` to share one uniform with the blocks |
| `args.moonPhase` | `0` | The moon phase, `0` (full moon) to `7`, in the game's order. Also settable on the handle |
| `args.angle` | `0` | Tilts the sun, moon and stars' path, in degrees off overhead; sunrise and sunset stay put on the horizon. Also settable on the handle |
| `args.skyColor` | the dimension's | The base sky color, the biome's in game. The day/night curve scales it |
| `args.fogColor` | the dimension's | The base fog color, what the sky fades to at the horizon |
| `args.distance` | `camera.far * 0.9` | How far out the sky sits in world units. The default stays inside the far plane of whichever camera renders it |
| `args.horizonFade` | `false` | Fade the sun and moon out below the horizon, over the game's 13500-14000 nightfall window and the matching angles at their other crossings. Off by default: the game keeps drawing them and lets terrain do the hiding |
| `args.version` | | The Minecraft version, which picks the sun and moon texture layout: `environment/celestial/` from 1.21.11, `environment/` before it, each falling back to the other |
| `args.tick` | `false` | Advance the day/night clock in real time, 20 ticks a second like the game, wrapping at 24000. Off, `daytime` only moves when you set it. Also settable on the handle |
| `args.fog` | | The [fog](#fog): a scene's fog handle, or the same number or object `createScene` takes. The sky fades to the fog colour within the render distance, and the fog colour takes on the sky colour at short distances, as in the game |
| `args.sunriseGlow` | the camera's direction | The horizon's share of the sunrise glow, `0` to `1`, as the fog's [`sunriseGlow`](#fog). With a fog, the sky follows the fog's setting. Also settable on the handle |

The handle:

| Field | Description |
|---|---|
| `group` | The sky group; add it to your scene. It re-centers on the camera every frame and draws before everything else |
| `daytime` | The time uniform, `{ value }`. The same object when you passed one in |
| `fog` | The fog handle, or `null`. Pass it to `createScene` as `lighting.fog` so the blocks share it |
| `moonPhase` | The moon phase, assignable |
| `angle` | The path tilt in degrees, assignable |
| `tick` | Whether the clock advances on its own, assignable |
| `sunriseGlow` | The fixed glow amount, or `null` to follow the camera, assignable. Sets the fog's when there is one |
| `dispose()` | Frees the geometry, materials, and textures, and removes the group from its parent |

## Clouds

`createClouds` builds the game's cloud layer from the pack's `environment/clouds.png`. It runs on its own clock and takes its colour from the sky's day/night curve.

```js
const clouds = await createClouds(assets, { daytime: handle.group.userData.daytime })
scene.add(clouds.group)
clouds.height = 200
```

### `createClouds(assets, args)`

| Option | Default | Description |
|---|---|---|
| `assets` | required | The assets source, as in [`renderBlock`](standard-api.md#renderblockargs). The cloud texture comes from the pack stack; without one the group draws nothing |
| `args.daytime` | `"noon"` | The time of day, as in [world lighting](#world-lighting), for the colour only. Pass a scene's `userData.daytime` to share one uniform with the blocks and sky |
| `args.anchor` | the rendering camera | What the layer is centred on: a camera or any object, followed as it moves, or a fixed position in world units (`[x, y, z]`, `{ x, y, z }` or a `Vector3`). Also settable on the handle |
| `args.origin` | `[0, 0, 0]` | The world coordinates of your scene's position `0, 0, 0`, the same as [`createScene`](scenes.md#createsceneassets-blocks-args)'s `origin`, so a scene built away from its real position gets the clouds and cloud height that sit over it in game. Also settable on the handle |
| `args.height` | `192.33` | The bottom of the layer, as a world height in blocks. Also settable on the handle |
| `args.offset` | `[0, 0]` | An extra shift of the cloud pattern in blocks, `[x, z]`, on top of `origin`, for a different arrangement over the same scene. Also settable on the handle |
| `args.time` | `0` | The cloud clock, in ticks. A save's `Time` reproduces that world's cloud positions. Also settable on the handle |
| `args.tick` | `true` | Advance the cloud clock in real time, 20 ticks a second. Also settable on the handle |
| `args.fancy` | `true` | The game's Fancy clouds. `false` draws Fast clouds, the flat sheet. Also settable on the handle |
| `args.range` | `128` | The game's cloud range option, in chunks |
| `args.color` | `#FFFFFF` | The base cloud colour, the overworld's in game. The day/night curve scales it |
| `args.alpha` | `0.8` | The layer's opacity. Also settable on the handle |

The handle:

| Field | Description |
|---|---|
| `group` | The cloud group; add it to your scene. It follows the camera |
| `daytime` | The time uniform, `{ value }`. The same object when you passed one in |
| `anchor` | The camera, object or position the layer is centred on, assignable |
| `update(anchor?)` | Re-centre the layer now, on the given camera or position, or on the current anchor. Only needed before a single `renderer.render` call when following a camera; a render loop and a position anchor don't need it, and [`renderModelScene`](scenes.md#rendermodelscenescene-camera-args) calls it for you |
| `time` | The cloud clock in ticks, assignable |
| `origin` | The world coordinates of the scene's position `0, 0, 0`, assignable |
| `height` | The bottom of the layer as a world height in blocks, assignable |
| `offset` | The extra `[x, z]` shift on top of `origin`, assignable |
| `alpha` | The layer's opacity, assignable. `0` stops drawing it |
| `tick` | Whether the clock advances on its own, assignable |
| `fancy` | Fancy or Fast clouds, assignable |
| `dispose()` | Frees the geometry and material, and removes the group from its parent |
