HammeriteEyeAdaptation3D

Core: lighting. Inherits Node3D

Eye adaptation: what the player is standing in decides how bright the screen is.

A map's lighting spans a hundred to one - a sunlit courtyard reads 35 where a back room reads 0.3 - and no fixed exposure serves both. This walks the exposure towards what the light around the camera actually is, and publishes it as the hammerite_exposure global shader uniform (HammeriteShaderGlobals.exposure()), which every brush, decal and probe-lit material multiplies its light by.

It adapts DOWNWARDS only, by default: see adaptation_floor. An eye that recovers a dark room is the correct simulation of an eye and the wrong game.

It needs a map, or a map_3d to take one from. Without one it has nothing to read and leaves the exposure alone. It runs by itself every frame while auto_update is on; a game that has to place the update among its own turns that off and calls update_exposure().

It also publishes the light_curve, the other, fixed half of the same pipeline: exposure decides how bright the picture is, the curve decides what a light level is worth to begin with.

Properties

TypeNameDefault
floatadaptation_floor6.0
boolauto_updatetrue
Camera3Dcamera
floatcurrent_exposure
floatexposure_speed0.5
HammeriteLightCurvelight_curve
floatlocal_light_weight0.6
floatlook_distance512.0
HammeriteMapmap
HammeriteMap3Dmap_3d
floatmax_exposure3.0
floatmin_exposure0.05
floatreference_light_level6.0
floatstop_down_speed2.0
floattarget_exposure
floattarget_light_weight0.4

Methods

ReturnsMethod
voidadapt(light_level: float, delta: float)
floatexposure_for(light_level: float) const
floatsample_local_light_intensity()
floatsample_look_light_intensity()
voidupdate_exposure(delta: float)
floatwhite_level() const

Property descriptions

adaptation_floor

var adaptation_floor: float = 6.0

The dimmest light the eye will adapt TO. Anything darker is rendered as though it were this bright. This is what keeps a shadow a shadow. At its default it sits exactly at reference_light_level, so the exposure never rises above 1.0. Lower it to let dim rooms recover, and max_exposure then says how far.

auto_update

var auto_update: bool = true

Whether update_exposure() runs by itself every frame. Off for a game that calls it at a moment of its own choosing.

camera

var camera: Camera3D

The camera the light is read at and along. Null reads at this node, and skips the reading ahead.

current_exposure

var current_exposure: float

The exposure last published, moved by adapt(). Writing it does not publish it.

exposure_speed

var exposure_speed: float = 0.5

How fast the eye opens up again after bright light, as a lerp rate per second. 0.5 is about two seconds to settle.

light_curve

var light_curve: HammeriteLightCurve

What a light level is WORTH to the eye, published to every shader that lights from the bake. Exposure moves the whole picture at once, so it can carry a room up or down and can never make the dark part of one darker than the lit part. Null leaves the linear picture alone. See HammeriteLightCurve.

local_light_weight

var local_light_weight: float = 0.6

Weight of the light where the camera stands in the average the exposure adapts to.

look_distance

var look_distance: float = 512.0

How far ahead the exposure looks. An eye adapts to the room, not to the wall in front of the nose.

map

var map: HammeriteMap

The map whose probes are read, through HammeriteMap.light_level_at(). Null, with no map_3d, leaves the exposure alone.

map_3d

var map_3d: HammeriteMap3D

The HammeriteMap3D showing map, when there is one: the light is read in its space, so a map that has been moved or turned is read where it stands. Its map is the one read when map is null.

max_exposure

var max_exposure: float = 3.0

The highest exposure allowed; it only comes into it when adaptation_floor is set below reference_light_level.

min_exposure

var min_exposure: float = 0.05

How far the exposure may go down. The floor keeps a sunlit wall from being crushed to nothing.

reference_light_level

var reference_light_level: float = 6.0

The light level that renders at exposure 1.0 - what counts as a normally lit room. Measured against a bake: a sunlit hall runs about 35, a lit interior 6, a back room under 1.

stop_down_speed

var stop_down_speed: float = 2.0

How fast the eye stops down for MORE light. Faster than exposure_speed, as an eye is.

target_exposure

var target_exposure: float

Where current_exposure is heading: the exposure the light last read calls for.

target_light_weight

var target_light_weight: float = 0.4

Weight of the light where camera is looking in the average the exposure adapts to.

Method descriptions

adapt()

func adapt(light_level: float, delta: float) -> void

Walk current_exposure one step towards what light_level calls for, over delta seconds, and publish it.

exposure_for()

func exposure_for(light_level: float) -> float

What exposure a given light level calls for, before any adapting to it.

sample_local_light_intensity()

func sample_local_light_intensity() -> float

The light where the camera stands - or this node, with no camera - or -1 if there is nothing to ask. Through HammeriteMap.light_level_at().

sample_look_light_intensity()

func sample_look_light_intensity() -> float

The light where the player is LOOKING, or -1 if there is nothing to ask: sampled from the probes at the far end of the view, as far as the air the eye is in reaches.

update_exposure()

func update_exposure(delta: float) -> void

Walk the exposure towards the light around the camera, and publish it. Runs every frame by itself while auto_update is on.

white_level()

func white_level() -> float

How bright white comes out once the eye has adapted: 1, or more where min_exposure will not let it stop down as far as the light calls for. A game's bloom threshold follows this.

Hammerite 2.0 · built from 6d57ab2d, 2026-10-17