Renderer · interface

Environment

Lighting and atmosphere for a frame. The game decides what goes in it.

Explained in Hello world.

interface Environment extends Atmosphere
import type { Environment } from '@driftengine/core';

Properties

NameTypeDescription
directionalDirVec3Dominant outdoor directional source: sun by day, moon by night.
directionalColorVec3
ambientVec3
ambientGroundoptionalVec3Ambient arriving from below. Omitted, it matches ambient and the fill is uniform.
More

A hemispheric fill is the difference between a room whose floor and ceiling are made of different things and one where they are the same colour at different brightness.

emissiveGainnumberStrength of self-illuminated geometry, once nightFactor lets it through.
nightFactornumberWhether self-illuminated geometry emits at all. Despite the name, this is not a clock.
More

It reads as a time of day and it is the emissive master switch: flat.ts multiplies every emissive term by it, so at 0 nothing in the world glows, whatever emissiveGain says and whatever a mesh declared. The name is accurate for the case it was written for, an outdoor world whose lamps come on at dusk, and it is a trap for every other one.

A daylit scene containing a lit thing must set this to 1 and live with the name. A studio with softboxes, a shop sign, a screen wall, a subway platform, a lava field: each is self-illuminated at noon. Reported from outside after an evening lost to a white cyclorama that shipped with four grey plastic rectangles where its lights should have been, because the field describing the time of day had been set correctly.

It is not renamed because it is on the public surface and a second name for one field is worse than an inaccurate one. Read it as "does emissive emit", and use emissiveGain for how strongly.

surfaceTimeoptionalnumberSeconds on the caller's clock, for the surfaces that animate — a sign's pulse, a ticker's scroll, a flipbook. The caller's rather than a clock read here, so a held clock holds every surface still and a replay replays them. 0 unless stated.
wetnessoptionalnumberHow wet the world is, 0 to 1: upward surfaces darken and polish, except materials that say they stay dry. The scene-wide counterpart of drawFilm, which stays for a puddle. 0 unless stated.
litWindowsoptionalnumberThe share of facade windows lit, 0 to 1, which a scene sets from its time of day. Windows switch on in steps by a per-window hash, so a rising share lights more windows rather than brightening the ones already lit. 0 unless stated.
lateWindowsoptionalnumberThe share of windows that stay lit however late it gets, 0 to 1, so the small hours are not a dark city. 0 unless stated.
nightEmissiveoptionalnumberEmission that only shows where the dominant light does not reach, 0 and off by default.
More

The case it exists for is a lamp field on something that turns. City lights are a property of the ground, so they are authored into the surface where the ground is. Whether they should be visible is a property of where the sun is, which the ground turns past, and nothing could say the second thing: emissive is per vertex and the light direction is per frame, and no term multiplied them.

Reported from outside with the arithmetic, on a rotating planet whose night lights were built, photographed and then deleted because they could not be made to work. With an ambient of 0.07 and a directional of 1.15, a patch bright enough to read on the night side arrived at sixteen times that on the day side, as an orange blot on a continent. The ratio is fixed by the lighting, so no choice of value separates the two.

Added to the ordinary emissive rather than replacing it, and weighted by how far the surface faces away from directionalDir, so the same mesh can carry day-side paint and night-side lamps. It is deliberately a hard cut at the terminator: softening it is what puts the lights back on the day side, which is the defect.

Independent of nightFactor, which is a fact about the world's clock. This is a fact about where a surface is pointing, and a planet has a night side at any hour.

lightViewProjReadonlyMat4World → light clip space, from computeLightMatrix.
shadowStrengthnumber0 disables shadow sampling — set to 0 whenever the map is not refreshed.
shadowDepthSpannumberLinear depth span returned by computeLightMatrix, in metres.
highlightMinVec3World-space box lit independently of the clock; 0 gain disables it.
highlightMaxVec3
highlightGainnumber
lightCountnumberActive point lights: 3 floats each for position/colour, 1 for radius.
lightPositionsFloat32Array
lightColorsFloat32Array
lightRadiiFloat32Array
lightSourceRadiiFloat32ArrayEach shaded light's emitter radius in metres. See PointLightBuffer.sourceRadii.
lightWeightsFloat32ArrayHow present each active light is, 0 to 1, from selectPointLights.
More

Shorter than MAX_POINT_LIGHTS means a consumer that packs lights itself and has nothing to say about presence; those lights are taken as fully present, which is what every consumer got before this existed.

lightDirectionsFloat32ArrayThree per active light: where a spot points, normalised. Zeroes make a light a point light.
More

Optional in spirit and required in the type, like every other member of this family: a short or absent array falls back in resolvePointLights to a direction of nothing and a cone that admits everything, which is the arithmetic every scene had before spot lights existed.

lightConeCosFloat32ArrayTwo per active light: the cosine of the inner cone angle, then of the outer.
lightFalloffExponentsoptionalFloat32ArrayOne per active light: its own falloff exponent, or 0 for the frame's. Optional: absent is 0 for every light. See PointLightSource.falloffExponent.
lightIesProfilesFloat32ArrayOne per active light: a row of the photometric atlas, or −1 for none.
lightIesAxesFloat32ArrayThree per active light: where an asymmetric profile's azimuth zero points, in world space.
More

Zeroes leave a profile on its first plane, which is what every axially symmetric fixture does anyway — so a scene that has never heard of this shades exactly as it did. See PointLightSet.lightIesAxes for why it cannot be derived from the light's direction.

lightCookiesFloat32ArrayOne per active light: a tile of the cookie atlas, or −1 for none.
lightChannelsoptionalFloat32ArrayOne per active light: the lighting channels it lights, a mask from 1 to 255. Absent, short, or a light at 1 is the default channel every surface is on too. See PointLightSet.lightChannels and SurfaceMaterial.lightChannels.
areaLightsAreaLightBuffer | nullThe rectangular emitters this frame, or null for none.
More

A buffer rather than loose arrays, unlike the point lights: those grew their arrays one at a time over years and the family is now seven wide, which is exactly the shape bindPointLights exists to stop anybody forgetting a member of. One object cannot be half-passed.

activeLightWorldIndicesInt32ArrayFor each active light slot, which world light it holds. The shadow system needs this to translate a world light into the shader slot the fragment loop is iterating.