@3driseai/3drise-viewer 0.1.9 → 0.1.11
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/dist/components/CloudsGenerator.d.ts.map +1 -1
- package/dist/components/CloudsGenerator.js +3 -1
- package/dist/components/EnvironmentManager.d.ts +30 -14
- package/dist/components/EnvironmentManager.d.ts.map +1 -1
- package/dist/components/EnvironmentManager.js +7 -2
- package/dist/components/effects/DataStreamEffect.d.ts.map +1 -1
- package/dist/components/effects/DataStreamEffect.js +14 -8
- package/dist/components/effects/NeuralNetworkEffect.d.ts.map +1 -1
- package/dist/components/effects/NeuralNetworkEffect.js +11 -9
- package/dist/components/volumetricClouds/Cirrus.d.ts +4 -0
- package/dist/components/volumetricClouds/Cirrus.d.ts.map +1 -1
- package/dist/components/volumetricClouds/Cirrus.js +4 -0
- package/dist/components/volumetricClouds/Cumulonimbus.js +2 -2
- package/dist/components/volumetricClouds/Cumulus.js +2 -2
- package/dist/components/volumetricClouds/Stratocumulus.js +2 -2
- package/dist/components/volumetricClouds/VolumetricCloudsPass.d.ts +4 -1
- package/dist/components/volumetricClouds/VolumetricCloudsPass.d.ts.map +1 -1
- package/dist/components/volumetricClouds/VolumetricCloudsPass.js +45 -3
- package/dist/components/volumetricClouds/types.d.ts +3 -1
- package/dist/components/volumetricClouds/types.d.ts.map +1 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useCloudLive.d.ts +1 -0
- package/dist/hooks/useCloudLive.d.ts.map +1 -1
- package/dist/hooks/useCloudLive.js +2 -1
- package/dist/hooks/useThunder.d.ts +37 -0
- package/dist/hooks/useThunder.d.ts.map +1 -0
- package/dist/hooks/useThunder.js +145 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/shaders/clouds/CloudShadowMaterial.d.ts +52 -0
- package/dist/shaders/clouds/CloudShadowMaterial.d.ts.map +1 -0
- package/dist/shaders/clouds/CloudShadowMaterial.js +110 -0
- package/dist/shaders/clouds/CloudVolumeMaterial.d.ts +10 -0
- package/dist/shaders/clouds/CloudVolumeMaterial.d.ts.map +1 -1
- package/dist/shaders/clouds/CloudVolumeMaterial.js +38 -2
- package/dist/shaders/glsl/cloudCore.glsl.d.ts +3 -3
- package/dist/shaders/glsl/cloudCore.glsl.d.ts.map +1 -1
- package/dist/shaders/glsl/cloudCore.glsl.js +81 -9
- package/dist/shaders/glsl/cloudShadow.glsl.d.ts +15 -0
- package/dist/shaders/glsl/cloudShadow.glsl.d.ts.map +1 -0
- package/dist/shaders/glsl/cloudShadow.glsl.js +35 -0
- package/dist/types/cloudConfigs.d.ts +53 -0
- package/dist/types/cloudConfigs.d.ts.map +1 -1
- package/dist/types/cloudConfigs.js +83 -51
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -1
- package/dist/utils/cloudShadowMap.d.ts +43 -0
- package/dist/utils/cloudShadowMap.d.ts.map +1 -0
- package/dist/utils/cloudShadowMap.js +60 -0
- package/dist/utils/cloudUniforms.d.ts +6 -1
- package/dist/utils/cloudUniforms.d.ts.map +1 -1
- package/dist/utils/cloudUniforms.js +18 -0
- package/dist/utils/gpuProfiler.d.ts +33 -0
- package/dist/utils/gpuProfiler.d.ts.map +1 -0
- package/dist/utils/gpuProfiler.js +115 -0
- package/package.json +2 -2
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import type { CloudDeckConfig, ThunderConfig } from '../types/cloudConfigs';
|
|
3
|
+
/**
|
|
4
|
+
* Flash scheduling for lightning.
|
|
5
|
+
*
|
|
6
|
+
* On the CPU rather than as a time hash in the shader, because three consumers need
|
|
7
|
+
* the *same* event: the volumetric emission uniforms, the scene light, and the
|
|
8
|
+
* temporal resolve (which has to stop blending while a flash is on screen, or the
|
|
9
|
+
* flash both under-reads and then ghosts for twenty frames). A GPU-side hash can
|
|
10
|
+
* drive only the first of those.
|
|
11
|
+
*
|
|
12
|
+
* Two things make it read as lightning rather than a blinking lamp:
|
|
13
|
+
*
|
|
14
|
+
* - **Exponential spacing.** Storms do not fire on a metronome. Waiting an
|
|
15
|
+
* exponentially distributed interval around the mean rate is the actual arrival
|
|
16
|
+
* process, and it costs one log().
|
|
17
|
+
* - **Multi-stroke flicker.** A single bolt is 2-5 return strokes over ~200 ms, each
|
|
18
|
+
* a fast attack and exponential decay. One clean flash looks synthetic; the
|
|
19
|
+
* stutter is what the eye recognises.
|
|
20
|
+
*/
|
|
21
|
+
export interface ThunderState {
|
|
22
|
+
/** 0 when dark. Peaks at ThunderConfig.intensity. */
|
|
23
|
+
intensity: number;
|
|
24
|
+
/** Bolt position in world space. Only meaningful while intensity > 0. */
|
|
25
|
+
position: THREE.Vector3;
|
|
26
|
+
/** True while the bolt is actually emitting light. */
|
|
27
|
+
active: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface ThunderDriver {
|
|
30
|
+
/** Advance by delta seconds and return the current state. */
|
|
31
|
+
update(dt: number, deck: CloudDeckConfig, cfg: ThunderConfig, camera: THREE.Camera): ThunderState;
|
|
32
|
+
}
|
|
33
|
+
export declare function createThunderDriver(): ThunderDriver;
|
|
34
|
+
/** One driver per mounted pass, stable across renders. */
|
|
35
|
+
export declare function useThunder(): ThunderDriver;
|
|
36
|
+
export default useThunder;
|
|
37
|
+
//# sourceMappingURL=useThunder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useThunder.d.ts","sourceRoot":"","sources":["../../src/hooks/useThunder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE5E;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,YAAY;IACzB,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC;IACxB,sDAAsD;IACtD,MAAM,EAAE,OAAO,CAAC;CACnB;AAID,MAAM,WAAW,aAAa;IAC1B,6DAA6D;IAC7D,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,YAAY,CAAC;CACrG;AAuED,wBAAgB,mBAAmB,IAAI,aAAa,CAiEnD;AAED,0DAA0D;AAC1D,wBAAgB,UAAU,IAAI,aAAa,CAM1C;AAED,eAAe,UAAU,CAAC"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { useMemo, useRef } from 'react';
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
/**
|
|
4
|
+
* Put the bolt somewhere the camera can actually see.
|
|
5
|
+
*
|
|
6
|
+
* Scattering bolts randomly around the origin wastes most of them: a flash behind the
|
|
7
|
+
* viewer lights nothing they are looking at, so at 8 a minute the sky mostly does
|
|
8
|
+
* nothing while the compute still runs. Instead pick a random point on screen, fire a
|
|
9
|
+
* ray through it, and take where that ray crosses the bolt's altitude. The flash is
|
|
10
|
+
* then guaranteed on screen and at the right height in the deck.
|
|
11
|
+
*
|
|
12
|
+
* Kept away from the frame edge (±0.75 of NDC) so the glow is not half cut off, and
|
|
13
|
+
* clamped in distance so a near-horizontal ray does not put the bolt at the horizon.
|
|
14
|
+
*/
|
|
15
|
+
function placeInView(out, camera, altitude, maxDistance) {
|
|
16
|
+
// Try a few points on screen and take the first that reaches cloud altitude within
|
|
17
|
+
// range. More than one try because how far a ray travels to the deck depends
|
|
18
|
+
// entirely on how steeply it climbs: looking level, a ray needs tens of kilometres
|
|
19
|
+
// to reach a 4 km base, while one aimed higher gets there in a couple. Sampling
|
|
20
|
+
// again with a different screen position is what lets a level camera still find a
|
|
21
|
+
// near bolt instead of only ever landing one at the horizon.
|
|
22
|
+
let bestT = Infinity;
|
|
23
|
+
let bestDir = null;
|
|
24
|
+
for (let i = 0; i < 4; i++) {
|
|
25
|
+
// Kept off the frame edge so the glow is not half cut off.
|
|
26
|
+
const ndc = _ndc.set((Math.random() - 0.5) * 1.5, (Math.random() - 0.5) * 1.5, 0.5);
|
|
27
|
+
const rd = _dir.copy(ndc).unproject(camera).sub(camera.position).normalize();
|
|
28
|
+
const dy = rd.y;
|
|
29
|
+
if (Math.abs(dy) < 1e-4)
|
|
30
|
+
continue;
|
|
31
|
+
const t = (altitude - camera.position.y) / dy;
|
|
32
|
+
if (t <= 0)
|
|
33
|
+
continue;
|
|
34
|
+
if (t <= maxDistance) {
|
|
35
|
+
bestT = t;
|
|
36
|
+
bestDir = _picked.copy(rd);
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
if (t < bestT) {
|
|
40
|
+
bestT = t;
|
|
41
|
+
bestDir = _picked.copy(rd);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (bestDir && isFinite(bestT)) {
|
|
45
|
+
// Past the limit only when every sample overshot — a very flat view of a high
|
|
46
|
+
// deck. Take it anyway rather than dropping to the fallback: a distant flash on
|
|
47
|
+
// the horizon is still a flash the viewer can see, which is the whole point.
|
|
48
|
+
out.copy(camera.position).addScaledVector(bestDir, Math.min(bestT, maxDistance * 4));
|
|
49
|
+
out.y = altitude;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// No ray reaches the deck at all — camera pointed down, or level with it.
|
|
53
|
+
const fwd = _dir;
|
|
54
|
+
camera.getWorldDirection(fwd);
|
|
55
|
+
fwd.y = 0;
|
|
56
|
+
if (fwd.lengthSq() < 1e-6)
|
|
57
|
+
fwd.set(0, 0, -1);
|
|
58
|
+
fwd.normalize();
|
|
59
|
+
out.copy(camera.position).addScaledVector(fwd, maxDistance * (0.35 + Math.random() * 0.4));
|
|
60
|
+
out.y = altitude;
|
|
61
|
+
}
|
|
62
|
+
const _ndc = new THREE.Vector3();
|
|
63
|
+
const _dir = new THREE.Vector3();
|
|
64
|
+
const _picked = new THREE.Vector3();
|
|
65
|
+
/** Exponential inter-arrival time for a mean rate given per minute. */
|
|
66
|
+
function nextInterval(ratePerMinute) {
|
|
67
|
+
const perSecond = Math.max(ratePerMinute, 0.01) / 60;
|
|
68
|
+
return -Math.log(1 - Math.random()) / perSecond;
|
|
69
|
+
}
|
|
70
|
+
export function createThunderDriver() {
|
|
71
|
+
let clock = 0;
|
|
72
|
+
let nextAt = 0;
|
|
73
|
+
let strokes = [];
|
|
74
|
+
let endsAt = 0;
|
|
75
|
+
let seeded = false;
|
|
76
|
+
const position = new THREE.Vector3();
|
|
77
|
+
const state = { intensity: 0, position, active: false };
|
|
78
|
+
const spawn = (deck, cfg, camera) => {
|
|
79
|
+
const n = Math.max(1, Math.round(cfg.strokes));
|
|
80
|
+
strokes = [];
|
|
81
|
+
let t = clock;
|
|
82
|
+
for (let i = 0; i < n; i++) {
|
|
83
|
+
// The first stroke is the brightest; the rest are returns, each weaker.
|
|
84
|
+
strokes.push({ start: t, peak: i === 0 ? 1 : 0.35 + Math.random() * 0.45 });
|
|
85
|
+
// Gaps of roughly one to three stroke lengths, which is the real cadence.
|
|
86
|
+
t += cfg.strokeDuration * (1 + Math.random() * 2.5);
|
|
87
|
+
}
|
|
88
|
+
endsAt = t + cfg.strokeDuration * 4;
|
|
89
|
+
// Schedule the next event from the START of this one, not its end. Adding the
|
|
90
|
+
// interval to endsAt makes the true period (interval + event duration), so the
|
|
91
|
+
// slider under-delivers — badly at high rates, where the ~0.5 s event is a
|
|
92
|
+
// third of the gap. Asking for 40/min was giving 27.
|
|
93
|
+
nextAt = clock + Math.max(nextInterval(cfg.rate), endsAt - clock);
|
|
94
|
+
const h = THREE.MathUtils.clamp(cfg.originHeight, 0, 1);
|
|
95
|
+
const altitude = deck.baseAltitude + (deck.topAltitude - deck.baseAltitude) * h;
|
|
96
|
+
placeInView(position, camera, altitude, Math.max(cfg.spread, 1000));
|
|
97
|
+
};
|
|
98
|
+
return {
|
|
99
|
+
update(dt, deck, cfg, camera) {
|
|
100
|
+
if (!cfg.enabled) {
|
|
101
|
+
strokes = [];
|
|
102
|
+
seeded = false;
|
|
103
|
+
state.intensity = 0;
|
|
104
|
+
state.active = false;
|
|
105
|
+
return state;
|
|
106
|
+
}
|
|
107
|
+
// Don't fire the instant the user ticks the box — that reads as a bug.
|
|
108
|
+
if (!seeded) {
|
|
109
|
+
seeded = true;
|
|
110
|
+
clock = 0;
|
|
111
|
+
nextAt = nextInterval(cfg.rate);
|
|
112
|
+
}
|
|
113
|
+
clock += dt;
|
|
114
|
+
if (strokes.length === 0 && clock >= nextAt)
|
|
115
|
+
spawn(deck, cfg, camera);
|
|
116
|
+
let sum = 0;
|
|
117
|
+
if (strokes.length > 0) {
|
|
118
|
+
// Fast attack, exponential decay. tau a third of the stroke so it has
|
|
119
|
+
// visibly fallen off by the time the next one lands.
|
|
120
|
+
const tau = Math.max(cfg.strokeDuration, 0.005) / 3;
|
|
121
|
+
for (const s of strokes) {
|
|
122
|
+
const age = clock - s.start;
|
|
123
|
+
if (age < 0 || age > cfg.strokeDuration * 6)
|
|
124
|
+
continue;
|
|
125
|
+
sum += s.peak * Math.exp(-age / tau);
|
|
126
|
+
}
|
|
127
|
+
if (clock >= endsAt)
|
|
128
|
+
strokes = [];
|
|
129
|
+
}
|
|
130
|
+
state.intensity = sum * cfg.intensity;
|
|
131
|
+
state.active = sum > 0.002;
|
|
132
|
+
return state;
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/** One driver per mounted pass, stable across renders. */
|
|
137
|
+
export function useThunder() {
|
|
138
|
+
const ref = useRef(null);
|
|
139
|
+
return useMemo(() => {
|
|
140
|
+
if (!ref.current)
|
|
141
|
+
ref.current = createThunderDriver();
|
|
142
|
+
return ref.current;
|
|
143
|
+
}, []);
|
|
144
|
+
}
|
|
145
|
+
export default useThunder;
|
package/dist/index.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export { buildTextureArray, decodeHeightmap, loadImage } from './utils/landscape
|
|
|
28
28
|
export type { DecodedHeightmap } from './utils/landscapeTextures';
|
|
29
29
|
export { applyOceanUniforms, buildWaveTable, seaState } from './utils/oceanUniforms';
|
|
30
30
|
export type { WaveTable } from './utils/oceanUniforms';
|
|
31
|
-
export { applyCameraUniforms, applyCirrusUniforms, applyDeckUniforms, applySunUniforms, } from './utils/cloudUniforms';
|
|
31
|
+
export { applyCameraUniforms, applyCirrusUniforms, applyDeckUniforms, applySunUniforms, applyThunderUniforms, } from './utils/cloudUniforms';
|
|
32
32
|
export { applyCloudsKeyframeAnimations, applyCloudsMouseMove, resolveDeckConfig, } from './utils/handleCloudsAnimations';
|
|
33
33
|
export { volumetricCloudPresets, cirrusPresets, getCloudPresetsForType, getCloudPresetByName, getCirrusPresetByName, searchCloudPresetsByTags, } from './presets/volumetricCloudPresets';
|
|
34
34
|
export type { VolumetricCloudPreset, CirrusPreset } from './presets/volumetricCloudPresets';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAG1F,OAAO,EAAE,iBAAiB,IAAI,iBAAiB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAG5F,cAAc,SAAS,CAAC;AAGxB,cAAc,cAAc,CAAC;AAG7B,cAAc,SAAS,CAAC;AAExB,YAAY,EAAE,oBAAoB,IAAI,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAGjG,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,iBAAiB,EAAE,WAAW,GAAG,MAAM,iBAAiB,CAAC;AACrG,OAAO,EAAE,mBAAmB,IAAI,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AACvF,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC7F,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC/E,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACvF,YAAY,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,qBAAqB,EACrB,eAAe,EACf,8BAA8B,GAC/B,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpG,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC1F,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACrF,YAAY,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAG1F,OAAO,EAAE,iBAAiB,IAAI,iBAAiB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAG5F,cAAc,SAAS,CAAC;AAGxB,cAAc,cAAc,CAAC;AAG7B,cAAc,SAAS,CAAC;AAExB,YAAY,EAAE,oBAAoB,IAAI,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAGjG,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,iBAAiB,EAAE,WAAW,GAAG,MAAM,iBAAiB,CAAC;AACrG,OAAO,EAAE,mBAAmB,IAAI,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AACvF,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC7F,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC/E,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACvF,YAAY,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,qBAAqB,EACrB,eAAe,EACf,8BAA8B,GAC/B,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpG,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC1F,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACrF,YAAY,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,6BAA6B,EAC7B,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,gCAAgC,CAAC;AAGxC,OAAO,EACL,sBAAsB,EACtB,aAAa,EACb,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,kCAAkC,CAAC;AAC1C,YAAY,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC5F,OAAO,EACL,cAAc,EACd,wBAAwB,EACxB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EACL,wBAAwB,EACxB,yBAAyB,EACzB,gBAAgB,EAChB,sBAAsB,EACtB,4BAA4B,GAC7B,MAAM,4BAA4B,CAAC;AACpC,YAAY,EACV,sBAAsB,EACtB,eAAe,EACf,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,iBAAiB,EACjB,gBAAgB,EAChB,yBAAyB,EACzB,eAAe,EACf,kBAAkB,EAClB,yBAAyB,EACzB,cAAc,EACd,eAAe,EACf,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,yBAAyB,EACzB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,+BAA+B,CAAC;AACvC,YAAY,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ export { buildLandscapeGeometry, rtinTileFor, setLandscapeBounds } from './utils
|
|
|
24
24
|
export { Rtin, RtinTile } from './utils/rtin.js';
|
|
25
25
|
export { buildTextureArray, decodeHeightmap, loadImage } from './utils/landscapeTextures.js';
|
|
26
26
|
export { applyOceanUniforms, buildWaveTable, seaState } from './utils/oceanUniforms.js';
|
|
27
|
-
export { applyCameraUniforms, applyCirrusUniforms, applyDeckUniforms, applySunUniforms, } from './utils/cloudUniforms.js';
|
|
27
|
+
export { applyCameraUniforms, applyCirrusUniforms, applyDeckUniforms, applySunUniforms, applyThunderUniforms, } from './utils/cloudUniforms.js';
|
|
28
28
|
export { applyCloudsKeyframeAnimations, applyCloudsMouseMove, resolveDeckConfig, } from './utils/handleCloudsAnimations.js';
|
|
29
29
|
// Presets
|
|
30
30
|
export { volumetricCloudPresets, cirrusPresets, getCloudPresetsForType, getCloudPresetByName, getCirrusPresetByName, searchCloudPresetsByTags, } from './presets/volumetricCloudPresets.js';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { type ThreeElement } from '@react-three/fiber';
|
|
3
|
+
export declare const CloudShadowMaterial: import("@react-three/fiber").ConstructorRepresentation<THREE.ShaderMaterial & {
|
|
4
|
+
u_shadowInverse: THREE.Matrix4;
|
|
5
|
+
u_shadowDir: THREE.Vector3;
|
|
6
|
+
u_shadowSteps: number;
|
|
7
|
+
u_base: any;
|
|
8
|
+
u_detail: any;
|
|
9
|
+
u_weather: any;
|
|
10
|
+
u_curl: any;
|
|
11
|
+
u_coverage: number;
|
|
12
|
+
u_density: number;
|
|
13
|
+
u_cloudType: number;
|
|
14
|
+
u_baseScale: number;
|
|
15
|
+
u_detailScale: number;
|
|
16
|
+
u_detailStrength: number;
|
|
17
|
+
u_curlStrength: number;
|
|
18
|
+
u_layerBottom: number;
|
|
19
|
+
u_layerTop: number;
|
|
20
|
+
u_shear: number;
|
|
21
|
+
u_anvil: number;
|
|
22
|
+
u_precip: number;
|
|
23
|
+
u_weatherScale: number;
|
|
24
|
+
u_evolution: number;
|
|
25
|
+
u_windDir: THREE.Vector2;
|
|
26
|
+
u_windSpeed: number;
|
|
27
|
+
u_rise: number;
|
|
28
|
+
u_churn: number;
|
|
29
|
+
u_sigmaT: number;
|
|
30
|
+
u_time: number;
|
|
31
|
+
u_host: number;
|
|
32
|
+
u_center: THREE.Vector3;
|
|
33
|
+
u_radius: number;
|
|
34
|
+
u_curvature: number;
|
|
35
|
+
u_sunDir: THREE.Vector3;
|
|
36
|
+
u_sunColor: THREE.Color;
|
|
37
|
+
u_sunIntensity: number;
|
|
38
|
+
u_turbidity: number;
|
|
39
|
+
u_rayleigh: number;
|
|
40
|
+
u_mieCoefficient: number;
|
|
41
|
+
u_mieG: number;
|
|
42
|
+
u_skyAmbient: THREE.Color;
|
|
43
|
+
u_groundAmbient: THREE.Color;
|
|
44
|
+
}> & {
|
|
45
|
+
key: string;
|
|
46
|
+
};
|
|
47
|
+
declare module '@react-three/fiber' {
|
|
48
|
+
interface ThreeElements {
|
|
49
|
+
cloudShadowMaterial: ThreeElement<typeof CloudShadowMaterial>;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=CloudShadowMaterial.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CloudShadowMaterial.d.ts","sourceRoot":"","sources":["../../../src/shaders/clouds/CloudShadowMaterial.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAU,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAkE9D,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkD/B,CAAA;AAID,OAAO,QAAQ,oBAAoB,CAAC;IAClC,UAAU,aAAa;QACrB,mBAAmB,EAAE,YAAY,CAAC,OAAO,mBAAmB,CAAC,CAAA;KAC9D;CACF"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { shaderMaterial } from '@react-three/drei';
|
|
3
|
+
import { extend } from '@react-three/fiber';
|
|
4
|
+
import { cloudField } from '../glsl/cloudCore.glsl.js';
|
|
5
|
+
/**
|
|
6
|
+
* Cloud shadows, rendered once per frame instead of once per ground fragment.
|
|
7
|
+
*
|
|
8
|
+
* Each texel of this map is one ray fired along the sun vector through the deck,
|
|
9
|
+
* returning the transmittance that reaches the ground beneath it. Surface materials
|
|
10
|
+
* then sample it with a single fetch — the whole point, since the previous design
|
|
11
|
+
* (`cloudField`, a six-sample march pasted into every surface shader) was expensive
|
|
12
|
+
* enough that nothing ever adopted it.
|
|
13
|
+
*
|
|
14
|
+
* Cheap on purpose. It uses `cloudDensityCheap`, so no detail-noise erosion: at the
|
|
15
|
+
* scale a shadow lands on terrain, the high-frequency worley octaves are far below a
|
|
16
|
+
* texel and would only alias. Shape and coverage are what cast a recognisable shadow.
|
|
17
|
+
*/
|
|
18
|
+
const vertex = /* glsl */ `
|
|
19
|
+
out vec2 vUv;
|
|
20
|
+
void main(){ vUv = uv; gl_Position = vec4(position.xy, 0.0, 1.0); }
|
|
21
|
+
`;
|
|
22
|
+
const fragment = /* glsl */ `
|
|
23
|
+
${cloudField}
|
|
24
|
+
|
|
25
|
+
in vec2 vUv;
|
|
26
|
+
out vec4 outColor;
|
|
27
|
+
|
|
28
|
+
/** Shadow clip space -> world. */
|
|
29
|
+
uniform mat4 u_shadowInverse;
|
|
30
|
+
uniform vec3 u_shadowDir; // sun -> ground, i.e. -sunDir
|
|
31
|
+
uniform int u_shadowSteps;
|
|
32
|
+
|
|
33
|
+
#define MAX_SHADOW_STEPS 24
|
|
34
|
+
|
|
35
|
+
void main(){
|
|
36
|
+
// Near plane of the shadow camera, which sits above any cloud top.
|
|
37
|
+
vec4 wp = u_shadowInverse * vec4(vUv * 2.0 - 1.0, -1.0, 1.0);
|
|
38
|
+
vec3 ro = wp.xyz / wp.w;
|
|
39
|
+
vec3 rd = normalize(u_shadowDir);
|
|
40
|
+
|
|
41
|
+
float t0, t1;
|
|
42
|
+
if (!layerRange(ro, rd, u_layerBottom, u_layerTop, t0, t1)) {
|
|
43
|
+
outColor = vec4(1.0);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Curvature is deliberately not applied here (heightAt with t = 0). The shadow
|
|
48
|
+
// camera is orthographic along the sun, so its rays are parallel and there is no
|
|
49
|
+
// single "distance from viewer" that the bend is defined against.
|
|
50
|
+
float dt = (t1 - t0) / float(u_shadowSteps);
|
|
51
|
+
float acc = 0.0;
|
|
52
|
+
for (int i = 0; i < MAX_SHADOW_STEPS; i++){
|
|
53
|
+
if (i >= u_shadowSteps) break;
|
|
54
|
+
vec3 p = ro + rd * (t0 + dt * (float(i) + 0.5));
|
|
55
|
+
acc += cloudDensityCheap(p, heightAt(p, 0.0)) * dt;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Shadows that reach zero read as holes cut in the ground. Real cloud shadow is
|
|
59
|
+
// lifted by light scattered in from the surrounding sky, so it floors well above
|
|
60
|
+
// black; the surface material's own ambient cannot recover it once it is gone.
|
|
61
|
+
float transmittance = max(exp(-acc * u_sigmaT), 0.12);
|
|
62
|
+
outColor = vec4(transmittance, transmittance, transmittance, 1.0);
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
65
|
+
export const CloudShadowMaterial = shaderMaterial({
|
|
66
|
+
u_shadowInverse: new THREE.Matrix4(),
|
|
67
|
+
u_shadowDir: new THREE.Vector3(0, -1, 0),
|
|
68
|
+
u_shadowSteps: 12,
|
|
69
|
+
// The cloudField subset of the deck uniforms. Pushed by applyDeckUniforms, the
|
|
70
|
+
// same call the volume material takes, so the shadow can never drift from the
|
|
71
|
+
// cloud casting it.
|
|
72
|
+
u_base: null,
|
|
73
|
+
u_detail: null,
|
|
74
|
+
u_weather: null,
|
|
75
|
+
u_curl: null,
|
|
76
|
+
u_coverage: 0.52,
|
|
77
|
+
u_density: 2.6,
|
|
78
|
+
u_cloudType: 0.5,
|
|
79
|
+
u_baseScale: 1 / 2600,
|
|
80
|
+
u_detailScale: 1 / 420,
|
|
81
|
+
u_detailStrength: 0.42,
|
|
82
|
+
u_curlStrength: 0.55,
|
|
83
|
+
u_layerBottom: 1500,
|
|
84
|
+
u_layerTop: 3400,
|
|
85
|
+
u_shear: 0.08,
|
|
86
|
+
u_anvil: 0,
|
|
87
|
+
u_precip: 0,
|
|
88
|
+
u_weatherScale: 1 / 24000,
|
|
89
|
+
u_evolution: 0.45,
|
|
90
|
+
u_windDir: new THREE.Vector2(1, 0.22).normalize(),
|
|
91
|
+
u_windSpeed: 6,
|
|
92
|
+
u_rise: 1.4,
|
|
93
|
+
u_churn: 4.5,
|
|
94
|
+
u_sigmaT: 0.013,
|
|
95
|
+
u_time: 0,
|
|
96
|
+
u_host: 0,
|
|
97
|
+
u_center: new THREE.Vector3(0, 620, 0),
|
|
98
|
+
u_radius: 520,
|
|
99
|
+
u_curvature: 1 / (2 * 6371000),
|
|
100
|
+
u_sunDir: new THREE.Vector3(0.35, 0.42, 0.84).normalize(),
|
|
101
|
+
u_sunColor: new THREE.Color(1, 1, 1),
|
|
102
|
+
u_sunIntensity: 1,
|
|
103
|
+
u_turbidity: 0.7,
|
|
104
|
+
u_rayleigh: 0.54,
|
|
105
|
+
u_mieCoefficient: 0.005,
|
|
106
|
+
u_mieG: 0.7,
|
|
107
|
+
u_skyAmbient: new THREE.Color(0.35, 0.48, 0.72),
|
|
108
|
+
u_groundAmbient: new THREE.Color(0.28, 0.26, 0.22),
|
|
109
|
+
}, vertex, fragment);
|
|
110
|
+
extend({ CloudShadowMaterial });
|
|
@@ -54,6 +54,11 @@ export declare const cloudVolumeDefaults: {
|
|
|
54
54
|
u_stepGrowth: number;
|
|
55
55
|
u_maxSpan: number;
|
|
56
56
|
u_detailFade: number;
|
|
57
|
+
u_flash: number;
|
|
58
|
+
u_flashPos: THREE.Vector3;
|
|
59
|
+
u_flashColor: THREE.Color;
|
|
60
|
+
u_flashRadius: number;
|
|
61
|
+
u_flashScatter: number;
|
|
57
62
|
u_host: number;
|
|
58
63
|
u_center: THREE.Vector3;
|
|
59
64
|
u_radius: number;
|
|
@@ -119,6 +124,11 @@ export declare const CloudVolumeMaterial: import("@react-three/fiber").Construct
|
|
|
119
124
|
u_stepGrowth: number;
|
|
120
125
|
u_maxSpan: number;
|
|
121
126
|
u_detailFade: number;
|
|
127
|
+
u_flash: number;
|
|
128
|
+
u_flashPos: THREE.Vector3;
|
|
129
|
+
u_flashColor: THREE.Color;
|
|
130
|
+
u_flashRadius: number;
|
|
131
|
+
u_flashScatter: number;
|
|
122
132
|
u_host: number;
|
|
123
133
|
u_center: THREE.Vector3;
|
|
124
134
|
u_radius: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CloudVolumeMaterial.d.ts","sourceRoot":"","sources":["../../../src/shaders/clouds/CloudVolumeMaterial.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAU,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"CloudVolumeMaterial.d.ts","sourceRoot":"","sources":["../../../src/shaders/clouds/CloudVolumeMaterial.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAU,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAuN9D,MAAM,WAAW,0BAA0B;IACzC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6E/B,CAAA;AAED,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAwD,CAAA;AAIxF,OAAO,QAAQ,oBAAoB,CAAC;IAClC,UAAU,aAAa;QACrB,mBAAmB,EAAE,YAAY,CAAC,OAAO,mBAAmB,CAAC,CAAA;KAC9D;CACF"}
|
|
@@ -41,6 +41,15 @@ uniform int u_steps;
|
|
|
41
41
|
uniform float u_stepGrowth, u_maxSpan, u_fogDensity, u_detailFade;
|
|
42
42
|
uniform float u_screenSpace;
|
|
43
43
|
|
|
44
|
+
// Lightning. Declared here rather than in the shared cloudUniforms chunk so the
|
|
45
|
+
// exported cloudField, which host apps pull into their own surface materials for
|
|
46
|
+
// cloud shadows, keeps exactly the uniform set it has today.
|
|
47
|
+
uniform float u_flash; // 0 when dark
|
|
48
|
+
uniform vec3 u_flashPos; // bolt, world space
|
|
49
|
+
uniform vec3 u_flashColor;
|
|
50
|
+
uniform float u_flashRadius; // softens the inverse square
|
|
51
|
+
uniform float u_flashScatter; // multiplies extinction between bolt and sample
|
|
52
|
+
|
|
44
53
|
#define MAX_STEPS 256
|
|
45
54
|
|
|
46
55
|
/** Interleaved gradient noise. Cheap, and it decorrelates well under a golden-ratio
|
|
@@ -122,7 +131,12 @@ void main(){
|
|
|
122
131
|
continue;
|
|
123
132
|
}
|
|
124
133
|
|
|
125
|
-
|
|
134
|
+
// One weather sample per shaded point, shared by the density evaluation and by
|
|
135
|
+
// every tap of the light march below. Previously each of the seven light taps
|
|
136
|
+
// re-sampled it (two 2D fetches apiece) and re-derived the coverage window.
|
|
137
|
+
CloudWeather cw = cloudWeatherAt(p);
|
|
138
|
+
|
|
139
|
+
float d = cloudDensityW(p, h, saturate(1.0 - t * u_detailFade), cw);
|
|
126
140
|
if (d <= 0.0){
|
|
127
141
|
empty++;
|
|
128
142
|
if (empty > 8){ inside = false; empty = 0; }
|
|
@@ -132,7 +146,7 @@ void main(){
|
|
|
132
146
|
empty = 0;
|
|
133
147
|
|
|
134
148
|
float sigmaE = d * u_sigmaT;
|
|
135
|
-
float lightD = lightMarch(p, t);
|
|
149
|
+
float lightD = lightMarch(p, t, cw);
|
|
136
150
|
|
|
137
151
|
vec3 amb = (u_skyAmbient * pow(saturate(h), 0.7)
|
|
138
152
|
+ u_groundAmbient * pow(saturate(1.0 - h), 0.9) * 0.5)
|
|
@@ -147,6 +161,23 @@ void main(){
|
|
|
147
161
|
* scatterEnergy(lightD, mu) * pw * cloudAlbedo(h)
|
|
148
162
|
+ amb) * sigmaE;
|
|
149
163
|
|
|
164
|
+
// Lightning: a point source buried in the volume, so the cloud lights from
|
|
165
|
+
// the inside out. Two falloffs, and both matter — inverse square alone gives
|
|
166
|
+
// a glowing ball that ignores the cloud it is inside, and extinction alone
|
|
167
|
+
// gives a flat wash. Together the near cell blows out and the next one over
|
|
168
|
+
// is merely lit, which is what a real flash does.
|
|
169
|
+
//
|
|
170
|
+
// The extinction term is analytic rather than a second light march toward the
|
|
171
|
+
// bolt: that would double the cost of the whole pass for one frame in a
|
|
172
|
+
// hundred. Deliberate approximation — no texture fetches, pure ALU, and the
|
|
173
|
+
// branch is uniform so it costs nothing while the sky is dark.
|
|
174
|
+
if (u_flash > 0.0){
|
|
175
|
+
float fd = length(u_flashPos - p);
|
|
176
|
+
float att = exp(-fd * u_sigmaT * u_flashScatter)
|
|
177
|
+
/ (1.0 + (fd * fd) / (u_flashRadius * u_flashRadius));
|
|
178
|
+
S += u_flashColor * (u_flash * att) * sigmaE;
|
|
179
|
+
}
|
|
180
|
+
|
|
150
181
|
// Hillaire's energy-conserving integration, not S * dt * T.
|
|
151
182
|
float trs = exp(-sigmaE * dt);
|
|
152
183
|
scattering += transmittance * ((S - S * trs) / max(sigmaE, 1e-6));
|
|
@@ -231,6 +262,11 @@ export const cloudVolumeDefaults = {
|
|
|
231
262
|
u_stepGrowth: 0.0045,
|
|
232
263
|
u_maxSpan: 120000,
|
|
233
264
|
u_detailFade: 1 / 16000,
|
|
265
|
+
u_flash: 0,
|
|
266
|
+
u_flashPos: new THREE.Vector3(0, 2000, 0),
|
|
267
|
+
u_flashColor: new THREE.Color('#cfe0ff'),
|
|
268
|
+
u_flashRadius: 900,
|
|
269
|
+
u_flashScatter: 0.35,
|
|
234
270
|
u_host: 0,
|
|
235
271
|
u_center: new THREE.Vector3(0, 620, 0),
|
|
236
272
|
u_radius: 520,
|
|
@@ -12,11 +12,11 @@ export declare const cloudCommon = "\n#define PI 3.14159265359\n#define saturate
|
|
|
12
12
|
/** Analytic single-scattering sky. Also supplies the clouds' ambient and aerial perspective. */
|
|
13
13
|
export declare const skyLib = "\nuniform vec3 u_sunDir;\nuniform vec3 u_sunColor;\nuniform float u_sunIntensity;\n\n// The four Preetham parameters, taken straight from SkySettings so the clouds are\n// lit by the same atmosphere SkyController draws. Hardcoding them here is exactly\n// what makes cloud lighting drift away from the sky behind it.\nuniform float u_turbidity;\nuniform float u_rayleigh;\nuniform float u_mieCoefficient;\nuniform float u_mieG;\n\n/** Sun radiance in the same units skyRadiance() returns. Phase functions integrate to 1\n * over the sphere, so they return ~1/4pi; without a real radiance the clouds render black. */\nconst float SUN_RADIANCE = 22.0;\n\nfloat rayleighPhase(float mu){ return (3.0 / (16.0 * PI)) * (1.0 + mu * mu); }\nfloat miePhaseHG(float mu, float g){\n float g2 = g * g;\n return (1.0 - g2) / (4.0 * PI * pow(max(1.0 + g2 - 2.0 * g * mu, 1e-4), 1.5));\n}\n/** Kasten-Young relative air mass. */\nfloat airMass(float cy){\n float z = degrees(acos(clamp(cy, -1.0, 1.0)));\n return min(1.0 / (max(cy, 0.0) + 0.15 * pow(max(93.885 - z, 0.5), -1.253)), 40.0);\n}\n\nvec3 skyRadiance(vec3 rd){\n float mu = dot(rd, u_sunDir);\n float up = rd.y;\n float am = airMass(max(up, -0.02));\n\n vec3 bR = vec3(0.0487, 0.1130, 0.2780) * u_turbidity * u_rayleigh;\n float bM = u_mieCoefficient * u_turbidity * 4.25;\n vec3 ext = bR * am + vec3(bM * am * 1.2);\n\n // A single-scattering model that applies the full reddened beam everywhere turns the\n // zenith teal at low sun. Most zenith light is in reality multiply scattered, so the\n // Rayleigh term sees a partly whitened sun; only the Mie glow keeps the full depth.\n vec3 rayCol = mix(u_sunColor, vec3(1.0), 0.60);\n vec3 insR = bR * am * rayleighPhase(mu) * rayCol;\n vec3 insM = vec3(bM * am * miePhaseHG(mu, u_mieG) * 1.3) * u_sunColor;\n\n vec3 col = ((insR + insM) / max(ext, vec3(1e-4))) * (1.0 - exp(-ext)) * 26.0;\n\n float below = saturate(-up * 14.0);\n vec3 groundCol = vec3(0.055, 0.052, 0.048) * u_sunColor * (0.25 + 0.75 * saturate(u_sunDir.y));\n return mix(col, groundCol, below);\n}\n\nvec3 sunDisc(vec3 rd){\n float mu = dot(rd, u_sunDir);\n float d = smoothstep(0.99976, 0.99992, mu);\n float limb = 1.0 - 0.42 * saturate((0.99992 - mu) / 0.00016);\n return u_sunColor * u_sunIntensity * d * limb * 42.0;\n}\n";
|
|
14
14
|
/** Every uniform the density field reads. Include before cloudDensity. */
|
|
15
|
-
export declare const cloudUniforms = "\nprecision highp float;\nprecision highp sampler3D;\nprecision highp sampler2D;\n\nuniform sampler3D u_base;\nuniform sampler3D u_detail;\nuniform sampler2D u_weather;\nuniform sampler2D u_curl;\nuniform float u_time;\n\nuniform float u_coverage, u_density, u_cloudType;\nuniform float u_baseScale, u_detailScale, u_detailStrength, u_curlStrength;\nuniform float u_layerBottom, u_layerTop, u_shear, u_anvil, u_precip;\nuniform float u_weatherScale, u_evolution;\nuniform vec2 u_windDir;\nuniform float u_windSpeed, u_rise, u_churn;\nuniform float u_host, u_radius, u_curvature;\nuniform vec3 u_center;\n";
|
|
16
|
-
export declare const cloudDensity = "\n/**\n * Height fraction inside the cloud body.\n * In layer mode the ray is bent by planetary curvature (t^2 / 2R) so the deck meets a\n * real horizon instead of running to infinity as a flat slab would. Bending the ray is\n * equivalent to raising the sample's altitude and keeps coordinates small, which a\n * literal 6371 km sphere does not.\n */\nfloat heightAt(vec3 p, float t){\n if (u_host < 0.5){\n return saturate((p.y - (u_center.y - u_radius)) / max(2.0 * u_radius, 1.0));\n }\n float y = p.y + t * t * u_curvature;\n return saturate((y - u_layerBottom) / max(u_layerTop - u_layerBottom, 1.0));\n}\n\n/** Two scales scrolling at different rates: coverage evolves rather than slides past. */\nvec4 sampleWeather(vec3 p){\n vec2 b = p.xz * u_weatherScale;\n vec2 drift = u_windDir * (u_time * u_windSpeed * 0.16) * u_weatherScale;\n vec4 a = texture(u_weather, b + drift);\n vec4 c = texture(u_weather, b * 2.17 - drift * 1.63 + vec2(0.31, 0.73));\n return mix(a, c, 0.36);\n}\n\n/**\n * Three archetypal vertical profiles, blended by type. This is what makes the four\n * models genuinely different shapes rather than one shape with different noise.\n * 0.0 stratus thin, bottom-weighted, flat top\n * 0.5 cumulus flat base, bulge in the upper middle, eroded cauliflower top\n * 1.0 cumulonimbus holds density to the tropopause, then spreads\n */\nfloat heightGradient(float h, float ty){\n float stratus = saturate(remap(h, 0.00, 0.06, 0.0, 1.0)) * saturate(remap(h, 0.20, 0.36, 1.0, 0.0));\n float cumulus = saturate(remap(h, 0.02, 0.20, 0.0, 1.0)) * saturate(remap(h, 0.56, 0.96, 1.0, 0.0));\n float cumulon = saturate(remap(h, 0.01, 0.09, 0.0, 1.0)) * saturate(remap(h, 0.82, 1.00, 1.0, 0.0));\n return mix(mix(stratus, cumulus, saturate(ty * 2.0)), cumulon, saturate((ty - 0.5) * 2.0));\n}\n\n/**\n *
|
|
15
|
+
export declare const cloudUniforms = "\nprecision highp float;\nprecision highp sampler3D;\nprecision highp sampler2D;\n\n/** Extinction. Declared with the density uniforms rather than the scattering ones\n * because cloudField \u2014 the subset exported for surface materials that want cloud\n * shadows \u2014 needs it to turn accumulated density into transmittance, and does not\n * compile on its own without it. */\nuniform float u_sigmaT;\n\nuniform sampler3D u_base;\nuniform sampler3D u_detail;\nuniform sampler2D u_weather;\nuniform sampler2D u_curl;\nuniform float u_time;\n\nuniform float u_coverage, u_density, u_cloudType;\nuniform float u_baseScale, u_detailScale, u_detailStrength, u_curlStrength;\nuniform float u_layerBottom, u_layerTop, u_shear, u_anvil, u_precip;\nuniform float u_weatherScale, u_evolution;\nuniform vec2 u_windDir;\nuniform float u_windSpeed, u_rise, u_churn;\nuniform float u_host, u_radius, u_curvature;\nuniform vec3 u_center;\n";
|
|
16
|
+
export declare const cloudDensity = "\n/**\n * Height fraction inside the cloud body.\n * In layer mode the ray is bent by planetary curvature (t^2 / 2R) so the deck meets a\n * real horizon instead of running to infinity as a flat slab would. Bending the ray is\n * equivalent to raising the sample's altitude and keeps coordinates small, which a\n * literal 6371 km sphere does not.\n */\nfloat heightAt(vec3 p, float t){\n if (u_host < 0.5){\n return saturate((p.y - (u_center.y - u_radius)) / max(2.0 * u_radius, 1.0));\n }\n float y = p.y + t * t * u_curvature;\n return saturate((y - u_layerBottom) / max(u_layerTop - u_layerBottom, 1.0));\n}\n\n/** Two scales scrolling at different rates: coverage evolves rather than slides past. */\nvec4 sampleWeather(vec3 p){\n vec2 b = p.xz * u_weatherScale;\n vec2 drift = u_windDir * (u_time * u_windSpeed * 0.16) * u_weatherScale;\n vec4 a = texture(u_weather, b + drift);\n vec4 c = texture(u_weather, b * 2.17 - drift * 1.63 + vec2(0.31, 0.73));\n return mix(a, c, 0.36);\n}\n\n/**\n * Three archetypal vertical profiles, blended by type. This is what makes the four\n * models genuinely different shapes rather than one shape with different noise.\n * 0.0 stratus thin, bottom-weighted, flat top\n * 0.5 cumulus flat base, bulge in the upper middle, eroded cauliflower top\n * 1.0 cumulonimbus holds density to the tropopause, then spreads\n */\nfloat heightGradient(float h, float ty){\n float stratus = saturate(remap(h, 0.00, 0.06, 0.0, 1.0)) * saturate(remap(h, 0.20, 0.36, 1.0, 0.0));\n float cumulus = saturate(remap(h, 0.02, 0.20, 0.0, 1.0)) * saturate(remap(h, 0.56, 0.96, 1.0, 0.0));\n float cumulon = saturate(remap(h, 0.01, 0.09, 0.0, 1.0)) * saturate(remap(h, 0.82, 1.00, 1.0, 0.0));\n return mix(mix(stratus, cumulus, saturate(ty * 2.0)), cumulon, saturate((ty - 0.5) * 2.0));\n}\n\n/**\n * The weather-derived terms, separated from the rest of the density evaluation.\n *\n * These are a function of horizontal position only, and the weather map's smallest\n * feature is a ~7 km cell (weatherCellSize / 2.17). A sun ray inside the cloud travels\n * a few hundred metres for a stratocumulus deck; over that distance these values do not\n * meaningfully change. Sampling them once per shaded point and reusing them for the\n * light march removes two texture fetches and a sin() from every one of its taps \u2014\n * which is most of the frame, since lighting is roughly 80% of the sample cost.\n *\n * This is the standard separation (Schneider does the same in Horizon): the light\n * march's job is to find how much *shape* is between here and the sun, and the shape\n * comes from the 3D noise, which is still evaluated per tap.\n */\nstruct CloudWeather {\n float ty; // blended cloud type\n float cov; // coverage BEFORE the height-dependent anvil term\n};\n\nCloudWeather cloudWeatherAt(vec3 p){\n vec4 w = sampleWeather(p);\n CloudWeather cw;\n cw.ty = saturate(u_cloudType + (w.b - 0.5) * 0.30);\n\n float cov = mix(w.r, w.g, 0.40);\n // Slow formation and decay: the coverage threshold itself breathes.\n cov += sin(u_time * 0.041 + w.b * 12.6) * 0.5 * u_evolution * 0.16;\n\n // The window slides AND narrows with coverage. Remapping to a fixed upper bound of 1.0\n // caps effective coverage near the weather map's median (~0.5), so overcast is\n // unreachable no matter how high the slider goes.\n cw.cov = saturate(remap(cov, 1.0 - u_coverage, 1.0 - u_coverage * 0.35, 0.0, 1.0));\n return cw;\n}\n\n/**\n * @param detail 0..1 \u2014 fades high-frequency erosion out with distance. Without it the\n * finest worley octave beats against the step size and the far field speckles.\n * @param cw weather at this column, from cloudWeatherAt().\n */\nfloat cloudDensityW(vec3 p, float h, float detail, CloudWeather cw){\n if (h <= 0.001 || h >= 0.999) return 0.0;\n\n float ty = cw.ty;\n float cov = cw.cov;\n if (cov <= 0.0) return 0.0;\n\n // Anvil: above the equilibrium level a storm spreads laterally. Gated on type so a\n // fair-weather cumulus never grows one.\n float anvil = saturate(remap(h, 0.70, 1.0, 0.0, 1.0)) * u_anvil * saturate((ty - 0.5) * 2.0);\n cov = saturate(mix(cov, cov * 1.9, anvil));\n\n // Object host: fade coverage toward the bounding sphere so the volume is not sliced\n // flat by its own container. Through coverage, not final density, so the detail\n // erosion still bites into the boundary and the silhouette stays ragged.\n if (u_host < 0.5){\n float rad = length(p - u_center) / max(u_radius, 1.0);\n cov *= saturate(remap(rad, 0.50, 0.97, 1.0, 0.0));\n if (cov <= 0.0) return 0.0;\n }\n\n // Wind shear tilts the column with altitude; then advection; then convective rise.\n // Three decoupled timescales is what reads as delicate rather than scrolling.\n vec3 sp = p;\n float thick = (u_host < 0.5) ? (2.0 * u_radius) : max(u_layerTop - u_layerBottom, 1.0);\n sp.xz += u_windDir * (h * u_shear * thick);\n sp.xz += u_windDir * (u_time * u_windSpeed);\n sp.y -= u_time * u_rise;\n\n vec4 b = texture(u_base, sp * u_baseScale);\n float lowFbm = b.g * 0.625 + b.b * 0.25 + b.a * 0.125;\n float shape = saturate(remap(b.r, lowFbm - 1.0, 1.0, 0.0, 1.0));\n shape *= heightGradient(h, ty);\n\n float d = saturate(remap(shape, 1.0 - cov, 1.0, 0.0, 1.0)) * cov;\n if (d <= 0.0) return 0.0;\n\n if (detail > 0.01){\n vec2 curl = texture(u_curl, sp.xz * u_baseScale * 2.4).rg * 2.0 - 1.0;\n vec3 dp = sp;\n // Curl warp is strongest at the base, where a cloud shears out into wisps.\n dp.xz += curl * u_curlStrength * (1.0 - h) * 260.0;\n dp.y -= u_time * u_churn;\n vec3 dn = texture(u_detail, dp * u_detailScale).rgb;\n float dFbm = dn.r * 0.625 + dn.g * 0.25 + dn.b * 0.125;\n // Wispy erosion low down, billowy erosion up top.\n float m = mix(1.0 - dFbm, dFbm, saturate(h * 3.2));\n d = saturate(remap(d, m * u_detailStrength * detail, 1.0, 0.0, 1.0));\n }\n\n return d * u_density;\n}\n\n/**\n * Scattering albedo. Rain-bearing cloud base holds large drops, which absorb far more\n * than the small droplets higher up \u2014 this is why a storm base is charcoal while its\n * top is brilliant white, and it is not something extinction alone reproduces.\n */\nfloat cloudAlbedo(float h){\n return mix(1.0, 0.22, u_precip * saturate(remap(h, 0.30, 0.02, 0.0, 1.0)));\n}\n\n/** Samples its own weather. Keeps the original signature for every existing caller,\n * including the cloudField surface-shadow path. */\nfloat cloudDensity(vec3 p, float h, float detail){\n return cloudDensityW(p, h, detail, cloudWeatherAt(p));\n}\n\nfloat cloudDensityCheap(vec3 p, float h){ return cloudDensity(p, h, 0.0); }\n\n/** Shape only, against weather already sampled at the shaded point. The light march\n * and the empty-space skip both want this. */\nfloat cloudDensityCheapW(vec3 p, float h, CloudWeather cw){\n return cloudDensityW(p, h, 0.0, cw);\n}\n";
|
|
17
17
|
/** Ray/layer and ray/sphere intersection. */
|
|
18
18
|
export declare const cloudGeometry = "\n/**\n * Roots of a*t^2 + rd.y*t + (ro.y - h) = 0 with a = 1/(2R).\n * The naive quadratic formula cancels catastrophically here: a is ~1e-7, so\n * (-b + sqrt(b*b - 4ac)) / 2a returns noise for steep rays and the deck vanishes\n * directly overhead. The stable form below is not optional.\n */\nint layerRoots(vec3 ro, vec3 rd, float h, out float r1, out float r2){\n float a = u_curvature, b = rd.y, c = ro.y - h;\n r1 = 1e20; r2 = 1e20;\n if (a < 1e-12){\n if (abs(b) < 1e-7) return 0;\n float t = -c / b;\n if (t > 0.0){ r1 = t; return 1; }\n return 0;\n }\n float disc = b * b - 4.0 * a * c;\n if (disc < 0.0) return 0;\n float sq = sqrt(disc);\n float q = -0.5 * (b + (b >= 0.0 ? sq : -sq));\n if (abs(q) < 1e-20) return 0;\n float ta = q / a;\n float tb = c / q;\n float lo = min(ta, tb), hi = max(ta, tb);\n int n = 0;\n if (lo > 0.0){ r1 = lo; n = 1; }\n if (hi > 0.0){ if (n == 0){ r1 = hi; n = 1; } else { r2 = hi; n = 2; } }\n return n;\n}\n\nbool layerRange(vec3 ro, vec3 rd, float hb, float ht, out float t0, out float t1){\n float b1, b2, c1, c2;\n int nb = layerRoots(ro, rd, hb, b1, b2);\n int nt = layerRoots(ro, rd, ht, c1, c2);\n float ts[4]; int n = 0;\n if (nb > 0){ ts[n] = b1; n++; }\n if (nb > 1){ ts[n] = b2; n++; }\n if (nt > 0){ ts[n] = c1; n++; }\n if (nt > 1){ ts[n] = c2; n++; }\n if (n == 0){\n if (ro.y > hb && ro.y < ht){ t0 = 0.0; t1 = 120000.0; return true; }\n return false;\n }\n for (int i = 0; i < 4; i++)\n for (int j = i + 1; j < 4; j++){\n if (j < n && ts[j] < ts[i]){ float tmp = ts[i]; ts[i] = ts[j]; ts[j] = tmp; }\n }\n bool inside = (ro.y > hb && ro.y < ht);\n if (inside){ t0 = 0.0; t1 = ts[0]; }\n else { t0 = ts[0]; t1 = (n > 1) ? ts[n - 1] : ts[0] + 6000.0; }\n return t1 > t0;\n}\n\nbool sphereRange(vec3 ro, vec3 rd, vec3 c, float r, out float t0, out float t1){\n vec3 oc = ro - c;\n float b = dot(oc, rd);\n float cc = dot(oc, oc) - r * r;\n float disc = b * b - cc;\n if (disc < 0.0) return false;\n float sq = sqrt(disc);\n t0 = max(-b - sq, 0.0);\n t1 = -b + sq;\n return t1 > t0;\n}\n";
|
|
19
|
-
export declare const cloudLighting = "\nuniform float
|
|
19
|
+
export declare const cloudLighting = "\nuniform float u_g1, u_g2;\nuniform float u_msExtinction, u_msEnergy, u_msPhase;\nuniform float u_powder, u_silver, u_ambient;\nuniform vec3 u_skyAmbient, u_groundAmbient;\nuniform int u_lightSteps;\nuniform float u_lightStep;\n\nfloat hg(float mu, float g){\n float g2 = g * g;\n return (1.0 - g2) / (4.0 * PI * pow(max(1.0 + g2 - 2.0 * g * mu, 1e-4), 1.5));\n}\n/** Forward lobe carries the silver lining; the small backward lobe is the glow you get\n * when the sun is behind you. A single lobe cannot do both. */\nfloat dualLobe(float mu, float g1, float g2){\n return mix(hg(mu, g1), hg(mu, -abs(g2)), 0.42);\n}\n\nconst vec3 kCone[6] = vec3[6](\n vec3( 0.38, 0.35, 0.14), vec3(-0.20, 0.60,-0.30), vec3(-0.50,-0.20, 0.40),\n vec3( 0.10,-0.55,-0.35), vec3( 0.55, 0.10,-0.55), vec3(-0.15, 0.25, 0.62)\n);\n\n/**\n * Six exponentially growing samples toward the sun, spread inside a cone so the\n * self-shadow is soft instead of banded, plus one long-range probe for distant occluders.\n * The step is a FRACTION of cloud depth, not metres: the same absolute value is wrong\n * for an 800 m deck and an 8 km tower.\n */\n/**\n * How far a light tap may travel, as a fraction of a weather cell, before it stops\n * reusing the shaded point's weather and samples its own.\n *\n * The steps grow 1.62x, so the last two taps cover most of the distance and carry\n * nearly all of the error \u2014 sharing weather across all six is fine for a 800 m\n * stratocumulus deck and badly wrong for a 8.4 km cumulonimbus, where the cone reaches\n * 5.5 km. Measured against the real baked weather map (p95 absolute coverage error):\n *\n * share all 6 strato 0.115 cumulus 0.173 Cb 0.408 <- too much on the big types\n * 0.0012 (here) strato 0.074 cumulus 0.106 Cb 0.115\n * 0.0008 strato 0.039 cumulus 0.057 Cb 0.053 <- if a storm looks wrong\n *\n * A distance rule rather than a fixed tap count because it self-tunes: thickness and\n * weatherCellSize are both user-facing sliders, and their ratio is what decides how\n * many taps are safe. It costs nothing in divergence either \u2014 the condition is built\n * from uniforms alone, so every fragment in the draw takes the same branch.\n */\n#define LIGHT_WEATHER_REUSE 0.0012\n\nfloat lightMarch(vec3 p, float rayT, CloudWeather cw){\n float thick = (u_host < 0.5) ? (2.0 * u_radius) : max(u_layerTop - u_layerBottom, 1.0);\n float st = u_lightStep * thick;\n float acc = 0.0;\n float t = 0.0;\n for (int i = 0; i < 6; i++){\n if (i >= u_lightSteps) break;\n vec3 sp = p + u_sunDir * (t + st * 0.5) + kCone[i] * (t * 0.38);\n float sh = heightAt(sp, rayT);\n // Near taps reuse the shaded point's weather; far ones sample their own. What every\n // tap still fetches per-sample is the 3D shape, which is what forms the shadow.\n acc += ((t * u_weatherScale < LIGHT_WEATHER_REUSE)\n ? cloudDensityCheapW(sp, sh, cw)\n : cloudDensityCheap(sp, sh)) * st;\n t += st;\n st *= 1.62;\n }\n // The long probe always samples its own: at 2.6x the accumulated distance it can be\n // 9 km out on a cumulonimbus, well outside the cell the shaded point sits in.\n vec3 fp = p + u_sunDir * (t * 2.6);\n acc += cloudDensityCheap(fp, heightAt(fp, rayT)) * st * 2.0;\n return acc;\n}\n\n/**\n * Wrenninge / Hillaire multiple-scattering octaves: each octave scales extinction,\n * energy and phase anisotropy down. A real cumulus has optical depth 5-40 and stays\n * white only because photons scatter hundreds of times; first-order exp(-tau) alone\n * renders it black. This approximation is the single biggest difference between a\n * physically-grounded cloud and a generic one.\n */\nvec3 scatterEnergy(float lightD, float mu){\n vec3 L = vec3(0.0);\n float a = 1.0, b = 1.0, c = 1.0;\n for (int n = 0; n < 3; n++){\n float ph = dualLobe(mu, u_g1 * c, u_g2 * c);\n L += vec3(b * ph * exp(-lightD * u_sigmaT * a));\n a *= u_msExtinction; b *= u_msEnergy; c *= u_msPhase;\n }\n L += vec3(u_silver * pow(saturate(mu), 8.0) * exp(-lightD * u_sigmaT * 0.35));\n return L;\n}\n";
|
|
20
20
|
/** Everything a fragment shader needs to evaluate the density field. */
|
|
21
21
|
export declare const cloudField: string;
|
|
22
22
|
/** The full model, including scattering. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudCore.glsl.d.ts","sourceRoot":"","sources":["../../../src/shaders/glsl/cloudCore.glsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,eAAO,MAAM,WAAW,2LAMvB,CAAA;AAED,gGAAgG;AAChG,eAAO,MAAM,MAAM,qxEAyDlB,CAAA;AAED,0EAA0E;AAC1E,eAAO,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"cloudCore.glsl.d.ts","sourceRoot":"","sources":["../../../src/shaders/glsl/cloudCore.glsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,eAAO,MAAM,WAAW,2LAMvB,CAAA;AAED,gGAAgG;AAChG,eAAO,MAAM,MAAM,qxEAyDlB,CAAA;AAED,0EAA0E;AAC1E,eAAO,MAAM,aAAa,46BAyBzB,CAAA;AAED,eAAO,MAAM,YAAY,+yNA0JxB,CAAA;AAED,6CAA6C;AAC7C,eAAO,MAAM,aAAa,kkEAgEzB,CAAA;AAED,eAAO,MAAM,aAAa,whIA2FzB,CAAA;AAED,wEAAwE;AACxE,eAAO,MAAM,UAAU,QAA+E,CAAA;AAEtG,4CAA4C;AAC5C,eAAO,MAAM,SAAS,QAAyC,CAAA"}
|