@geoblocks/cesium-post-process 0.2.0 → 0.3.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.
package/README.md CHANGED
@@ -18,13 +18,16 @@ Each effect is inactive after construction. Every option is also a property, app
18
18
 
19
19
  ```javascript
20
20
  import {Color, Rectangle} from '@cesium/engine';
21
- import {AnalogVideo, Clouds, ColorIsolation, DigitalVideo, DroneDisplay, Dufour, Infrared, Jello, LensDistortion, Lightning, MotionBlur, Precipitation, SnowLine, SpeedLines, Spotlight, Super8, Technicolor, TiltShift, Toy, ValleyFog, WhiteModel} from '@geoblocks/cesium-post-process';
21
+ import {AnalogVideo, Clouds, ColorIsolation, DigitalVideo, DroneDisplay, Dufour, Infrared, Jello, LensDistortion, Lightning, MotionBlur, Precipitation, RainRings, SnowLine, SpeedLines, Spotlight, Super8, Technicolor, TiltShift, Toy, ValleyFog, WhiteModel} from '@geoblocks/cesium-post-process';
22
22
 
23
23
  const snowLine = new SnowLine(viewer, {
24
24
  altitude: 2000, // meters above the ellipsoid where the snow is half covering
25
25
  transition: 200, // height of the band over which the snow thins out, in meters
26
26
  maxSlope: 50, // steepest slope that holds snow, in degrees
27
27
  coverage: 1, // opacity of the snow, 0 to 1
28
+ patchiness: 0, // 0 to 1: at 1 the coverage is the share of the ground the snow covers, the noise leaving the rest bare
29
+ waterLevel: 564, // meters above the ellipsoid: what lies within a quarter meter of it holds no snow, so a flat lake stays water
30
+ sunDirection: sun, // the direction toward the sun the snow is shaded by, in world coordinates; Cesium's own sun if none
28
31
  });
29
32
  snowLine.active = true;
30
33
 
@@ -66,6 +69,17 @@ precipitation.active = true;
66
69
  // the rain turns to snow, animatable
67
70
  precipitation.snow = 1;
68
71
 
72
+ const rainRings = new RainRings(viewer, {
73
+ intensity: 0.5, // the share of the cells that have a drop, 0 to 1
74
+ cell: 2, // size of a cell, in meters: one drop in each
75
+ reach: 0.35, // how far a ring grows on the ground, in meters
76
+ waterLevel: 372, // height of the water above the ellipsoid, in meters, where the rings are wider and slower; undefined for none
77
+ maxSlope: 30, // steepest slope that takes a ring, in degrees
78
+ color: new Color(0.31, 0.39, 0.5, 0.6), // slate on a light ground, pale on a dark one; the alpha is how strongly it shows
79
+ origin: position, // the point of the world the cells are laid about; the camera's position when activated if none
80
+ });
81
+ rainRings.active = true;
82
+
69
83
  const clouds = new Clouds(viewer, {
70
84
  map: precipitation.map, // where it rains, as Precipitation's map; undefined for no clouds
71
85
  cloudBase: 2500, // altitude of the cloud base, in meters above the ellipsoid
@@ -225,9 +239,11 @@ const stage = new PostProcessStage({
225
239
 
226
240
  `Normal`, also prepended after `EyeFromDepth`, gives a pixel's normal in eye coordinates from the depth of its neighbors, facing the camera: `vec3 normalAt(sampler2D depthTexture, vec2 uv, vec3 center, float distance)`, with `center` the pixel's eye coordinates and the neighbors `distance` pixels away, on each axis the nearer side so that the scene's edges do not bend it; and `vec3 smoothNormalAt(sampler2D depthTexture, vec2 uv, vec3 center)`, averaged over two distances so that the terrain's flat triangles blend into a smoother surface. It needs no uniforms.
227
241
 
242
+ `localFromEye(frame, viewMatrix)` gives the matrix from the eye's coordinates to a local frame (such as `FixedFrameTransforms.eastNorthUpToFixedFrame(origin)`), made in double precision, for a shader that lays a pattern over the ground: `(localFromEye * vec4(eye.xyz, 1.0)).xy` is the pixel's place in meters, to the millimeter, where the world coordinates would step by half a meter.
243
+
228
244
  ### Order of the effects
229
245
 
230
- 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`, `WhiteModel` or `Dufour`, `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.
246
+ 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`, `RainRings`, `Lightning`, `Toy`, `WhiteModel` or `Dufour`, `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 and the rings where the rain lands 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.
231
247
 
232
248
  ### Notes
233
249
 
@@ -249,15 +265,17 @@ Cesium runs the stages in the order they are added and cannot insert one elsewhe
249
265
 
250
266
  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.
251
267
 
268
+ `RainRings` lays the ground out in cells of a local frame, east-north-up about `origin`, and in each lands a drop, as often as `intensity` says, where the cell's hash puts it: a thin ring grows from it over about 0.6 s and fades, on what is flatter than `maxSlope`, wider and slower where the surface is within a quarter meter of `waterLevel`. It finds the surfaces from the depth, so it rings the ground, the roofs and the water alike and nothing in the air, and tells the slope from the normal rebuilt from the neighboring pixels. The cells are in a local frame with the globe's offset cancelled in double precision on the CPU (`localFromEye`), so they stay where they are as the camera moves and do not step by half a meter with the single-precision world coordinates a shader gets. A ring never grows past 0.3 cell, so that its cell's edge does not clip it, and fades out where a pixel is wider than 8 to 20 cm on the ground, where it would only be a speckle. It renders the scene 30 times a second while active with an intensity, also in `requestRenderMode`. It has no sense of what is under a roof: rain does not fall there, and the rings do.
269
+
252
270
  `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.
