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 - the whole picture comes up together, so the corner the player chose to stand in comes up with it. 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, which is 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. A game that wants somewhere dark to stand needs both. They meet only in the shader - see HammeriteLightCurve for why the two cannot be measured against each other here.
Properties
Methods
| Returns | Method |
|---|---|
float | sample_local_light_intensity() |
float | sample_look_light_intensity() |
void | update_exposure(delta: float) |
void | adapt(light_level: float, delta: float) |
float | white_level() |
float | exposure_for(light_level: float) |
Property descriptions
camera
var camera: Camera3D = null
The camera the light is read at and along. Null reads at this node, and skips the reading ahead.
map
var map: HammeriteMap = null
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 = null
The node 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.
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.
light_curve
var light_curve: HammeriteLightCurve = null
What a light level is WORTH to the eye, published to every shader that lights from the bake. The other half of this node's job, and the half exposure cannot do: 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.
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, which is slow enough to be felt as an eye adapting rather than as a light being turned down.
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: looking out of the dark into the sun is a moment of glare, not two seconds of it.
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.
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.
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. Everything brighter is brought down, everything darker is lifted. Measured against a bake: a sunlit hall runs about 35, a lit interior 6, a back room under 1.
min_exposure
var min_exposure: float = 0.05
How far the exposure may go. The floor keeps a sunlit wall from being crushed to nothing; the ceiling says how much of a dark room the eye may recover, and only comes into it at all when adaptation_floor is set below reference_light_level.
max_exposure
var max_exposure: float = 3.0
The highest exposure allowed; see min_exposure.
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 - which is to say, not adapted to at all. This is what keeps a shadow a shadow. Adaptation is one scalar over the whole picture, so lifting a dark room lifts everything in it: the corner the player is standing in comes up with the rest, and the place there was to hide stops being one. Above the floor the eye still stops down, which is what a sunlit courtyard needs - so the adapting happens in the bright half, where it is wanted, and nowhere else. At its default it sits exactly at reference_light_level, so the exposure never rises above 1.0: the eye only ever stops down, and a dark room is as dark as it was baked. Lower it to let dim rooms recover, and max_exposure then says how far.
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.
current_exposure
var current_exposure: float = 1.0
The exposure last published, moved by adapt(). Writing it does not publish it.
target_exposure
var target_exposure: float = 1.0
Where current_exposure is heading: the exposure the light last read calls for.
Method descriptions
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(), which is the one place that answers this - the weighted average that used to live here read only the four switchable slots, so a room lit by anything unswitchable came back dark, and the exposure hunted for light that was already there.
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, not from the lightmap texel under the crosshair: a texel carries only the four switchable slots, so a wall lit by the sun - which bakes static - read as pitch black and dragged the exposure up in the brightest room in the map.
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.
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.
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, or a courtyard brighter than the floor allows for glows however long the player stands in it.
exposure_for()
func exposure_for(light_level: float) -> float
What exposure a given light level calls for, before any adapting to it.