@geoblocks/cesium-post-process 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +235 -2
  3. package/analog-video.js +138 -0
  4. package/blur.js +39 -0
  5. package/cesium-post-process.js +23 -0
  6. package/color-isolation.js +69 -0
  7. package/depth-test.js +37 -0
  8. package/digital-video.js +108 -0
  9. package/drone-display.js +170 -0
  10. package/focus.js +25 -0
  11. package/frame-clock.js +61 -0
  12. package/height.js +23 -0
  13. package/infrared.js +40 -0
  14. package/jello.js +146 -0
  15. package/lens-distortion.js +68 -0
  16. package/lightning.js +321 -0
  17. package/motion-blur.js +117 -0
  18. package/package.json +49 -4
  19. package/precipitation.js +493 -0
  20. package/shaders/Alpenglow.js +2 -0
  21. package/shaders/AnalogVideo.js +2 -0
  22. package/shaders/Beam.js +2 -0
  23. package/shaders/CloudsBlur.js +2 -0
  24. package/shaders/CloudsComposite.js +2 -0
  25. package/shaders/CloudsDensity.js +2 -0
  26. package/shaders/CloudsDepth.js +2 -0
  27. package/shaders/CloudsMarch.js +2 -0
  28. package/shaders/CloudsResolve.js +2 -0
  29. package/shaders/CloudsShadow.js +2 -0
  30. package/shaders/CloudsShadowMap.js +2 -0
  31. package/shaders/CloudsSlab.js +2 -0
  32. package/shaders/ColorIsolation.js +2 -0
  33. package/shaders/DepthEdges.js +2 -0
  34. package/shaders/DigitalVideo.js +2 -0
  35. package/shaders/DroneDisplay.js +2 -0
  36. package/shaders/EyeFromDepth.js +2 -0
  37. package/shaders/Filmic.js +2 -0
  38. package/shaders/Flashlight.js +2 -0
  39. package/shaders/Fog.js +2 -0
  40. package/shaders/Frame.js +2 -0
  41. package/shaders/Hash.js +2 -0
  42. package/shaders/Height.js +2 -0
  43. package/shaders/Hsl.js +2 -0
  44. package/shaders/Infrared.js +2 -0
  45. package/shaders/Jello.js +2 -0
  46. package/shaders/LensDistortion.js +2 -0
  47. package/shaders/Lightning.js +2 -0
  48. package/shaders/MotionBlur.js +2 -0
  49. package/shaders/MotionBlurVelocity.js +2 -0
  50. package/shaders/Noise.js +2 -0
  51. package/shaders/Normal.js +2 -0
  52. package/shaders/Phase.js +2 -0
  53. package/shaders/Precipitation.js +2 -0
  54. package/shaders/PrecipitationShaftBlur.js +2 -0
  55. package/shaders/PrecipitationShafts.js +2 -0
  56. package/shaders/PrecipitationWind.js +2 -0
  57. package/shaders/RainHaze.js +2 -0
  58. package/shaders/Shockwave.js +2 -0
  59. package/shaders/SkyTable.js +2 -0
  60. package/shaders/SnowLine.js +2 -0
  61. package/shaders/SpeedLines.js +2 -0
  62. package/shaders/Spotlight.js +2 -0
  63. package/shaders/SpotlightBeam.js +2 -0
  64. package/shaders/SpotlightBeamBlur.js +2 -0
  65. package/shaders/SpotlightCone.js +2 -0
  66. package/shaders/Super8.js +2 -0
  67. package/shaders/Technicolor.js +2 -0
  68. package/shaders/TiltShift.js +2 -0
  69. package/shaders/Toy.js +2 -0
  70. package/shaders/Trails.js +2 -0
  71. package/shaders/ValleyFog.js +2 -0
  72. package/shaders/WeatherMap.js +2 -0
  73. package/shaders/WhiteModel.js +2 -0
  74. package/snow-line.js +108 -0
  75. package/speed-lines.js +71 -0
  76. package/spotlight.js +193 -0
  77. package/strikes.js +30 -0
  78. package/super8.js +164 -0
  79. package/technicolor.js +56 -0
  80. package/tilt-shift.js +124 -0
  81. package/toy.js +192 -0
  82. package/valley-fog.js +105 -0
  83. package/white-model.js +105 -0
