@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.
- package/LICENSE +29 -0
- package/README.md +235 -2
- package/analog-video.js +138 -0
- package/blur.js +39 -0
- package/cesium-post-process.js +23 -0
- package/color-isolation.js +69 -0
- package/depth-test.js +37 -0
- package/digital-video.js +108 -0
- package/drone-display.js +170 -0
- package/focus.js +25 -0
- package/frame-clock.js +61 -0
- package/height.js +23 -0
- package/infrared.js +40 -0
- package/jello.js +146 -0
- package/lens-distortion.js +68 -0
- package/lightning.js +321 -0
- package/motion-blur.js +117 -0
- package/package.json +49 -4
- package/precipitation.js +493 -0
- package/shaders/Alpenglow.js +2 -0
- package/shaders/AnalogVideo.js +2 -0
- package/shaders/Beam.js +2 -0
- package/shaders/CloudsBlur.js +2 -0
- package/shaders/CloudsComposite.js +2 -0
- package/shaders/CloudsDensity.js +2 -0
- package/shaders/CloudsDepth.js +2 -0
- package/shaders/CloudsMarch.js +2 -0
- package/shaders/CloudsResolve.js +2 -0
- package/shaders/CloudsShadow.js +2 -0
- package/shaders/CloudsShadowMap.js +2 -0
- package/shaders/CloudsSlab.js +2 -0
- package/shaders/ColorIsolation.js +2 -0
- package/shaders/DepthEdges.js +2 -0
- package/shaders/DigitalVideo.js +2 -0
- package/shaders/DroneDisplay.js +2 -0
- package/shaders/EyeFromDepth.js +2 -0
- package/shaders/Filmic.js +2 -0
- package/shaders/Flashlight.js +2 -0
- package/shaders/Fog.js +2 -0
- package/shaders/Frame.js +2 -0
- package/shaders/Hash.js +2 -0
- package/shaders/Height.js +2 -0
- package/shaders/Hsl.js +2 -0
- package/shaders/Infrared.js +2 -0
- package/shaders/Jello.js +2 -0
- package/shaders/LensDistortion.js +2 -0
- package/shaders/Lightning.js +2 -0
- package/shaders/MotionBlur.js +2 -0
- package/shaders/MotionBlurVelocity.js +2 -0
- package/shaders/Noise.js +2 -0
- package/shaders/Normal.js +2 -0
- package/shaders/Phase.js +2 -0
- package/shaders/Precipitation.js +2 -0
- package/shaders/PrecipitationShaftBlur.js +2 -0
- package/shaders/PrecipitationShafts.js +2 -0
- package/shaders/PrecipitationWind.js +2 -0
- package/shaders/RainHaze.js +2 -0
- package/shaders/Shockwave.js +2 -0
- package/shaders/SkyTable.js +2 -0
- package/shaders/SnowLine.js +2 -0
- package/shaders/SpeedLines.js +2 -0
- package/shaders/Spotlight.js +2 -0
- package/shaders/SpotlightBeam.js +2 -0
- package/shaders/SpotlightBeamBlur.js +2 -0
- package/shaders/SpotlightCone.js +2 -0
- package/shaders/Super8.js +2 -0
- package/shaders/Technicolor.js +2 -0
- package/shaders/TiltShift.js +2 -0
- package/shaders/Toy.js +2 -0
- package/shaders/Trails.js +2 -0
- package/shaders/ValleyFog.js +2 -0
- package/shaders/WeatherMap.js +2 -0
- package/shaders/WhiteModel.js +2 -0
- package/snow-line.js +108 -0
- package/speed-lines.js +71 -0
- package/spotlight.js +193 -0
- package/strikes.js +30 -0
- package/super8.js +164 -0
- package/technicolor.js +56 -0
- package/tilt-shift.js +124 -0
- package/toy.js +192 -0
- package/valley-fog.js +105 -0
- package/white-model.js +105 -0
package/precipitation.js
ADDED
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
import {Cartesian3, Cartesian4} from '@cesium/core';
|
|
2
|
+
import {PostProcessStage, PostProcessStageComposite} from '@cesium/engine';
|
|
3
|
+
// @ts-expect-error the engine exports its textures, framebuffers and render states, but does not type them
|
|
4
|
+
import {Framebuffer, RenderState, Texture} from '@cesium/engine';
|
|
5
|
+
import {cloudShadow, hasCloudCover} from './cloud-cover.js';
|
|
6
|
+
import {acquireTerrainDepth, releaseTerrainDepth} from './depth-test.js';
|
|
7
|
+
import Effect from './effect.js';
|
|
8
|
+
import {acquireFrames, releaseFrames} from './frame-clock.js';
|
|
9
|
+
import {heightUniforms} from './height.js';
|
|
10
|
+
import CloudsShadow from './shaders/CloudsShadow.js';
|
|
11
|
+
import EyeFromDepth from './shaders/EyeFromDepth.js';
|
|
12
|
+
import Fog from './shaders/Fog.js';
|
|
13
|
+
import Hash from './shaders/Hash.js';
|
|
14
|
+
import Height from './shaders/Height.js';
|
|
15
|
+
import Noise from './shaders/Noise.js';
|
|
16
|
+
import Phase from './shaders/Phase.js';
|
|
17
|
+
import PrecipitationShader from './shaders/Precipitation.js';
|
|
18
|
+
import PrecipitationShaftBlur from './shaders/PrecipitationShaftBlur.js';
|
|
19
|
+
import PrecipitationShafts from './shaders/PrecipitationShafts.js';
|
|
20
|
+
import PrecipitationWind from './shaders/PrecipitationWind.js';
|
|
21
|
+
import RainHaze from './shaders/RainHaze.js';
|
|
22
|
+
import SkyTable from './shaders/SkyTable.js';
|
|
23
|
+
import WeatherMapShader from './shaders/WeatherMap.js';
|
|
24
|
+
import WeatherMap from './weather-map.js';
|
|
25
|
+
|
|
26
|
+
// the streaks are the drops' motion over a frame's exposure
|
|
27
|
+
const FRAME_RATE = 30;
|
|
28
|
+
// the shader's time wraps at an hour, a whole number of the periods of its gusts and its flutter
|
|
29
|
+
const TIME_PERIOD = 3600;
|
|
30
|
+
// terminal velocity of the drops and of the flakes, in m/s, at speed 1; in between, geometrically,
|
|
31
|
+
// so that the slush at half is about 3 m/s
|
|
32
|
+
const RAIN_VELOCITY = 9;
|
|
33
|
+
const SNOW_VELOCITY = 1;
|
|
34
|
+
// the cells along the fall per radian at the nearest layer's distance: taller than wide in the rain,
|
|
35
|
+
// where the streaks are long, square in the snow, in between geometrically, and that distance, in
|
|
36
|
+
// meters (Precipitation.glsl's NEAREST): the grid scrolls by the speed over the distance, in
|
|
37
|
+
// radians per second, times the cells per radian
|
|
38
|
+
const RAIN_CELLS_ALONG = 4.44;
|
|
39
|
+
const SNOW_CELLS_ALONG = 20;
|
|
40
|
+
const NEAREST = 2;
|
|
41
|
+
// the exposure of the drops and of the flakes, in seconds, in between geometrically: a streak is the
|
|
42
|
+
// motion over it, a 1/30 s video frame for the drops; the flakes, at 1 m/s, would be half as long as
|
|
43
|
+
// wide at 1/30 s and look like streaks, not like flakes: a shorter exposure keeps them round
|
|
44
|
+
const RAIN_EXPOSURE = 1 / 30;
|
|
45
|
+
const SNOW_EXPOSURE = 1 / 90;
|
|
46
|
+
// the longest trail, in cells: Precipitation.glsl looks one cell above each pixel's for it
|
|
47
|
+
const MAX_TRAIL = 1;
|
|
48
|
+
const TWO_PI = 2 * Math.PI;
|
|
49
|
+
// the layers of drops, 2 to 16 m away, a drop every 0.05 rad around at the horizon in the nearest
|
|
50
|
+
// one, and the radius of its flakes in pixels, halved in each further layer (Precipitation.glsl's
|
|
51
|
+
// LAYERS, NEAREST, and the rest of its drops)
|
|
52
|
+
const LAYERS = 4;
|
|
53
|
+
const CELLS_AROUND = 20;
|
|
54
|
+
const FLAKE_RADIUS = 4;
|
|
55
|
+
// Precipitation.glsl's PERIOD, the cells after which the grid repeats
|
|
56
|
+
const PERIOD = 1000;
|
|
57
|
+
// longest time between two frames, in seconds, after a pause in requestRenderMode
|
|
58
|
+
const MAX_STEP = 0.1;
|
|
59
|
+
// the shafts are kilometers wide and soft: an eighth of the resolution, a 64th of the pixels, is
|
|
60
|
+
// enough for their raymarch and their blur (Precipitation.glsl's SHAFT_SCALE)
|
|
61
|
+
const SHAFT_SCALE = 0.125;
|
|
62
|
+
// the sky table's texels toward the horizon, besides the one overhead, RainHaze.glsl's SKY_TABLE
|
|
63
|
+
const SKY_TABLE = 64;
|
|
64
|
+
// the sunlight under the clouds, as a share of its intensity
|
|
65
|
+
const OVERCAST = 0.5;
|
|
66
|
+
// the camera goes from under the clouds to above them over this height around the cloud base, in
|
|
67
|
+
// meters, as the drops in Precipitation.glsl
|
|
68
|
+
const CLOUD_BASE_FADE = 50;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* GLSL's smoothstep.
|
|
72
|
+
* @param {number} edge0
|
|
73
|
+
* @param {number} edge1
|
|
74
|
+
* @param {number} x
|
|
75
|
+
*/
|
|
76
|
+
function smoothstep(edge0, edge1, x) {
|
|
77
|
+
const t = Math.min(Math.max((x - edge0) / (edge1 - edge0), 0), 1);
|
|
78
|
+
return t * t * (3 - 2 * t);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @typedef {import('./weather-map.js').WeatherMapOptions} PrecipitationMapOptions
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
// the scenes under clouds, with their light and shadows before: rain and snow may be active at
|
|
86
|
+
// once, the first one dims the light, the last one restores it
|
|
87
|
+
/** @type {WeakMap<import('@cesium/engine').Scene, {count: number, light: import('@cesium/engine').Light, intensity: number, shadows: boolean}>} */
|
|
88
|
+
const overcast = new WeakMap();
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {import('@cesium/engine').Scene} scene
|
|
92
|
+
*/
|
|
93
|
+
function acquireOvercast(scene) {
|
|
94
|
+
const clouds = overcast.get(scene);
|
|
95
|
+
if (clouds) {
|
|
96
|
+
clouds.count++;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
overcast.set(scene, {count: 1, light: scene.light, intensity: scene.light.intensity, shadows: scene.shadowMap.enabled});
|
|
100
|
+
scene.light.intensity *= OVERCAST;
|
|
101
|
+
scene.shadowMap.enabled = false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The sunlight and the shadows under the clouds, in proportion to how hard it rains at the camera.
|
|
106
|
+
* @param {import('@cesium/engine').Scene} scene
|
|
107
|
+
* @param {number} local
|
|
108
|
+
*/
|
|
109
|
+
function shadeOvercast(scene, local) {
|
|
110
|
+
const clouds = overcast.get(scene);
|
|
111
|
+
if (!clouds) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
clouds.light.intensity = clouds.intensity * (1 - (1 - OVERCAST) * local);
|
|
115
|
+
scene.shadowMap.enabled = clouds.shadows && local < 0.5;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @param {import('@cesium/engine').Scene} scene
|
|
120
|
+
*/
|
|
121
|
+
function releaseOvercast(scene) {
|
|
122
|
+
const clouds = overcast.get(scene);
|
|
123
|
+
if (!clouds || --clouds.count > 0) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
clouds.light.intensity = clouds.intensity;
|
|
127
|
+
scene.shadowMap.enabled = clouds.shadows;
|
|
128
|
+
overcast.delete(scene);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Precipitation, from rain to snow: streaks of falling drops, or soft
|
|
133
|
+
* fluttering flakes, in layers at different distances over the whole scene,
|
|
134
|
+
* hidden behind what is nearer, a haze veiling the distance and shafts of
|
|
135
|
+
* heavier precipitation below the cloud base, in a gusting wind. Between rain
|
|
136
|
+
* and snow, the same drops slow down, shorten and soften, as in sleet. While
|
|
137
|
+
* active, the sky is overcast: no shadows, and a dimmer sunlight. The
|
|
138
|
+
* precipitation falls, so while active with an intensity the scene renders at
|
|
139
|
+
* 30 frames per second, also in requestRenderMode. With a map, it falls where
|
|
140
|
+
* the map says, in cells seen from outside through their shafts and haze,
|
|
141
|
+
* and the sky is overcast in proportion to the map at the camera, but not
|
|
142
|
+
* above the cloud base.
|
|
143
|
+
*/
|
|
144
|
+
export default class Precipitation extends Effect {
|
|
145
|
+
/**
|
|
146
|
+
* @param {import('@cesium/engine').CesiumWidget} viewer
|
|
147
|
+
* @param {{intensity?: number, wind?: number, speed?: number, cloudBase?: number, snow?: number, map?: PrecipitationMapOptions}} [options]
|
|
148
|
+
*/
|
|
149
|
+
constructor(viewer, options = {}) {
|
|
150
|
+
super(viewer);
|
|
151
|
+
this.intensity_ = options.intensity ?? 0.5;
|
|
152
|
+
this.wind_ = options.wind ?? 10;
|
|
153
|
+
this.speed_ = options.speed ?? 1;
|
|
154
|
+
this.cloudBase_ = options.cloudBase ?? 2500;
|
|
155
|
+
this.snow_ = options.snow ?? 0;
|
|
156
|
+
// without a map, it rains everywhere
|
|
157
|
+
this.weatherMap_ = new WeatherMap(1);
|
|
158
|
+
this.weatherMap_.map = options.map;
|
|
159
|
+
// the map's intensity at the camera, read each frame
|
|
160
|
+
this.localIntensity_ = 1;
|
|
161
|
+
// the scroll of the grid, added up from frame to frame: the speed changes with the snow, and
|
|
162
|
+
// time times the speed would move all the drops when it does
|
|
163
|
+
this.offset_ = 0;
|
|
164
|
+
this.lastTime_ = 0;
|
|
165
|
+
// what the layers share in the shader, computed here once rather than for each pixel
|
|
166
|
+
this.layerShape_ = Array.from({length: LAYERS}, () => new Cartesian4());
|
|
167
|
+
this.layerLook_ = Array.from({length: LAYERS}, () => new Cartesian4());
|
|
168
|
+
this.onPreRender_ = () => {
|
|
169
|
+
const now = performance.now() / 1000;
|
|
170
|
+
const dt = Math.min(now - this.lastTime_, MAX_STEP);
|
|
171
|
+
this.lastTime_ = now;
|
|
172
|
+
this.offset_ = (this.offset_ + (dt * this.fallSpeed_() * this.cellsAlong_()) / NEAREST) % PERIOD;
|
|
173
|
+
this.updateLayers_();
|
|
174
|
+
const {longitude, latitude, height} = this.viewer.scene.camera.positionCartographic;
|
|
175
|
+
this.localIntensity_ = this.weatherMap_.intensityAt(longitude, latitude);
|
|
176
|
+
// above the clouds, the sun shines
|
|
177
|
+
const below = 1 - smoothstep(this.cloudBase_ - CLOUD_BASE_FADE, this.cloudBase_ + CLOUD_BASE_FADE, height);
|
|
178
|
+
shadeOvercast(this.viewer.scene, this.localIntensity_ * below);
|
|
179
|
+
this.drawSkyTable_(this.viewer.scene);
|
|
180
|
+
};
|
|
181
|
+
// the clear sky the haze scatters, drawn at each frame (SkyTable.glsl)
|
|
182
|
+
/** @type {any} */
|
|
183
|
+
this.skyTable_ = undefined;
|
|
184
|
+
/** @type {any} */
|
|
185
|
+
this.skyTableFramebuffer_ = undefined;
|
|
186
|
+
/** @type {any} */
|
|
187
|
+
this.skyTableCommand_ = undefined;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Draws the sky table into its own texture before the stages run.
|
|
192
|
+
* @param {import('@cesium/engine').Scene} scene
|
|
193
|
+
*/
|
|
194
|
+
drawSkyTable_(scene) {
|
|
195
|
+
// @ts-expect-error the scene's context is not typed
|
|
196
|
+
const context = scene.context;
|
|
197
|
+
if (!context) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (!this.skyTable_) {
|
|
201
|
+
this.skyTable_ = new Texture({context, width: SKY_TABLE + 1, height: 1});
|
|
202
|
+
this.skyTableFramebuffer_ = new Framebuffer({context, colorTextures: [this.skyTable_], destroyAttachments: false});
|
|
203
|
+
this.skyTableCommand_ = context.createViewportQuadCommand(Height + Fog + Phase + RainHaze + SkyTable, {
|
|
204
|
+
framebuffer: this.skyTableFramebuffer_,
|
|
205
|
+
renderState: RenderState.fromCache({viewport: {x: 0, y: 0, width: SKY_TABLE + 1, height: 1}}),
|
|
206
|
+
uniformMap: {
|
|
207
|
+
skyLightIntensity: () => scene.skyAtmosphere?.atmosphereLightIntensity ?? 50,
|
|
208
|
+
cloudBase: () => this.cloudBase_,
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
this.skyTableCommand_.execute(context);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
destroySkyTable_() {
|
|
216
|
+
this.skyTableCommand_?.shaderProgram?.destroy();
|
|
217
|
+
this.skyTableCommand_ = undefined;
|
|
218
|
+
this.skyTableFramebuffer_ = this.skyTableFramebuffer_?.destroy();
|
|
219
|
+
this.skyTable_ = this.skyTable_?.destroy();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Speed of the drops or flakes, in m/s.
|
|
224
|
+
*/
|
|
225
|
+
fallSpeed_() {
|
|
226
|
+
return this.speed_ * RAIN_VELOCITY * (SNOW_VELOCITY / RAIN_VELOCITY) ** this.snow_;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* The layers' uniforms from the snow and the cells along the fall.
|
|
231
|
+
*/
|
|
232
|
+
updateLayers_() {
|
|
233
|
+
const cellsAlong = this.cellsAlong_();
|
|
234
|
+
for (let i = 0; i < LAYERS; i++) {
|
|
235
|
+
const scale = 2 ** i;
|
|
236
|
+
// a whole number of cells around, so that they close up
|
|
237
|
+
const count = Math.floor(TWO_PI * CELLS_AROUND * scale + 0.5);
|
|
238
|
+
Cartesian4.fromElements(count / TWO_PI, cellsAlong * scale, count, NEAREST * scale, this.layerShape_[i]);
|
|
239
|
+
const rainRadius = Math.max(0.5, 1 - 0.25 * i);
|
|
240
|
+
const flakeRadius = Math.max(FLAKE_RADIUS / scale, 0.75);
|
|
241
|
+
const rainBrightness = 0.45 - 0.07 * i;
|
|
242
|
+
const snowBrightness = 0.8 - 0.12 * i;
|
|
243
|
+
// the largest flake and its soft edge, the nearest layer's wider, or the thin streak and its
|
|
244
|
+
const flakeReach = i === 0 ? 1.4 * flakeRadius * 1.8 : 1.4 * flakeRadius + 0.5;
|
|
245
|
+
Cartesian4.fromElements(
|
|
246
|
+
rainBrightness + (snowBrightness - rainBrightness) * this.snow_,
|
|
247
|
+
rainRadius + 0.5 + (flakeReach - rainRadius - 0.5) * this.snow_,
|
|
248
|
+
rainRadius,
|
|
249
|
+
flakeRadius,
|
|
250
|
+
this.layerLook_[i],
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* The length of a drop's trail, in cells: its motion over the exposure.
|
|
257
|
+
*/
|
|
258
|
+
trail_() {
|
|
259
|
+
const exposure = RAIN_EXPOSURE * (SNOW_EXPOSURE / RAIN_EXPOSURE) ** this.snow_;
|
|
260
|
+
return Math.min((this.fallSpeed_() * exposure * this.cellsAlong_()) / NEAREST, MAX_TRAIL);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* The cells of the grid along the fall per radian, at the nearest layer's distance.
|
|
265
|
+
*/
|
|
266
|
+
cellsAlong_() {
|
|
267
|
+
return RAIN_CELLS_ALONG * (SNOW_CELLS_ALONG / RAIN_CELLS_ALONG) ** this.snow_;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* @override
|
|
272
|
+
* @param {import('@cesium/engine').Scene} scene
|
|
273
|
+
*/
|
|
274
|
+
createStage_(scene) {
|
|
275
|
+
// PrecipitationWind's uniforms, for both passes
|
|
276
|
+
const shared = {
|
|
277
|
+
...heightUniforms(scene),
|
|
278
|
+
// reduced in double precision: a float would lose the time's fraction of a second
|
|
279
|
+
time: () => (performance.now() / 1000) % TIME_PERIOD,
|
|
280
|
+
wind: () => this.wind_,
|
|
281
|
+
speed: () => this.speed_,
|
|
282
|
+
cloudBase: () => this.cloudBase_,
|
|
283
|
+
intensity: () => this.intensity_,
|
|
284
|
+
snow: () => this.snow_,
|
|
285
|
+
};
|
|
286
|
+
const shafts = new PostProcessStage({
|
|
287
|
+
fragmentShader: EyeFromDepth + Noise + Height + PrecipitationWind + WeatherMapShader + Fog + Phase + RainHaze + CloudsShadow + PrecipitationShafts,
|
|
288
|
+
uniforms: {
|
|
289
|
+
...shared,
|
|
290
|
+
// the shadow map of the clouds in the scene, whose gaps light the haze
|
|
291
|
+
shadowed: () => (cloudShadow(scene) ? 1 : 0),
|
|
292
|
+
// @ts-expect-error the scene's context is not typed
|
|
293
|
+
shadowMap: () => cloudShadow(scene)?.texture() ?? scene.context.defaultTexture,
|
|
294
|
+
shadowCenter: () => cloudShadow(scene)?.center ?? Cartesian3.ZERO,
|
|
295
|
+
shadowExtent: () => cloudShadow(scene)?.extent() ?? 1,
|
|
296
|
+
shadowBase: () => cloudShadow(scene)?.base() ?? 0,
|
|
297
|
+
shadowTop: () => cloudShadow(scene)?.top() ?? 0,
|
|
298
|
+
// @ts-expect-error the scene's context is not typed
|
|
299
|
+
map: () => this.weatherMap_.texture(scene.context),
|
|
300
|
+
// @ts-expect-error the scene's context is not typed
|
|
301
|
+
nextMap: () => this.weatherMap_.nextTexture(scene.context),
|
|
302
|
+
mapBlend: () => this.weatherMap_.blend,
|
|
303
|
+
mapBounds: () => this.weatherMap_.bounds,
|
|
304
|
+
},
|
|
305
|
+
textureScale: SHAFT_SCALE,
|
|
306
|
+
});
|
|
307
|
+
// blurred at the same resolution
|
|
308
|
+
const blurredShafts = new PostProcessStage({
|
|
309
|
+
fragmentShader: PrecipitationShaftBlur,
|
|
310
|
+
uniforms: {shaftTexture: shafts.name},
|
|
311
|
+
textureScale: SHAFT_SCALE,
|
|
312
|
+
});
|
|
313
|
+
const stage = new PostProcessStageComposite({
|
|
314
|
+
stages: [
|
|
315
|
+
shafts,
|
|
316
|
+
blurredShafts,
|
|
317
|
+
new PostProcessStage({
|
|
318
|
+
fragmentShader: EyeFromDepth + Hash + Height + PrecipitationWind + Fog + Phase + RainHaze + PrecipitationShader,
|
|
319
|
+
uniforms: {
|
|
320
|
+
...shared,
|
|
321
|
+
shaftTexture: blurredShafts.name,
|
|
322
|
+
localIntensity: () => this.localIntensity_,
|
|
323
|
+
clouds: () => (hasCloudCover(scene) ? 1 : 0),
|
|
324
|
+
shadowed: () => (cloudShadow(scene) ? 1 : 0),
|
|
325
|
+
// @ts-expect-error the scene's context is not typed
|
|
326
|
+
skyTable: () => this.skyTable_ ?? scene.context.defaultTexture,
|
|
327
|
+
trail: () => this.trail_(),
|
|
328
|
+
layerShape: () => this.layerShape_,
|
|
329
|
+
layerLook: () => this.layerLook_,
|
|
330
|
+
offset: () => this.offset_,
|
|
331
|
+
},
|
|
332
|
+
}),
|
|
333
|
+
],
|
|
334
|
+
// the precipitation is drawn over the scene, not over the shafts
|
|
335
|
+
inputPreviousStageTexture: false,
|
|
336
|
+
});
|
|
337
|
+
// no pass, and no frames, without an intensity
|
|
338
|
+
stage.enabled = this.intensity_ > 0;
|
|
339
|
+
return stage;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* @override
|
|
344
|
+
* @param {import('@cesium/engine').Scene} scene
|
|
345
|
+
*/
|
|
346
|
+
activated_(scene) {
|
|
347
|
+
this.lastTime_ = performance.now() / 1000;
|
|
348
|
+
this.updateLayers_();
|
|
349
|
+
scene.preRender.addEventListener(this.onPreRender_);
|
|
350
|
+
acquireTerrainDepth(scene);
|
|
351
|
+
acquireOvercast(scene);
|
|
352
|
+
if (this.intensity_ > 0) {
|
|
353
|
+
acquireFrames(scene, FRAME_RATE);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* @override
|
|
359
|
+
* @param {import('@cesium/engine').Scene} scene
|
|
360
|
+
*/
|
|
361
|
+
deactivating_(scene) {
|
|
362
|
+
if (this.intensity_ > 0) {
|
|
363
|
+
releaseFrames(scene, FRAME_RATE);
|
|
364
|
+
}
|
|
365
|
+
releaseOvercast(scene);
|
|
366
|
+
releaseTerrainDepth(scene);
|
|
367
|
+
scene.preRender.removeEventListener(this.onPreRender_);
|
|
368
|
+
this.destroySkyTable_();
|
|
369
|
+
this.weatherMap_.destroy();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Where it rains, or undefined for everywhere: an image whose red channel
|
|
374
|
+
* is the local intensity, 0 to 1, over a rectangle of longitudes and
|
|
375
|
+
* latitudes, north up. The precipitation fades in over a pixel of the
|
|
376
|
+
* image. Set it again after drawing on the image, which is read then.
|
|
377
|
+
*/
|
|
378
|
+
get map() {
|
|
379
|
+
return this.weatherMap_.map;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* The map's intensity at the camera, 0 to 1, read each frame while active;
|
|
384
|
+
* 1 without a map.
|
|
385
|
+
*/
|
|
386
|
+
get localIntensity() {
|
|
387
|
+
return this.localIntensity_;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
set map(value) {
|
|
391
|
+
this.weatherMap_.map = value;
|
|
392
|
+
this.viewer.scene.requestRender();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* The map the precipitation crossfades to by the map blend, over the same
|
|
397
|
+
* rectangle, as the radar's next hour, or undefined for none.
|
|
398
|
+
*/
|
|
399
|
+
get nextMap() {
|
|
400
|
+
return this.weatherMap_.nextMap;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
set nextMap(value) {
|
|
404
|
+
this.weatherMap_.nextMap = value;
|
|
405
|
+
this.viewer.scene.requestRender();
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* From the map, 0, to the next map, 1.
|
|
410
|
+
*/
|
|
411
|
+
get mapBlend() {
|
|
412
|
+
return this.weatherMap_.blend;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
set mapBlend(value) {
|
|
416
|
+
this.weatherMap_.blend = value;
|
|
417
|
+
this.viewer.scene.requestRender();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* How hard it rains or snows, 0 to 1: the share of the drops or flakes
|
|
422
|
+
* shown, their opacity, and the haze, from 39 km of visibility to 4.4 km in
|
|
423
|
+
* the rain, taken as 0 to 10 mm/h under a square root, from 20 km to 500 m in
|
|
424
|
+
* the snow.
|
|
425
|
+
*/
|
|
426
|
+
get intensity() {
|
|
427
|
+
return this.intensity_;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
set intensity(value) {
|
|
431
|
+
const raining = this.intensity_ > 0;
|
|
432
|
+
this.intensity_ = value;
|
|
433
|
+
if (this.stage_ && raining !== value > 0) {
|
|
434
|
+
this.stage_.enabled = value > 0;
|
|
435
|
+
(value > 0 ? acquireFrames : releaseFrames)(this.viewer.scene, FRAME_RATE);
|
|
436
|
+
}
|
|
437
|
+
this.viewer.scene.requestRender();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Tilt of the fall from the vertical, toward the east, in degrees.
|
|
442
|
+
*/
|
|
443
|
+
get wind() {
|
|
444
|
+
return this.wind_;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
set wind(value) {
|
|
448
|
+
this.wind_ = value;
|
|
449
|
+
this.viewer.scene.requestRender();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Speed of the precipitation, 1 at its terminal velocity, 9 m/s for the
|
|
454
|
+
* drops and 1 m/s for the flakes: faster drops draw longer streaks.
|
|
455
|
+
*/
|
|
456
|
+
get speed() {
|
|
457
|
+
return this.speed_;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
set speed(value) {
|
|
461
|
+
this.speed_ = value;
|
|
462
|
+
this.viewer.scene.requestRender();
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Altitude of the cloud base, in meters above the ellipsoid: the
|
|
467
|
+
* precipitation falls below it, in shafts, and none falls around a camera
|
|
468
|
+
* above it.
|
|
469
|
+
*/
|
|
470
|
+
get cloudBase() {
|
|
471
|
+
return this.cloudBase_;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
set cloudBase(value) {
|
|
475
|
+
this.cloudBase_ = value;
|
|
476
|
+
this.viewer.scene.requestRender();
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* How much the rain has turned to snow, 0 to 1: the drops slow down from
|
|
481
|
+
* 9 m/s to 1 m/s, shorten and soften into flakes, with the haze, its light
|
|
482
|
+
* and the shafts going from the rain's to the snow's. In between, they are
|
|
483
|
+
* the short, soft streaks of sleet.
|
|
484
|
+
*/
|
|
485
|
+
get snow() {
|
|
486
|
+
return this.snow_;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
set snow(value) {
|
|
490
|
+
this.snow_ = value;
|
|
491
|
+
this.viewer.scene.requestRender();
|
|
492
|
+
}
|
|
493
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// This file is automatically rebuilt from Alpenglow.glsl.
|
|
2
|
+
export default "// The red light of the low sun on the peaks while the valleys are already in shadow. Above an\n// altitude, the ground is lit in rose on the faces away from the sun and in orange on the faces\n// toward it, the scene's colors multiplied rather than replaced so that the relief stays readable;\n// below, the ground turns darker and bluer.\nuniform sampler2D colorTexture;\nuniform sampler2D depthTexture;\n// meters above the ellipsoid: half lit at the altitude, over a band transition meters high\nuniform float altitude;\nuniform float transition;\n// 0 to 1\nuniform float strength;\nin vec2 v_textureCoordinates;\n\n// the light of the glow, little brighter than white in red so that snow turns pink rather than\n// grey without clipping to a flat color\nconst vec3 GLOW_SHADE = vec3(1.05, 0.62, 0.78);\nconst vec3 GLOW_SUN = vec3(1.1, 0.72, 0.5);\n// part of the glow on the faces away from the sun, lit by the pink sky\nconst float SKY_GLOW = 0.45;\n// the valleys in the shadow of the mountains\nconst vec3 VALLEY_SHADE = vec3(0.55, 0.6, 0.78);\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 sun = max(dot(normal, czm_sunDirectionEC), 0.0);\n\n float high = smoothstep(altitude - 0.5 * transition, altitude + 0.5 * transition, heightAt(eye.xyz));\n vec3 glow = mix(GLOW_SHADE, GLOW_SUN, sun);\n vec3 light = mix(VALLEY_SHADE, mix(vec3(1.0), glow, mix(SKY_GLOW, 1.0, sun)), high);\n // eye.w is 0 for the sky\n light = mix(vec3(1.0), light, strength * eye.w);\n out_FragColor = vec4(clamp(sceneColor.rgb * light, 0.0, 1.0), sceneColor.a);\n}\n";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// This file is automatically rebuilt from AnalogVideo.glsl.
|
|
2
|
+
export default "// An analog FPV video feed, as in the recordings of FPV pilots' goggles: a soft PAL picture,\n// washed out, its color bleeding sideways (the signal carries the brightness at a higher\n// resolution than the color, yet smeared a little), with ringing beside sharp edges and lines\n// that jitter sideways. As the signal weakens (low over the ground, behind obstacles), the\n// color goes first, the picture washes out toward a cold white and fills with colored streaks:\n// short dashes along the video lines, the clicks of FM video below its threshold. Now and then\n// the receiver loses the sync for a frame or two: the picture tears, a white band and black\n// below it. Without a signal, the receiver shows its own noise: a dark field of colored\n// streaks, the picture and its display gone. After ntsc-rs's chroma lowpass and delay, luma\n// smear and ringing, and the recordings of FPV drones flying low and crashing.\nprecision highp sampler2D;\nuniform sampler2D colorTexture;\n// seconds\nuniform float time;\n// 0 to 1\nuniform float noise;\n// per frame: how weak the signal is now, 0 to 1, 1 without a signal; where the picture tears,\n// as a fraction of its height from the top, or -1\nuniform float weak;\nuniform float tear;\nin vec2 v_textureCoordinates;\n\n// PAL video runs at 25 frames per second, in 576 lines of about 720 samples\nconst float FRAME_RATE = 25.0;\nconst vec2 VIDEO = vec2(720.0, 576.0);\n// in video samples: the color spread on either side, and shifted to the right; the overshoot\n// beside edges; the brightness trailing to the right, as through a lowpass filter\nconst float BLEED = 3.0;\nconst float BLEED_SHIFT = 1.0;\nconst float RING_DISTANCE = 1.5;\nconst float RING = 0.5;\nconst float SMEAR = 1.0;\n// the white band of a tear, as a fraction of the picture height\nconst float TEAR_BAND = 0.07;\n\n// BT.601 luma and color differences\nvec3 toYuv(vec3 rgb) {\n float y = dot(rgb, vec3(0.299, 0.587, 0.114));\n return vec3(y, 0.492 * (rgb.b - y), 0.877 * (rgb.r - y));\n}\n\nvec3 toRgb(vec3 yuv) {\n float b = yuv.x + yuv.y / 0.492;\n float r = yuv.x + yuv.z / 0.877;\n return vec3(r, (yuv.x - 0.299 * r - 0.114 * b) / 0.587, b);\n}\n\nfloat luma(vec2 uv) {\n return toYuv(texture(colorTexture, uv).rgb).x;\n}\n\n// the streaks of one layer on a video line: at most one starts in each cell of the line, and runs\n// for up to maxSpan samples, shorter than a cell so that only the previous cell reaches this one;\n// with this chance per cell; its color, and its coverage in alpha\nvec4 streaks(float x, float line, float frame, float cell, float maxSpan, float chance, float seed) {\n float index = floor(x / cell);\n vec4 streak = vec4(0.0);\n for (int i = 0; i < 2; i++) {\n // a new pattern each frame, offset by more cells than a line holds so that frames do not repeat\n vec2 id = vec2(index - float(i) + frame * 61.0 + seed, line);\n float start = (index - float(i) + hash(id + 0.5)) * cell;\n float span = 2.0 + (maxSpan - 2.0) * hash(id + 1.5) * hash(id + 1.5);\n float along = (x - start) / span;\n float on = step(hash(id), chance) * step(0.0, along) * step(along, 1.0);\n float shape = on * smoothstep(0.0, 0.15, along) * (1.0 - smoothstep(0.7, 1.0, along));\n if (shape > streak.a) {\n // bright, whitish or colored: magenta, green, blue, yellow\n float angle = 6.2832 * hash(id + 3.5);\n vec3 yuv = vec3(0.55 + 0.45 * hash(id + 2.5), 0.22 * hash(id + 4.5) * vec2(cos(angle), sin(angle)));\n streak = vec4(toRgb(yuv), shape);\n }\n }\n return streak;\n}\n\nvoid main() {\n float frame = mod(floor(time * FRAME_RATE), 1000.0);\n // the video line, counted from the top, and the sample along it\n float fromTop = 1.0 - v_textureCoordinates.y;\n float line = floor(fromTop * VIDEO.y);\n // the lines jitter sideways, more as the signal weakens\n float shake = (hash(vec2(line, frame)) - 0.5) * (0.4 + 2.0 * weak);\n vec2 uv = vec2(v_textureCoordinates.x + shake / VIDEO.x, 1.0 - (line + 0.5) / VIDEO.y);\n float x = uv.x * VIDEO.x;\n\n // the brightness smeared to the right, with ringing beside the edges; the color averaged sideways\n // and shifted\n vec4 sceneColor = texture(colorTexture, uv);\n vec3 yuv = toYuv(sceneColor.rgb);\n float texel = 1.0 / VIDEO.x;\n yuv.x = 0.5 * yuv.x + 0.3 * luma(uv - vec2(0.5 * SMEAR * texel, 0.0)) + 0.2 * luma(uv - vec2(SMEAR * texel, 0.0));\n float left = luma(uv - vec2(RING_DISTANCE * texel, 0.0));\n float right = luma(uv + vec2(RING_DISTANCE * texel, 0.0));\n yuv.x += RING * (yuv.x - 0.5 * (left + right));\n vec2 color = vec2(0.0);\n for (int i = -2; i <= 2; i++) {\n float offset = (BLEED_SHIFT + BLEED * float(i) / 2.0) * texel;\n color += toYuv(texture(colorTexture, uv - vec2(offset, 0.0)).rgb).yz;\n }\n yuv.yz = color / 5.0;\n\n // washed out: less color and contrast, lifted blacks; as the signal weakens, the color lost and\n // the picture washed out toward a cold white\n float wash = smoothstep(0.4, 0.85, weak);\n yuv.yz *= 0.75 * (1.0 - smoothstep(0.35, 0.6, weak));\n yuv.x = 0.06 + 0.86 * yuv.x;\n yuv.x = mix(yuv.x, 0.55 + 0.45 * yuv.x, 0.6 * wash);\n yuv.yz += vec2(0.04, -0.02) * wash;\n vec3 rgb = toRgb(yuv);\n\n // grain, one value per video sample and a new pattern each frame, heavier in the dark\n float grain = hash(vec2(floor(x), line) + vec2(frame * 37.0, frame * 17.0)) - 0.5;\n rgb += grain * noise * 0.2 * (1.0 - 0.6 * yuv.x);\n\n // the sync lost: below the tear, a white band, then black\n if (tear >= 0.0) {\n float below = step(tear, fromTop);\n rgb = mix(rgb, vec3(0.02 + 0.05 * grain), below);\n rgb = mix(rgb, vec3(0.92), below * (1.0 - smoothstep(0.0, TEAR_BAND, fromTop - tear)));\n }\n\n // no signal: the receiver's dark noise instead of the picture\n rgb = mix(rgb, vec3(0.08 + 0.12 * grain), smoothstep(0.92, 1.0, weak));\n\n // the streaks, rare in a good signal, everywhere without one: long ones, and short ones as the\n // signal fails\n vec4 longStreak = streaks(x, line, frame, 24.0, 18.0, 0.35 * weak * weak * weak, 0.0);\n vec4 shortStreak = streaks(x, line, frame, 8.0, 6.0, 0.6 * smoothstep(0.5, 1.0, weak), 500.0);\n vec4 streak = longStreak.a > shortStreak.a ? longStreak : shortStreak;\n rgb = mix(rgb, streak.rgb, 0.85 * streak.a);\n out_FragColor = vec4(clamp(rgb, 0.0, 1.0), sceneColor.a);\n}\n";
|
package/shaders/Beam.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// This file is automatically rebuilt from Beam.glsl.
|
|
2
|
+
export default "// The beam of a cone of light in the haze, for the spotlight and for lights of your own:\n// raymarched along the part of the view ray inside the cone, found analytically, so that no\n// sample is wasted and pixels next to each other see the same stretch of it. Needs Hash, and\n// Noise for the dust.\n\nstruct Beam {\n // the cone: its apex and the direction it points, in eye coordinates\n vec3 apex;\n vec3 forward;\n // cosines of its half angle, and of the angle within which its core is hot\n float edgeCos;\n float coreCos;\n // brightness of the core over the rest of the cone, 0 for a plain penumbra\n float hotCore;\n // the light's position, from which the haze's brightness falls off as reference^2 over\n // (core^2 + distance^2): 1 at the reference distance for a small core, softened within it\n vec3 light;\n float reference;\n float core;\n // how far along the view ray the haze is lit, meters, and its extinction on the way, per meter\n float reach;\n float extinction;\n // Henyey-Greenstein asymmetry, 0 to 1: forward scattering\n float anisotropy;\n // exponent of the spacing of the samples along the ray: 1 for even, 2 for dense at the start\n float spacing;\n // the lit haze fades in over this stretch of the view ray, meters; none when empty\n vec2 nearFade;\n // dust catching the light: noise across the beam, constant along each line from the light so\n // that it streaks, in world coordinates so that it stays when the camera turns; its contrast\n // and its density across the width of the beam\n float dust;\n float dustScale;\n};\n\nconst int BEAM_SAMPLES = 8;\n\n// the part of the view ray, from the camera at the origin, inside the cone: entry and exit\n// distances, exit before entry when the ray misses it\nvec2 coneSegment(vec3 ray, vec3 apex, vec3 forward, float edgeCos) {\n // points t * ray in the cone: dot(p, forward)^2 >= edgeCos^2 dot(p, p), with p from the apex\n vec3 v = -apex;\n float k2 = edgeCos * edgeCos;\n float rd = dot(ray, forward);\n float vd = dot(v, forward);\n float a = rd * rd - k2;\n // a ray along the surface of the cone would divide by zero\n a = abs(a) < 1e-7 ? -1e-7 : a;\n float b = rd * vd - k2 * dot(ray, v);\n float c = vd * vd - k2 * dot(v, v);\n float discriminant = b * b - a * c;\n if (discriminant < 0.0) {\n return vec2(1.0, 0.0);\n }\n float root = sqrt(discriminant);\n float t1 = (-b - root) / a;\n float t2 = (-b + root) / a;\n vec2 segment = a < 0.0 ? vec2(min(t1, t2), max(t1, t2))\n // a ray steeper than the cone stays in it on one side, of the lit nappe or the other one\n : rd > 0.0 ? vec2(max(t1, t2), 1e30) : vec2(-1e30, min(t1, t2));\n // ahead of the apex, not in the mirrored cone behind it\n return vd + 0.5 * (segment.x + segment.y) * rd > 0.0 ? segment : vec2(1.0, 0.0);\n}\n\n// Henyey-Greenstein phase, normalized over the sphere\nfloat henyeyGreenstein(float cosTheta, float g) {\n float g2 = g * g;\n return (1.0 - g2) / (4.0 * czm_pi * pow(1.0 + g2 - 2.0 * g * cosTheta, 1.5));\n}\n\n// the haze lit by the beam along the view ray up to sceneDistance: samples over the part of the\n// ray inside the cone, each weighted by the softness of the cone with its core, the dust, the\n// falloff from the light and what the haze has dimmed so far, dithered per pixel so that they do\n// not band, then scattered toward the camera\nfloat beamAlong(vec3 ray, float sceneDistance, Beam beam) {\n vec2 segment = coneSegment(ray, beam.apex, beam.forward, beam.edgeCos);\n float start = max(segment.x, 0.0);\n float end = min(min(segment.y, sceneDistance), beam.reach);\n if (end <= start) {\n return 0.0;\n }\n float length_ = end - start;\n float jitter = pixelNoise(gl_FragCoord.xy);\n float scattered = 0.0;\n float transmittance = exp(-beam.extinction * start);\n for (int i = 0; i < BEAM_SAMPLES; i++) {\n float u = (float(i) + jitter) / float(BEAM_SAMPLES);\n float t = start + length_ * pow(u, beam.spacing);\n // pow(0, 0) is undefined, a NaN on some GPUs, where the jitter is 0\n float step = length_ * beam.spacing * pow(max(u, 1e-6), beam.spacing - 1.0) / float(BEAM_SAMPLES);\n vec3 p = ray * t;\n vec3 fromApex = p - beam.apex;\n float cosine = dot(fromApex, beam.forward) / length(fromApex);\n float cone = smoothstep(beam.edgeCos, beam.coreCos, cosine) + beam.hotCore * smoothstep(beam.coreCos, 1.0, cosine);\n vec3 fromLight = p - beam.light;\n if (beam.dust > 0.0) {\n float along = max(dot(fromLight, beam.forward), 1e-3);\n vec3 section = czm_inverseViewRotation * ((fromLight - along * beam.forward) * 2.0 / along);\n float noise = gradientNoise(beam.dustScale * section) + 0.25 * gradientNoise(2.0 * beam.dustScale * section);\n cone *= 1.0 + beam.dust * noise;\n }\n float falloff = beam.reference * beam.reference / (beam.core * beam.core + dot(fromLight, fromLight));\n float fade = beam.nearFade.y > beam.nearFade.x ? smoothstep(beam.nearFade.x, beam.nearFade.y, t) : 1.0;\n scattered += transmittance * cone * falloff * fade * step;\n transmittance *= exp(-beam.extinction * step);\n }\n return scattered * henyeyGreenstein(dot(ray, beam.forward), beam.anisotropy);\n}\n";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// This file is automatically rebuilt from CloudsBlur.glsl.
|
|
2
|
+
export default "// The clouds blurred over the neighboring texels at the march's resolution, as the precipitation's\n// shafts are: an edge thinner than a texel would otherwise come out of the upscale as a square. A\n// 3 x 3 binomial kernel here costs a 16th of the reads the same blur would take at the full\n// resolution. Only the neighbors at about the texel's depth count, so a cloud over the sky does not\n// bleed onto a ridge in front of it.\nprecision highp sampler2D;\nuniform sampler2D cloudTexture;\n// the farthest distance of the scene in each texel's block of pixels, from CloudsDepth.glsl\nuniform sampler2D cloudDepth;\nin vec2 v_textureCoordinates;\n\n// texels are at the same depth within this share of it, CloudsComposite.glsl's\nconst float DEPTH_TOLERANCE = 0.1;\n\nvoid main() {\n // czm_viewport is this pass's texture, the size of the march's\n vec2 texel = 1.0 / czm_viewport.zw;\n float here = texture(cloudDepth, v_textureCoordinates).r;\n vec4 sum = vec4(0.0);\n float weights = 0.0;\n for (int y = -1; y <= 1; y++) {\n for (int x = -1; x <= 1; x++) {\n vec2 uv = v_textureCoordinates + vec2(float(x), float(y)) * texel;\n float weight = (x == 0 ? 2.0 : 1.0) * (y == 0 ? 2.0 : 1.0) * (abs(texture(cloudDepth, uv).r - here) <= DEPTH_TOLERANCE * here ? 1.0 : 0.0);\n sum += weight * texture(cloudTexture, uv);\n weights += weight;\n }\n }\n out_FragColor = sum / weights;\n}\n";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// This file is automatically rebuilt from CloudsComposite.glsl.
|
|
2
|
+
export default "// The clouds over the scene: the march's samples at a fraction of the resolution, upscaled\n// bilinearly from those whose depth, the farthest of their block of pixels, is about this pixel's,\n// and from the one nearest in depth where none is, rather than weighting the bilinear weights by\n// the depths, which \"can select only one sample, but that sample is not similar to the surface\n// depth you need at all\" (Pesce 2016): a ridge in front of a cloud keeps a sharp outline rather\n// than a halo of the march's resolution. The clouds' shadows darken the scene under them, from the\n// shadow map. Needs EyeFromDepth, Height, CloudsSlab and CloudsShadow.\nprecision highp sampler2D;\nuniform sampler2D colorTexture;\nuniform sampler2D depthTexture;\n// the light the clouds scatter toward the camera, and the share of the scene seen through them\nuniform sampler2D cloudTexture;\n// the farthest distance of the scene in each texel's block of pixels, from CloudsDepth.glsl\nuniform sampler2D cloudDepth;\nin vec2 v_textureCoordinates;\n\n// the march's resolution, clouds.js's CLOUD_SCALE, and the sky's distance, past any terrain,\n// CloudsDepth.glsl's\nconst float CLOUD_SCALE = 0.25;\nconst float SKY_DISTANCE = 1e9;\n// texels are at this pixel's depth within this share of it, CloudsBlur.glsl's\nconst float DEPTH_TOLERANCE = 0.1;\n// the clouds' shadow takes at most this share of the scene's light: the sunlit share of a lit\n// scene, as precipitation.js's OVERCAST\nconst float SHADOW_STRENGTH = 0.5;\n\nvoid main() {\n vec4 sceneColor = texture(colorTexture, v_textureCoordinates);\n vec2 size = ceil(czm_viewport.zw * CLOUD_SCALE);\n vec2 position = v_textureCoordinates * size - 0.5;\n vec2 corner = floor(position);\n vec2 f = position - corner;\n float here = eyeDistance(depthTexture, v_textureCoordinates, SKY_DISTANCE);\n vec4 sum = vec4(0.0);\n float weights = 0.0;\n vec4 nearest = vec4(0.0);\n float nearestGap = 1e30;\n for (int y = 0; y <= 1; y++) {\n for (int x = 0; x <= 1; x++) {\n vec2 uv = (corner + vec2(float(x), float(y)) + 0.5) / size;\n vec4 sampled = texture(cloudTexture, uv);\n float gap = abs(texture(cloudDepth, uv).r - here);\n float weight = gap <= DEPTH_TOLERANCE * here ? (x == 0 ? 1.0 - f.x : f.x) * (y == 0 ? 1.0 - f.y : f.y) : 0.0;\n sum += weight * sampled;\n weights += weight;\n if (gap < nearestGap) {\n nearestGap = gap;\n nearest = sampled;\n }\n }\n }\n vec4 cloud = weights > 1e-4 ? sum / weights : nearest;\n vec4 eye = eyeAt(depthTexture, v_textureCoordinates);\n if (eye.w != 0.0) {\n vec3 positionWC = (czm_inverseView * vec4(eye.xyz, 1.0)).xyz;\n sceneColor.rgb *= mix(1.0 - SHADOW_STRENGTH, 1.0, exp(-shadowDepth(positionWC)));\n }\n out_FragColor = vec4(sceneColor.rgb * cloud.a + cloud.rgb, sceneColor.a);\n}\n";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// This file is automatically rebuilt from CloudsDensity.glsl.
|
|
2
|
+
export default "// The clouds' density at a point, which the march and the shadow map share: as tall and as solid\n// as the rain under it is heavy, shaped by billows of noise that drift with the wind. Needs\n// CloudsSlab and WeatherMap.\n// the noise: the billows of the base shape in red, the details in green and blue\nprecision highp sampler2D;\nuniform highp sampler3D cloudNoise;\n// the noise over the clouds' map, a square that repeats, from cloud-noise.js\nuniform sampler2D mapNoise;\n// meters above the ellipsoid\nuniform float cloudTop;\n// the direction the wind blows toward, and how far it has carried the noise, in world coordinates\nuniform vec3 windDirection;\nuniform vec3 windOffset;\n// scale of the opacity\nuniform float density;\n\n// the clouds over the lightest rain are this thick, in meters, clouds.js's THIN_DEPTH\nconst float THIN_DEPTH = 1500.0;\n// the clouds' base is flat, reaching their full density over this share of their height,\n// remap(h, 0, 0.05, 0, 1): the lifting condensation level is about the same across an air mass; the\n// deck is full up to this share of its height, then thins out to its top; towers thin out all the\n// way up, in the convective envelope pow(1 - h, 1.5) (Schneider 2022); and spread an anvil from\n// this share of their height to this one, their coverage raised to a power from 1 to this one,\n// pow(coverage, remap(h, 0.7, 0.8, 1, lerp(1, 0.5, anvil_bias))) (Schneider 2017). The report\n// gives these as starting points, not from a source it could check\nconst float FLAT_BASE = 0.05;\nconst float DECK_TOP = 0.65;\nconst float TOWER_ENVELOPE = 1.5;\nconst vec3 ANVIL = vec3(0.7, 0.8, 0.5);\n// the cloud shield over dry ground is broken: its coverage is this share of the clouds' over the\n// rain, which the rain raises to the full\nconst float SHIELD_COVERAGE = 0.5;\n// the map's cells are read jittered by up to this share of a cell, uv + 0.3 * perlin2(uv * k)\n// cells, so that their squares do not show; and the coverage varies by up to this share around its\n// mean, by noise over 8 cells, 8 km of the radar's 1 km cells, a pattern that is not the billows'\n// noise, whose tile would show (Högfeldt 2016). Both from a square of noise made once\n// (cloud-noise.js), read once rather than computed three times at each sample, which spans this\n// many of the map's cells, cloud-noise.js's MAP_TILE_CELLS\nconst float MAP_JITTER = 0.3;\nconst float COVERAGE_NOISE = 0.4;\nconst float MAP_TILE_CELLS = 64.0;\n\n// the clouds' map at a point in world coordinates, jittered, its coverage varied, 0 outside the map\nvec4 cloudMapAt(vec3 positionWC) {\n vec2 cells = vec2(textureSize(map, 0));\n vec2 uv = mapUv(positionWC);\n // the jitter in red and green, the coverage's variation in blue, -1 to 1\n vec3 noise = texture(mapNoise, uv * cells / MAP_TILE_CELLS).rgb * 2.0 - 1.0;\n uv += MAP_JITTER * noise.rg / cells;\n if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0)))) {\n return vec4(0.0);\n }\n vec4 texel = mix(texture(map, uv), texture(nextMap, uv), mapBlend);\n texel.g = clamp(texel.g * (1.0 + COVERAGE_NOISE * noise.b), 0.0, 1.0);\n return texel;\n}\n// extinction inside a solid cloud, per meter: about 1.3 km of visibility\nconst float EXTINCTION = 0.003;\n// the noise repeats every this many meters, in 4 billows across, with this many voxels, clouds.js's\n// NOISE_SIZE, and its details are about this size\nconst float NOISE_TILE = 16000.0;\nconst float NOISE_VOXELS = 64.0;\nconst float DETAIL_SIZE = 1000.0;\n// the scale of the larger lookup of the noise, not a fraction that would line its tiles up\nconst float LARGE_SCALE = 0.27;\n// the noise is read this many mipmaps blurrier than a pixel's footprint: its features then span a\n// few pixels of the pass, rather than one that the upscale would turn into a square\nconst float LOD_BIAS = 1.5;\n// the clouds lean downwind by this many meters from the base to the top of the slab,\n// p += height_fraction * wind_direction * 500 (Schneider 2017)\nconst float WIND_SKEW = 500.0;\n// how much the details erode the thin edges of the clouds\nconst float EROSION = 0.6;\n// the details are wisps at the base of a cloud, billows above this share of its height: inverted\n// Worley noise at the base for \"some nice whispy shapes\" (Schneider 2015)\nconst float WISPY_BASE = 0.3;\n\n// the cloud's density at a point, 0 to the density uniform, from the clouds' map (cloud-map.js):\n// where its shield covers the ground, as tall as its type, a deck over drizzle to towers over heavy\n// rain, and as solid as the rain under it is heavy, which it gives, with the share of the height of\n// its column and its dimensional profile. The noise is read at the mipmap of the footprint of a\n// pixel, in meters, and the details fade out as it grows: sampled once a pixel, finer noise would\n// alias into speckles\nfloat cloudDensity(vec3 positionWC, float h, float footprint, out float rain, out float column, out float dimensional) {\n // the rain in red, the shield's coverage in green, the clouds' type in blue\n vec4 cell = cloudMapAt(positionWC);\n float m = cell.r;\n float type = cell.b;\n rain = m;\n column = 0.0;\n dimensional = 0.0;\n if (cell.g <= 0.0) {\n return 0.0;\n }\n float top = cloudBase + mix(THIN_DEPTH, cloudTop - cloudBase, type);\n float y = (h - cloudBase) / (top - cloudBase);\n column = y;\n if (y <= 0.0 || y >= 1.0) {\n return 0.0;\n }\n float base = clamp(y / FLAT_BASE, 0.0, 1.0);\n float profile = base * mix(1.0 - smoothstep(DECK_TOP, 1.0, y), pow(1.0 - y, TOWER_ENVELOPE), type);\n float lod = max(log2(footprint * NOISE_VOXELS / NOISE_TILE) + LOD_BIAS, 0.0);\n // the noise drifts with the wind and the map does not, so the clouds stay where it rains and\n // their billows pass through them (Schneider 2017)\n float slabFraction = clamp((h - cloudBase) / (cloudTop - cloudBase), 0.0, 1.0);\n vec3 q = (positionWC - windOffset - WIND_SKEW * slabFraction * windDirection) / NOISE_TILE;\n vec4 noise = textureLod(cloudNoise, fract(q), lod);\n // the same noise about four times larger, its axes swapped, breaks up the repetition of the tile,\n // which far views would show as a grid\n float large = textureLod(cloudNoise, fract(LARGE_SCALE * q.zxy + 0.5), max(lod + log2(LARGE_SCALE), 0.0)).r;\n noise.r = clamp(noise.r + 0.6 * (large - 0.5), 0.0, 1.0);\n float coverage = cell.g * mix(SHIELD_COVERAGE, 1.0, m);\n coverage = pow(coverage, mix(1.0, mix(1.0, ANVIL.z, type), clamp((y - ANVIL.x) / (ANVIL.y - ANVIL.x), 0.0, 1.0)));\n dimensional = profile * coverage;\n // the profile times the coverage carves the noise rather than scaling it,\n // saturate(noise - (1 - dimensional_profile)) (Schneider 2022): the core stays solid, and only\n // the noise's highest billows reach the top and the base. Ramped up over a softness rather than\n // sharpened by a power as in Nubis Cubed (Schneider 2023): with 250 m voxels, a sharp edge\n // follows their interpolation in a grid. The edges soften as the pixels grow, which would\n // otherwise cut them in steps of the pass's resolution\n float soft = 0.15 + 0.35 * clamp(footprint / 2000.0, 0.0, 1.0);\n float shape = smoothstep(0.0, soft, noise.r - (1.0 - profile * coverage));\n // the details erode where the cloud is thin, its edges, and leave its core solid\n float billows = mix(noise.g, noise.b, 0.4);\n float detail = mix(1.0 - billows, billows, clamp(y / WISPY_BASE, 0.0, 1.0)) * (1.0 - smoothstep(0.25, 0.5, footprint / DETAIL_SIZE));\n float erosion = EROSION * detail * (1.0 - shape);\n return density * clamp((shape - erosion) / (1.0 - erosion), 0.0, 1.0);\n}\n";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// This file is automatically rebuilt from CloudsDepth.glsl.
|
|
2
|
+
export default "// The farthest distance of the scene in each block of pixels of the clouds' march, at its\n// resolution: the march goes as far as it, and the blur and the upscale compare their texels by it,\n// so a cloud over the sky does not stop at a ridge in its block. \"There is absolutely no physical or\n// theoretical basis for this. It just looks better than halos\" (Cantlay, GPU Gems 3, ch. 23).\n// Needs EyeFromDepth.\nprecision highp sampler2D;\nuniform sampler2D depthTexture;\nin vec2 v_textureCoordinates;\n\n// the pixels of a block across, one over clouds.js's CLOUD_SCALE, and the sky's distance, past any\n// terrain\nconst int BLOCK = 4;\nconst float SKY_DISTANCE = 1e9;\n\nvoid main() {\n // czm_viewport is this pass's texture, the march's size\n vec2 pixel = 1.0 / (czm_viewport.zw * float(BLOCK));\n float farthest = 0.0;\n for (int y = 0; y < BLOCK; y++) {\n for (int x = 0; x < BLOCK; x++) {\n vec2 offset = (vec2(float(x), float(y)) - 0.5 * float(BLOCK - 1)) * pixel;\n farthest = max(farthest, eyeDistance(depthTexture, v_textureCoordinates + offset, SKY_DISTANCE));\n }\n }\n out_FragColor = vec4(farthest, 0.0, 0.0, 1.0);\n}\n";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// This file is automatically rebuilt from CloudsMarch.glsl.
|
|
2
|
+
export default "// Clouds from a map of the rain, raymarched at a fraction of the resolution: the light they scatter\n// toward the camera, and the share of the scene still seen through them, along each view ray. They\n// fill a slab above the cloud base where the map has rain: a flat layer over drizzle, towers up to\n// the cloud top over the heaviest rain, with a rounded base and top, shaped by billows of noise\n// that repeats in world coordinates, read from a texture made once (cloud-noise.js): wispy over\n// light rain and solid over heavy rain, with details eroding their edges, not their cores. The sun lights them through a few\n// samples toward it, with a silver lining toward it, its light scattered more than once brightening\n// their cores, and the sky lights them, brighter at their top: thick clouds are dark gray from\n// below and white from above. The steps are short near the camera and longer with the distance, so\n// a camera inside a cloud is in its fog. Precipitation, activated after them, draws the rain's haze\n// over them. Needs EyeFromDepth, Height, CloudsSlab, WeatherMap, CloudsDensity, Fog, Phase and\n// RainHaze.\nprecision highp sampler2D;\nuniform sampler2D depthTexture;\n// the farthest distance of the scene in this texel's block of pixels, from CloudsDepth.glsl\nuniform sampler2D cloudDepth;\n// the highest rain around each block of the map's pixels, over both maps, from weather-map.js\nuniform sampler2D maxMap;\n// the frames drawn, for the jitter to change at each, and CloudsResolve.glsl to accumulate them\nuniform float frame;\nin vec2 v_textureCoordinates;\n\nconst int STEPS = 32;\n// the ray is probed at this many points of the max map before its march, less than a block of it\n// apart over MAX_DISTANCE, 5 km for the 4 km blocks of a 1 km map: the march starts where the ray\n// drops below the tallest cloud they find, and is skipped where they find no rain, rather than\n// spending its steps in the empty air above the clouds (the coverage early-out of Schneider 2015)\nconst int RAY_PROBES = 8;\n// the sun's light through the cloud, sampled at these distances toward it, in meters\nconst vec2 LIGHT_DISTANCES = vec2(200.0, 700.0);\n// the sky's light on the clouds at their base and at their top, by day, and the moon's share of it\n// at night. By day, their tops see the sky overhead, in the color of Cesium's at its luminance, and\n// their bases the sunlight the ground bounces (Hillaire 2016, sec. 5.5.1), less as the sun is lower\n// than this sine of its height. Cesium's sky itself, and an albedo of 0.1 to 0.3 for the ground,\n// would leave the tops gray: our two samples toward the sun let little of it through\nconst vec3 AMBIENT_BASE = vec3(0.32, 0.35, 0.40);\nconst vec3 AMBIENT_TOP = vec3(0.62, 0.66, 0.72);\nconst float HIGH_SUN = 0.5;\n// the sky's color on the clouds' tops is desaturated toward its gray, keeping this share of it: in\n// full, its hue turns the tops sky blue, where the sun's white light dominates them (Hillaire 2016,\n// sec. 5.5.1, lets artists desaturate the sky's light)\nconst float SKY_SATURATION = 0.3;\n// the sky's light inside a cloud is occluded by its dimensional profile, its height profile times\n// its coverage, pow(1 - dimensional_profile, 0.5) (Schneider 2022): bright at its edges and top,\n// dark in its core; and by the lack of light from below at its base, in the share of its column's height\n// pow(remap(h, 0.07, 0.14, 0.1, 1.0), 0.8) (Schneider 2017): its bases are dark\nconst vec2 DARK_BASE = vec2(0.07, 0.14);\n// the clouds over the heaviest rain absorb this share of the light they intercept, for the\n// \"foreboding dark clouds\" of Horizon Zero Dawn (Schneider 2015)\nconst float RAIN_ALBEDO = 0.8;\nconst float SUN = 0.8;\n// the sun's light scattered more than once, as octaves of the light scattered once (Wrenninge's,\n// Hillaire 2016, sec. 5.8, eq. 19-20): octave n has a^n of the scattering, b^n of the extinction and\n// c^n of the phase's eccentricity, so it reaches deeper into the cloud, more alike in all\n// directions. a <= b keeps it from scattering more light than reaches it; 0.5 each, Unreal's\n// defaults, and 2 octaves, as Unreal and RDR2\nconst int OCTAVES = 2;\nconst float OCTAVE_SCATTERING = 0.5;\nconst float OCTAVE_EXTINCTION = 0.5;\nconst float OCTAVE_ECCENTRICITY = 0.5;\nconst vec3 MOONLIGHT = vec3(0.60, 0.63, 0.70);\n\n// Henyey-Greenstein's phase function, times 4 pi: 1 for light scattered alike in all directions\nfloat phase(float cosTheta, float g) {\n return 4.0 * czm_pi * henyeyGreenstein(cosTheta, g);\n}\n\nvoid main() {\n vec4 eye = eyeAt(depthTexture, v_textureCoordinates);\n vec3 direction = normalize(eye.xyz);\n // the farthest of the texel's pixels, the sky past the slab: the slab ends its rays\n float sceneDistance = texture(cloudDepth, v_textureCoordinates).r;\n vec2 segment = slab(dot(direction, up), sceneDistance, slabTop);\n float span = segment.y - segment.x;\n if (span <= 0.0) {\n out_FragColor = vec4(0.0, 0.0, 0.0, 1.0);\n return;\n }\n vec3 directionWC = czm_inverseViewRotation * direction;\n // the clouds' map's coverage, and its tallest type\n float covered = 0.0;\n float highest = 0.0;\n for (int k = 0; k < RAY_PROBES; k++) {\n vec2 uv = mapUv(czm_viewerPositionWC + directionWC * (segment.x + span * (float(k) + 0.5) / float(RAY_PROBES)));\n if (all(greaterThanEqual(uv, vec2(0.0))) && all(lessThanEqual(uv, vec2(1.0)))) {\n vec4 cell = texture(maxMap, uv);\n covered = max(covered, cell.g);\n highest = max(highest, cell.b);\n }\n }\n segment = slab(dot(direction, up), sceneDistance, cloudBase + mix(THIN_DEPTH, cloudTop - cloudBase, highest));\n span = segment.y - segment.x;\n if (covered <= 0.0 || span <= 0.0) {\n out_FragColor = vec4(0.0, 0.0, 0.0, 1.0);\n return;\n }\n vec3 lightWC = czm_inverseViewRotation * czm_lightDirectionEC;\n // a forward lobe for the silver lining, a weak backward one, for each octave: the sun is a\n // direction, the angle is the same along the ray\n float cosTheta = dot(direction, czm_lightDirectionEC);\n float octaves[OCTAVES];\n float contribution = 1.0;\n float eccentricity = 1.0;\n for (int n = 0; n < OCTAVES; n++) {\n octaves[n] = contribution * mix(phase(cosTheta, -0.2 * eccentricity), phase(cosTheta, 0.6 * eccentricity), 0.7);\n contribution *= OCTAVE_SCATTERING;\n eccentricity *= OCTAVE_ECCENTRICITY;\n }\n float day = daylight();\n // the far clouds fade into the sky behind them as Cesium's fog fades the terrain, by its factor at\n // each step's distance: the same aerial perspective on the clouds as on the earth, without which\n // \"a visual discrepancy happens at the horizon between clouds and earth\" (Hillaire 2016, sec.\n // 5.9.1). The sky toward the view, at least SKY_ELEVATION above the horizon\n vec3 fogColor = skyToward(direction, max(asin(clamp(dot(direction, up), -1.0, 1.0)), SKY_ELEVATION));\n vec3 sunLight = SUN * czm_lightColor * day;\n // the sky overhead, and the sunlight from the ground, by day; the moon's at night\n vec3 zenith = clearSkyBelow(cloudBase, czm_inverseViewRotation * up, czm_lightDirectionWC);\n vec3 skyHue = mix(vec3(1.0), zenith / max(czm_luminance(zenith), 1e-3), SKY_SATURATION);\n vec3 skyAbove = mix(MOONLIGHT, skyHue, day) * czm_luminance(AMBIENT_TOP);\n vec3 groundBelow = AMBIENT_BASE * mix(MOONLIGHT, czm_lightColor * clamp(dot(up, czm_lightDirectionEC) / HIGH_SUN, 0.0, 1.0), day);\n // interleaved gradient noise, a different start along the ray for each pixel, and at each frame,\n // by the golden ratio (Wolfe 2020)\n // the angle a pixel of this pass covers\n float pixelAngle = 2.0 / (czm_projection[1][1] * czm_viewport.w);\n float jitter = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))) + 0.618034 * frame);\n vec3 light = vec3(0.0);\n float transmittance = 1.0;\n float rain;\n float column;\n float dimensional;\n float unused;\n for (int i = 0; i < STEPS; i++) {\n float s = (float(i) + jitter) / float(STEPS);\n float t = segment.x + span * s * s;\n float dt = span * (2.0 * s + 1.0 / float(STEPS)) / float(STEPS);\n vec3 pointEC = direction * t;\n float h = heightAt(pointEC);\n vec3 pointWC = czm_viewerPositionWC + directionWC * t;\n float footprint = t * pixelAngle;\n float sigma = EXTINCTION * cloudDensity(pointWC, h, footprint, rain, column, dimensional);\n if (sigma <= 0.0) {\n continue;\n }\n // the sun's light through the cloud toward it\n float opticalDepth = 0.0;\n float previous = 0.0;\n for (int j = 0; j < 2; j++) {\n // jittered as the step, for the samples of neighboring pixels not to band (Hillaire 2016,\n // sec. 5.5.2)\n float distanceToward = LIGHT_DISTANCES[j] * (0.75 + 0.5 * jitter);\n opticalDepth += EXTINCTION * cloudDensity(pointWC + lightWC * distanceToward, heightAt(pointEC + czm_lightDirectionEC * distanceToward), footprint, unused, unused, unused) * (distanceToward - previous);\n previous = distanceToward;\n }\n float sunThrough = 0.0;\n float occlusion = 1.0;\n for (int n = 0; n < OCTAVES; n++) {\n sunThrough += octaves[n] * exp(-opticalDepth * occlusion);\n occlusion *= OCTAVE_EXTINCTION;\n }\n float y = clamp((h - cloudBase) / (cloudTop - cloudBase), 0.0, 1.0);\n float ambientOcclusion = sqrt(max(1.0 - dimensional, 0.0)) * pow(mix(0.1, 1.0, clamp((column - DARK_BASE.x) / (DARK_BASE.y - DARK_BASE.x), 0.0, 1.0)), 0.8);\n vec3 scattered = sunLight * sunThrough + mix(groundBelow, skyAbove, y) * ambientOcclusion;\n float stepTransmittance = exp(-sigma * dt);\n // the light scattered over the step, the integral of the source over it, (S - S T) / sigma_t\n // (Hillaire 2016, eq. 17): the albedo, sigma_s / sigma_t, of what the step intercepts\n vec3 seen = czm_fog(t, scattered * mix(1.0, RAIN_ALBEDO, rain), fogColor, czm_fogVisualDensityScalar);\n light += transmittance * seen * (1.0 - stepTransmittance);\n transmittance *= stepTransmittance;\n if (transmittance < 0.03) {\n break;\n }\n }\n out_FragColor = vec4(min(light, vec3(1.0)), transmittance);\n}\n";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// This file is automatically rebuilt from CloudsResolve.glsl.
|
|
2
|
+
export default "// The clouds accumulated over the frames, at the march's resolution: each frame's march, jittered\n// anew, blended into the clouds of the previous frames, reprojected to where they are seen now, as\n// Frostbite's, whose 14 samples a frame \"look converged\" (Hillaire 2016, sec. 5.5.3). The previous\n// frames' clouds are reprojected through where the ray enters the slab, near what is seen of them\n// from outside, rather than through their transmittance-weighted distance (Sakmary 2022), which the\n// march has no channel left for, and clamped to the current frame's 3 x 3 neighborhood so that they\n// do not trail behind what moved. Needs EyeFromDepth, Height and CloudsSlab.\nprecision highp sampler2D;\nuniform sampler2D depthTexture;\n// the farthest distance of the scene in this texel's block of pixels, from CloudsDepth.glsl\nuniform sampler2D cloudDepth;\n// this frame's march, and the accumulation of the previous frames, from clouds.js\nuniform sampler2D cloudTexture;\nuniform sampler2D historyTexture;\n// from the eye coordinates of this frame to the clip coordinates of the previous one\nuniform mat4 reprojection;\n// the share of the previous frames in the blend, 0 when there are none\nuniform float historyWeight;\nin vec2 v_textureCoordinates;\n\n// inside the slab, the clouds are reprojected through a point this far along the ray, in meters\nconst float INSIDE_DISTANCE = 2000.0;\n\nvoid main() {\n vec4 current = texture(cloudTexture, v_textureCoordinates);\n // czm_viewport is this pass's texture, the march's size\n vec2 texel = 1.0 / czm_viewport.zw;\n vec4 low = current;\n vec4 high = current;\n for (int y = -1; y <= 1; y++) {\n for (int x = -1; x <= 1; x++) {\n vec4 neighbor = texture(cloudTexture, v_textureCoordinates + vec2(float(x), float(y)) * texel);\n low = min(low, neighbor);\n high = max(high, neighbor);\n }\n }\n vec3 direction = normalize(eyeAt(depthTexture, v_textureCoordinates).xyz);\n vec2 segment = slab(dot(direction, up), texture(cloudDepth, v_textureCoordinates).r, slabTop);\n // where the ray misses the slab, nothing is there to move: only the view turns\n vec4 point = segment.y > segment.x ? vec4(direction * max(segment.x, min(segment.y, INSIDE_DISTANCE)), 1.0) : vec4(direction, 0.0);\n vec4 clip = reprojection * point;\n vec2 previous = clip.xy / clip.w * 0.5 + 0.5;\n bool seen = clip.w > 0.0 && all(greaterThanEqual(previous, vec2(0.0))) && all(lessThanEqual(previous, vec2(1.0)));\n vec4 history = clamp(texture(historyTexture, previous), low, high);\n out_FragColor = mix(current, history, seen ? historyWeight : 0.0);\n}\n";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// This file is automatically rebuilt from CloudsShadow.glsl.
|
|
2
|
+
export default "// The clouds' shadow map, a Beer shadow map: across an area around the camera, seen from the sun,\n// the optical depth of the clouds along the sun's rays, from the top of the slab down to each\n// quarter of its height, so that a point finds how much cloud lies between it and the sun. One\n// read rather than a march toward the sun, as Unreal's (it loses only the \"volumetric self shadow\n// color\") and three-clouds' in WebGL2.\nprecision highp sampler2D;\nuniform sampler2D shadowMap;\n// the ground point the map is centered on, in world coordinates, and the base and the top of the\n// slab it covers, in meters above the ellipsoid, from clouds.js\nuniform vec3 shadowCenter;\n// the map's side, in meters, wider as the camera is higher, from clouds.js\nuniform float shadowExtent;\nuniform float shadowBase;\nuniform float shadowTop;\n\n// the shadow fades out over the outer share of the map's half side, so that its edge does not show\nconst float SHADOW_FADE = 0.2;\n\n// the axes of the map, across the sun's rays: the sun's direction in world coordinates crossed\n// with the up at its center, and the third direction\nvoid shadowAxes(out vec3 across, out vec3 along) {\n vec3 up = normalize(shadowCenter);\n across = cross(czm_lightDirectionWC, up);\n across = dot(across, across) > 1e-6 ? normalize(across) : normalize(cross(czm_lightDirectionWC, vec3(1.0, 0.0, 0.0)));\n along = cross(czm_lightDirectionWC, across);\n}\n\n// a point's height above the sphere through the map's center: over the map, it differs from the\n// height above the ellipsoid by meters\nfloat shadowHeight(vec3 positionWC) {\n return length(positionWC) - length(shadowCenter);\n}\n\n// the optical depth of the clouds between a point in world coordinates and the sun, 0 off the map\nfloat shadowDepth(vec3 positionWC) {\n vec3 across;\n vec3 along;\n shadowAxes(across, along);\n vec3 offset = positionWC - shadowCenter;\n vec2 uv = vec2(dot(offset, across), dot(offset, along)) / shadowExtent + 0.5;\n float edge = 2.0 * max(abs(uv.x - 0.5), abs(uv.y - 0.5));\n if (edge >= 1.0) {\n return 0.0;\n }\n float fade = 1.0 - smoothstep(1.0 - SHADOW_FADE, 1.0, edge);\n vec4 depths = texture(shadowMap, uv);\n // the share of the slab above the point, in quarters, through the depths at each\n float f = 4.0 * clamp((shadowTop - shadowHeight(positionWC)) / max(shadowTop - shadowBase, 1.0), 0.0, 1.0);\n return fade * (f < 1.0 ? f * depths.x : f < 2.0 ? mix(depths.x, depths.y, f - 1.0) : f < 3.0 ? mix(depths.y, depths.z, f - 2.0) : mix(depths.z, depths.w, f - 3.0));\n}\n";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// This file is automatically rebuilt from CloudsShadowMap.glsl.
|
|
2
|
+
export default "// The clouds' shadow map, drawn by clouds.js into its own texture: for each texel, the sun's ray\n// through it marched from the top of the slab down to its base, the optical depth of the clouds\n// kept at each quarter of the slab's height, unless the ray crosses no clouds. Needs CloudsSlab,\n// WeatherMap, Noise, CloudsDensity and CloudsShadow.\n// the highest coverage around each block of the clouds' map, from weather-map.js\nprecision highp sampler2D;\nuniform sampler2D maxMap;\nin vec2 v_textureCoordinates;\n\n// the steps along each ray, 4 for each quarter, and the texels across, clouds.js's SHADOW_SIZE, for\n// a texel's footprint, for the noise's mipmap\nconst int SHADOW_STEPS = 16;\nconst float SHADOW_SIZE = 512.0;\n// the ray is probed at this many points of the max map before its march, as the clouds' march, and\n// skipped where they find no clouds: the max map's 12 km windows overlap between probes as long as\n// the ray crosses the slab over less than about 100 km, the sun above about 4 degrees; lower, a\n// small cell between two probes casts no shadow\nconst int SHADOW_PROBES = 8;\n// the sun is taken this high at least, the sine of its height: lower, its rays cross the slab over\n// more than 100 km\nconst float LOW_SUN = 0.05;\n\nvoid main() {\n vec3 across;\n vec3 along;\n shadowAxes(across, along);\n vec2 xy = (v_textureCoordinates - 0.5) * shadowExtent;\n vec3 start = shadowCenter + across * xy.x + along * xy.y;\n float sine = dot(czm_lightDirectionWC, normalize(start));\n if (sine <= 0.0) {\n // the sun is down: no shadows to speak of\n out_FragColor = vec4(0.0);\n return;\n }\n sine = max(sine, LOW_SUN);\n float h0 = shadowHeight(start);\n // from the top of the slab down to its base, along the ray\n float top = (slabTop - h0) / sine;\n float base = (cloudBase - h0) / sine;\n float covered = 0.0;\n for (int k = 0; k < SHADOW_PROBES; k++) {\n vec2 uv = mapUv(start + czm_lightDirectionWC * mix(top, base, (float(k) + 0.5) / float(SHADOW_PROBES)));\n if (all(greaterThanEqual(uv, vec2(0.0))) && all(lessThanEqual(uv, vec2(1.0)))) {\n covered = max(covered, texture(maxMap, uv).g);\n }\n }\n if (covered <= 0.0) {\n out_FragColor = vec4(0.0);\n return;\n }\n float ds = (top - base) / float(SHADOW_STEPS);\n float depth = 0.0;\n vec4 depths = vec4(0.0);\n float unused;\n for (int i = 0; i < SHADOW_STEPS; i++) {\n vec3 point = start + czm_lightDirectionWC * mix(top, base, (float(i) + 0.5) / float(SHADOW_STEPS));\n depth += EXTINCTION * cloudDensity(point, shadowHeight(point), shadowExtent / SHADOW_SIZE, unused, unused, unused) * ds;\n if (i == SHADOW_STEPS / 4 - 1) {\n depths.x = depth;\n } else if (i == SHADOW_STEPS / 2 - 1) {\n depths.y = depth;\n } else if (i == 3 * SHADOW_STEPS / 4 - 1) {\n depths.z = depth;\n }\n }\n depths.w = depth;\n out_FragColor = depths;\n}\n";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// This file is automatically rebuilt from CloudsSlab.glsl.
|
|
2
|
+
export default "// The slab of the clouds along a view ray, which the march and the resolve share: where the ray's\n// height reaches a height, and the part of the ray in the slab. Needs Height.\n// meters above the ellipsoid\nuniform float cloudBase;\n// the top of the tallest cloud the map makes, where the march starts from above, from clouds.js\nuniform float slabTop;\n\n// the ray is marched as far as this from where it enters the slab, in meters, not from the camera:\n// from far above, the slab itself is farther than that. A grazing ray marched farther would take\n// steps longer than the billows and turn the far clouds into streaks; deeper than this into the\n// slab, it is nearly always behind a cloud\nconst float MAX_DISTANCE = 40000.0;\n\n// where the height along the view ray, at a distance t in direction k = dot(direction, up), reaches\n// c: the height is cameraHeight + k t + (1 - k k) t t / (2 radius), as heightAt's. False if it never\n// does. Without the cancellation of the textbook formula, also for a vertical ray, where the\n// quadratic term is 0\nbool crossings(float c, float k, out float near, out float far) {\n float a = (1.0 - k * k) / (2.0 * radius);\n float d = cameraHeight - c;\n float discriminant = k * k - 4.0 * a * d;\n if (discriminant < 0.0) {\n return false;\n }\n float q = -0.5 * (k + (k < 0.0 ? -1.0 : 1.0) * sqrt(discriminant));\n q = q == 0.0 ? 1e-9 : q;\n float r0 = a > 1e-12 ? q / a : (q > 0.0 ? 1e20 : -1e20);\n float r1 = d / q;\n near = min(r0, r1);\n far = max(r0, r1);\n return true;\n}\n\n// the part of the ray in the slab up to a top, from where it enters it to where it leaves it, meets\n// the ground or is MAX_DISTANCE long; empty when it misses the slab\nvec2 slab(float k, float sceneDistance, float top) {\n float near;\n float far;\n // always above the tops\n if (!crossings(top, k, near, far)) {\n return vec2(0.0);\n }\n float start = max(near, 0.0);\n float end = far;\n if (crossings(cloudBase, k, near, far)) {\n if (cameraHeight >= cloudBase) {\n // going down below the base\n if (near > 0.0) {\n end = min(end, near);\n }\n } else {\n // coming up through the base\n start = max(start, far);\n }\n }\n end = min(end, min(sceneDistance, start + MAX_DISTANCE));\n return vec2(start, max(end, start));\n}\n";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// This file is automatically rebuilt from ColorIsolation.glsl.
|
|
2
|
+
export default "// Color isolation: one hue keeps its color, the rest of the scene turns gray. After prod80's\n// ReShade Color Isolation (MIT), https://github.com/prod80/prod80-ReShade-Repository\nprecision highp sampler2D;\nuniform sampler2D colorTexture;\n// the hue kept and the half width of the selection, 0 to 1 for the whole circle\nuniform float hue;\nuniform float range;\n// 0 to 1\nuniform float strength;\nin vec2 v_textureCoordinates;\n\nvoid main() {\n vec4 sceneColor = texture(colorTexture, v_textureCoordinates);\n vec3 color = clamp(sceneColor.rgb, 0.0, 1.0);\n float gray = dot(color, vec3(0.2126, 0.7152, 0.0722));\n float h = rgbToHsl(color).x;\n // a triangle around the hue, on the circle\n float r = 1.0 / max(range, 1e-3);\n float weight = max(1.0 - abs((h - hue) * r), 0.0)\n + max(1.0 - abs((h + 1.0 - hue) * r), 0.0)\n + max(1.0 - abs((h - 1.0 - hue) * r), 0.0);\n float w = clamp(weight, 0.0, 1.0);\n float keep = w * w * w * (w * (w * 6.0 - 15.0) + 10.0);\n vec3 isolated = mix(vec3(gray), color, keep);\n out_FragColor = vec4(mix(color, isolated, strength), sceneColor.a);\n}\n";
|