Film fading and ageing
How unequal dye survival, metallic-silver loss, support staining, and spatially uneven deterioration change a stored film element over time.
Visible effect
Density records survive at different rates
Film does not acquire one universal “old” look. Colour records can lose density at different rates, shifting hue as well as contrast. A black-and-white silver image can weaken through a different chemical route, while the support and residual processing products may add stain or local nonuniformity.
Fading is usually attached to the physical element and changes slowly across space. It does not sparkle from frame to frame like noise, and it should not be confused with an intentional colour grade applied uniformly to a digital copy.
Physics
Loss from the image and stain from the support are separate
In colour film, cyan, magenta, and yellow image dyes absorb different wavelength bands. If their densities decline unequally, the reproduced image develops a colour cast as well as lower contrast. The exact direction is stock-, process-, storage-, and generation-dependent; red or magenta fading is familiar, but it is not a universal law for every element.
Metallic-silver images deteriorate through different chemical mechanisms. Meanwhile the base, binders, residual chemistry, and environmental exposure can add overall stain or make deterioration spatially uneven. Heat, moisture, pollutants, light exposure, and poor processing history can all change the rate and form of damage.
Mathematics
Work in optical density, where loss and stain have opposite roles
The teaching model treats decoded linear RGB T as a proxy for scanned film transmittance and converts it to optical density D. Dye-loss vector ℓd and neutral silver loss ℓs reduce surviving image density. Base term b adds density, with a stronger blue-channel component to produce a warm stain. A static low-frequency field m introduces nonuniform ageing.
This display-referred approximation is useful for reasoning about signs: losing image density clears the element, while staining the support darkens and colours it. A calibrated restoration model requires measured film densities and known scanner transforms.
Shader
One density-domain material transform
The implementation uses one source sample per pixel. It decodes sRGB, converts transmittance to density, applies separate dye survival, neutral density loss, and base stain terms, then returns through transmittance to display encoding.
// WHAT: Approximate aged-film fading as image-density loss plus base staining.
// HOW: Build a low-frequency mottling field, convert linear transmittance to
// optical density, reduce surviving dye/silver density, add stain, and convert
// the resulting density back to transmitted light.
// WHY: Image fading clears recorded density while an aged film base can add
// density; treating both mechanisms as one RGB colour grade hides that contrast.
// These noise helpers create visual material nonuniformity. Hash constants only
// decorrelate neighbouring cells and are not chemical or grain measurements.
float fadingHash(vec2 position) {
return fract(
sin(dot(position, vec2(127.1, 311.7))) * 43758.5453123
);
}
float fadingNoise(vec2 position) {
vec2 cell = floor(position);
vec2 local = fract(position);
// Cubic interpolation removes hard square boundaries between random cells.
vec2 smoothLocal = local * local * (3.0 - 2.0 * local);
float lower = mix(
fadingHash(cell),
fadingHash(cell + vec2(1.0, 0.0)),
smoothLocal.x
);
float upper = mix(
fadingHash(cell + vec2(0.0, 1.0)),
fadingHash(cell + vec2(1.0)),
smoothLocal.x
);
return mix(lower, upper, smoothLocal.y);
}
vec3 filmFading(
vec3 linearSource,
vec2 uv,
vec3 dyeLoss,
float silverLoss,
float baseStain,
float mottle,
float mottleScale,
float effectMix
) {
// Combine broad and finer noise. The 65/35 weights and offset simply avoid
// a single-scale repeating field; they are visual approximation parameters.
float noiseFrequency = mix(
1.5,
8.0,
clamp(mottleScale, 0.0, 1.0)
);
float broadNoise = fadingNoise(uv * noiseFrequency);
float fineNoise = fadingNoise(
uv * noiseFrequency * 2.07 + vec2(11.0, -7.0)
);
float combinedNoise = broadNoise * 0.65 + fineNoise * 0.35;
float localVariation = (combinedNoise * 2.0 - 1.0)
* clamp(mottle, 0.0, 1.0);
// Optical density D=-log10(T), where T is linear transmittance. A floor keeps
// log(0) finite for completely black input pixels.
vec3 originalDensity = -log(
max(linearSource, vec3(0.0001))
) / log(10.0);
// dyeLoss is per-channel. Higher requested loss leaves less image density.
vec3 dyeSurvival = clamp(
vec3(1.0) - clamp(dyeLoss, vec3(0.0), vec3(1.0))
* max(0.55, 1.0 + localVariation * 0.35),
vec3(0.0),
vec3(1.0)
);
float silverSurvival = clamp(
1.0 - silverLoss * (1.0 + localVariation * 0.2),
0.0,
1.0
);
// A blue-heavy density vector yields a visually warm transmitted stain.
vec3 addedBaseDensity = baseStain
* vec3(0.035, 0.12, 0.38)
* (1.0 + localVariation * 0.25);
vec3 agedDensity = max(
vec3(0.0),
originalDensity * dyeSurvival * silverSurvival + addedBaseDensity
);
// Invert D=-log10(T): transmittance T=10^(-D).
vec3 agedTransmittance = pow(vec3(10.0), -agedDensity);
return mix(
linearSource,
agedTransmittance,
clamp(effectMix, 0.0, 1.0)
);
}- Source texturesRGB · one texture read per pixel
- Density-domain material ageingOne fullscreen render pass
- Aspect-fill sample
- Decode sRGB and convert transmittance to optical density
- Reduce surviving cyan, magenta, yellow, and neutral density
- Add wavelength-biased support stain
- Apply static low-frequency nonuniformity
- Return through transmittance and encode to sRGB
- Display output
Why these steps are here
- Decode first. Display-encoded values are not proportional to transmitted light.
- Enter density space. Absorbing layers combine more legibly as optical density.
- Separate each dye record. Unequal survival creates a cast instead of generic desaturation.
- Add support stain independently. New density is not the same mechanism as lost image density.
- Keep variation static. Ageing belongs to the stored element, not the display refresh.
Notes
- The Source selector includes the shared colour chart because neutral patches make channel-specific dye loss easy to diagnose.
- Presets illustrate mechanisms; they do not claim to reproduce a named stock, laboratory process, or measured archive element.
- Restoration should preserve an untouched scan and document any density, colour-balance, or stain correction separately.
- A texture or measured correction field is preferable when matching real edge fading, reel exposure, mould, or local chemical damage.
References
Library of Congress — Film Storage White Paper — preservation reference on temperature, humidity, dye fading, base decay, and storage life.
Image Permanence Institute / FilmCare — Dye fading — conservation reference explaining unequal dye fading and the resulting colour-balance and contrast changes.