package/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2020-present, camptocamp SA
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md CHANGED
@@ -1,3 +1,236 @@
1
- # Temporary Holding Version
1
+ # Cesium Post Process
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Screen-space effects for any CesiumJS scene, each one a post-process stage that exists only while the effect is active.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm i --save @geoblocks/cesium-post-process
9
+ ```
10
+
11
+ ## Demo
12
+
13
+ [Demo](https://geoblocks.github.io/cesium-helpers/cesium-post-process.html)
14
+
15
+ ## Usage
16
+
17
+ Each effect is inactive after construction. Every option is also a property, applied live.
18
+
19
+ ```javascript
20
+ import {Color, Rectangle} from '@cesium/engine';
21
+ import {AnalogVideo, Clouds, ColorIsolation, DigitalVideo, DroneDisplay, Infrared, Jello, LensDistortion, Lightning, MotionBlur, Precipitation, SnowLine, SpeedLines, Spotlight, Super8, Technicolor, TiltShift, Toy, ValleyFog, WhiteModel} from '@geoblocks/cesium-post-process';
22
+
23
+ const snowLine = new SnowLine(viewer, {
24
+ altitude: 2000, // meters above the ellipsoid where the snow is half covering
25
+ transition: 200, // height of the band over which the snow thins out, in meters
26
+ maxSlope: 50, // steepest slope that holds snow, in degrees
27
+ coverage: 1, // opacity of the snow, 0 to 1
28
+ });
29
+ snowLine.active = true;
30
+
31
+ const valleyFog = new ValleyFog(viewer, {
32
+ top: 1500, // altitude of the top of the fog, meters above the ellipsoid
33
+ density: 0.005, // per meter, about 1 / visibility
34
+ softness: 100, // height of the band under the top over which the fog thins out, in meters
35
+ color: new Color(0.85, 0.88, 0.92, 1),
36
+ });
37
+ valleyFog.active = true;
38
+
39
+ // what the focus effects look at: a position, a function returning one (called every frame, for a
40
+ // moving target), or undefined for what is in the middle of the screen
41
+ const focus = () => marker.position;
42
+
43
+ const spotlight = new Spotlight(viewer, {
44
+ focus,
45
+ power: 1, // brightness of the light, 1 for the searchlight: the pool and the beam scale with it
46
+ radius: 200, // radius of the pool of light on flat ground, in meters
47
+ softness: 0.5, // width of the penumbra, as a fraction of the radius
48
+ darkness: 0.8, // darkening and desaturation outside the light, 0 to 1
49
+ beam: 0.25, // brightness of the beam in the air, 0 to 1
50
+ beamAnisotropy: 0.4, // Henyey-Greenstein asymmetry of the haze, 0 to 1: brighter looking toward the light
51
+ });
52
+ spotlight.active = true;
53
+
54
+ const precipitation = new Precipitation(viewer, {
55
+ intensity: 0.5, // how hard it rains or snows: share of the drops or flakes shown, their opacity and the haze, 0 to 1
56
+ snow: 0, // share of the precipitation that is snow: 0 rain, 1 snow, sleet in between
57
+ wind: 10, // tilt of the fall from the vertical, toward the east, in degrees
58
+ speed: 1, // speed of the fall, 1 at its terminal velocity: 9 m/s for the drops, 1 m/s for the flakes
59
+ cloudBase: 2500, // altitude of the cloud base, in meters above the ellipsoid: the precipitation falls below it
60
+ map: { // where it rains, or undefined for everywhere: an image whose red channel is the local intensity,
61
+ image: canvas, // 0 to 1, north up over a rectangle of longitudes and latitudes, 0 at its edges
62
+ rectangle: Rectangle.fromDegrees(5.96, 45.82, 10.49, 47.81),
63
+ },
64
+ });
65
+ precipitation.active = true;
66
+ // the rain turns to snow, animatable
67
+ precipitation.snow = 1;
68
+
69
+ const clouds = new Clouds(viewer, {
70
+ map: precipitation.map, // where it rains, as Precipitation's map; undefined for no clouds
71
+ cloudBase: 2500, // altitude of the cloud base, in meters above the ellipsoid
72
+ cloudTop: 9000, // altitude of the highest tops, over the heaviest rain; 1500 m thick over drizzle
73
+ density: 1, // scale of the clouds' opacity
74
+ });
75
+ clouds.active = true;
76
+ // the map's intensity at the camera when it is inside the clouds, 0 to 1
77
+ clouds.localDensity;
78
+
79
+ const lightning = new Lightning(viewer, {
80
+ intensity: 0.5, // how active the storm is: strikes at random, up to one every 2.5 seconds at 1
81
+ cloudBase: 2500, // altitude of the cloud base, in meters above the ellipsoid: the channels start there
82
+ radius: 5000, // how far from the camera the strikes land, in meters
83
+ inFront: false, // land the strikes within 30 degrees of where the camera looks, not all around it
84
+ map: precipitation.map, // where it rains, as Precipitation's map: the strikes land on heavy rain, around what the camera sees
85
+ });
86
+ lightning.active = true;
87
+ // the foot of each new strike on the ground, to play its thunder for example
88
+ lightning.strikeEvent.addEventListener((ground) => console.log(ground));
89
+
90
+ const toy = new Toy(viewer, {
91
+ light: 0.22, // how much brighter a convex edge gets, the depth bending outward
92
+ shade: 0.18, // how much darker a concave one
93
+ bevel: 0, // the edges' bevel in meters, their light fading over it close up, as molded plastic's; 0 for a pixel
94
+ outline: 0.4, // how much darker the silhouettes' outline, a pixel wide; 0 for none
95
+ jump: 20, // how far the depth jumps, in pixels' sizes, to be a silhouette, not a crease; lower close up, a chair's foot not lit
96
+ saturation: 1.04, // the colors' saturation, 1 to keep it
97
+ warm: [1.03, 1, 0.95], // a factor each of red, green and blue
98
+ sky: [Color.fromCssColorString('#fbf1e4'), Color.fromCssColorString('#e9d3bc')], // a gradient for the sky, top to bottom; omit to keep it
99
+ });
100
+ toy.active = true;
101
+
102
+ // or, in Toy's place, an architect's white model: a Toy with another grading, its options all taken
103
+ const whiteModel = new WhiteModel(viewer, {
104
+ saturation: 0, // the colors' saturation: 0 for white, above it an accent's hue kept
105
+ values: [0.35, 0.97], // the darkest and the lightest values, the scene's black and white pressed between them
106
+ poche: 0.1, // the value under which a color keeps it, a section's solid black; 0 to press every color
107
+ warm: [1, 0.995, 0.985], // the plaster's tint
108
+ occlusion: 0.35, // how much darker the surfaces get where they meet, in a corner or at a wall's foot; 0 for none
109
+ radius: 1.5, // how wide the corners' shadow is, in meters, 48 pixels at the most
110
+ outline: 0.25, // a fine outline
111
+ sky: [Color.fromCssColorString('#dcdedd'), Color.fromCssColorString('#f3f3f1')], // a pale gray gradient, lighter toward the horizon
112
+ });
113
+ whiteModel.active = true;
114
+
115
+ const tiltShift = new TiltShift(viewer, {
116
+ focus,
117
+ range: 0.3, // width of the sharp band, in factors of two of the focal distance (an orthographic camera: shares of the view's height)
118
+ blur: 4, // blur away from the focus, the sigma of Cesium's blur stage
119
+ saturation: 0.3, // color and contrast boost of miniature photographs, 0 to 1
120
+ });
121
+ tiltShift.active = true;
122
+
123
+ const motionBlur = new MotionBlur(viewer, {
124
+ strength: 1, // fraction of the exposure, 0 to 1
125
+ exposure: 1 / 24, // exposure time, in seconds: 1/48 is the 180 degree shutter of film
126
+ });
127
+ motionBlur.active = true;
128
+
129
+ const speedLines = new SpeedLines(viewer, {
130
+ focus,
131
+ strength: 1, // zoom blur toward the focus and streaks radiating from it, 0 to 1
132
+ });
133
+ speedLines.active = true;
134
+
135
+ // the colors
136
+ const colorIsolation = new ColorIsolation(viewer, {
137
+ hue: 0, // hue kept in color, in degrees: 0 red, 60 yellow, 120 green, 180 cyan, 240 blue, 300 magenta
138
+ range: 60, // half width of the hues kept, in degrees
139
+ strength: 1, // how gray the rest turns, 0 to 1
140
+ });
141
+ colorIsolation.active = true;
142
+
143
+ const infrared = new Infrared(viewer, {
144
+ strength: 1, // black and white infrared film: bright foliage, dark sky and water; 0 to 1
145
+ });
146
+ infrared.active = true;
147
+
148
+ // the film looks: one or the other, or Technicolor under Super 8
149
+ const technicolor = new Technicolor(viewer, {
150
+ strength: 1, // how far from the original colors, 0 to 1
151
+ aspectRatio: 0, // width over height of the visible frame, black bars around it; 1.37 is the Academy ratio; 0 for none
152
+ });
153
+ technicolor.active = true;
154
+
155
+ const super8 = new Super8(viewer, {
156
+ fade: 0.5, // faded colors: less saturation, lifted blacks, a warm cast; 0 to 1
157
+ halation: 0.5, // warm glow bleeding around the highlights, 0 to 1
158
+ grain: 0.15, // film grain, 0 to 1
159
+ weave: 1.5, // drift of the picture in the gate, in CSS pixels
160
+ flicker: 0.08, // change of brightness from frame to frame, 0 to 1
161
+ lightLeaks: 0.5, // warm light leaks along the edge, 0 to 1
162
+ aspectRatio: 4 / 3, // width over height of the frame, with rounded corners; 0 for the whole canvas
163
+ });
164
+ super8.active = true;
165
+
166
+ // the drone camera
167
+ const lensDistortion = new LensDistortion(viewer, {
168
+ distortion: 0.5, // barrel distortion of a wide-angle lens, 0 to 1
169
+ vignette: 0.5, // darkening of the corners, 0 to 1
170
+ });
171
+ lensDistortion.active = true;
172
+
173
+ const jello = new Jello(viewer, {
174
+ amount: 0.1, // shake of the camera, whose rolling shutter makes straight edges wobble, 0 to 1
175
+ frequency: 30, // vibration, in Hz
176
+ });
177
+ jello.active = true;
178
+
179
+ const droneDisplay = new DroneDisplay(viewer, {
180
+ opacity: 1, // ArduPilot's HD OSD: horizon, mode, heading, altitude, speeds, battery, link, throttle; 0 to 1
181
+ });
182
+ droneDisplay.active = true;
183
+ droneDisplay.rxLoss = true; // failsafe while the control link is lost: RTL, and FAILSAFE blinking
184
+
185
+ const analogVideo = new AnalogVideo(viewer, {
186
+ noise: 0.5, // grain, 0 to 1
187
+ interference: 0.15, // how weak the signal is: streaks, lost color, a washed-out picture, sync tears; 1 for no signal, the receiver's snow
188
+ });
189
+ analogVideo.active = true;
190
+
191
+ // or, instead of the analog video
192
+ const digitalVideo = new DigitalVideo(viewer, {
193
+ breakup: 0.5, // how often and how badly the signal breaks up into macroblocks, 0 to 1
194
+ });
195
+ digitalVideo.active = true;
196
+ super8.grain = 0.3;
197
+ super8.destroy();
198
+ ```
199
+
200
+ ### Shader chunk
201
+
202
+ `EyeFromDepth` is the GLSL chunk the depth-based effects prepend to their fragment shaders: `vec4 eyeAt(sampler2D depthTexture, vec2 uv)` gives a pixel's eye coordinates from the depth texture, with `w` 0 for the sky, with or without a logarithmic depth buffer. It is exported for effects of your own, and for `@geoblocks/cesium-flyto`'s depth of field.
203
+
204
+ ### Order of the effects
205
+
206
+ Cesium runs the stages in the order they are added and cannot insert one elsewhere. Activate the effects in this order: `SnowLine`, `ValleyFog`, `Spotlight`, `Clouds`, `Precipitation`, `Lightning`, `Toy` or `WhiteModel`, `TiltShift`, `MotionBlur`, `SpeedLines`, `ColorIsolation`, `Infrared`, `Technicolor`, `Super8`, `LensDistortion`, `Jello`, `DroneDisplay`, `AnalogVideo` or `DigitalVideo`. The snow is then under the fog, the clouds behind the rain and the lightning in front of them, the rain or the falling snow in the scene, the lens effects apply to the whole scene, and the film looks apply to everything, their black bars included. The drone camera comes last, in the order of a real one: its lens bends the picture, its sensor reads it with the jello of its shake, its display is drawn over the picture and stays straight, and the video signal, analog or digital, disturbs both. To activate an effect later, deactivate and activate again the active effects that come after it.
207
+
208
+ ### Notes
209
+
210
+ `WhiteModel` is the look of an architect's plaster model or white render: the colors turned to their value and pressed into a narrow range of whites, so the light and the shapes alone tell the surfaces apart; lower `values` for more contrast, a `saturation` above 0 to keep an accent (the project in a color, the context white). The darkest colors, under `poche` (a tenth by default), keep their value, fading into the whites about it: a section's poché stays solid black; give 0 if deep shadows or a dusk scene should stay gray. An accent's hue is scaled down rather than clipped where a channel would pass 1. It is a `Toy` with another grading and shares its edges, found in the depth (`DepthEdges`, a shader chunk both prepend): the creases lit and shaded, the outline on the near side of a jump. Where surfaces meet it adds an occlusion, the Alchemy ambient obscurance ("The Alchemy Screen-Space Ambient Obscurance Algorithm", McGuire et al., HPG 2011): each pixel's normal from the depth's slopes, then the share of 12 samples on a spiral in a disc of `radius` meters that rise above its surface, each the more as it rises steeper and none beyond the radius, the spiral turned from pixel to pixel. A corner's foot gets about a half, a flat floor none. Zoomed out, when the disc is under a few pixels wide, the occlusion fades out: its samples would fall within the depth's noise. Each pixel then reads the depth about 20 times. For the whitest model, give the scene's materials light colors too: the grading keeps their differences in value.
211
+
212
+ `ColorIsolation`, `Infrared`, `Technicolor` and `Super8` work on display colors, after Cesium's tone mapping. `ColorIsolation`, `Infrared` and `Technicolor` follow prod80's [ReShade shaders](https://github.com/prod80/prod80-ReShade-Repository) (MIT). `Technicolor` imitates the three-strip process: the scene is split into red, green and blue records, each printed with its complementary dye, which gives its dense, saturated primaries. `Super8` blurs the highlights into a red-orange halation, as light scattered back through the film base, draws its grain as clumps a few pixels wide that differ slightly between the dye layers, and moves at 18 frames per second (grain, weave and flicker change with each film frame, light leaks drift slowly), so while it is active the scene renders 18 times per second, also with `requestRenderMode`.
213
+
214
+ `MotionBlur` blurs each pixel along its motion on the screen since the previous frame, over the exposure time, with the depth-weighted gathering of "A Reconstruction Filter for Plausible Motion Blur" (McGuire et al., I3D 2012). Only the camera moves: each pixel's motion follows from its depth and the previous frame's view, written once per pixel into a velocity buffer at half resolution rather than recomputed for each sample, and the samples are weighted by depth so that near terrain and far ridges or sky do not smear into each other. Without the paper's neighborhood velocity pass, a pixel only gathers along its own motion: the blur of moving terrain does not spill over a background that does not move, such as the sky, and its outline stays sharp there. The exposure is scaled by the time between frames, so the blur is the same whatever the frame rate; after a pause in `requestRenderMode`, that time is taken as at most a tenth of a second. At strength 0 its stage is disabled.
215
+
216
+ `LensDistortion` keeps the corners of the picture in place and magnifies its middle, so it keeps filling the screen; at distortion 0 and vignette 0 its stage is disabled. `DroneDisplay` imitates the OSD of a fixed-wing plane flying ArduPilot on an HD video system, as seen in combat drone footage: 50 columns and 18 rows of 12 x 18 character cells, as large as fits the screen, with the readouts near its edges, in the blocky white glyphs of an analog chip with a black outline, some of them in color. Its artificial horizon follows ArduPilot's `draw_horizon`: 9 dots of 2 x 2 pixels, moved by the camera's pitch (at most 20 degrees, a row per 5 degrees) and tilted at the angle of its roll, each at one of 9 heights in its cell, or past 45 degrees of roll one per row at one of 9 places across it; upside down, it blinks. It is a symbol rather than a line on the real horizon. Around it: the flight mode, FBWA, top left; the heading, top middle; top right, a link bar, green, orange then red as the link quality drops, and a battery symbol filled in green in 7 steps as a 3S battery drains, over 40 minutes at the cruise throttle and faster above it, and its voltage, sagging under the throttle's load; on the right, the altitude since the display turned on under a mountain with a green dot, the vertical speed with one or two green arrows, and the throttle; and the air speed at the bottom, after its pale blue label. The heading, the altitude and both speeds are the camera's, the speeds smoothed over about half a second (the air speed is its speed over the ground, without wind); the link quality is made up, and the throttle holds a cruise of 70 %, 5 % more per m/s of climb and less diving, which drains the battery. With `rxLoss`, the display is in failsafe: the mode turns to RTL, the link bar to red, and the FAILSAFE warning blinks red twice per second below the middle. The glyphs are drawn here, not copied from a font. `AnalogVideo` follows ntsc-rs's chroma lowpass and delay and its ringing, on a picture sampled at PAL's 576 lines of about 720 samples, and the recordings of analog FPV drones flying low and crashing: as the signal weakens, the color goes first (from an interference of about 0.4), the picture washes out toward a cold white and fills with colored streaks, short dashes along the video lines; the receiver loses the sync for two frames now and then, up to 1.5 times per second, and the picture tears into a white band with black below; at 1 there is no signal, and the receiver shows its own dark snow of streaks, the picture and the display gone. The picture moves at 25 frames per second, so while it is active the scene renders 25 times per second, also with `requestRenderMode`.
217
+
218
+ `Jello` follows the readout pass of [Readout](https://github.com/stoatworks-labs/readout) (MIT): the sensor reads its rows over 20 ms, so each row sees the camera at a different moment of its shake, a few sinusoids in x, y and rotation whose phases are computed in double precision on the CPU; the picture is magnified slightly so that its edges do not show; at amount 0 its stage is disabled and the scene is not rendered for it. `DigitalVideo` breaks the picture at random into 16 pixel macroblocks, each rebuilt from its corners with fewer colors as a codec that has thrown its detail away, some displaced, with strips shifted sideways and the colors split as in three.js's DigitalGlitch, and sometimes a short black screen; without the previous frames, it cannot freeze the picture. Both move, so while one is active the scene renders 30 times per second, also with `requestRenderMode`. Together, the animated effects (`Super8`, `AnalogVideo`, `Jello`, `DigitalVideo`, `Rain`) share one render clock per scene: the scene renders once per frame of the fastest of them, not once for each.
219
+
220
+ `SpeedLines` draws new streaks, 24 times per second, only when the scene renders: it does not render the scene by itself, so the streaks move while the camera does, or with any animation that renders the scene. At strength 0 its stage is disabled and costs nothing.
221
+
222
+ `Precipitation` follows the composite rainfall of ATI's ToyShop ("Artist-Directable Real-Time Rain Rendering in City Environments", Tatarchuk 2006): several layers of drops 2 to 16 m away in one full-screen pass, nearer ones larger, faster and more opaque, each hidden behind what is nearer than it. The rain veils the distance in a haze whose extinction follows the measured law of the rain, 0.1 per km of clear air plus 0.11 R^0.85 per km at R mm/h (Montero-Martinez et al. 2025), intensity 1 being 10 mm/h under a square root: from 39 km of visibility at intensity 0 to 4.4 km at 1. The rain's extinction drifts in bands 40 % around its mean, with the shafts' noise (Wronski 2014), and the clear air's and a low mist's thin out with the height, integrated in closed form (Quilez), so the valleys fill and the ridges stand above the haze. The rain has no color of its own, so the haze is the light it scatters toward the eye: the clear sky of Cesium's atmosphere at the horizon, warm toward the sun and blue away from it, turning to the gray of an overcast sky, three times brighter overhead than at the horizon (CIE 2003) and darker as it rains harder at the camera, and the sun's forward glow through thin clouds, or with `Clouds` in the scene through the gaps of their shadow map, for shafts of light through the haze, lit at night in a moonlit blue as `ValleyFog`'s fog, and in shafts of heavier rain hanging below the cloud base: patches of noise fixed to the ground, more of them as it rains harder, slanting with the wind, thinning out into the clouds and drifting with it, sampled 12 times along each view ray up to 20 km, in a pass at an eighth of the resolution, blurred at that resolution: they are kilometers wide and soft, and a 64th of the pixels is enough for their raymarch. That pass also finds the extinction along each ray and the share of it in the rain, and the haze's length below the cloud base is taken at each pixel from its depth, so ridges keep a sharp outline against what is behind them. Above the cloud base, no drops fall around the camera. The wind comes in gusts, which sway the fall and the shafts, and the drops waver in them. The drops refract the haze's light, biased toward white as film crews add milk to the water for rain that shows: they show over dark terrain and fade into a bright sky. While it is active, the sky is overcast: the scene's shadows are off and its light is half as bright, both restored when it is deactivated. The drops are laid out on the sphere of view directions, in Mercator coordinates around the direction they fall toward, where each layer is a regular grid, without a texture: the streaks fall down the screen at the horizon and radiate from below the camera when it looks down, rather than falling parallel to the ground as on a screen-aligned layer. The shafts and the haze are over the part of each view ray below the cloud base, where it rains, so from far above a pixel shows the rain in the column under it. With a `map`, the shafts and the haze are sampled from it along that part, in the shafts' pass, so a rain cell is seen from outside through them, and the drops, the dimmed light and the shadows follow the map's intensity at the camera, read on the CPU, below the cloud base: above it, the sun shines; the map is sampled bilinearly, so the rain fades in over a pixel of it. The drops follow the camera's rotation but not its motion. The streaks are the drops' motion over a 1/30 s exposure, so while it is active the scene renders 30 times per second, also with `requestRenderMode`; at intensity 0 its stage is disabled and the scene is not rendered for it. The haze reads Cesium's clear sky from a table of 65 colors drawn at each frame, toward the horizon by azimuth from the sun and overhead, rather than computing it twice at each pixel, which took about 6 ms. On an Intel UHD 630 at 1341 x 660, it adds about 2.5 ms to the frame.
223
+
224
+ Its `snow`, from 0 to 1, turns the rain to snow in one population, so that nothing in between looks like two kinds of precipitation falling together: the drops slow down from 9 m/s to 1 m/s (geometrically, so 3 m/s at 0.5, as in sleet), their streak, the motion over the exposure, shortens until they are round flakes, their thin sharp line grows into a soft flake that flutters across its cell at its own rate, the nearest layer out of focus as in Andrew Baldwin's "Just snow" (Shadertoy), and there are more of them, as there are in a snowfall; the haze goes from the rain's to the snow's, thicker, from 20 km of visibility to 500 m, with lighter shafts and a brighter light the flakes scatter. Each drop is a capsule from a point in its cell to the end of its trail. The cells of the grid are taller than wide in the rain and square in the snow, so that a trail stays within the cell above the pixel's: animating `snow` stretches the whole field smoothly along the fall, and the scroll along the fall is added up from frame to frame, as the speed changes with it. The cost does not depend on `snow`: about 4.3 to 4.8 ms at 1920 x 1024 on an Intel UHD 630, mostly the four layers; the layers' constants are computed once per frame on the CPU, and each pixel first rejects the drops that are nowhere near it. Wang and Wade's precipitation for Flight Simulator 2004 ("Rendering Falling Rain and Snow", 2004) also draws both with one technique. Several may be active at once: the first one dims the light and turns the shadows off, the last one restores them.
225
+
226
+ `Clouds` fills a slab above the cloud base from a map of the rain, from which it derives a map of the clouds on the CPU, about 60 ms for the 1024 x 768 radar map, once for each map: a radar sees the rain rather than the clouds, so the rain is dilated by 5 km and softened into a cloud shield wider than it, broken over dry ground and full over the rain, and the rain rate sets the clouds' type, from a flat deck 1500 m thick over drizzle to towers up to `cloudTop` from 10 mm/h, `remap(log(R), log(0.5), log(10), 0, 1)`, the precipitation turning the map "to cumulonimbus clouds" as in Schneider's "The Real-Time Volumetric Cloudscapes of Horizon Zero Dawn" (2015). Their bases are flat, the deck full up to two thirds of its height, the towers thinning out upward in the convective envelope of Schneider's "Nubis, Evolved" (2022) and spreading an anvil near their top, as in his "Nubis: Authoring Real-Time Volumetric Cloudscapes" (2017); the map is read jittered by a third of a cell of noise, so its 1 km squares do not show, and the coverage varies by 40 % with an 8 km noise that is not the billows' (Högfeldt 2016), both from a 256 x 256 square of noise made once that tiles every 64 cells of the map, read once rather than computed at each sample. Their shape is a noise made once on the CPU, about a third of a second for the page: a 64³ cube that repeats every 16 km, with billows of Perlin-Worley noise for the base shape and Worley noise at two finer scales for the details, which erode the edges of the cloud but not its core, as wisps at its base and billows above. The height profile times the coverage carves the noise rather than scaling it, `saturate(noise - (1 - profile))` (Schneider 2022), so the core stays solid and only the highest billows reach the top, ramping up over a softness that grows with the distance rather than sharpened by a power as in "Nubis Cubed" (2023), which with voxels 250 m wide would show their grid. It is a 3D texture read with its mipmaps at the footprint of the pixels, and a second, four times larger read of it breaks up the repetition of the tile in far views; the details fade out and the edges soften with the distance, so the far clouds do not alias at the pass's resolution. The clouds are raymarched in 32 steps along each view ray, short near the camera and longer with the distance, in a pass at a quarter of the resolution: each ray first probes a map of the highest rain around 4 km blocks at 8 points, so the march starts at the tallest cloud it can meet, rather than in the empty air above the clouds, and is skipped where it meets none; each step's light is integrated over it, and jittered anew at each frame, then accumulated over the frames, about 10 frames' worth, reprojected through where the ray enters the slab and clamped to each frame's neighborhood, as in Hillaire's "Physically Based Sky, Atmosphere and Cloud Rendering in Frostbite" (2016); it is blurred over 3 x 3 texels of about the same depth, and upscaled bilinearly from the texels at the pixel's depth, the farthest of each texel's block of pixels, or from the nearest in depth, so ridges in front of a cloud keep a sharp outline without a halo (Pesce 2016). The sun lights them through two samples toward it, with the light scattered more than once as two octaves (Hillaire 2016) and a Henyey-Greenstein phase function with a forward lobe for the silver lining; the sky lights them in the color of Cesium's sky overhead and the ground from below, less in their core than at their edges and least at their base, and the clouds over heavy rain absorb a fifth of their light, so their bases are dark gray from below, their tops white from above, and in a faintly cool moonlight at night. The far clouds fade into the sky behind them by Cesium's fog factor, as the terrain does. The shadow map holds, across a square around the camera seen from the sun, 80 km wide or four times as wide as the camera is high up to 800 km, fading out toward its edges, the clouds' optical depth from the top of the slab down to each quarter of its height, 512 x 512 texels marched 16 steps each, but for those whose rays meet no clouds, drawn again only when the camera moves a 40th of its width, the clock changes or the clouds change, at most 4 times a second: the clouds cast their shadows on the terrain and light `Precipitation`'s haze through their gaps. It is not read for the clouds' own light: with four heights, those of a thin deck would take in the deck itself, darkening its top. Seen from far above they are cloud tops; coming down, the camera flies into them and the view turns to fog; activated before `Precipitation`, its drops, shafts and haze are drawn over the clouds, so from below the cloud base they are seen through the rain; from above, `Precipitation` leaves the rain under the cloud base to them, its shafts and haze hidden while clouds are active in the scene. Use the same `map` and `cloudBase` as `Precipitation`. Both crossfade from their `map` to a `nextMap` over the same rectangle and of the same size by `mapBlend`, 0 to 1, as the radar from one hour to the next: one more read of the map for each of its lookups. Without a map there are no clouds. Their noise drifts toward the east with the `wind`, 10 m/s by default, leaning 500 m downwind from the base to the top of the slab (Schneider 2017), while the map stays put: the clouds stay where it rains and their billows pass through them. It goes by the scene's clock, a time-lapse while it runs faster and still while it is paused; with a `drift`, in m/s, 0 by default, the noise also drifts by the real time, so the clouds move while the clock is paused, and the scene renders 30 times per second while they are active, also with `requestRenderMode`. After the camera, the clock or the map changes, the scene renders at 30 frames per second for half a second, for the accumulation to settle, also with `requestRenderMode`, then rests. Each ray is marched at most 40 km into the slab: farther, a grazing ray's steps would be longer than the billows and the clouds on the horizon would turn to streaks. On an Intel UHD 630 at 1341 x 660, they add about 6.5 ms to the frame seen from 30 km above, 5.1 ms at an angle from 12 km, 4.8 ms inside the clouds and 3.9 ms toward clouds on the horizon; drawing the shadow map again, while the clock runs, adds no more than a few milliseconds to those frames.
227
+
228
+ `Lightning` draws cloud-to-ground strikes at random around the camera, or in front of it with `inFront`, on the ground, so that they stay where they are as the camera moves, and, with a map, only on heavy rain, more surely as it is heavier, from about 2.5 to 8 mm/h, within the radius or as far from the camera as it is high, up to 150 km. Each is a channel from the cloud base to the ground that wanders to either side, by noise of the position along it in four octaves, linear between random values so that it is straight segments with sharp turns, with branches that leave it downward at an angle, wander the same way and fade toward their ends. It is drawn over the picture in screen space, with a hot core in a halo, at least a pixel and a half wide: the channel is centimeters wide, and would vanish at its true width a thousand meters away. The distance to it is a function of the position along the line, a few noise lookups for each channel, not a polyline for each pixel. It is hidden behind nearer terrain. Seen from above the cloud base, the channel is hidden in the cloud, and the flash lights the cloud over the strike from inside: a glow 4 km wide, 1500 m above the cloud base, following the flicker of the strokes. It follows the lightning flash as it is described in Rakov and Uman's "Lightning: Physics and Effects" (2003): 2 to 4 return strokes tens of milliseconds apart down one channel, the first the brightest, with a dimmer current between them keeping it lit, and a flash that follows their flicker: it lights each pixel in proportion to its own color, less with its distance to the foot of the strike, and the sky toward the cloud above it. The core is white and the halo and the flash are blue with a violet cast: the channel is a plasma of about 30000 K, whose light is mostly the lines of nitrogen, with the hydrogen line of the water in a storm's air adding red (Kieu et al., "High-speed spectroscopy of lightning-like discharges", 2021). A far strike turns yellow, then red, as the scattering of the air takes the blue out of its light (an extinction that goes with the wavelength to the minus 4), and its flash tints the sky warm. Up to four strikes are active at once, each over after a second. The strokes flicker, so while it is active the scene renders 30 times per second, also with `requestRenderMode`; at intensity 0 its stage is disabled and the scene is not rendered for it. Without a strike the pass costs nothing measurable, and the flash alone as little: the cost is the channel, each pixel near it computing its distance to the line and to the branches, whose constants come from the CPU once for each strike, and pixels farther than the channel's reach skip it. On an Intel UHD 630 at 1070 x 611, a strike on the screen adds about 2.3 ms to the frame while it lasts, a few tenths of a second, and four at once about 3.8 ms; the cost goes with the area around the channel, so about three times that at 1920 x 1024. It does not dim the sky itself, so it goes with `Precipitation`, which does.
229
+
230
+ `Spotlight`'s beam in the air is raymarched, as Killzone Shadow Fall's volumetrics (Valient, SIGGRAPH 2014), in a pass at half the resolution: 8 samples along the part of each view ray inside the cone, found analytically, dithered per pixel, each weighted by the cone's penumbra, the dust, the inverse square from the light softened around it and the extinction of the haze so far, then scattered toward the camera with a Henyey-Greenstein phase of asymmetry `beamAnisotropy`. A 3 x 3 blur at that resolution, weighted by the scene distance, smooths the dither out, and the full resolution takes the four texels around each pixel, interpolated where they are at its distance, else the nearest in distance, so that the haze stops at the ridges in front of it rather than spilling onto them; the half-float texture holds the log2 of the distance. On an Intel UHD 630 at 1920 x 1080, the beam costs about 1.9 ms, from 5 ms at full resolution with 16 samples. It holds from any viewpoint, inside the cone included, where the former analytic beam, which took its penumbra where the ray passes closest to the axis, went dark; its dust streaks are noise sampled along the beam, radiating from the light. The light is added to a picture Cesium has already tone mapped, so the output goes through a filmic roll-off (ACES) rather than clipping to flat white.
231
+
232
+ `SnowLine` and `ValleyFog` compute each pixel's height relative to the camera, in double precision on the CPU for the camera, so they stay accurate far from the origin. The snow's slope comes from the normal rebuilt from the neighboring pixels. The fog is lit by the sun like Cesium's own fog: its color during the day, a moonlit blue at night, as bright as `scene.fog.minimumBrightness`. Both work in 3D only.
233
+
234
+ `SnowLine`, `ValleyFog`, `Precipitation`, `Clouds`, `Lightning`, `Toy`, `WhiteModel`, `TiltShift`, `Spotlight` and `MotionBlur` read the depth buffer. The post-process stages only get the terrain's depth when it is depth tested, so `globe.depthTestAgainstTerrain` is on while any of them is active and restored when the last one is deactivated: billboards and labels that show through the terrain can be hidden by it meanwhile.
235
+
236
+ Each effect is a full-screen pass, about 1 to 5 ms per frame at full HD on an integrated GPU, on top of a scene that may already be near its budget: Chrome then stalls for hundreds of milliseconds at a time rather than slowing down evenly. If activating an effect brings that on, the cheapest lever is `scene.msaaSamples = 1` while it is active, restored when it is deactivated, as `@geoblocks/cesium-flyto` does for its flights: 4x multisampling costs more than the effect itself, and a moving picture hides the aliasing.
@@ -0,0 +1,138 @@
1
+ import {PostProcessStage} from '@cesium/engine';
2
+ import Effect from './effect.js';
3
+ import {acquireFrames, releaseFrames} from './frame-clock.js';
4
+ import AnalogVideoShader from './shaders/AnalogVideo.js';
5
+ import Hash from './shaders/Hash.js';
6
+
7
+ // PAL video runs at 25 frames per second, as in AnalogVideo.glsl
8
+ const FRAME_RATE = 25;
9
+ // the strength of the signal fades in and out at this rate, in changes per second
10
+ const FADE_RATE = 0.4;
11
+ // the sync is lost for a frame or two now and then, up to this many times per second when the
12
+ // signal is weak
13
+ const TEAR_RATE = 1.5;
14
+
15
+ const fract = (/** @type {number} */ x) => x - Math.floor(x);
16
+
17
+ /**
18
+ * The shader's hash, in [0, 1), so that the values computed here per frame and
19
+ * those computed there per pixel come from one family.
20
+ * @param {number} x
21
+ * @param {number} y
22
+ * @return {number}
23
+ */
24
+ export function hash(x, y) {
25
+ let px = fract(x * 0.1031);
26
+ let py = fract(y * 0.1031);
27
+ let pz = px;
28
+ const dot = px * (py + 33.33) + py * (pz + 33.33) + pz * (px + 33.33);
29
+ px += dot;
30
+ py += dot;
31
+ pz += dot;
32
+ return fract((px + py) * pz);
33
+ }
34
+
35
+ /**
36
+ * How the signal fares at a time, all the picture over: how weak it is, 0 to 1
37
+ * (it wavers around the interference, and is 1 only without a signal); and,
38
+ * in the frames where the receiver loses the sync, where the picture tears, as
39
+ * a fraction of its height from the top, or -1.
40
+ * @param {number} seconds
41
+ * @param {number} interference 0 to 1
42
+ * @return {{weak: number, tear: number}}
43
+ */
44
+ export function signalAt(seconds, interference) {
45
+ const fade = seconds * FADE_RATE;
46
+ const t = fract(fade);
47
+ const wander = hash(Math.floor(fade), 5) + (hash(Math.floor(fade) + 1, 5) - hash(Math.floor(fade), 5)) * t * t * (3 - 2 * t);
48
+ const weak = interference >= 1 ? 1 : interference * (0.75 + 0.25 * wander);
49
+ // a tear lasts two frames: decided once for each pair
50
+ const pair = Math.floor((seconds * FRAME_RATE) / 2) % 1000;
51
+ const chance = (2 * TEAR_RATE / FRAME_RATE) * Math.min(Math.max((interference - 0.3) / 0.5, 0), 1);
52
+ const tear = interference < 1 && hash(pair, 11) < chance ? 0.2 + 0.6 * hash(pair, 13) : -1;
53
+ return {weak, tear};
54
+ }
55
+
56
+ /**
57
+ * An analog FPV video feed: a soft, washed-out PAL picture with its color
58
+ * bleeding sideways, which, as the signal weakens, loses its color, washes
59
+ * out, fills with colored streaks along the video lines and tears when the
60
+ * sync is lost, down to the dark snow of a receiver without a signal. The
61
+ * picture moves, so while active the scene renders at 25 frames per second,
62
+ * also in requestRenderMode.
63
+ */
64
+ export default class AnalogVideo extends Effect {
65
+ /**
66
+ * @param {import('@cesium/engine').CesiumWidget} viewer
67
+ * @param {{noise?: number, interference?: number}} [options]
68
+ */
69
+ constructor(viewer, options = {}) {
70
+ super(viewer);
71
+ this.noise_ = options.noise ?? 0.5;
72
+ this.interference_ = options.interference ?? 0.15;
73
+ // the uniforms updated once per frame
74
+ this.weak_ = 0;
75
+ this.tear_ = -1;
76
+ this.onPreRender_ = () => {
77
+ const signal = signalAt(performance.now() / 1000, this.interference_);
78
+ this.weak_ = signal.weak;
79
+ this.tear_ = signal.tear;
80
+ };
81
+ }
82
+
83
+ /** @override */
84
+ createStage_() {
85
+ return new PostProcessStage({
86
+ fragmentShader: Hash + AnalogVideoShader,
87
+ uniforms: {
88
+ time: () => performance.now() / 1000,
89
+ noise: () => this.noise_,
90
+ weak: () => this.weak_,
91
+ tear: () => this.tear_,
92
+ },
93
+ });
94
+ }
95
+
96
+ /**
97
+ * @override
98
+ * @param {import('@cesium/engine').Scene} scene
99
+ */
100
+ activated_(scene) {
101
+ scene.preRender.addEventListener(this.onPreRender_);
102
+ // a new picture only with each video frame, not with each frame of the display
103
+ acquireFrames(scene, FRAME_RATE);
104
+ }
105
+
106
+ /**
107
+ * @override
108
+ * @param {import('@cesium/engine').Scene} scene
109
+ */
110
+ deactivating_(scene) {
111
+ releaseFrames(scene, FRAME_RATE);
112
+ scene.preRender.removeEventListener(this.onPreRender_);
113
+ }
114
+
115
+ /**
116
+ * Grain, 0 to 1.
117
+ */
118
+ get noise() {
119
+ return this.noise_;
120
+ }
121
+
122
+ set noise(value) {
123
+ this.noise_ = value;
124
+ }
125
+
126
+ /**
127
+ * How weak the signal is, 0 to 1: a few streaks when it is low, the color
128
+ * lost from about 0.4, then a washed-out picture under more and more streaks,
129
+ * torn when the sync is lost; 1 for no signal at all, the receiver's snow.
130
+ */
131
+ get interference() {
132
+ return this.interference_;
133
+ }
134
+
135
+ set interference(value) {
136
+ this.interference_ = value;
137
+ }
138
+ }
package/blur.js ADDED
@@ -0,0 +1,39 @@
1
+ import {PostProcessStage, PostProcessStageComposite, PostProcessStageSampleMode} from '@cesium/engine';
2
+ // @ts-expect-error the engine exports its shader sources, but does not type them
3
+ import {_shadersGaussianBlur1D as GaussianBlur1D} from '@cesium/engine';
4
+
5
+ /**
6
+ * A Gaussian blur in two passes, as PostProcessStageLibrary.createBlurStage()
7
+ * builds it, with its own name: the library names every blur czm_blur, and a
8
+ * collection holds one stage per name, so two effects with a library blur
9
+ * break each other when one is removed.
10
+ * @param {string} name of the composite; the passes are named after it
11
+ * @param {number} [textureScale=1] resolution of the passes relative to the viewport: a wide blur
12
+ * costs a quarter at half resolution, with a step half as long for the same spread
13
+ * @return {PostProcessStageComposite} with `sigma` and `stepSize` uniforms shared by both passes
14
+ */
15
+ export default function createBlur(name, textureScale = 1) {
16
+ const passes = ['x', 'y'].map(
17
+ (axis, direction) =>
18
+ new PostProcessStage({
19
+ name: `${name}_${axis}`,
20
+ fragmentShader: `#define USE_STEP_SIZE\n${GaussianBlur1D}`,
21
+ uniforms: {delta: 1, sigma: 2, stepSize: 1, direction},
22
+ sampleMode: PostProcessStageSampleMode.LINEAR,
23
+ textureScale,
24
+ })
25
+ );
26
+ const shared = (/** @type {string} */ uniform) => ({
27
+ get: () => passes[0].uniforms[uniform],
28
+ set: (/** @type {number} */ value) => {
29
+ for (const pass of passes) {
30
+ pass.uniforms[uniform] = value;
31
+ }
32
+ },
33
+ });
34
+ return new PostProcessStageComposite({
35
+ name,
36
+ stages: passes,
37
+ uniforms: Object.defineProperties({}, {sigma: shared('sigma'), stepSize: shared('stepSize')}),
38
+ });
39
+ }
@@ -0,0 +1,23 @@
1
+ export {default as AnalogVideo} from './analog-video.js';
2
+ export {default as Clouds} from './clouds.js';
3
+ export {default as ColorIsolation} from './color-isolation.js';
4
+ export {default as DigitalVideo} from './digital-video.js';
5
+ export {default as DroneDisplay} from './drone-display.js';
6
+ export {default as Infrared} from './infrared.js';
7
+ export {default as Jello} from './jello.js';
8
+ export {default as LensDistortion} from './lens-distortion.js';
9
+ export {default as Lightning} from './lightning.js';
10
+ export {default as MotionBlur} from './motion-blur.js';
11
+ export {default as Precipitation} from './precipitation.js';
12
+ export {default as SnowLine} from './snow-line.js';
13
+ export {default as SpeedLines} from './speed-lines.js';
14
+ export {default as Spotlight} from './spotlight.js';
15
+ export {default as Super8} from './super8.js';
16
+ export {default as Technicolor} from './technicolor.js';
17
+ export {default as TiltShift} from './tilt-shift.js';
18
+ export {default as Toy} from './toy.js';
19
+ export {default as ValleyFog} from './valley-fog.js';
20
+ export {default as WhiteModel} from './white-model.js';
21
+
22
+ // the shader chunk the depth-based effects share, for effects of your own
23
+ export {default as EyeFromDepth} from './shaders/EyeFromDepth.js';
@@ -0,0 +1,69 @@
1
+ import {PostProcessStage} from '@cesium/engine';
2
+ import Effect from './effect.js';
3
+ import ColorIsolationShader from './shaders/ColorIsolation.js';
4
+ import Hsl from './shaders/Hsl.js';
5
+
6
+ /**
7
+ * Color isolation: one hue keeps its color, the rest of the scene turns gray.
8
+ */
9
+ export default class ColorIsolation extends Effect {
10
+ /**
11
+ * @param {import('@cesium/engine').CesiumWidget} viewer
12
+ * @param {{hue?: number, range?: number, strength?: number}} [options]
13
+ */
14
+ constructor(viewer, options = {}) {
15
+ super(viewer);
16
+ this.hue_ = options.hue ?? 0;
17
+ this.range_ = options.range ?? 60;
18
+ this.strength_ = options.strength ?? 1;
19
+ }
20
+
21
+ /** @override */
22
+ createStage_() {
23
+ return new PostProcessStage({
24
+ fragmentShader: Hsl + ColorIsolationShader,
25
+ uniforms: {
26
+ hue: () => this.hue_ / 360,
27
+ range: () => this.range_ / 360,
28
+ strength: () => this.strength_,
29
+ },
30
+ });
31
+ }
32
+
33
+ /**
34
+ * Hue kept in color, in degrees: 0 red, 60 yellow, 120 green, 180 cyan,
35
+ * 240 blue, 300 magenta.
36
+ */
37
+ get hue() {
38
+ return this.hue_;
39
+ }
40
+
41
+ set hue(value) {
42
+ this.hue_ = value;
43
+ this.viewer.scene.requestRender();
44
+ }
45
+
46
+ /**
47
+ * Half width of the hues kept, in degrees.
48
+ */
49
+ get range() {
50
+ return this.range_;
51
+ }
52
+
53
+ set range(value) {
54
+ this.range_ = value;
55
+ this.viewer.scene.requestRender();
56
+ }
57
+
58
+ /**
59
+ * How gray the rest turns, 0 to 1.
60
+ */
61
+ get strength() {
62
+ return this.strength_;
63
+ }
64
+
65
+ set strength(value) {
66
+ this.strength_ = value;
67
+ this.viewer.scene.requestRender();
68
+ }
69
+ }
package/depth-test.js ADDED
@@ -0,0 +1,37 @@
1
+ // The post-process stages only get the depth of the terrain when it is depth tested. Several
2
+ // effects may need it at once: the first one turns depth testing on, the last one restores it.
3
+
4
+ /** @type {WeakMap<import('@cesium/engine').Globe, {count: number, previous: boolean}>} */
5
+ const users = new WeakMap();
6
+
7
+ /**
8
+ * @param {import('@cesium/engine').Scene} scene
9
+ */
10
+ export function acquireTerrainDepth(scene) {
11
+ const globe = scene.globe;
12
+ if (!globe) {
13
+ return;
14
+ }
15
+ const user = users.get(globe);
16
+ if (user) {
17
+ user.count++;
18
+ return;
19
+ }
20
+ users.set(globe, {count: 1, previous: globe.depthTestAgainstTerrain});
21
+ globe.depthTestAgainstTerrain = true;
22
+ }
23
+
24
+ /**
25
+ * @param {import('@cesium/engine').Scene} scene
26
+ */
27
+ export function releaseTerrainDepth(scene) {
28
+ const globe = scene.globe;
29
+ const user = globe && users.get(globe);
30
+ if (!globe || !user) {
31
+ return;
32
+ }
33
+ if (--user.count === 0) {
34
+ globe.depthTestAgainstTerrain = user.previous;
35
+ users.delete(globe);
36
+ }
37
+ }