253
271
 
254
272
  `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.
255
273
 
256
274
  `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.
257
275
 
258
- `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.
276
+ `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. `SnowLine`'s `patchiness` turns the coverage from the snow's opacity into the share of the ground it covers: the same noise that breaks up the edge of the snow line thins it into patches. Its `sunDirection` is for a scene lit by a light of its own, where Cesium's sun would shade the snow the wrong way: `scene.light.direction` is the way a light travels, so give its negation.
259
277
 
260
- `SnowLine`, `ValleyFog`, `Precipitation`, `Clouds`, `Lightning`, `Toy`, `WhiteModel`, `Dufour`, `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.
278
+ `SnowLine`, `ValleyFog`, `Precipitation`, `RainRings`, `Clouds`, `Lightning`, `Toy`, `WhiteModel`, `Dufour`, `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.
261
279
 
262
280
  The depth they read is the nearest of Cesium's frustums': with `scene.logarithmicDepthBuffer` off, Cesium splits the view into frustums of `farToNearRatio` (1000 by default) from the near plane, and only the nearest one's depth reaches the stages. What lies past the split has no depth there: `Toy` and `WhiteModel` paint their sky over it and draw no edge on it, `TiltShift` blurs it as the far background, `SnowLine` and `ValleyFog` take it for sky. Keep the logarithmic depth buffer on, or set `camera.frustum.near` so that one frustum holds the scene (a near plane of 0.3 m reaches 300 m).
263
281
 
@@ -10,6 +10,7 @@ export {default as LensDistortion} from './lens-distortion.js';
10
10
  export {default as Lightning} from './lightning.js';
11
11
  export {default as MotionBlur} from './motion-blur.js';
12
12
  export {default as Precipitation} from './precipitation.js';
13
+ export {default as RainRings} from './rain-rings.js';
13
14
  export {default as SnowLine} from './snow-line.js';
14
15
  export {default as SpeedLines} from './speed-lines.js';
15
16
  export {default as Spotlight} from './spotlight.js';
@@ -24,4 +25,5 @@ export {default as WhiteModel} from './white-model.js';
24
25
  export {default as EyeFromDepth} from './shaders/EyeFromDepth.js';
25
26
  export {default as Height} from './shaders/Height.js';
26
27
  export {heightUniforms} from './height.js';
28
+ export {localFromEye} from './local-frame.js';
27
29
  export {default as Normal} from './shaders/Normal.js';
package/local-frame.js ADDED
@@ -0,0 +1,15 @@
1
+ import {Matrix4} from '@cesium/core';
2
+
3
+ /**
4
+ * The local frame's coordinates from the eye's, for a shader that wants a pixel's place in a small frame (east-north-up about a point) to
5
+ * lay a pattern over the ground that stays where it is as the camera moves. Made in double precision on the CPU: the globe's offset
6
+ * (about 6.4 million meters) cancels in the product, so the matrix a shader gets is small and a place comes out to the millimeter. In world
7
+ * coordinates, a single precision float steps by half a meter.
8
+ * @param {Matrix4} frame The local frame in the world, such as `FixedFrameTransforms.eastNorthUpToFixedFrame(origin)`.
9
+ * @param {Matrix4} viewMatrix The camera's view matrix.
10
+ * @param {Matrix4} [result]
11
+ * @return {Matrix4}
12
+ */
13
+ export function localFromEye(frame, viewMatrix, result = new Matrix4()) {
14
+ return Matrix4.inverseTransformation(Matrix4.multiply(viewMatrix, frame, result), result);
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geoblocks/cesium-post-process",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "license": "BSD-3-Clause",
5
5
  "type": "module",
6
6
  "repository": {
@@ -32,8 +32,10 @@
32
32
  "jello.js",
33
33
  "lens-distortion.js",
34
34
  "lightning.js",
35
+ "local-frame.js",
35
36
  "motion-blur.js",
36
37
  "precipitation.js",
38
+ "rain-rings.js",
37
39
  "snow-line.js",
38
40
  "speed-lines.js",
39
41
  "spotlight.js",
@@ -54,5 +56,5 @@
54
56
  "@cesium/core": ">=0.1.0",
55
57
  "@cesium/engine": ">=10"
56
58
  },
57
- "gitHead": "71fc611a0eb4caf6ff6d99cbbab64f266fa73dd6"
59
+ "gitHead": "866feb087ad153c2303fdf12b670ec78f192f825"
58
60
  }
