Takram's atmosphere
Contrails lit by the sunlight and skylight @takram/three-atmosphere works out at the aircraft's altitude.
A contrail is white because its ice scatters, so under
@takram/three-atmosphere
it should scatter the atmosphere's own light. Bring the sun down: the
sunlight through more air reddens and dims, the sky darkens, and the trails
follow both, at the exposure the sky meters. Change the altitude and the sky
is the one at that height, as the trails are.
The setup, the volume pass with the aerial perspective as its backdrop and
the scene's place on the earth, is on core's page.
<Contrails> finds the volume pass through its <VolumePassContext>, as
the plumes and the vapor do, and draws over the atmosphere's backdrop.
The light at the aircraft
The trails take the light as the vapor does: the sun as an irradiance,
scattered through the crystals' phase function, and the sky as a radiance,
the same from every direction. Takram's getSplitScalarIlluminance gives
both at the aircraft, from the tables the sky is drawn with.
- The sun is its
direct: the solar irradiance through the air above. - The sky is the mean of the dome above, Eh / π from its horizontal illuminance, and the sunlit ground below.
<Contrails
airframe={airframe}
look={{
sunDirection, // towards the sun, world space
sunColor: lighting.sun,
sunIntensity: 1,
skyColor: lighting.sky,
skyIntensity: 1,
}}
/>Both are already in the atmosphere's units, so the look's exposure stays
at 1 and the camera photographs the trails with the sky.
The light is read once, at the aircraft, for the whole trail. A trail sinks with its wake, at most some 100 m behind the docs' fighter, and the light a few hundred metres lower is the same to well under 1 %.
The air in front of it
A trail is kilometres long, so the air between it and the camera is worth working out. The trails get no aerial perspective of their own: at the tropopause, with takram's defaults, the air's extinction is about 3.6 × 10⁻³ per kilometre in green and 8.4 × 10⁻³ in blue (Rayleigh at e−11/8 of its sea-level 1.36 × 10⁻², almost no aerosol above its 1.2 km scale height, and a little ozone). Five kilometres of it, the far end of the example's trail, keep 98 % of the light in green and 96 % in blue. At 6 km, the bottom of the altitude slider, they keep 97 % and 93 %.
Seen from the ground, through tens of kilometres of the denser air below, the trails would need it.
import type { ContrailLook } from "@aeronautic/contrails";
import { type FC, type RefObject, useMemo, useRef, useState } from "react";
import { AtmosphereStage, useAtmosphereLighting } from "./atmosphere";
import {
ContrailsControls,
ContrailsFighter,
type Settings,
START_SETTINGS,
} from "./contrails";
// The fighter's contrails under takram's sky. The ice is lit by the sunlight
// and the skylight the atmosphere works out at the aircraft's altitude, so
// the trails redden and dim with the sun as the sky does
// Low enough to colour the light, high enough to light the trails: the sun
// round the horizon where the stage's own sun is, ahead of the camera and to
// its left, so the crystals scatter it forwards
const SUN_DEG = 20;
const SUN_AZIMUTH_DEG = 135;
/**
* The fighter and its trails, in the atmosphere's light.
* @param props The settings, and where to say what the trails are doing
* @returns The model and its trails, once there is light to draw them in
*/
const Fighter: FC<{
settings: Settings;
readout: RefObject<HTMLSpanElement | null>;
}> = ({ settings, readout }) => {
const lighting = useAtmosphereLighting();
// The sun as an irradiance and the sky as a radiance, as the trails take
// them, both already in the atmosphere's units
const look = useMemo<Partial<ContrailLook> | undefined>(
() =>
lighting
? {
sunDirection: lighting.sunDirection,
sunColor: lighting.sun,
sunIntensity: 1,
skyColor: lighting.sky,
skyIntensity: 1,
}
: undefined,
[lighting],
);
if (!look) return null;
return <ContrailsFighter settings={settings} readout={readout} look={look} />;
};
/**
* The fighter's contrails in the atmosphere, the sun and the day yours.
* @returns The example
*/
export const AtmosphereContrails: FC = () => {
const [settings, set_settings] = useState<Settings>(START_SETTINGS);
const [sun_deg, set_sun_deg] = useState(SUN_DEG);
const readout = useRef<HTMLSpanElement>(null);
const change = (next: Partial<Settings>) =>
set_settings((previous) => ({ ...previous, ...next }));
return (
<AtmosphereStage
sunElevationDeg={sun_deg}
sunAzimuthDeg={SUN_AZIMUTH_DEG}
// The sky is the one at the altitude the trails are flown at
altitudeM={settings.altitude_m}
camera={[44, 6, -76]}
target={[0, -2, 34]}
fov={16}
overlay={
<>
<ContrailsControls settings={settings} change={change}>
<label>
sun {sun_deg}°
<input
type="range"
min={-2}
max={90}
step={0.5}
value={sun_deg}
onChange={(event) => set_sun_deg(event.target.valueAsNumber)}
/>
</label>
</ContrailsControls>
<span ref={readout} className="example-note" />
</>
}
>
<Fighter settings={settings} readout={readout} />
</AtmosphereStage>
);
};
export default AtmosphereContrails;