weather: add dynamic procedural snow cover
This commit is contained in:
1 parent
92dd812072
commit
6553d2fd1d
2 files changed
+81
-1
No files matched your search
@@ -149,12 +149,25 @@ window.CONFIG = {
|
|||||||
dryMinutes: 30, // roads dry out after this many dry minutes
|
dryMinutes: 30, // roads dry out after this many dry minutes
|
||||||
roadGain: 0.8, // extra road-ribbon glow when wet
|
roadGain: 0.8, // extra road-ribbon glow when wet
|
||||||
lightGain: 0.35 // extra halo on vehicle lights when wet
|
lightGain: 0.35 // extra halo on vehicle lights when wet
|
||||||
|
},
|
||||||
|
snowCover: {
|
||||||
|
enabled: true,
|
||||||
|
meltMinutes: 90, // snow melts after this many warm minutes
|
||||||
|
baseLineM: 900, // snowline at about +5 C
|
||||||
|
tempLineMPerC: 80, // snowline moves down as temperature drops
|
||||||
|
minLineM: 0, // full snowline in deep cold
|
||||||
|
maxLineM: 2500, // no snow above this line when warm
|
||||||
|
transitionM: 120, // soft transition band around the snowline
|
||||||
|
slopeMax: 0.72, // steep slopes keep less snow
|
||||||
|
gain: 0.85 // maximum whiteness of the snow albedo
|
||||||
}
|
}
|
||||||
// weatherEffects: { enabled: false }
|
// weatherEffects: { enabled: false }
|
||||||
// weatherEffects: { rain: { maxAlpha: 0.55, speed: 1.4 }, snow: { enabled: false } }
|
// weatherEffects: { rain: { maxAlpha: 0.55, speed: 1.4 }, snow: { enabled: false } }
|
||||||
// weatherEffects: { lightning: { gain: 2.0, minDelayS: 3, maxDelayS: 12 } }
|
// weatherEffects: { lightning: { gain: 2.0, minDelayS: 3, maxDelayS: 12 } }
|
||||||
// weatherEffects: { wetRoads: { enabled: false } }
|
// weatherEffects: { wetRoads: { enabled: false } }
|
||||||
// weatherEffects: { wetRoads: { dryMinutes: 10, roadGain: 1.4, lightGain: 0.7 } }
|
// weatherEffects: { wetRoads: { dryMinutes: 10, roadGain: 1.4, lightGain: 0.7 } }
|
||||||
|
// weatherEffects: { snowCover: { baseLineM: 600, tempLineMPerC: 120, gain: 1.0 } }
|
||||||
|
// weatherEffects: { snowCover: { enabled: false } }
|
||||||
},
|
},
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
|
|||||||
Vendored
+68
-1
@@ -896,6 +896,11 @@ var TERRAIN_FRAG_SRC = [
|
|||||||
'uniform float uCloudShadowAmbient;', // how much of the skylight a cloud also blocks
|
'uniform float uCloudShadowAmbient;', // how much of the skylight a cloud also blocks
|
||||||
'uniform sampler2D uElev;', // height field, same grid the mesh was built from
|
'uniform sampler2D uElev;', // height field, same grid the mesh was built from
|
||||||
'uniform float uElevScale;', // metres -> world units (incl. exaggeration)
|
'uniform float uElevScale;', // metres -> world units (incl. exaggeration)
|
||||||
|
'uniform float uSnowCover;', // accumulated snow cover 0..1
|
||||||
|
'uniform float uSnowLineM;', // dynamic snowline in metres
|
||||||
|
'uniform float uSnowTransitionM;', // soft transition band around the snowline
|
||||||
|
'uniform float uSnowSlopeMax;', // slope cutoff for snow retention
|
||||||
|
'uniform float uSnowGain;', // maximum whiteness
|
||||||
'uniform float uTerrainShadowOn;',
|
'uniform float uTerrainShadowOn;',
|
||||||
'uniform float uTerrainShadowFloor;', // brightness a fully shadowed slope keeps
|
'uniform float uTerrainShadowFloor;', // brightness a fully shadowed slope keeps
|
||||||
'uniform vec3 uAirColor;', // Rayleigh airlight (sun-tinted, blue-biased)
|
'uniform vec3 uAirColor;', // Rayleigh airlight (sun-tinted, blue-biased)
|
||||||
@@ -969,6 +974,15 @@ var TERRAIN_FRAG_SRC = [
|
|||||||
// ramp: the sun altitude drives colour and strength continuously.
|
// ramp: the sun altitude drives colour and strength continuously.
|
||||||
'float outsideMask = clamp(outside, 0.0, 1.0);',
|
'float outsideMask = clamp(outside, 0.0, 1.0);',
|
||||||
'vec3 alb = mix(texture2D(uGround, gUv).rgb, vColor, outsideMask) * uAlbedoTint;',
|
'vec3 alb = mix(texture2D(uGround, gUv).rgb, vColor, outsideMask) * uAlbedoTint;',
|
||||||
|
'if (uSnowCover > 0.001) {',
|
||||||
|
'float elevM = (vY + earthDrop(vWorld.xz)) / max(0.000001, uElevScale);',
|
||||||
|
'float slope = 1.0 - clamp(n.y, 0.0, 1.0);',
|
||||||
|
'float snowMask = smoothstep(uSnowLineM + uSnowTransitionM, uSnowLineM - uSnowTransitionM, elevM);',
|
||||||
|
'snowMask *= smoothstep(uSnowSlopeMax, uSnowSlopeMax * 0.45, slope);',
|
||||||
|
'snowMask *= 0.82 + 0.18 * hash3(floor(vWorld * 80.0));',
|
||||||
|
'snowMask *= uSnowCover * uSnowGain;',
|
||||||
|
'alb = mix(alb, vec3(0.92, 0.95, 1.0), clamp(snowMask, 0.0, 1.0));',
|
||||||
|
'}',
|
||||||
'float ndl = max(0.0, dot(n, uSunDir));',
|
'float ndl = max(0.0, dot(n, uSunDir));',
|
||||||
// Cloud shadow: march from the surface point towards the sun
|
// Cloud shadow: march from the surface point towards the sun
|
||||||
// through the cloud layer and accumulate optical depth. Same
|
// through the cloud layer and accumulate optical depth. Same
|
||||||
@@ -1237,6 +1251,11 @@ function init(canvas) {
|
|||||||
var terrainUelev = terrainProg ? gl.getUniformLocation(terrainProg, 'uElev') : null;
|
var terrainUelev = terrainProg ? gl.getUniformLocation(terrainProg, 'uElev') : null;
|
||||||
var terrainUterrainShadow = terrainProg ? gl.getUniformLocation(terrainProg, 'uTerrainShadowOn') : null;
|
var terrainUterrainShadow = terrainProg ? gl.getUniformLocation(terrainProg, 'uTerrainShadowOn') : null;
|
||||||
var terrainUterrainShadowFloor = terrainProg ? gl.getUniformLocation(terrainProg, 'uTerrainShadowFloor') : null;
|
var terrainUterrainShadowFloor = terrainProg ? gl.getUniformLocation(terrainProg, 'uTerrainShadowFloor') : null;
|
||||||
|
var terrainUsnowCover = terrainProg ? gl.getUniformLocation(terrainProg, 'uSnowCover') : null;
|
||||||
|
var terrainUsnowLineM = terrainProg ? gl.getUniformLocation(terrainProg, 'uSnowLineM') : null;
|
||||||
|
var terrainUsnowTransitionM = terrainProg ? gl.getUniformLocation(terrainProg, 'uSnowTransitionM') : null;
|
||||||
|
var terrainUsnowSlopeMax = terrainProg ? gl.getUniformLocation(terrainProg, 'uSnowSlopeMax') : null;
|
||||||
|
var terrainUsnowGain = terrainProg ? gl.getUniformLocation(terrainProg, 'uSnowGain') : null;
|
||||||
|
|
||||||
// 1x1 black texture: bound wherever a real layer does not exist yet
|
// 1x1 black texture: bound wherever a real layer does not exist yet
|
||||||
// (e.g. the basemap before the first tile, or terrain disabled) so the
|
// (e.g. the basemap before the first tile, or terrain disabled) so the
|
||||||
@@ -1325,7 +1344,10 @@ function init(canvas) {
|
|||||||
uCloudShadowFloor: terrainUcsFloor,
|
uCloudShadowFloor: terrainUcsFloor,
|
||||||
uCloudShadowAmbient: terrainUcsAmb,
|
uCloudShadowAmbient: terrainUcsAmb,
|
||||||
uElev: terrainUelev, uTerrainShadowOn: terrainUterrainShadow,
|
uElev: terrainUelev, uTerrainShadowOn: terrainUterrainShadow,
|
||||||
uTerrainShadowFloor: terrainUterrainShadowFloor
|
uTerrainShadowFloor: terrainUterrainShadowFloor,
|
||||||
|
uSnowCover: terrainUsnowCover, uSnowLineM: terrainUsnowLineM,
|
||||||
|
uSnowTransitionM: terrainUsnowTransitionM, uSnowSlopeMax: terrainUsnowSlopeMax,
|
||||||
|
uSnowGain: terrainUsnowGain
|
||||||
// vbo / vertCount / wireVbo / wireVertCount are read from the
|
// vbo / vertCount / wireVbo / wireVertCount are read from the
|
||||||
// module-level terrainVbo/terrainVertCount/terrainWireVbo/
|
// module-level terrainVbo/terrainVertCount/terrainWireVbo/
|
||||||
// terrainWireVertCount (set by buildTerrainMesh).
|
// terrainWireVertCount (set by buildTerrainMesh).
|
||||||
@@ -6073,6 +6095,43 @@ function updateRoadWetness(now) {
|
|||||||
return roadWetness;
|
return roadWetness;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var SNOW_COVER_CFG = WEATHER_FX_CFG.snowCover || {};
|
||||||
|
var SNOW_COVER_ON = SNOW_COVER_CFG.enabled !== false;
|
||||||
|
var SNOW_COVER_MELT_MINUTES = (typeof SNOW_COVER_CFG.meltMinutes === 'number') ? Math.max(1, SNOW_COVER_CFG.meltMinutes) : 90;
|
||||||
|
var SNOW_COVER_BASE_LINE = (typeof SNOW_COVER_CFG.baseLineM === 'number') ? Math.max(0, SNOW_COVER_CFG.baseLineM) : 900;
|
||||||
|
var SNOW_COVER_TEMP_LINE = (typeof SNOW_COVER_CFG.tempLineMPerC === 'number') ? Math.max(0, SNOW_COVER_CFG.tempLineMPerC) : 80;
|
||||||
|
var SNOW_COVER_MIN_LINE = (typeof SNOW_COVER_CFG.minLineM === 'number') ? Math.max(0, SNOW_COVER_CFG.minLineM) : 0;
|
||||||
|
var SNOW_COVER_MAX_LINE = (typeof SNOW_COVER_CFG.maxLineM === 'number') ? Math.max(SNOW_COVER_MIN_LINE, SNOW_COVER_CFG.maxLineM) : 2500;
|
||||||
|
var SNOW_COVER_TRANSITION = (typeof SNOW_COVER_CFG.transitionM === 'number') ? Math.max(1, SNOW_COVER_CFG.transitionM) : 120;
|
||||||
|
var SNOW_COVER_SLOPE_MAX = (typeof SNOW_COVER_CFG.slopeMax === 'number') ? Math.max(0.1, Math.min(1.2, SNOW_COVER_CFG.slopeMax)) : 0.72;
|
||||||
|
var SNOW_COVER_GAIN = (typeof SNOW_COVER_CFG.gain === 'number') ? Math.max(0, Math.min(1.2, SNOW_COVER_CFG.gain)) : 0.85;
|
||||||
|
var snowCoverAmount = 0, lastSnowT = null, snowLineM = SNOW_COVER_MAX_LINE;
|
||||||
|
|
||||||
|
function updateSnowCover(now) {
|
||||||
|
if (!SNOW_COVER_ON) {
|
||||||
|
snowCoverAmount = 0;
|
||||||
|
snowLineM = SNOW_COVER_MAX_LINE;
|
||||||
|
lastSnowT = null;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (lastSnowT === null) { lastSnowT = now; return snowCoverAmount; }
|
||||||
|
var dt = Math.max(0, Math.min(5, now - lastSnowT));
|
||||||
|
lastSnowT = now;
|
||||||
|
var wx = window.__currentWeather || {};
|
||||||
|
var tempC = Number(wx.tempC || 0);
|
||||||
|
var snow = Number(wx.snowMm || 0);
|
||||||
|
var precip = Number(wx.precipMm || 0);
|
||||||
|
var coldSnow = (tempC < 2.0 ? 1.0 : 0.0);
|
||||||
|
var input = Math.min(1.0, (snow + (tempC < 1.0 ? precip * 0.55 : 0.0)) * 0.85) * coldSnow;
|
||||||
|
var melt = dt / (SNOW_COVER_MELT_MINUTES * 60.0);
|
||||||
|
if (tempC > 2.0) melt *= 1.0 + Math.min(4.0, tempC - 2.0) * 0.35;
|
||||||
|
snowCoverAmount = Math.max(0, Math.min(1, snowCoverAmount + input * dt * 0.22 - melt));
|
||||||
|
var line = SNOW_COVER_BASE_LINE - (5.0 - tempC) * SNOW_COVER_TEMP_LINE;
|
||||||
|
line = Math.max(SNOW_COVER_MIN_LINE, Math.min(SNOW_COVER_MAX_LINE, line));
|
||||||
|
snowLineM = line;
|
||||||
|
return snowCoverAmount;
|
||||||
|
}
|
||||||
|
|
||||||
function updateLightning(now) {
|
function updateLightning(now) {
|
||||||
if (!WEATHER_LIGHTNING_ON) {
|
if (!WEATHER_LIGHTNING_ON) {
|
||||||
lightningFlash = 0;
|
lightningFlash = 0;
|
||||||
@@ -6733,6 +6792,11 @@ window.clouds3dRender = function(target) {
|
|||||||
gl.uniform3f(terr.uMoonColor, _mBeam[0], _mBeam[1], _mBeam[2]);
|
gl.uniform3f(terr.uMoonColor, _mBeam[0], _mBeam[1], _mBeam[2]);
|
||||||
gl.uniform1f(terr.uMoonInt, _moonI);
|
gl.uniform1f(terr.uMoonInt, _moonI);
|
||||||
gl.uniform1f(terr.uSkyAmb, _amb * ATMO_SKY_GAIN);
|
gl.uniform1f(terr.uSkyAmb, _amb * ATMO_SKY_GAIN);
|
||||||
|
gl.uniform1f(terr.uSnowCover, _snowCover);
|
||||||
|
gl.uniform1f(terr.uSnowLineM, snowLineM);
|
||||||
|
gl.uniform1f(terr.uSnowTransitionM, SNOW_COVER_TRANSITION);
|
||||||
|
gl.uniform1f(terr.uSnowSlopeMax, SNOW_COVER_SLOPE_MAX);
|
||||||
|
gl.uniform1f(terr.uSnowGain, SNOW_COVER_GAIN);
|
||||||
gl.uniform1f(terr.uStarAmb, _star * ATMO_STAR_GROUND);
|
gl.uniform1f(terr.uStarAmb, _star * ATMO_STAR_GROUND);
|
||||||
gl.uniform3f(terr.uAlbedoTint, _pl[0] / _pm * ATMO_ALBEDO_GAIN,
|
gl.uniform3f(terr.uAlbedoTint, _pl[0] / _pm * ATMO_ALBEDO_GAIN,
|
||||||
_pl[1] / _pm * ATMO_ALBEDO_GAIN, _pl[2] / _pm * ATMO_ALBEDO_GAIN);
|
_pl[1] / _pm * ATMO_ALBEDO_GAIN, _pl[2] / _pm * ATMO_ALBEDO_GAIN);
|
||||||
@@ -6827,6 +6891,7 @@ window.clouds3dRender = function(target) {
|
|||||||
// aircraft): the mean over the per-light thresholds.
|
// aircraft): the mean over the per-light thresholds.
|
||||||
var carNight = lightFactor(window.__currentSunAlt || 0);
|
var carNight = lightFactor(window.__currentSunAlt || 0);
|
||||||
var _roadWet = updateRoadWetness(now);
|
var _roadWet = updateRoadWetness(now);
|
||||||
|
var _snowCover = updateSnowCover(now);
|
||||||
if (carProg && (carVboM || carVboB) && carNight > 0.01) {
|
if (carProg && (carVboM || carVboB) && carNight > 0.01) {
|
||||||
var carPxScale = Math.max(0.45, Math.min(1.25, gl.drawingBufferHeight / 1080));
|
var carPxScale = Math.max(0.45, Math.min(1.25, gl.drawingBufferHeight / 1080));
|
||||||
var tCurve = Math.max(0, Math.min(1, (carNight - 0.10) / 0.40));
|
var tCurve = Math.max(0, Math.min(1, (carNight - 0.10) / 0.40));
|
||||||
@@ -7357,6 +7422,8 @@ window.clouds3dRender = function(target) {
|
|||||||
snow: +_weather[1].toFixed(2),
|
snow: +_weather[1].toFixed(2),
|
||||||
lightning: +lightningFlash.toFixed(2),
|
lightning: +lightningFlash.toFixed(2),
|
||||||
wet: +roadWetness.toFixed(2),
|
wet: +roadWetness.toFixed(2),
|
||||||
|
snowCover: +snowCoverAmount.toFixed(2),
|
||||||
|
snowLineM: Math.round(snowLineM),
|
||||||
code: Number((window.__currentWeather || {}).code || 0)
|
code: Number((window.__currentWeather || {}).code || 0)
|
||||||
};
|
};
|
||||||
var _rainI = _weather[0];
|
var _rainI = _weather[0];
|
||||||
|
|||||||
Reference in new issue
Block a user