package/rain-rings.js ADDED
@@ -0,0 +1,215 @@
1
+ import {Cartesian3, Cartesian4, Color, FixedFrameTransforms, Matrix4, Math as CesiumMath} from '@cesium/core';
2
+ import {PostProcessStage} from '@cesium/engine';
3
+ import {acquireTerrainDepth, releaseTerrainDepth} from './depth-test.js';
4
+ import Effect from './effect.js';
5
+ import {acquireFrames, releaseFrames} from './frame-clock.js';
6
+ import {heightUniforms} from './height.js';
7
+ import {localFromEye} from './local-frame.js';
8
+ import EyeFromDepth from './shaders/EyeFromDepth.js';
9
+ import Hash from './shaders/Hash.js';
10
+ import Height from './shaders/Height.js';
11
+ import Normal from './shaders/Normal.js';
12
+ import RainRingsShader from './shaders/RainRings.js';
13
+
14
+ // the rings grow and fade over a fraction of a second: a render for each frame of a video
15
+ const FRAME_RATE = 30;
16
+ // the shader's time wraps at an hour, a whole number of the rings' periods
17
+ const TIME_PERIOD = 3600;
18
+ // the water's level when there is no water: far above everything
19
+ const NO_WATER = 1e9;
20
+
21
+ /**
22
+ * Rings where the rain lands: on the ground, the roofs and the water, in each cell of a local frame a drop landing, a thin ring growing from it
23
+ * and fading, wider and slower on water. For a view of a place from far enough that the drops themselves are too small to see; or under
24
+ * `Precipitation`, the drops falling and these where they land. Found from the depth, with the surface's slope from the normal rebuilt from the
25
+ * neighboring pixels: nothing lies in the air.
26
+ */
27
+ export default class RainRings extends Effect {
28
+ /**
29
+ * @param {import('@cesium/engine').CesiumWidget} viewer
30
+ * @param {{intensity?: number, cell?: number, reach?: number, waterLevel?: number, maxSlope?: number, rate?: number,
31
+ * color?: Color, origin?: Cartesian3}} [options] The share of the cells that have a drop, 0 to 1; the cell's size and the ring's growth
32
+ * on the ground, in meters (the ring on water is clipped at 0.3 cell); the water's height above the ellipsoid, in meters, or undefined
33
+ * for none; the steepest slope that takes a ring, in degrees; rings a second in a cell; the ring's color, its alpha how strongly it shows; the
34
+ * point of the world the cells are laid about.
35
+ */
36
+ constructor(viewer, options = {}) {
37
+ super(viewer);
38
+ this.intensity_ = options.intensity ?? 0.5;
39
+ this.cell_ = options.cell ?? 2;
40
+ this.reach_ = options.reach ?? 0.35;
41
+ this.waterLevel_ = options.waterLevel;
42
+ this.maxSlope_ = options.maxSlope ?? 30;
43
+ this.rate_ = options.rate ?? 1.6;
44
+ this.color_ = options.color ?? new Color(0.31, 0.39, 0.5, 0.6);
45
+ this.origin_ = options.origin && Cartesian3.clone(options.origin);
46
+ /** @type {Matrix4 | undefined} */
47
+ this.localFrame_ = undefined;
48
+ this.colorScratch_ = new Cartesian4();
49
+ this.localScratch_ = new Matrix4();
50
+ }
51
+
52
+ /**
53
+ * The local frame, east-north-up about the origin: the camera's position when the effect is activated, if none was given.
54
+ * @private
55
+ * @param {import('@cesium/engine').Scene} scene
56
+ */
57
+ frameAt_(scene) {
58
+ this.origin_ ??= Cartesian3.clone(scene.camera.positionWC);
59
+ const frame = this.localFrame_ ?? FixedFrameTransforms.eastNorthUpToFixedFrame(this.origin_, scene.ellipsoid, new Matrix4());
60
+ this.localFrame_ = frame;
61
+ return frame;
62
+ }
63
+
64
+ /**
65
+ * @override
66
+ * @param {import('@cesium/engine').Scene} scene
67
+ */
68
+ createStage_(scene) {
69
+ const stage = new PostProcessStage({
70
+ fragmentShader: EyeFromDepth + Height + Hash + Normal + RainRingsShader,
71
+ uniforms: {
72
+ ...heightUniforms(scene),
73
+ localFromEye: () => localFromEye(this.frameAt_(scene), scene.camera.viewMatrix, this.localScratch_),
74
+ intensity: () => this.intensity_,
75
+ cell: () => this.cell_,
76
+ reach: () => this.reach_,
77
+ waterLevel: () => this.waterLevel_ ?? NO_WATER,
78
+ maxSlope: () => CesiumMath.toRadians(this.maxSlope_),
79
+ rate: () => this.rate_,
80
+ time: () => (performance.now() / 1000) % TIME_PERIOD,
81
+ color: () => Cartesian4.fromColor(this.color_, this.colorScratch_),
82
+ },
83
+ });
84
+ // no pass, and no frames, without an intensity
85
+ stage.enabled = this.intensity_ > 0;
86
+ return stage;
87
+ }
88
+
89
+ /**
90
+ * @override
91
+ * @param {import('@cesium/engine').Scene} scene
92
+ */
93
+ activated_(scene) {
94
+ this.frameAt_(scene);
95
+ acquireTerrainDepth(scene);
96
+ if (this.intensity_ > 0) {
97
+ acquireFrames(scene, FRAME_RATE);
98
+ }
99
+ }
100
+
101
+ /**
102
+ * @override
103
+ * @param {import('@cesium/engine').Scene} scene
104
+ */
105
+ deactivating_(scene) {
106
+ if (this.intensity_ > 0) {
107
+ releaseFrames(scene, FRAME_RATE);
108
+ }
109
+ releaseTerrainDepth(scene);
110
+ }
111
+
112
+ /**
113
+ * The share of the cells that have a drop, 0 (no rings, no frames) to 1.
114
+ */
115
+ get intensity() {
116
+ return this.intensity_;
117
+ }
118
+
119
+ set intensity(value) {
120
+ const raining = this.intensity_ > 0;
121
+ this.intensity_ = value;
122
+ if (this.stage_ && raining !== value > 0) {
123
+ this.stage_.enabled = value > 0;
124
+ (value > 0 ? acquireFrames : releaseFrames)(this.viewer.scene, FRAME_RATE);
125
+ }
126
+ this.viewer.scene.requestRender();
127
+ }
128
+
129
+ /**
130
+ * The size of a cell, in meters: one drop in each, as often as the intensity says.
131
+ */
132
+ get cell() {
133
+ return this.cell_;
134
+ }
135
+
136
+ set cell(value) {
137
+ this.cell_ = value;
138
+ this.viewer.scene.requestRender();
139
+ }
140
+
141
+ /**
142
+ * How far a ring grows on the ground, in meters: over water, 1.7 times as far, but never past 0.3 cell, or the edge of its cell would clip it.
143
+ */
144
+ get reach() {
145
+ return this.reach_;
146
+ }
147
+
148
+ set reach(value) {
149
+ this.reach_ = value;
150
+ this.viewer.scene.requestRender();
151
+ }
152
+
153
+ /**
154
+ * The water's height above the ellipsoid, in meters, or undefined for none: what lies within a quarter meter of it is water, where the rings
155
+ * are wider and slower.
156
+ */
157
+ get waterLevel() {
158
+ return this.waterLevel_;
159
+ }
160
+
161
+ set waterLevel(value) {
162
+ this.waterLevel_ = value;
163
+ this.viewer.scene.requestRender();
164
+ }
165
+
166
+ /**
167
+ * The steepest slope that takes a ring, in degrees.
168
+ */
169
+ get maxSlope() {
170
+ return this.maxSlope_;
171
+ }
172
+
173
+ set maxSlope(value) {
174
+ this.maxSlope_ = value;
175
+ this.viewer.scene.requestRender();
176
+ }
177
+
178
+ /**
179
+ * Rings a second in a cell.
180
+ */
181
+ get rate() {
182
+ return this.rate_;
183
+ }
184
+
185
+ set rate(value) {
186
+ this.rate_ = value;
187
+ this.viewer.scene.requestRender();
188
+ }
189
+
190
+ /**
191
+ * The ring's color, its alpha how strongly it shows. Slate on a light ground, pale on a dark one.
192
+ */
193
+ get color() {
194
+ return this.color_;
195
+ }
196
+
197
+ set color(value) {
198
+ this.color_ = value;
199
+ this.viewer.scene.requestRender();
200
+ }
201
+
202
+ /**
203
+ * The point of the world the cells are laid about, which the effect keeps where it is as the camera moves; the camera's position when
204
+ * the effect is activated if none is given. Keep it within a few kilometers of what is looked at.
205
+ */
206
+ get origin() {
207
+ return this.origin_;
208
+ }
209
+
210
+ set origin(value) {
211
+ this.origin_ = value && Cartesian3.clone(value);
212
+ this.localFrame_ = undefined;
213
+ this.viewer.scene.requestRender();
214
+ }
215
+ }
@@ -0,0 +1,2 @@
1
+ // This file is automatically rebuilt from RainRings.glsl.
2
+ export default "// Rings where the rain lands: the ground cut into cells of a local frame, in each cell a drop landing, as often as the intensity says, where the\n// cell's hash puts it, a thin ring growing from it and fading as it grows, on what is flat enough. Over water slower and wider. The cells are in a\n// local frame, in double precision from the CPU (local-frame.js), so they stay where they are as the camera moves and do not step with the globe's\n// size. Needs EyeFromDepth, Height, Hash and Normal.\nprecision highp sampler2D;\nuniform sampler2D colorTexture;\nuniform sampler2D depthTexture;\nuniform mat4 localFromEye;\n// 0 to 1: the share of the cells that have a drop\nuniform float intensity;\n// meters: a cell's size, and how far a ring on the ground grows\nuniform float cell;\nuniform float reach;\n// meters above the ellipsoid, far above anything with no water\nuniform float waterLevel;\n// radians\nuniform float maxSlope;\n// rings a second\nuniform float rate;\n// seconds\nuniform float time;\n// the color of a ring, its alpha how strongly it shows\nuniform vec4 color;\nin vec2 v_textureCoordinates;\n\n// a ring over water reaches this much farther, and runs this much slower\nconst float WATER_REACH = 1.7;\nconst float WATER_RATE = 0.5;\n// meters from the water's level within which a surface is water, and beyond which it is not\nconst vec2 WET = vec2(0.1, 0.25);\n// the drop lands in the middle of its cell, this far from its edge, in cells: a ring is clipped at the edge\nconst float MARGIN = 0.3;\n// the narrowest line, in meters\nconst float LINE = 0.045;\n// a pixel wider than the first, in meters, fades the rings out to none at the second: they would be a speckle\nconst vec2 SMALL = vec2(0.08, 0.2);\n// half the width of the slope limit, in radians\nconst float SLOPE_EDGE = 0.1;\n\nvoid main() {\n vec4 sceneColor = texture(colorTexture, v_textureCoordinates);\n vec4 eye = eyeAt(depthTexture, v_textureCoordinates);\n if (intensity <= 0.0 || eye.w == 0.0) {\n out_FragColor = sceneColor;\n return;\n }\n vec3 normal = smoothNormalAt(depthTexture, v_textureCoordinates, eye.xyz);\n float slope = acos(clamp(dot(normal, up), -1.0, 1.0));\n vec2 local = (localFromEye * vec4(eye.xyz, 1.0)).xy;\n float wet = 1.0 - smoothstep(WET.x, WET.y, abs(heightAt(eye.xyz) - waterLevel));\n\n vec2 id = floor(local / cell);\n vec2 centre = (id + MARGIN + (1.0 - 2.0 * MARGIN) * vec2(hash(id + 3.1), hash(id + 7.7))) * cell;\n float phase = fract(time * rate * mix(1.0, WATER_RATE, wet) + hash(id + 11.3));\n // never past the cell's edge from the centre\n float grown = min(reach * mix(1.0, WATER_REACH, wet), MARGIN * cell) * phase;\n float distance = length(local - centre);\n float width = max(LINE, 1.5 * fwidth(distance));\n float ring = (1.0 - smoothstep(0.0, width, abs(distance - grown))) * (1.0 - phase);\n ring *= float(hash(id) < intensity);\n ring *= 1.0 - smoothstep(maxSlope - SLOPE_EDGE, maxSlope + SLOPE_EDGE, slope);\n ring *= 1.0 - smoothstep(SMALL.x, SMALL.y, length(fwidth(local)));\n out_FragColor = vec4(mix(sceneColor.rgb, color.rgb, ring * color.a), sceneColor.a);\n}\n";
@@ -1,2 +1,2 @@
1
1
  // This file is automatically rebuilt from SnowLine.glsl.
