Drawing the Real Night Sky in a BrowserSection 2 of 10
Astronomy: Where the Stars Are
Turning a place and a time into a direction in the sky, along two deliberately different paths: stars are fixed at build time, solar-system bodies are computed every frame.
Drawing the real night sky comes down to one computation: at this place, at this instant, in which direction does this object appear? This section covers the quantities that answer that question, why stars and solar-system bodies travel two completely different code paths, and where the line between library and hand-written code was drawn.
All of it lives in the In Asterarium, the modules that reference neither three.js, nor React, nor any state store: the astronomy maths and the code around it. They run without a browser, so testing them is a matter of handing in an input and checking the value that comes back.. The reason for keeping it separate is testing. Because its functions touch no outside state and need neither a browser nor The web standard that lets a page draw 3D graphics with the GPU, the chip built for rendering, straight from the browser. No plugin is involved., the layer runs directly under a Node test runner and can be checked against the figures printed in an astronomical almanac — a yearbook that tabulates computed positions for the year — and against independent implementations.
The Celestial Sphere and Two Coordinate Systems
Every object is treated as a point on the In astronomy, an imaginary sphere centred on the observer, used to describe the direction in which something appears. It carries no distance at all: it keeps the direction to an object and discards how far away it really is. In this article the word also names the container of 3D objects standing in for it, and since one scene unit is about a metre, the stars sit on a sphere of 1000 units, a kilometre in radius. — an imaginary sphere of fixed radius centred on the observer. That sphere is literally present in the scene: stars sit on a sphere of radius 1000. Scene lengths are counted in units of one metre (the convention is set out in the section "Coordinate Frames and the Orientation of the Sky"), so that is a sphere of radius about a kilometre. The number changes nothing about how the sky looks, because the radius only has to preserve each star's direction. The one condition is that it falls inside what the camera draws. That range starts at the The near clipping distance of a camera: nothing closer is drawn. The nearer it is placed, the less precision is left for distant objects, so its position decides how reliably far things sort., 2 units or two metres away. It ends at the The far clipping distance of a camera: nothing beyond it is drawn. Its ratio to the near clipping distance decides how finely distant objects can be told apart in depth, so it is set as tight as the scene allows., at 1e7 units — ten million of them, about ten thousand kilometres.
A point on that sphere can be named by many different pairs of angles. Two are used here. A coordinate system built by extending Earth's equator and rotation axis outward onto the sky. A position is given as two angles: right ascension, which works like longitude, and declination, which works like latitude. It does not turn with the Earth, so a star's values stay nearly fixed. are anchored to the Earth's rotation axis and use The longitude-like angle on the sky, measured eastward along the celestial equator, which is Earth's equator projected outward, starting from the vernal equinox where the Sun's yearly path crosses it. It is conventionally written in hours, where 24 hours is 360 degrees. and The latitude-like angle on the sky, measured from the celestial equator, which is Earth's equator projected outward: positive to the north, negative to the south, up to 90 degrees.. A coordinate system based on the observer's own horizon, giving a position as two angles: azimuth, measured round from north, and altitude, measured up from the horizon. The same star takes different values from another place or at another time. are anchored to the observer's own horizon: The angle measured along the observer's horizon eastward from due north: east is 90 degrees, south 180, west 270. (north = 0 degrees, east = 90 degrees) and The angle of an object above the observer's horizon: 0 degrees at the horizon, 90 degrees straight overhead, negative when the object is below it. (horizon = 0 degrees, zenith = +90 degrees).
What separates the two is how they behave in time. A star's right ascension and declination barely change: the Earth's rotation changes where the observer is pointing, not where the star is. Horizontal coordinates, by contrast, drift visibly within minutes. That is why star catalogues are published in equatorial coordinates, and why Asterarium consumes those numbers as they come.
Earth's rotation angle expressed as a clock, measured against the vernal equinox, the crossing of the celestial equator and the Sun's yearly path, rather than against the Sun. Because it is an angle written in units of time, 24 hours is 360 degrees and one hour is 15 degrees. In almanac terms, where a star appears is set by this value rather than by solar time, though whether a given implementation uses it directly is another question. is the bridge between them: the Earth's rotation angle relative to the The point where the Sun's yearly path crosses the celestial equator, the projection of Earth's equator onto the sky, moving from south to north. Longitude-like angles on the sky are all measured from it.. The Earth turns once every 24 hours with respect to the Sun, but once every 23 hours 56 minutes with respect to the distant stars. Because the sidereal turn is about four minutes shorter, a given star reaches The moment an object crosses the observer's meridian, the line running from due north through the zenith to due south, and reaches its highest point of the day. In the northern hemisphere it is then usually due south. about four minutes earlier each night, and the offset works its way almost all the way around the clock over a year. The pure computation layer takes Greenwich sidereal time from the library and adds the observer's longitude to get local sidereal time. The path that puts stars on screen, however, never reads that value: the A rectangular array of numbers. In 3D it holds a coordinate transform, a rotation or a displacement, as one object, and multiplying transforms together collapses a whole chain of them into a single matrix. that turns the celestial sphere is built from the date by the Sunlight bouncing off the molecules and particles of the air and spreading in every direction. Blue light scatters most, which is why the daytime sky is blue, and a low Sun, whose light crosses far more air, turns red. library, @takram/three-atmosphere. Computing how the atmosphere looks requires the orientation of the Earth itself, so that library already holds a matrix for that orientation. Sidereal time is used explicitly by the simple conversion behind the info panel, object search and aiming, and by the projector page (/projector/).
Three Effects That Bend the Apparent Position
Where an object geometrically is and where it appears to be are not quite the same. Three effects account for most of the gap, and each is handled differently depending on what is being drawn. The sizes below are given in arcsecond — A unit of angle equal to one 3600th of a degree. The full Moon is about 1800 arcseconds across. and arcminute — A unit of angle equal to one 60th of a degree, that is, 60 arcseconds., and there are two thresholds to keep apart. One is where a single point looks displaced, at roughly one arcminute, which is 60 arcseconds. The other is where a constellation looks reshaped. That judgement is made across a pattern tens of degrees wide, in which neighbouring stars joined by a constellation line sit a few degrees apart as a rough guide, so a few arcminutes still go unnoticed and it takes tens of arcminutes before the shape itself looks different.
- aberration of light — The small tilt of an incoming ray towards the observer's own direction of travel, caused by that motion. Earth's orbital motion displaces a star by up to about 20 arcseconds, roughly a 180th of a degree. — the observer's own motion tilts the incoming light slightly forward; the Earth's orbital contribution reaches about 20 arcseconds. It is included for solar-system bodies. Stars keep their catalogue One of the reference epochs that say when a set of astronomical coordinates applies: noon Universal Time on 1 January 2000. Most star catalogues give their positions in it. positions, uncorrected.
- atmospheric refraction — The bending of light by Earth's atmosphere, which makes an object appear higher than it really is. The lift is largest near the horizon, where it reaches about 34 arcminutes, or 0.57 degrees. — the atmosphere lifts objects above their true altitude, by about 34 arcminutes near the horizon, comparable to the apparent diameter of the Sun or Moon, roughly half a degree. Standard refraction is applied when converting solar-system bodies to horizontal coordinates. It is not applied to the star field.
- The change in an object's apparent direction when it is viewed from a different position. Distances to the stars are measured from annual parallax, the yearly shift caused by Earth's orbit, an angle that stays under one arcsecond, a 3600th of a degree, even for the nearest star. What shifts a drawn position instead is diurnal parallax, the shift that comes of observing from a point on Earth's surface rather than from Earth's centre, which reaches about one degree for the Moon. — the observer stands on the surface, not at the centre of the Earth. For the Moon this shifts the direction by up to about 1 degree, so latitude, longitude and elevation are all passed in and positions are computed topocentrically. For stars the effect is utterly negligible.
Aberration and refraction are left out of the star field for different reasons. Aberration's 20 arcseconds is a third of the displacement that makes a single point look moved, so it is simply invisible. Refraction is not: about one arcminute even at 45 degrees altitude, and more than thirty times that near the horizon, about 34 arcminutes. It is left out because the amount of refraction depends on altitude, and a shift that differs at every altitude cannot be expressed by the current path, which turns the whole celestial sphere through one rotation. Tens of thousands of stars reach the screen by exactly that single rotation matrix. Its content is the The slow circling of Earth's rotation axis, one turn in about 26,000 years. It carries the vernal equinox, the zero point of sky coordinates, along with it, so every star's coordinates drift as well. from the year 2000 reference to the reference of the day, followed by the Earth rotation (how it is assembled is in the section "Coordinate Frames and the Orientation of the Sky").
The door is not closed, though. The The GPU program that decides where each vertex of a shape lands on screen. It runs once per vertex. that draws the stars already computes each star's direction after the celestial-sphere rotation has been applied, and the vertical part of that direction is the sine of the altitude. It uses that value already, to make the twinkle stronger low in the sky than overhead. Deriving the altitude-dependent lift from the same number and nudging each star upward is possible in principle. It simply has not been implemented.
Solar-system bodies, computed one at a time, do get refraction. That split cuts both ways. The altitude of the Sun itself comes out closer to the real sky — the same effect that lifts a setting Sun above the horizon. But its position relative to the stars behind it does not, because the star field carries no refraction: near the horizon the Sun or Moon is drawn up to about 34 arcminutes higher than the star pattern around it. That second half is a known trade-off, left in deliberately.
Two Paths: Stars at Build Time, Solar-System Bodies Every Frame
Stars and solar-system bodies get their positions in completely different ways. There are tens of thousands of stars, and for this purpose they can be treated as not moving at all. The solar-system bodies this app draws number just nine — the Sun, the Moon, and the seven planets other than Earth: Mercury, Venus, Mars, Jupiter, Saturn, Uranus and Neptune — but they move fast and they need more precision than the stars do. That level is the arcminute, which is where the naked eye gives out. They need it because, unlike a star drawn as a point, they have a visible size: the Moon is a disc half a degree across, and how it is lit, or how closely it approaches a planet or a star, shifts visibly with a few arcminutes. That asymmetry is the design.
Star positions are settled before anything ships. They are fixed at build time, before any visitor opens the page. A build script reads the star catalogue AT-HYG (the long-standing HYG catalogue extended with observations such as Tycho photometry and Gaia parallaxes, published openly), converts each star's J2000 equatorial coordinates into a A vector whose length is exactly 1, so that it carries a direction and nothing else. It is the natural way to hold a quantity like where a star appears, which is a direction without a distance., and bakes the result into a binary file. At runtime no star is positioned individually; the whole sphere is turned by a single matrix. Whether there are ten thousand stars or a hundred thousand, the positioning work stays the same: assemble one matrix. Drawing them does scale with their number, so the count drawn is trimmed to suit the device. The In Asterarium, a preset that changes the rendering load as a group. There are four, low, medium, high and ultra: low draws the 6,000 brightest stars, medium the 8,037 of the brighter catalogue, high and ultra all 38,168, and the tier also sets the pixel-density ceiling and whether stars twinkle. The level moves up or down on its own with the measured frame rate. decides that count, and the data layout that makes trimming possible — stars ordered brightest first, so the renderer takes as many as it needs from the front — is described in the section "The Data Pipeline".
Solar-system bodies go the other way: they are recomputed frame — One image of a moving picture. Smooth motion needs roughly 60 of them a second, which leaves about 16 milliseconds to build each one. A coordinate frame is a different use of the same word.. For the Sun, the Moon and the seven planets from Mercury to Neptune, Earth excluded — nine bodies in all — a function called bodyState() returns horizontal coordinates, equatorial coordinates of date, A number for how bright an object looks: smaller is brighter, and a difference of one magnitude is a factor of about 2.512 in brightness. Under a dark sky the naked eye reaches roughly 6 to 6.5. and The apparent size of a body expressed as an angle: the angle subtended by the radius of its disc. The Sun and the Moon both measure about 0.25 degrees in radius, half a degree across, so the two are very nearly equal. The Moon-Earth distance varies, so the lunar figure drifts up and down: an eclipse with the Moon at its larger sizes is total, one with the Moon smaller leaves a ring of Sun showing and is annular. for a given place and instant; for the eight bodies other than the Sun it also returns The angle between the Sun and the observer as seen from the body itself. For the Moon, 0 degrees is full and 180 degrees is new. How much of the disc looks lit follows from this angle alone, so it cannot tell a waxing Moon from a waning one. and The fraction of a body's apparent disc that looks lit: 0 at new Moon, 0.5 at a half Moon, 1 at full.. All nine calls together take about 0.12 ms — positions only, no drawing, timed on the development machine, and the figure will differ on other hardware. What matters is the ratio, not the absolute figure: the number to weigh it against is the budget for a whole frame, drawing included: 16.7 ms at 60 frames per second. The positions take under one per cent of that, so no throttling or frame-skipping was needed.
const time = MakeTime(new Date(t))
const observer = new Observer(loc.latDeg, loc.lonDeg, loc.elevationM)
const aeBody = BODY_MAP[body]
// Topocentric equatorial coordinates, of-date, with aberration.
const eq = Equator(aeBody, time, observer, true, true)
const hor = Horizon(time, observer, eq.ra, eq.dec, 'normal')
const illum = Illumination(aeBody, time)
// eq.dist is topocentric distance in AU. Angular radius = asin(R / d).
const distKm = eq.dist * KM_PER_AU
const angularRadiusDeg = (Math.asin(RADIUS_KM[body] / distKm) * 180) / Math.PI
const state: BodyState = {
horizontal: { azDeg: hor.azimuth, altDeg: hor.altitude },
equatorialOfDate: { raHours: eq.ra, decDeg: eq.dec },
mag: illum.mag,
angularRadiusDeg,
}
// Then: phase angle and illuminated fraction, for every body but the Sun.It is short, but the three corrections named above are all in it. The observer is built from latitude, longitude and elevation, so positions come out Measured from a point on Earth's surface rather than from Earth's centre. For a body as near as the Moon the two directions differ by up to about one degree., which is the parallax correction. The two trailing true arguments ask for Coordinates referred to the equator and the vernal equinox — the crossing of the celestial equator and the Sun's yearly path — as those stand at the moment in question. Because Earth's rotation axis slowly changes direction, they drift away from J2000 coordinates, which are pinned to the year 2000, the further from that year one goes. Catalogues are written in J2000, while where something appears right now is worked out in coordinates of date. — referred to the equator and equinox of the moment, not of the year 2000 — and for aberration. The normal argument on the horizontal conversion selects the standard refraction model. Only the apparent radius is hand-computed, from the body radius and the distance.
That leaves an obvious question: the stars keep the equator and equinox of the year 2000 while the bodies use those of the moment, so do the two disagree? They do not. They arrive at the same present-day sky by two separate routes. Stars pass through the matrix that turns the whole celestial sphere, and that matrix carries them from the year 2000 reference to the reference of the day. Solar-system bodies never touch that matrix at all: the horizontal coordinates bodyState() returns are converted straight into a direction vector in The coordinate system that places objects in Asterarium's 3D scene: one unit is about a metre, the origin is the observer, and the axis convention is called NUE, for North, Up and East. Distances here are drawing conveniences, not real ones: stars go on a sphere of radius 1000 (about 1 km), and the Sun, Moon and planets sit outside it at two million (about 2,000 km). The layer of air the sky model draws is some 60,000 units, or 60 km, thick.. Horizontal coordinates are referred to the sky of the moment already, so nothing has to be added.
The matrix itself comes from the atmospheric scattering library, built from the date. The atmosphere calculation needs the orientation of the Earth, so the library holds that matrix anyway and simply exposes it. Its content, as noted above, is the precession from the year 2000 reference to the reference of the day, followed by the Earth rotation. The precession amounts to about 0.4 degrees as of 2026, enough to move a constellation by an amount the naked eye can pick out, so it matters that the matrix carries it.
| Objects | When position is fixed | Source of position | Corrections applied |
|---|---|---|---|
| Stars | Once, at build time | Catalogue J2000 equatorial coordinates | No proper motion, aberration or refraction, parallax ignored; precession absorbed by the scene rotation matrix |
| Sun, Moon, planets | Every frame | Library computation for that instant | Aberration, refraction, topocentric parallax |
A star's slow drift across the sky from year to year. Even Barnard's Star, the fastest of any star in the sky, moves only about 10 arcseconds a year, a 360th of a degree. Barnard's Star is itself magnitude 9.5, fainter than the 8th-magnitude cut of the catalogue this article ships, so it is not among the stars drawn. is left out of the star field for the same reason: at this scale it is invisible. Most stars move less than 0.1 arcseconds per year, so 26 years past the catalogue's year 2000 The reference moment that says when a set of coordinates or orbital values applies. J2000 is the one most widely used for star positions., as of 2026, they have shifted by a few arcseconds. The slice taken from AT-HYG is about 38,000 stars brighter than magnitude 8; it reaches to 8 because that is the faintest magnitude actually drawn, 6.8, plus 1.2 magnitudes of headroom. (How the catalogue is split into two tiers, and which settings draw how deep, is covered in the section "The Data Pipeline".) Within that slice the fastest is Groombridge 1830 at about 7.1 arcseconds per year, and among the bright stars Arcturus moves about 2.3 arcseconds per year. Over 26 years that comes to roughly 3 arcminutes and 1 arcminute — nothing that changes the shape of a constellation spanning tens of degrees.
The same judgement flips on a page with a different purpose. The interstellar flight page (/starflight/) is about proper motion, so it builds a separate catalogue carrying measured proper motion and radial velocity and extrapolates 100,000 years in either direction. Whether an effect is negligible is not a property of the star; it is a property of what the page is trying to show.
What Was Delegated, and What Was Not
A single engine, astronomy-engine, provides every body position. Three properties settled the choice. It is MIT-licensed, so it can be shipped with the site. It is self-contained in A formula that approximates where a body is as a sum of polynomials in time and trigonometric terms. The formula itself yields the answer, so no precomputed table of positions has to be loaded., so there is no precomputed table of positions to download. And the accuracy its authors publish for solar-system bodies is around one arcminute, roughly the limit of the naked eye. Of the three, the first two are what fit a site with no server-side code: everything runs inside the browser and adds no extra download. Accuracy has nothing to do with servers; it is judged on a different scale entirely, whether it suffices for a sky meant to match what the eye sees. That said, when the accuracy needed and the accuracy published are both around an arcminute, the margin is thin. So the eclipse page (/eclipse/), where an arcminute shows up directly in the picture, does not use these general-purpose positions at all: it calls the library's dedicated eclipse search to get the contact times for a chosen place. That runs once when the place is picked, not once per frame.
The library supplies topocentric right ascension and declination in coordinates of date, the conversion to horizontal coordinates with refraction, magnitude, phase angle and illuminated fraction, sidereal time, rise/set and The stretch of time when the Sun is below the horizon but the sky is still lit. It deepens as the Sun sinks, so an evening runs civil twilight, down to 6 degrees below the horizon, then nautical to 12, then astronomical to 18. A morning takes the same stages in reverse, from astronomical through nautical and civil to sunrise. searches, lunar phase and lunar The slight rocking of the Moon as seen from Earth. Because of it, about 59 per cent of the lunar surface becomes visible over time.. What is hand-written is a thin layer above it: angle-to-unit-vector conversions, the position angle of the Moon's The lit edge of a crescent or gibbous body. On the sky it faces whichever side the Sun is on. Its position angle states that direction as a number: the bearing of the middle of the lit edge, measured from celestial north towards the east., the assembly of a day's worth of events, and a low-precision horizontal conversion.
That low-precision path exists because it answers a different question. Info-panel readouts, object search and the aiming of the camera at a chosen object all deal with stars and clusters, which are effectively fixed, so the standard rotation from equatorial to horizontal coordinates is enough for an altitude and an azimuth. That rotation is fixed by the observer's latitude and the local sidereal time alone; no refraction, no aberration. Solar-system bodies never take that path; they always go through bodyState(). A simple horizontal conversion for the fixed stars and bodyState() for the solar-system bodies sit side by side in the codebase. The rule about which one serves which purpose therefore has to be settled, or the two get mixed up later.
Magnitude, the Moon, and Twilight
Stellar brightness is expressed as magnitude: smaller numbers are brighter, and a difference of five magnitudes is defined as exactly a factor of 100 in flux. One magnitude is therefore the fifth root of that, about 2.512 — the Pogson ratio. Turning the catalogue's magnitudes into a linear intensity the renderer can use takes a single expression, the return line of the function below. In words: each step of one magnitude divides the intensity by about 2.512, and the reference magnitude comes out at exactly 1. Which magnitude serves as that reference is chosen by the caller. The star A small program that runs on the GPU, the chip built for rendering, to compute where a vertex goes or what colour a pixel takes. It runs once for every vertex or every pixel it is handed. uses the same formula with 6.5 as its reference, putting a star at roughly the naked-eye limit under a dark sky at intensity 1.
const POGSON = Math.pow(100, 1 / 5) // 2.511886431509…
export function magToIntensity(mag: number, refMag = 0): number {
return Math.pow(POGSON, refMag - mag)
}For the Moon, beyond phase angle and illuminated fraction, two more quantities are computed: the position angle of the bright limb and libration. Which way the lit side of a crescent points on screen is decided by that bright-limb position angle, which follows the classical formula from the equatorial coordinates of Sun and Moon. Phase angle, however, is never used to decide waxing versus waning: it is 0 near full moon and 180 near new moon, and it reads about 90 at both first and last quarter, so the way out and the way back cannot be told apart. The difference in An angle measured eastward along the ecliptic, the Sun's yearly path across the sky, starting from the vernal equinox where that path crosses the celestial equator. It is the natural longitude for the Moon and the planets. between Moon and Sun is used instead — 0 degrees at new moon, 180 at full, 360 at the next new moon, increasing in one direction throughout.
Twilight is split into three stages by solar altitude: civil at −6 degrees, nautical at −12, astronomical at −18. Alongside sunrise, sunset, moonrise and moonset for the day, the routine that assembles a day of events searches for six twilight instants — the evening crossing of each threshold on the way down, and the morning crossing on the way up. How dark the sky actually looks is decided by the atmospheric scattering; the question of when things happen belongs here.
At high latitudes there are days when the Sun never sets and days when it never rises. On such a day the event simply has no time. The pure computation layer refuses to paper over that with a zero or an end-of-day timestamp: the field is left undefined, so the caller can see that the event did not occur. The display therefore holds up under a midnight sun or a polar night. The discipline is to never invent a value that does not exist.
Every decision in this section answers the same question: how much accuracy is needed, and how often? Differences the eye cannot resolve are not computed at all, and even a visible one is skipped when the drawing path has no room for it. That line is what leaves enough room to draw the sky sixty times a second in a browser, from a static page with no server-side code behind it.