跪拜 Guibai
← All articles
Cesium · Vue.js

A Single Fragment Shader Adds a Physically Plausible, Twinkling Night Sky to CesiumJS

By Tony费 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

GIS and digital-twin applications often need a convincing night mode that doesn't look like a cheap Instagram filter. This approach costs one draw call, runs entirely on the GPU, and gives users a real-time slider to dial in the exact atmosphere, which is far more flexible than swapping static imagery or toggling a hard-coded dark layer.

Summary

A single fragment shader, registered as a Cesium PostProcessStage, darkens the scene, masks the sky region by Y-coordinate, and scatters procedurally generated stars across the upper half of the viewport. Each star's brightness oscillates independently via a sine function keyed to a random seed, producing a scattered, breathing twinkle rather than a uniform blink. A low-saturation cool-blue base color is mixed into the darkened pixels to prevent the flat, gray-black look that comes from simple brightness reduction.

The effect is fully parametric: a Vue slider drives a `nightStrength` uniform that controls overall darkness, star visibility, and the intensity of the blue tint. Toggling the effect off removes the stage from the post-process pipeline and stops the `requestAnimationFrame` loop that advances the time uniform, so no idle GPU work remains.

The shader logic is compact enough to study in one sitting. A `smoothstep` on the Y texture coordinate confines stars to the sky, a classic `fract(sin(dot(...)))` hash seeds their positions, and a `step` threshold at 0.997 keeps the star density sparse. The result is a night sky that feels physically motivated rather than a screen-space overlay.

Takeaways
Stars are generated only on the upper half of the screen using a Y-coordinate smoothstep mask, so terrain and ground features stay star-free.
Each star's twinkle is driven by `sin(time * 3.2 + randVal * 20.0)`, giving every star an independent brightness rhythm instead of a synchronized pulse.
A cool-blue base color (`vec3(0.04, 0.06, 0.12)`) is mixed into the darkened scene to avoid the flat gray-black look of simple brightness reduction.
The `nightStrength` uniform (range 0–0.8) controls scene darkening, star visibility, and blue-tint intensity through a single slider.
Toggling the effect off removes the PostProcessStage and stops the `requestAnimationFrame` loop, so no GPU cycles are wasted when the night mode is disabled.
The star field uses a `step(0.997, randVal)` threshold on a hash function to keep star density sparse and natural-looking across a 1200x-multiplied UV grid.
Conclusions

Treating the night sky as a shader-driven post-process effect rather than a baked texture or a separate overlay keeps the implementation decoupled from the terrain and imagery layers, so it works with any base map without modification.

The `smoothstep` sky mask is the detail that sells the effect; without it, stars would appear on mountains and buildings, instantly breaking the illusion of a real night scene.

Using `requestAnimationFrame` to advance a time uniform is a lightweight alternative to Cesium's own clock system when the only thing that needs animating is a shader parameter, avoiding unnecessary scene ticks.

Concepts & terms
PostProcessStage
A CesiumJS API that applies a full-screen fragment shader to the rendered frame. It allows custom color transforms, effects, or filters without modifying the underlying 3D scene geometry.
smoothstep
A GLSL built-in function that performs Hermite interpolation between two edges. Used here to create a soft transition between the ground and sky regions so stars fade in gradually near the horizon rather than appearing at a hard cutoff.
fract(sin(dot(...))) hash
A widely used pseudo-random number generator in shaders. It takes a vector input, computes a dot product with a constant vector, applies sine, multiplies by a large number, and keeps only the fractional part to produce a seemingly random value between 0 and 1.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