2
- export default "// Snow above an altitude, on slopes gentle enough to hold it, shaded by the sun and by the\n// brightness of the scene so that the relief stays readable. The slope comes from a normal\n// rebuilt from the neighboring pixels and smoothed over the facets of the terrain. Noise anchored\n// to the ground breaks up the edge of the snow, which lasts lower on the slopes away from the sun.\nprecision highp sampler2D;\nuniform sampler2D colorTexture;\nuniform sampler2D depthTexture;\n// meters above the ellipsoid: half covered at the altitude, over a band transition meters high\nuniform float altitude;\nuniform float transition;\n// radians\nuniform float maxSlope;\n// 0 to 1\nuniform float coverage;\nin vec2 v_textureCoordinates;\n\nconst vec3 SNOW_SHADE = vec3(0.80, 0.87, 1.0);\nconst vec3 SNOW_SUN = vec3(1.0, 0.98, 0.94);\n// meters the snow line moves down on a slope facing away from the sun, and up on one facing it\nconst float ASPECT_SHIFT = 0.6;\n// meters the noise moves the snow line, as a fraction of the transition\nconst float NOISE_SHIFT = 0.8;\n// part of the snow that still covers the steep slopes at the limit, as a fraction of the coverage\nconst float STEEP_SNOW = 0.35;\n// half width of the slope limit, in radians (8 degrees), so that the snow thins out across the\n// facets of the terrain rather than stopping at their edges\nconst float SLOPE_EDGE = 0.14;\n\nfloat hash(vec3 p) {\n p = fract(p * 0.3183099 + 0.1);\n p *= 17.0;\n return fract(p.x * p.y * p.z * (p.x + p.y + p.z));\n}\n\nfloat valueNoise(vec3 p) {\n vec3 i = floor(p);\n vec3 f = fract(p);\n f = f * f * (3.0 - 2.0 * f);\n return mix(\n mix(mix(hash(i), hash(i + vec3(1, 0, 0)), f.x), mix(hash(i + vec3(0, 1, 0)), hash(i + vec3(1, 1, 0)), f.x), f.y),\n mix(mix(hash(i + vec3(0, 0, 1)), hash(i + vec3(1, 0, 1)), f.x), mix(hash(i + vec3(0, 1, 1)), hash(i + vec3(1, 1, 1)), f.x), f.y),\n f.z);\n}\n\n// 0 to 1, from a few octaves anchored to the ground\nfloat groundNoise(vec3 world) {\n return 0.55 * valueNoise(world / 90.0) + 0.3 * valueNoise(world / 30.0) + 0.15 * valueNoise(world / 10.0);\n}\n\nvoid main() {\n vec4 sceneColor = texture(colorTexture, v_textureCoordinates);\n vec4 eye = eyeAt(depthTexture, v_textureCoordinates);\n vec3 normal = smoothNormalAt(depthTexture, v_textureCoordinates, eye.xyz);\n float slope = acos(clamp(dot(normal, up), -1.0, 1.0));\n\n float sun = max(dot(normal, czm_sunDirectionEC), 0.0);\n float noise = groundNoise((czm_inverseView * vec4(eye.xyz, 1.0)).xyz) - 0.5;\n // lower on the slopes away from the sun, irregular along the edge\n float shift = transition * (NOISE_SHIFT * noise + ASPECT_SHIFT * (0.5 - sun));\n float snowLine = altitude + shift;\n float snow = smoothstep(snowLine - 0.5 * transition, snowLine + 0.5 * transition, heightAt(eye.xyz));\n // opaque before the top of the band, so that the imagery does not show through the snow\n snow = smoothstep(0.0, 0.7, snow);\n // the steep slopes keep a thin, patchy cover, and the rock shows through\n float steep = smoothstep(maxSlope - SLOPE_EDGE, maxSlope + SLOPE_EDGE, slope);\n snow *= 1.0 - steep * (1.0 - STEEP_SNOW * smoothstep(0.35, 0.65, noise + 0.5));\n // eye.w is 0 for the sky\n snow *= coverage * eye.w;\n float brightness = czm_luminance(sceneColor.rgb);\n // blue in the shade, warm in the sun, with the relief of the scene kept in the brightness\n vec3 lit = mix(SNOW_SHADE, SNOW_SUN, sun) * clamp(0.68 + 0.25 * sun + 0.1 * brightness, 0.0, 1.0);\n out_FragColor = vec4(mix(sceneColor.rgb, lit, snow), sceneColor.a);\n}\n";
2
+ export default "// Snow above an altitude, on slopes gentle enough to hold it, shaded by the sun and by the\n// brightness of the scene so that the relief stays readable. The slope comes from a normal\n// rebuilt from the neighboring pixels and smoothed over the facets of the terrain. Noise anchored\n// to the ground breaks up the edge of the snow, which lasts lower on the slopes away from the sun.\nprecision highp sampler2D;\nuniform sampler2D colorTexture;\nuniform sampler2D depthTexture;\n// meters above the ellipsoid: half covered at the altitude, over a band transition meters high\nuniform float altitude;\nuniform float transition;\n// radians\nuniform float maxSlope;\n// 0 to 1\nuniform float coverage;\n// 0 to 1: how far the coverage thins the snow into patches, where 0 fades it\nuniform float patchiness;\n// meters above the ellipsoid, far above anything with no water: none lies within a quarter meter of it\nuniform float waterLevel;\n// the sun toward which the snow is shaded, in eye coordinates, when ownSun is 1; else Cesium's\nuniform vec3 sunEC;\nuniform float ownSun;\nin vec2 v_textureCoordinates;\n\nconst vec3 SNOW_SHADE = vec3(0.80, 0.87, 1.0);\nconst vec3 SNOW_SUN = vec3(1.0, 0.98, 0.94);\n// meters the snow line moves down on a slope facing away from the sun, and up on one facing it\nconst float ASPECT_SHIFT = 0.6;\n// meters the noise moves the snow line, as a fraction of the transition\nconst float NOISE_SHIFT = 0.8;\n// part of the snow that still covers the steep slopes at the limit, as a fraction of the coverage\nconst float STEEP_SNOW = 0.35;\n// half width of the slope limit, in radians (8 degrees), so that the snow thins out across the\n// facets of the terrain rather than stopping at their edges\nconst float SLOPE_EDGE = 0.14;\n// the coverage, moved by the noise, from which the snow begins and is whole when it is patches; how far the noise moves it\nconst vec2 PATCH = vec2(0.25, 0.6);\nconst float PATCH_NOISE = 1.6;\n// meters from the water's level within which a surface is water, and beyond which it is not\nconst vec2 WET = vec2(0.1, 0.25);\n\nfloat hash(vec3 p) {\n p = fract(p * 0.3183099 + 0.1);\n p *= 17.0;\n return fract(p.x * p.y * p.z * (p.x + p.y + p.z));\n}\n\nfloat valueNoise(vec3 p) {\n vec3 i = floor(p);\n vec3 f = fract(p);\n f = f * f * (3.0 - 2.0 * f);\n return mix(\n mix(mix(hash(i), hash(i + vec3(1, 0, 0)), f.x), mix(hash(i + vec3(0, 1, 0)), hash(i + vec3(1, 1, 0)), f.x), f.y),\n mix(mix(hash(i + vec3(0, 0, 1)), hash(i + vec3(1, 0, 1)), f.x), mix(hash(i + vec3(0, 1, 1)), hash(i + vec3(1, 1, 1)), f.x), f.y),\n f.z);\n}\n\n// 0 to 1, from a few octaves anchored to the ground\nfloat groundNoise(vec3 world) {\n return 0.55 * valueNoise(world / 90.0) + 0.3 * valueNoise(world / 30.0) + 0.15 * valueNoise(world / 10.0);\n}\n\nvoid main() {\n vec4 sceneColor = texture(colorTexture, v_textureCoordinates);\n vec4 eye = eyeAt(depthTexture, v_textureCoordinates);\n vec3 normal = smoothNormalAt(depthTexture, v_textureCoordinates, eye.xyz);\n float slope = acos(clamp(dot(normal, up), -1.0, 1.0));\n\n float sun = max(dot(normal, ownSun > 0.5 ? sunEC : czm_sunDirectionEC), 0.0);\n float noise = groundNoise((czm_inverseView * vec4(eye.xyz, 1.0)).xyz) - 0.5;\n // lower on the slopes away from the sun, irregular along the edge\n float shift = transition * (NOISE_SHIFT * noise + ASPECT_SHIFT * (0.5 - sun));\n float snowLine = altitude + shift;\n float snow = smoothstep(snowLine - 0.5 * transition, snowLine + 0.5 * transition, heightAt(eye.xyz));\n // opaque before the top of the band, so that the imagery does not show through the snow\n snow = smoothstep(0.0, 0.7, snow);\n // the steep slopes keep a thin, patchy cover, and the rock shows through\n float steep = smoothstep(maxSlope - SLOPE_EDGE, maxSlope + SLOPE_EDGE, slope);\n snow *= 1.0 - steep * (1.0 - STEEP_SNOW * smoothstep(0.35, 0.65, noise + 0.5));\n // the coverage an opacity, or, by the patchiness, the share of the ground the snow covers, the noise leaving it in patches\n float scattered = smoothstep(PATCH.x, PATCH.y, coverage + PATCH_NOISE * noise);\n // eye.w is 0 for the sky\n snow *= mix(coverage, scattered, patchiness) * eye.w;\n snow *= smoothstep(WET.x, WET.y, abs(heightAt(eye.xyz) - waterLevel));\n float brightness = czm_luminance(sceneColor.rgb);\n // blue in the shade, warm in the sun, with the relief of the scene kept in the brightness\n vec3 lit = mix(SNOW_SHADE, SNOW_SUN, sun) * clamp(0.68 + 0.25 * sun + 0.1 * brightness, 0.0, 1.0);\n out_FragColor = vec4(mix(sceneColor.rgb, lit, snow), sceneColor.a);\n}\n";
package/snow-line.js CHANGED
@@ -1,4 +1,4 @@
1
- import {Math as CesiumMath} from '@cesium/core';
1
+ import {Cartesian3, Math as CesiumMath, Matrix4} from '@cesium/core';
2
2
  import {PostProcessStage} from '@cesium/engine';
