diff --git a/config.js b/config.js index ffd4e21..8de18e3 100644 --- a/config.js +++ b/config.js @@ -333,6 +333,14 @@ window.CONFIG = { maxAlpha: 0.65, color: [0.58, 0.64, 0.72] }, + airHaze: { + enabled: true, + scaleHeightKm: 8.5, + density: 0.16, + gain: 1.0, + maxAlpha: 0.45, + color: [0.58, 0.64, 0.72] + }, model: { enabled: false // legacy full atmosphere model remains off }, diff --git a/vendor/clouds3d.js b/vendor/clouds3d.js index 088589d..bada5f5 100644 --- a/vendor/clouds3d.js +++ b/vendor/clouds3d.js @@ -110,6 +110,12 @@ var FRAG_SRC = [ 'uniform float uSunAirMass;', // relative air mass of the sun beam 'uniform float uRayleighGain;', 'uniform float uPhysicalSkyOn;', + 'uniform float uAirHazeOn;', + 'uniform float uAirHazeDensity;', + 'uniform float uAirHazeScaleHeightUnits;', + 'uniform float uAirHazeGain;', + 'uniform float uAirHazeMaxAlpha;', + 'uniform vec3 uAirHazeColor;', 'uniform vec3 uCloudAlbedo;', // theme-tinted but brightness-normalised 'uniform float uAirDensity;', // slant optical depth per world unit 'uniform float uCloudHaze;', // clouds sit above most of the haze: much weaker @@ -303,6 +309,51 @@ var FRAG_SRC = [ '}', 'return uRayleighBeta * column;', '}', + 'float airHazeColumn(vec3 rd) {', + 'if (uCurvatureOn < 0.5) {', + 'float viewY = max(rd.y, 0.0);', + 'float mView = (viewY > 0.02) ? min(38.0, 1.0 / viewY) : 38.0;', + 'return mView;', + '}', + 'float R = max(1.0, uEarthRadius);', + 'float H = max(1.0, uAtmoHeightUnits);', + 'float Hs = max(0.05, uAirHazeScaleHeightUnits);', + 'vec3 C = vec3(uCamPos.x, -R, uCamPos.z);', + 'vec3 oc = uCamPos - C;', + 'float b = dot(oc, rd);', + 'float rOut = R + H;', + 'float cOut = dot(oc, oc) - rOut * rOut;', + 'float tStart = 0.0;', + 'float tEnd = 100.0;', + 'float discOut = b * b - cOut;', + 'if (discOut > 0.0) {', + 'float sq = sqrt(discOut);', + 'float t1 = -b - sq;', + 'float t2 = -b + sq;', + 'if (cOut > 0.0) tStart = max(0.0, t1);', + 'tEnd = max(0.0, t2);', + '}', + 'float cIn = dot(oc, oc) - R * R;', + 'float discIn = b * b - cIn;', + 'if (discIn > 0.0) {', + 'float sq = sqrt(discIn);', + 'float t1 = -b - sq;', + 'float t2 = -b + sq;', + 'if (t1 > 0.0) tEnd = min(tEnd, t1);', + 'else if (t2 > 0.0) tEnd = min(tEnd, t2);', + '}', + 'if (tEnd <= tStart) return 0.0;', + 'float step = (tEnd - tStart) / 16.0;', + 'float t = tStart + step * 0.5;', + 'float column = 0.0;', + 'for (int i = 0; i < 16; i++) {', + 'vec3 p = uCamPos + rd * t;', + 'float alt = max(0.0, length(p - C) - R);', + 'column += exp(-alt / Hs) * step / Hs;', + 't += step;', + '}', + 'return column;', + '}', // Two modes driven by uMode: // 0 = SKY: full-screen pass that paints the sky gradient and // stars into the framebuffer (opaque, alpha = 1). @@ -402,6 +453,12 @@ var FRAG_SRC = [ 'vec3 phys = (single + multi + ambient) * uRayleighGain;', 'col = mix(col, phys, 0.82);', '}', + 'if (uAirHazeOn > 0.5) {', + 'float hazeColumn = airHazeColumn(rd);', + 'float hazeTau = uAirHazeDensity * hazeColumn;', + 'float hazeAlpha = clamp((1.0 - exp(-hazeTau)) * uAirHazeGain, 0.0, uAirHazeMaxAlpha);', + 'col = mix(col, uAirHazeColor, hazeAlpha);', + '}', 'float nightFactor = 1.0 - smoothstep(-0.15, 0.05, uSunAlt);', 'float skyMask = smoothstep(-0.20, 0.40, rd.y);', // Stars must not sit on the moon: the disc is opaque, so every @@ -924,6 +981,12 @@ function init(canvas) { var uSunAirMass = gl.getUniformLocation(prog, 'uSunAirMass'); var uRayleighGain = gl.getUniformLocation(prog, 'uRayleighGain'); var uPhysicalSkyOn = gl.getUniformLocation(prog, 'uPhysicalSkyOn'); + var uAirHazeOn = gl.getUniformLocation(prog, 'uAirHazeOn'); + var uAirHazeDensity = gl.getUniformLocation(prog, 'uAirHazeDensity'); + var uAirHazeScaleHeightUnits = gl.getUniformLocation(prog, 'uAirHazeScaleHeightUnits'); + var uAirHazeGain = gl.getUniformLocation(prog, 'uAirHazeGain'); + var uAirHazeMaxAlpha = gl.getUniformLocation(prog, 'uAirHazeMaxAlpha'); + var uAirHazeColor = gl.getUniformLocation(prog, 'uAirHazeColor'); var uCloudAlbedo = gl.getUniformLocation(prog, 'uCloudAlbedo'); var uAirDensity = gl.getUniformLocation(prog, 'uAirDensity'); var uCloudHaze = gl.getUniformLocation(prog, 'uCloudHaze'); @@ -1029,7 +1092,11 @@ function init(canvas) { uMoonInt: uMoonInt, uSkyAmb: uSkyAmb, uStarAmb: uStarAmb, uPhysical: uPhysical, uAirColor: uAirColor, uRayleighBeta: uRayleighBeta, uSunAirMass: uSunAirMass, - uRayleighGain: uRayleighGain, uPhysicalSkyOn: uPhysicalSkyOn, uCloudAlbedo: uCloudAlbedo, + uRayleighGain: uRayleighGain, uPhysicalSkyOn: uPhysicalSkyOn, + uAirHazeOn: uAirHazeOn, uAirHazeDensity: uAirHazeDensity, + uAirHazeScaleHeightUnits: uAirHazeScaleHeightUnits, + uAirHazeGain: uAirHazeGain, uAirHazeMaxAlpha: uAirHazeMaxAlpha, + uAirHazeColor: uAirHazeColor, uCloudAlbedo: uCloudAlbedo, uAirDensity: uAirDensity, uCloudHaze: uCloudHaze, uSkyTex: uSkyTex, uSkyOn: uSkyOn, uStarLon: uStarLon, uPole: uPole, uMoonTex: uMoonTex, uMoonTexOn: uMoonTexOn, uMoonR: uMoonR, @@ -2516,6 +2583,24 @@ var TERRAIN_HAZE_COLOR = new Float32Array( ? [TERRAIN_HAZE_CFG.color[0], TERRAIN_HAZE_CFG.color[1], TERRAIN_HAZE_CFG.color[2]] : [0.58, 0.64, 0.72] ); +var AIR_HAZE_CFG = ATMO.airHaze || TERRAIN_HAZE_CFG || {}; +var AIR_HAZE_ON = (typeof AIR_HAZE_CFG.enabled === 'boolean') + ? AIR_HAZE_CFG.enabled + : (TERRAIN_HAZE_ON && AIR_HAZE_CFG.enabled !== false); +var AIR_HAZE_SCALE_HEIGHT_KM = (typeof AIR_HAZE_CFG.scaleHeightKm === 'number') + ? Math.max(1.0, Math.min(30.0, AIR_HAZE_CFG.scaleHeightKm)) : 8.5; +var AIR_HAZE_SCALE_HEIGHT_UNITS = AIR_HAZE_SCALE_HEIGHT_KM / KM_PER_TILE; +var AIR_HAZE_DENSITY = (typeof AIR_HAZE_CFG.density === 'number') + ? Math.max(0.01, Math.min(5.0, AIR_HAZE_CFG.density)) : 0.16; +var AIR_HAZE_GAIN = (typeof AIR_HAZE_CFG.gain === 'number') + ? Math.max(0.1, Math.min(5.0, AIR_HAZE_CFG.gain)) : 1.0; +var AIR_HAZE_MAX_ALPHA = (typeof AIR_HAZE_CFG.maxAlpha === 'number') + ? Math.max(0.05, Math.min(1.0, AIR_HAZE_CFG.maxAlpha)) : 0.45; +var AIR_HAZE_COLOR = new Float32Array( + Array.isArray(AIR_HAZE_CFG.color) && AIR_HAZE_CFG.color.length >= 3 + ? [AIR_HAZE_CFG.color[0], AIR_HAZE_CFG.color[1], AIR_HAZE_CFG.color[2]] + : [0.58, 0.64, 0.72] +); var CURV_CFG = (window.CONFIG && window.CONFIG.curvature) || {}; var CURVATURE_ON = CURV_CFG.enabled !== false; var CURV_EXAG = (typeof CURV_CFG.exaggeration === 'number') @@ -5608,6 +5693,12 @@ window.clouds3dRender = function(target) { gl.uniform1f(s.uSunAirMass, airMass(Math.max(sunAlt, 0.0))); gl.uniform1f(s.uRayleighGain, ATMO_RAYLEIGH_GAIN); gl.uniform1f(s.uPhysicalSkyOn, ATMO_PHYSICAL_SKY ? 1.0 : 0.0); + gl.uniform1f(s.uAirHazeOn, AIR_HAZE_ON ? 1.0 : 0.0); + gl.uniform1f(s.uAirHazeDensity, AIR_HAZE_DENSITY); + gl.uniform1f(s.uAirHazeScaleHeightUnits, AIR_HAZE_SCALE_HEIGHT_UNITS); + gl.uniform1f(s.uAirHazeGain, AIR_HAZE_GAIN); + gl.uniform1f(s.uAirHazeMaxAlpha, AIR_HAZE_MAX_ALPHA); + gl.uniform3f(s.uAirHazeColor, AIR_HAZE_COLOR[0], AIR_HAZE_COLOR[1], AIR_HAZE_COLOR[2]); gl.uniform3f(s.uSunColor, _beam[0], _beam[1], _beam[2]); gl.uniform1f(s.uSunInt, _beamL); gl.uniform3f(s.uMoonColor, _mBeam[0], _mBeam[1], _mBeam[2]);