HammeriteLightCurve
Core: lighting. Inherits Resource
What a measured light level is worth to the eye, as a gain over the level itself.
Exposure is one number for the whole picture. It can carry a room up or down, and it cannot make the dark half of a room darker than the lit half - so a bake that is physically honest is a bake with nowhere to hide in it: a candle really does put a readable amount of light on every wall of a small room, and read linearly that is a room with no shadow in it at all. This is the curve over the reading. gain is sampled at level / lit_surface_level and multiplies the light there, so a flat 1.0 is the linear picture unchanged and a dip at the left-hand end is what makes shadow shadow. It is a gain rather than a transfer for two reasons: the identity is a shape anybody can recognise at a glance, and nothing above lit_surface_level is clamped - a sunlit courtyard keeps its range instead of being flattened onto the curve's end. Two orderings that are the whole point of it: - It shapes the LIGHT, before a surface's own colour is applied. A black rug in a lit room is a lit black rug, not a patch of shadow, and what the picture calls dark is where the light is. - It is applied BEFORE the exposure. The eye then adapts to a world already shaped, rather than the shaping sliding about underneath as the eye moves - and since adaptation is one scalar for the frame it can lift a dark room without ever putting its contrast back. What it is handed is the light on a SURFACE - the irradiance a shader assembles for one texel, ambient and dynamic and static together - and not the volume reading HammeriteMap.light_level_at() answers with. The two are the same units and nothing like the same scale: measured in one room of a game built on it, the probe at chest height read 1.76 while nine surface texels in ten were under 0.07. So lit_surface_level cannot be copied from a game's stealth tuning, and the way to find it is to look - the editor's lightmap visualisation draws this exact quantity with nothing else in it. Hammerite measures and this shapes the measurement for the eye; what counts as dark enough to hide in is still the game's, over HammeriteMap.light_level_at(), and nothing here touches that reading. The picture never gets to decide what can be seen. The lift is the player's half: a brightness setting that lets them see into shadow without the shadow stopping being one. Exposure cannot do that - a gain on the whole picture puts lit walls into white long before a dark corner is readable. The lift is a power below 1 over the level under lit_surface_level, applied after the curve: black stays black, a lit surface stays as lit, the order of everything between is kept, and the most it can do is lift_exponent. What it adds can be drawn in lift_tint, so a lifted shadow reads as shadow by its colour when it no longer does by how dark it is.
Properties
| Type | Name | Default |
|---|---|---|
String | note | "" |
Curve | gain | null |
float | lit_surface_level | 0.3 |
int | resolution | 256 |
float | lift_exponent | 0.5 |
Color | lift_tint | Color(1, 1, 1, 0) |
float | lift | 0.0 |
Methods
| Returns | Method |
|---|---|
float | gain_at(light_level: float) |
float | shaped(light_level: float) |
float | lifted(fraction: float) |
ImageTexture | lut() |
void | publish() |
HammeriteLightCurve | published() static |
void | publish_none() static |
Constants
UNIFORM_LUT
const UNIFORM_LUT = &"hammerite_light_curve"
The global shader uniform the lookup texture (lut()) is published to. This and the other UNIFORM_* globals are what the shipped shaders read the curve through; enabling the plugin declares any the project has not (see HammeriteShaderGlobals).
UNIFORM_LIT_LEVEL
const UNIFORM_LIT_LEVEL = &"hammerite_light_curve_lit_surface_level"
Where lit_surface_level is published.
UNIFORM_ENABLED
const UNIFORM_ENABLED = &"hammerite_light_curve_enabled"
Where whether the curve or the lift applies at all is published.
UNIFORM_LIFT
const UNIFORM_LIFT = &"hammerite_light_curve_lift"
Where the strength of the lift, from lift and lift_exponent, is published.
UNIFORM_LIFT_TINT
const UNIFORM_LIFT_TINT = &"hammerite_light_curve_lift_tint"
Where lift_tint is published.
MIN_LIT_LEVEL
const MIN_LIT_LEVEL = 0.0001
Keeps the division that normalises a level away from zero.
Property descriptions
note
var note: String = ""
Why it is the way it is, for whoever tunes it. Never seen by a player. A field rather than a comment in the file: a .tres comment is dropped the moment Godot rewrites the resource, and what belongs here is where a number was MEASURED, which is the one thing about a tuning file nobody can work out again by looking at it.
gain
var gain: Curve = null
What each light level is multiplied by, sampled across 0..1 - flat 1.0 for the linear picture. Null means no opinion, which publishes as the curve being off rather than as a curve of zeroes: an unset resource must never be able to black a level out.
lit_surface_level
var lit_surface_level: float = 0.3
The SURFACE light level the curve's right-hand edge stands for. Anything brighter takes that same end value, so the curve shapes the range a room is lit in and leaves the sun alone. Not a number to guess at, and not one to borrow from a stealth system - see the note above about which quantity this is. Measure it: turn on the lightmap visualisation, which draws the light with no surface colour and no exposure in it, and read where a directly lit floor sits.
resolution
var resolution: int = 256
How many texels the shader's lookup gets across the range - finer than the curve editor can be drawn in, and the whole of what it costs is one row of floats.
lift_exponent
var lift_exponent: float = 0.5
The power the light below lit_surface_level is raised to at full lift. 0.5 takes a shadow at 2% of a lit surface to 14% and one at 10% to 32%.
lift_tint
var lift_tint: Color = Color(1, 1, 1, 0)
The colour the lift's added light is drawn in, as a hue - its brightness is ignored. Alpha is how much of the added light takes it rather than the light's own colour: 0 lifts shadow in the colour it already was.
lift
var lift: float = 0.0
How far the player has asked to see into shadow, 0..1. Not exported: it is a setting, not tuning, and a value saved into the resource would be every player's.
Method descriptions
gain_at()
func gain_at(light_level: float) -> float
What light_level is multiplied by. 1.0 where there is no curve to ask.
shaped()
func shaped(light_level: float) -> float
What light_level is worth once the curve and the lift have had it, in the same units it came in. The luminance of what the shader draws: the lift's tint moves its hue and not its level.
lifted()
func lifted(fraction: float) -> float
fraction of a lit surface's light, as the lift draws it. Anything at or past 1 is left alone.
lut()
func lut() -> ImageTexture
The curve as the shader reads it: one row of single-channel texels across 0..1. Each texel holds what the gain there takes AWAY (1 - gain) rather than the gain itself, so that zero means "leave the light alone". A sampler global nobody has published reads black - measured, not assumed - and an addon whose host forgot to declare this one would otherwise have multiplied every surface in the game by nothing.
publish()
func publish() -> void
Hand this curve to every shader that reads light, and keep doing so as it is edited. There is one published curve, since what it fills are global uniforms: publishing this replaces whichever was, and that one's edits no longer reach the picture.
published()
static func published() -> HammeriteLightCurve
The curve publish() last handed to the shaders, or null after publish_none().
publish_none()
static func publish_none() -> void
Put the linear picture back. What a project with no curve to publish leaves behind it.