3
3
  import {acquireTerrainDepth, releaseTerrainDepth} from './depth-test.js';
4
4
  import Effect from './effect.js';
@@ -8,6 +8,11 @@ import Height from './shaders/Height.js';
8
8
  import Normal from './shaders/Normal.js';
9
9
  import SnowLineShader from './shaders/SnowLine.js';
10
10
 
11
+ // the water's level when there is no water: far above everything
12
+ const NO_WATER = 1e9;
13
+
14
+ const sunScratch = new Cartesian3();
15
+
11
16
  /**
12
17
  * Snow above an altitude, on the slopes gentle enough to hold it, shaded by
13
18
  * the sun.
@@ -15,7 +20,8 @@ import SnowLineShader from './shaders/SnowLine.js';
15
20
  export default class SnowLine extends Effect {
16
21
  /**
17
22
  * @param {import('@cesium/engine').CesiumWidget} viewer
18
- * @param {{altitude?: number, transition?: number, maxSlope?: number, coverage?: number}} [options]
23
+ * @param {{altitude?: number, transition?: number, maxSlope?: number, coverage?: number, patchiness?: number, waterLevel?: number,
24
+ * sunDirection?: Cartesian3}} [options]
19
25
  */
20
26
  constructor(viewer, options = {}) {
21
27
  super(viewer);
@@ -23,6 +29,9 @@ export default class SnowLine extends Effect {
23
29
  this.transition_ = options.transition ?? 200;
24
30
  this.maxSlope_ = options.maxSlope ?? 50;
25
31
  this.coverage_ = options.coverage ?? 1;
32
+ this.patchiness_ = options.patchiness ?? 0;
33
+ this.waterLevel_ = options.waterLevel;
34
+ this.sunDirection_ = options.sunDirection && Cartesian3.normalize(options.sunDirection, new Cartesian3());
26
35
  }
27
36
 
28
37
  /**
@@ -38,6 +47,10 @@ export default class SnowLine extends Effect {
38
47
  transition: () => this.transition_,
39
48
  maxSlope: () => CesiumMath.toRadians(this.maxSlope_),
40
49
  coverage: () => this.coverage_,
50
+ patchiness: () => this.patchiness_,
51
+ waterLevel: () => this.waterLevel_ ?? NO_WATER,
52
+ sunEC: () => this.sunDirection_ ? Matrix4.multiplyByPointAsVector(scene.camera.viewMatrix, this.sunDirection_, sunScratch) : sunScratch,
53
+ ownSun: () => this.sunDirection_ ? 1 : 0,
41
54
  },
42
55
  });
43
56
  }
@@ -105,4 +118,43 @@ export default class SnowLine extends Effect {
105
118
  this.coverage_ = value;
106
119
  this.viewer.scene.requestRender();
107
120
  }
121
+
122
+ /**
123
+ * How far the coverage thins the snow into patches, 0 to 1: at 0 the coverage is the snow's opacity, at 1 the share of the ground it
124
+ * covers, the noise leaving the rest bare.
125
+ */
126
+ get patchiness() {
127
+ return this.patchiness_;
128
+ }
129
+
130
+ set patchiness(value) {
131
+ this.patchiness_ = value;
132
+ this.viewer.scene.requestRender();
133
+ }
134
+
135
+ /**
136
+ * The water's height above the ellipsoid, in meters, or undefined for none: what lies within a quarter meter of it holds no snow,
137
+ * so that a flat lake stays water.
138
+ */
139
+ get waterLevel() {
140
+ return this.waterLevel_;
141
+ }
142
+
143
+ set waterLevel(value) {
144
+ this.waterLevel_ = value;
145
+ this.viewer.scene.requestRender();
146
+ }
147
+
148
+ /**
149
+ * The direction toward the sun that the snow is shaded by, in world coordinates, or undefined for Cesium's own sun. For a scene lit by a
150
+ * light of its own: `scene.light.direction` is the way the light travels, so give its negation.
151
+ */
152
+ get sunDirection() {
153
+ return this.sunDirection_;
154
+ }
155
+
156
+ set sunDirection(value) {
157
+ this.sunDirection_ = value && Cartesian3.normalize(value, new Cartesian3());
158
+ this.viewer.scene.requestRender();
159
+ }
108
160
  }