weather: add dynamic procedural snow cover

This commit is contained in:
Richard Zink committed 2026-10-13 05:43:04 +02:00
1 parent 92dd812072
commit 6553d2fd1d
2 files changed
+81 -1

No files matched your search

+68 -1
View File
@@ -896,6 +896,11 @@ var TERRAIN_FRAG_SRC = [
'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 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 uTerrainShadowFloor;', // brightness a fully shadowed slope keeps
'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.
'float outsideMask = clamp(outside, 0.0, 1.0);',
'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));',
// Cloud shadow: march from the surface point towards the sun
// 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 terrainUterrainShadow = terrainProg ? gl.getUniformLocation(terrainProg, 'uTerrainShadowOn') : 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
// (e.g. the basemap before the first tile, or terrain disabled) so the
@@ -1325,7 +1344,10 @@ function init(canvas) {
uCloudShadowFloor: terrainUcsFloor,
uCloudShadowAmbient: terrainUcsAmb,
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
// module-level terrainVbo/terrainVertCount/terrainWireVbo/
// terrainWireVertCount (set by buildTerrainMesh).
@@ -6073,6 +6095,43 @@ function updateRoadWetness(now) {
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) {
if (!WEATHER_LIGHTNING_ON) {
lightningFlash = 0;
@@ -6733,6 +6792,11 @@ window.clouds3dRender = function(target) {
gl.uniform3f(terr.uMoonColor, _mBeam[0], _mBeam[1], _mBeam[2]);
gl.uniform1f(terr.uMoonInt, _moonI);
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.uniform3f(terr.uAlbedoTint, _pl[0] / _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.
var carNight = lightFactor(window.__currentSunAlt || 0);
var _roadWet = updateRoadWetness(now);
var _snowCover = updateSnowCover(now);
if (carProg && (carVboM || carVboB) && carNight > 0.01) {
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));
@@ -7357,6 +7422,8 @@ window.clouds3dRender = function(target) {
snow: +_weather[1].toFixed(2),
lightning: +lightningFlash.toFixed(2),
wet: +roadWetness.toFixed(2),
snowCover: +snowCoverAmount.toFixed(2),
snowLineM: Math.round(snowLineM),
code: Number((window.__currentWeather || {}).code || 0)
};
var _rainI = _weather[0];