fix(sky): blur the faint Milky Way glow wider so unresolved stars leave no clumps

The fine 0.4 degree blur kept the residual glow of unresolved stars as
mottled patches at the band's edges. Blend towards a 2 degree blur where
the coarse copy is faint (smoothstep over 0.5-8 % of the range), so the
core keeps its dust lanes and the faint sky goes smooth. Faces 85 -> 62 kB.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
German KhokhlovandClaude Opus 5.5 committed 2026-10-20 13:02:48 +04:00
1 parent e8fd1f2d01
commit 86ae5e9cf4
10 files changed
+51 -4

No files matched your search

+1 -1
View File
@@ -2,4 +2,4 @@
Yale Bright Star Catalogue, drawn as sharp points at their catalogue
positions for the scene clock, over a soft Milky Way built from ESO/S.
Brunier's panorama (CC BY 4.0). The Sun and Moon stay, the anime style fades
the Milky Way out, and the new assets weigh 319 kB (gekh, #1243).
the Milky Way out, and the new assets weigh 296 kB (gekh, #1243).
+38 -1
View File
@@ -23,7 +23,8 @@
* sit where today's sky has nothing and are far brighter than any star.
* 4. Blur with a three-pass box approximation of a Gaussian, σ = BLUR_DEG on
* the sky: rows wrap in longitude and widen by 1/cos(latitude) so the
* blur stays round towards the poles.
* blur stays round towards the poles. Faint glow blends to a second,
* FAINT_BLUR_DEG blur, so it carries no clumps of unresolved stars.
* 5. Resample into six FACE_SIZE² cube faces in the standard WebGL face
* orientation (the renderer uploads them without flipY), with the ICRF
* axes as the cube axes — +X to the March equinox, +Z to the north
@@ -43,6 +44,12 @@ import { fileURLToPath } from 'node:url';
export const FACE_SIZE = 1024;
export const BLUR_DEG = 0.4;
// Faint glow takes a much wider blur: there the panorama is grain of
// unresolved stars, which a 0.4° blur leaves as clumps. Bright parts keep the
// fine blur and their dust lanes; FAINT_RANGE (fraction of black-to-white)
// is where the two blend.
export const FAINT_BLUR_DEG = 2;
const FAINT_RANGE = [0.005, 0.08];
const WORK_WIDTH = 4096;
const OPEN_RADIUS = 3;
const WEBP_QUALITY = 96;
@@ -300,6 +307,26 @@ export function softBlack(x, knee = SOFT_KNEE) {
return x <= 0 ? 0 : (x * x) / (x + knee);
}
export function smoothstep(edge0, edge1, x) {
const t = Math.min(Math.max((x - edge0) / (edge1 - edge0), 0), 1);
return t * t * (3 - 2 * t);
}
/**
* Per pixel, keep `fine` where `weight(luminance of coarse)` is 1 and take
* `coarse` where it is 0, blending in between. The weight follows the coarse
* copy, so it is smooth itself and draws no seams along dark lanes. Writes
* into `fine`.
*/
function blendByBrightness(fine, coarse, weight) {
const [r] = fine;
for (let i = 0; i < r.length; i++) {
const w = weight((coarse[0][i] + coarse[1][i] + coarse[2][i]) / 3);
for (let c = 0; c < 3; c++)
fine[c][i] = coarse[c][i] + (fine[c][i] - coarse[c][i]) * w;
}
}
/** Value at `fraction` of the sorted luminance. */
function luminancePercentile(planes, fraction) {
const [r, g, b] = planes;
@@ -342,9 +369,19 @@ async function main() {
// After the opening: it sinks the starry sky to the dark gaps between
// stars, and a fill taken before it would stay brighter than the sky.
for (const planet of PLANETS) paintOut(planes, width, height, planet);
const wide = planes.map((plane) => Float32Array.from(plane));
for (const plane of planes) blurPlane(plane, width, height, sigmaPx);
for (const plane of wide)
blurPlane(plane, width, height, (FAINT_BLUR_DEG / 180) * height);
const black = luminancePercentile(planes, BLACK_PERCENTILE);
const white = luminancePercentile(planes, WHITE_PERCENTILE);
blendByBrightness(planes, wide, (luminance) =>
smoothstep(
FAINT_RANGE[0],
FAINT_RANGE[1],
(luminance - black) / (white - black),
),
);
let total = 0;
for (const face of CUBE_FACES) {
const pixels = Buffer.alloc(FACE_SIZE * FACE_SIZE * 3);
+3 -2
View File
@@ -8,7 +8,7 @@ whole-globe view and show compression blocks.
| File | Content | Size |
| ---- | ------- | ---- |
| `bright_stars.json` | 9,096 stars: J2000 RA/Dec, V magnitude, B−V | 235 KB |
| `milkyway_{px,nx,py,ny,pz,nz}.webp` | Milky Way glow, six 1024² cube faces, no stars | 85 KB |
| `milkyway_{px,nx,py,ny,pz,nz}.webp` | Milky Way glow, six 1024² cube faces, no stars | 62 KB |
## Stars — Yale Bright Star Catalogue
@@ -44,7 +44,8 @@ Centre.
**Changes made** (CC BY 4.0 asks for them to be stated): resampled to
4096×2048, stars removed with a 7×7 morphological opening, two planets
(Jupiter, by position) painted over with the median of the surrounding sky,
blurred (σ = 0.4°), reprojected from galactic coordinates to ICRF cube faces,
blurred (σ = 0.4°, blending to σ = 2° in the faint glow so unresolved
stars leave no clumps), reprojected from galactic coordinates to ICRF cube faces,
black level set at the median sky with a soft knee (the faint glow away
from the galactic plane fades to black), gamma-encoded and saved as
WebP. Build:
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

+9
View File
@@ -9,6 +9,7 @@ import {
cubeFaceDirection,
directionToPlateCarree,
icrfToGalactic,
smoothstep,
softBlack,
} from '../../scripts/build-milky-way.mjs';
import { raDecToUnitVector } from '../sky/stars.js';
@@ -121,3 +122,11 @@ test('the black level eases in instead of cutting the faint sky into contours',
assert.ok(softBlack(0.003) < 0.0003);
close(softBlack(1), 1 - 0.03, 1e-3);
});
test('the faint-glow blend weight eases between its edges', () => {
assert.equal(smoothstep(0.1, 0.2, 0), 0);
assert.equal(smoothstep(0.1, 0.2, 0.1), 0);
close(smoothstep(0.1, 0.2, 0.15), 0.5, 1e-9);
assert.equal(smoothstep(0.1, 0.2, 0.2), 1);
assert.equal(smoothstep(0.1, 0.2, 9), 1);
});