atmosphere: add initial volumetric atmosphere shell

This commit is contained in:
Richard Zink committed 2026-10-12 13:59:59 +02:00
1 parent 1f925c6f68
commit b9094ebd8b
2 files changed
+113 -1

No files matched your search

+104 -1
View File
@@ -85,7 +85,15 @@ var FRAG_SRC = [
'uniform float uSkyOn;', // 0 until the star texture is built
'uniform float uStarLon;', // sidereal rotation offset (radians)
'uniform vec3 uPole;', // north celestial pole in world coords
'uniform int uMode;', // 0 = sky+stars, 1 = clouds (transparent over mesh)
'uniform int uMode;', // 0 = sky+stars, 1 = clouds, 2 = atmosphere shell
'uniform float uAtmoModelOn;',
'uniform float uAtmoHeightUnits;',
'uniform float uAtmoScaleHeightUnits;',
'uniform float uAtmoDensity;',
'uniform float uAtmoGrayTau;',
'uniform float uAtmoGain;',
'uniform vec3 uAtmoBlueColor;',
'uniform vec3 uAtmoGrayColor;',
'uniform vec3 uSunColor;', // direct beam after atmospheric extinction
'uniform float uSunInt;',
'uniform vec3 uMoonColor;',
@@ -227,6 +235,64 @@ var FRAG_SRC = [
'vec3 ro = nearH.xyz / nearH.w;',
'vec3 rd = normalize(farH.xyz / farH.w - ro);',
'if (uMode == 2) {',
'if (uAtmoModelOn < 0.5) { gl_FragColor = vec4(0.0); return; }',
'float R = max(1.0, uEarthRadius);',
'float H = max(0.1, uAtmoHeightUnits);',
'float Hs = max(0.01, uAtmoScaleHeightUnits);',
'vec3 C = vec3(ro.x, -R, ro.z);',
'vec3 oc = ro - C;',
'float b = dot(oc, rd);',
'float rOut = R + H;',
'float cOut = dot(oc, oc) - rOut * rOut;',
'float discOut = b * b - cOut;',
'float tStart = 0.0;',
'float tEnd = 100.0;',
'if (discOut > 0.0) {',
'float sq = sqrt(discOut);',
'float t1 = -b - sq;',
'float t2 = -b + sq;',
'if (dot(oc, oc) > rOut * rOut) tStart = max(0.0, t1);',
'tEnd = max(0.0, t2);',
'}',
'float cIn = dot(oc, oc) - R * R;',
'float discIn = b * b - cIn;',
'float tInner = 100000.0;',
'if (discIn > 0.0) {',
'float sq = sqrt(discIn);',
'float t1 = -b - sq;',
'float t2 = -b + sq;',
'if (t1 > 0.0) tInner = t1;',
'else if (t2 > 0.0) tInner = t2;',
'}',
'if (tInner < 100000.0) tEnd = min(tEnd, tInner);',
'if (tEnd <= tStart) { gl_FragColor = vec4(0.0); return; }',
'float step = (tEnd - tStart) / 32.0;',
'float t = tStart + step * 0.5;',
'float tau = 0.0;',
'vec3 scatter = vec3(0.0);',
'float cosTA = dot(rd, uSunDir);',
'float cosTM = dot(rd, uMoonDir);',
'float sunPhase = 0.25 + 0.75 * pow(max(0.0, cosTA), 2.0);',
'float moonPhase = 0.25 + 0.75 * pow(max(0.0, cosTM), 2.0);',
'for (int i = 0; i < 32; i++) {',
'vec3 p = ro + rd * t;',
'float alt = max(0.0, length(p - C) - R);',
'float dens = exp(-alt / Hs);',
'float dtau = uAtmoDensity * dens * step;',
'float transBefore = exp(-tau);',
'float grayMix = clamp(tau / max(0.01, uAtmoGrayTau), 0.0, 1.0);',
'vec3 atmoCol = mix(uAtmoBlueColor, uAtmoGrayColor, grayMix);',
'vec3 light = uSunColor * uSunInt * sunPhase + uMoonColor * uMoonInt * moonPhase * 2.0 + max(vec3(uSkyAmb), uStarAmb * vec3(0.7, 0.85, 1.0)) * 0.65;',
'scatter += atmoCol * light * dtau * transBefore;',
'tau += dtau;',
't += step;',
'}',
'float alpha = clamp(1.0 - exp(-tau), 0.0, 1.0);',
'vec3 col = scatter / max(alpha, 1e-4) * uAtmoGain;',
'gl_FragColor = vec4(col, alpha);',
'return;',
'}',
'if (uMode == 0) {',
// skyT floor at 0.5: at and below the horizon the sky keeps
// the exact horizon colour - the same value the terrain's
@@ -853,6 +919,11 @@ function init(canvas) {
uCloudTop: uCloudTop, uCloudBot: uCloudBot,
uElev: uElev, uElevOn: uElevOn, uElevScale: uElevScale,
uSunDir: uSunDir, uMoonDir: uMoonDir, uMode: uMode,
uAtmoModelOn: uAtmoModelOn, uAtmoHeightUnits: uAtmoHeightUnits,
uAtmoScaleHeightUnits: uAtmoScaleHeightUnits, uAtmoDensity: uAtmoDensity,
uAtmoGrayTau: uAtmoGrayTau, uAtmoGain: uAtmoGain,
uEarthRadius: uEarthRadius,
uAtmoBlueColor: uAtmoBlueColor, uAtmoGrayColor: uAtmoGrayColor,
uSunColor: uSunColor, uSunInt: uSunInt, uMoonColor: uMoonColor,
uMoonInt: uMoonInt, uSkyAmb: uSkyAmb, uStarAmb: uStarAmb,
uPhysical: uPhysical, uAirColor: uAirColor, uCloudAlbedo: uCloudAlbedo,
@@ -2041,6 +2112,23 @@ var RAYLEIGH = [0.192, 0.451, 1.0];
var ATMO_AIR_GAIN = (typeof ATMO.airGain === 'number') ? Math.max(0.05, ATMO.airGain) : 0.55;
var ATMO_HAZE_RESIDUAL = (typeof ATMO.hazeResidual === 'number')
? Math.max(0.02, Math.min(0.95, ATMO.hazeResidual)) : 0.30;
var ATMO_DISTANCE_FOG = ATMO.distanceFog !== false;
var ATMO_MODEL = ATMO.model || {};
var ATMO_MODEL_ON = ATMO_MODEL.enabled !== false;
var ATMO_MODEL_HEIGHT_KM = (typeof ATMO_MODEL.heightKm === 'number')
? Math.max(1.0, Math.min(80.0, ATMO_MODEL.heightKm)) : 35.0;
var ATMO_MODEL_HEIGHT_UNITS = ATMO_MODEL_HEIGHT_KM / KM_PER_TILE;
var ATMO_MODEL_SCALE_HEIGHT_KM = (typeof ATMO_MODEL.scaleHeightKm === 'number')
? Math.max(1.0, Math.min(30.0, ATMO_MODEL.scaleHeightKm)) : 8.5;
var ATMO_MODEL_SCALE_HEIGHT_UNITS = ATMO_MODEL_SCALE_HEIGHT_KM / KM_PER_TILE;
var ATMO_MODEL_DENSITY = (typeof ATMO_MODEL.density === 'number')
? Math.max(0.01, Math.min(5.0, ATMO_MODEL.density)) : 0.12;
var ATMO_MODEL_GRAY_TAU = (typeof ATMO_MODEL.grayTau === 'number')
? Math.max(0.1, Math.min(10.0, ATMO_MODEL.grayTau)) : 1.5;
var ATMO_MODEL_GAIN = (typeof ATMO_MODEL.gain === 'number')
? Math.max(0.1, Math.min(5.0, ATMO_MODEL.gain)) : 2.0;
var ATMO_MODEL_BLUE_COLOR = new Float32Array([0.22, 0.42, 0.95]);
var ATMO_MODEL_GRAY_COLOR = new Float32Array([0.72, 0.76, 0.82]);
// Integrated starlight + airglow. Physically this is about a millionth of
// daylight, so it is a stylised floor (CONFIG.atmosphere.starGain) that
// keeps clouds, contrails and the ground from going fully black on a
@@ -5112,6 +5200,15 @@ window.clouds3dRender = function(target) {
gl.uniform3f(s.uMoonColor, _mBeam[0], _mBeam[1], _mBeam[2]);
gl.uniform1f(s.uMoonInt, _moonI);
gl.uniform1f(s.uSkyAmb, _amb * ATMO_SKY_GAIN);
gl.uniform1f(s.uAtmoModelOn, ATMO_MODEL_ON ? 1.0 : 0.0);
gl.uniform1f(s.uAtmoHeightUnits, ATMO_MODEL_HEIGHT_UNITS);
gl.uniform1f(s.uAtmoScaleHeightUnits, ATMO_MODEL_SCALE_HEIGHT_UNITS);
gl.uniform1f(s.uAtmoDensity, ATMO_MODEL_DENSITY);
gl.uniform1f(s.uAtmoGrayTau, ATMO_MODEL_GRAY_TAU);
gl.uniform1f(s.uAtmoGain, ATMO_MODEL_GAIN);
gl.uniform1f(s.uEarthRadius, EARTH_RADIUS_UNITS);
gl.uniform3fv(s.uAtmoBlueColor, ATMO_MODEL_BLUE_COLOR);
gl.uniform3fv(s.uAtmoGrayColor, ATMO_MODEL_GRAY_COLOR);
gl.uniform1f(s.uStarAmb, _star);
// Theme cloud hue kept, brightness normalised: the light model, not the
// palette, decides how bright a cloud is.
@@ -5937,6 +6034,12 @@ window.clouds3dRender = function(target) {
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.uniform1i(s.uMode, 1);
gl.drawArrays(gl.TRIANGLES, 0, 3);
if (ATMO_MODEL_ON) {
gl.uniform1i(s.uMode, 2);
gl.drawArrays(gl.TRIANGLES, 0, 3);
}
// Restore default state for the next frame.
gl.disable(gl.BLEND);
gl.enable(gl.DEPTH_TEST);