feat: terrain-following cloud base
This commit is contained in:
1 parent
c02a198f3f
commit
3e17279499
2 files changed
+54
-6
No files matched your search
@@ -335,6 +335,14 @@ window.CONFIG = {
|
|||||||
start: 0.35, // world units: clouds fade out very close to camera
|
start: 0.35, // world units: clouds fade out very close to camera
|
||||||
end: 2.5 // full density from this distance onward
|
end: 2.5 // full density from this distance onward
|
||||||
},
|
},
|
||||||
|
// Terrain-following cloud base. The DEM height grid is sampled in
|
||||||
|
// the cloud shader, so the cloud bottom rises locally to keep a
|
||||||
|
// clearance above the visible ground. 'bottom' remains the global
|
||||||
|
// floor for areas without terrain data.
|
||||||
|
groundFollow: {
|
||||||
|
enabled: true, // false = flat cloud deck
|
||||||
|
clearanceM: 50 // metres above terrain, exaggerated with terrain
|
||||||
|
},
|
||||||
// Coverage per weather level, 0 = clear, 1 = overcast. Only the
|
// Coverage per weather level, 0 = clear, 1 = overcast. Only the
|
||||||
// levels listed here override the built-in defaults
|
// levels listed here override the built-in defaults
|
||||||
// (sunny 0.20, cloudy 0.70, stormy 0.80, night 0.45). Live weather
|
// (sunny 0.20, cloudy 0.70, stormy 0.80, night 0.45). Live weather
|
||||||
|
|||||||
Vendored
+46
-6
@@ -85,6 +85,10 @@ var FRAG_SRC = [
|
|||||||
'uniform float uSunAlt;',
|
'uniform float uSunAlt;',
|
||||||
'uniform float uCloudTop;', // cloud deck upper edge (world Y)
|
'uniform float uCloudTop;', // cloud deck upper edge (world Y)
|
||||||
'uniform float uCloudBot;', // cloud deck lower edge (world Y)
|
'uniform float uCloudBot;', // cloud deck lower edge (world Y)
|
||||||
|
'uniform float uCloudGroundFollow;',
|
||||||
|
'uniform float uCloudClearanceUnits;',
|
||||||
|
'float terrElev(vec2 xz);',
|
||||||
|
'float cloudBaseY(vec2 xz);',
|
||||||
'uniform float uCloudNearFadeStart;',
|
'uniform float uCloudNearFadeStart;',
|
||||||
'uniform float uCloudNearFadeEnd;',
|
'uniform float uCloudNearFadeEnd;',
|
||||||
'uniform sampler2D uElev;', // terrarium RGB height grid, same UV as uGround
|
'uniform sampler2D uElev;', // terrarium RGB height grid, same UV as uGround
|
||||||
@@ -230,9 +234,9 @@ var FRAG_SRC = [
|
|||||||
'float d = fbm3(cp * 1.80 + vec3(uTime * 0.015, 7.3, uTime * 0.012));',
|
'float d = fbm3(cp * 1.80 + vec3(uTime * 0.015, 7.3, uTime * 0.012));',
|
||||||
'n = n + d * mix(0.22, 0.0, soft / 0.75);',
|
'n = n + d * mix(0.22, 0.0, soft / 0.75);',
|
||||||
'}',
|
'}',
|
||||||
// Cloud layer between uCloudBot and uCloudTop, densest in the
|
// Cloud layer between the terrain-following base and uCloudTop.
|
||||||
// middle (bell curve). The layer follows the Earth curvature.
|
'float bot = cloudBaseY(p.xz);',
|
||||||
'float h = clamp((py - uCloudBot) / (uCloudTop - uCloudBot), 0.0, 1.0);',
|
'float h = clamp((py - bot) / max(0.01, uCloudTop - bot), 0.0, 1.0);',
|
||||||
'float profile = 4.0 * h * (1.0 - h); // bell curve',
|
'float profile = 4.0 * h * (1.0 - h); // bell curve',
|
||||||
'profile = max(profile, 0.0);',
|
'profile = max(profile, 0.0);',
|
||||||
'return smoothstep(baseThresh, baseThresh + 0.34 + soft * 0.28, n) * profile;',
|
'return smoothstep(baseThresh, baseThresh + 0.34 + soft * 0.28, n) * profile;',
|
||||||
@@ -266,6 +270,14 @@ var FRAG_SRC = [
|
|||||||
'vec3 t = texture2D(uElev, euv).rgb;',
|
'vec3 t = texture2D(uElev, euv).rgb;',
|
||||||
'return (t.r * 256.0 + t.g + t.b / 256.0) * 255.0 - 32768.0;',
|
'return (t.r * 256.0 + t.g + t.b / 256.0) * 255.0 - 32768.0;',
|
||||||
'}',
|
'}',
|
||||||
|
'float cloudBaseY(vec2 xz) {',
|
||||||
|
'float base = uCloudBot;',
|
||||||
|
'if (uCloudGroundFollow > 0.5 && uElevOn > 0.5) {',
|
||||||
|
'float terrainY = terrElev(xz) * uElevScale;',
|
||||||
|
'base = max(base, terrainY + uCloudClearanceUnits);',
|
||||||
|
'}',
|
||||||
|
'return min(base, uCloudTop - 0.05);',
|
||||||
|
'}',
|
||||||
'vec2 curvedLayerRoots(float layerY, vec3 ro, vec3 rd) {',
|
'vec2 curvedLayerRoots(float layerY, vec3 ro, vec3 rd) {',
|
||||||
'float R = max(1.0, uEarthRadius);',
|
'float R = max(1.0, uEarthRadius);',
|
||||||
'float a = (uCurvatureOn > 0.5) ? dot(rd.xz, rd.xz) / (2.0 * R) : 0.0;',
|
'float a = (uCurvatureOn > 0.5) ? dot(rd.xz, rd.xz) / (2.0 * R) : 0.0;',
|
||||||
@@ -421,7 +433,8 @@ var FRAG_SRC = [
|
|||||||
'float d = fbm3(q * 1.80 + vec3(uTime * 0.015, 7.3, uTime * 0.012));',
|
'float d = fbm3(q * 1.80 + vec3(uTime * 0.015, 7.3, uTime * 0.012));',
|
||||||
'n = n + d * mix(0.22, 0.0, soft / 0.75);',
|
'n = n + d * mix(0.22, 0.0, soft / 0.75);',
|
||||||
'}',
|
'}',
|
||||||
'float h = clamp((alt - uCloudBot) / max(0.01, uCloudTop - uCloudBot), 0.0, 1.0);',
|
'float bot = cloudBaseY(p.xz);',
|
||||||
|
'float h = clamp((alt - bot) / max(0.01, uCloudTop - bot), 0.0, 1.0);',
|
||||||
'float profile = 4.0 * h * (1.0 - h);',
|
'float profile = 4.0 * h * (1.0 - h);',
|
||||||
'profile = max(profile, 0.0);',
|
'profile = max(profile, 0.0);',
|
||||||
'return smoothstep(baseThresh, baseThresh + 0.34 + soft * 0.28, n) * profile;',
|
'return smoothstep(baseThresh, baseThresh + 0.34 + soft * 0.28, n) * profile;',
|
||||||
@@ -725,7 +738,8 @@ var FRAG_SRC = [
|
|||||||
'float soft = smoothstep(4.0, 20.0, t);',
|
'float soft = smoothstep(4.0, 20.0, t);',
|
||||||
'float d = cloudDensitySpherical(p, alt, baseThresh, soft);',
|
'float d = cloudDensitySpherical(p, alt, baseThresh, soft);',
|
||||||
'if (d > 0.02) {',
|
'if (d > 0.02) {',
|
||||||
'float lit = clamp((alt - uCloudBot) / max(0.01, uCloudTop - uCloudBot), 0.0, 1.0);',
|
'float botHere = cloudBaseY(p.xz);',
|
||||||
|
'float lit = clamp((alt - botHere) / max(0.01, uCloudTop - botHere), 0.0, 1.0);',
|
||||||
'vec3 albC = uCloudAlbedo * mix(0.78, 1.0, lit);',
|
'vec3 albC = uCloudAlbedo * mix(0.78, 1.0, lit);',
|
||||||
'float selfLit = clamp(1.0 - d * 0.8, 0.18, 1.0);',
|
'float selfLit = clamp(1.0 - d * 0.8, 0.18, 1.0);',
|
||||||
'vec3 direct = albC * uSunColor * uSunInt * phase * sunUp * selfLit;',
|
'vec3 direct = albC * uSunColor * uSunInt * phase * sunUp * selfLit;',
|
||||||
@@ -834,6 +848,14 @@ var CLOUD_SAMPLER_GLSL = [
|
|||||||
'vec3 t = texture2D(uElev, euv).rgb;',
|
'vec3 t = texture2D(uElev, euv).rgb;',
|
||||||
'return (t.r * 256.0 + t.g + t.b / 256.0) * 255.0 - 32768.0;',
|
'return (t.r * 256.0 + t.g + t.b / 256.0) * 255.0 - 32768.0;',
|
||||||
'}',
|
'}',
|
||||||
|
'float cloudBaseYT(vec2 xz) {',
|
||||||
|
'float base = uCloudBot;',
|
||||||
|
'if (uCloudGroundFollow > 0.5) {',
|
||||||
|
'float terrainY = terrElevT(xz) * uElevScale;',
|
||||||
|
'base = max(base, terrainY + uCloudClearanceUnits);',
|
||||||
|
'}',
|
||||||
|
'return min(base, uCloudTop - 0.05);',
|
||||||
|
'}',
|
||||||
// Terrain self-shadow: march from the surface towards the sun over the
|
// Terrain self-shadow: march from the surface towards the sun over the
|
||||||
// height field and keep the smallest clearance between ray and ground.
|
// height field and keep the smallest clearance between ray and ground.
|
||||||
// The clearance (not a hard hit test) gives a soft penumbra, and the
|
// The clearance (not a hard hit test) gives a soft penumbra, and the
|
||||||
@@ -887,7 +909,8 @@ var CLOUD_SAMPLER_GLSL = [
|
|||||||
'float d = fbm3(cp * 1.80 + vec3(uTime * 0.015, 7.3, uTime * 0.012));',
|
'float d = fbm3(cp * 1.80 + vec3(uTime * 0.015, 7.3, uTime * 0.012));',
|
||||||
'n = n + d * mix(0.22, 0.0, soft / 0.75);',
|
'n = n + d * mix(0.22, 0.0, soft / 0.75);',
|
||||||
'}',
|
'}',
|
||||||
'float h = clamp((py - uCloudBot) / (uCloudTop - uCloudBot), 0.0, 1.0);',
|
'float bot = cloudBaseYT(p.xz);',
|
||||||
|
'float h = clamp((py - bot) / max(0.01, uCloudTop - bot), 0.0, 1.0);',
|
||||||
'float profile = 4.0 * h * (1.0 - h);',
|
'float profile = 4.0 * h * (1.0 - h);',
|
||||||
'profile = max(profile, 0.0);',
|
'profile = max(profile, 0.0);',
|
||||||
'return smoothstep(baseThresh, baseThresh + 0.34 + soft * 0.28, n) * profile;',
|
'return smoothstep(baseThresh, baseThresh + 0.34 + soft * 0.28, n) * profile;',
|
||||||
@@ -914,6 +937,8 @@ var TERRAIN_FRAG_SRC = [
|
|||||||
'uniform vec3 uWind;', // cloud advection vector, world units / s
|
'uniform vec3 uWind;', // cloud advection vector, world units / s
|
||||||
'uniform float uCloudBot;',
|
'uniform float uCloudBot;',
|
||||||
'uniform float uCloudTop;',
|
'uniform float uCloudTop;',
|
||||||
|
'uniform float uCloudGroundFollow;',
|
||||||
|
'uniform float uCloudClearanceUnits;',
|
||||||
'uniform float uDensity;', // cloud coverage, same as the sky pass
|
'uniform float uDensity;', // cloud coverage, same as the sky pass
|
||||||
'uniform float uCloudShadowOn;',
|
'uniform float uCloudShadowOn;',
|
||||||
'uniform float uCloudShadowDensity;', // optical depth per density sample
|
'uniform float uCloudShadowDensity;', // optical depth per density sample
|
||||||
@@ -1145,6 +1170,8 @@ function init(canvas) {
|
|||||||
var uSunAlt = gl.getUniformLocation(prog, 'uSunAlt');
|
var uSunAlt = gl.getUniformLocation(prog, 'uSunAlt');
|
||||||
var uCloudTop = gl.getUniformLocation(prog, 'uCloudTop');
|
var uCloudTop = gl.getUniformLocation(prog, 'uCloudTop');
|
||||||
var uCloudBot = gl.getUniformLocation(prog, 'uCloudBot');
|
var uCloudBot = gl.getUniformLocation(prog, 'uCloudBot');
|
||||||
|
var uCloudGroundFollow = gl.getUniformLocation(prog, 'uCloudGroundFollow');
|
||||||
|
var uCloudClearanceUnits = gl.getUniformLocation(prog, 'uCloudClearanceUnits');
|
||||||
var uElev = gl.getUniformLocation(prog, 'uElev');
|
var uElev = gl.getUniformLocation(prog, 'uElev');
|
||||||
var uElevOn = gl.getUniformLocation(prog, 'uElevOn');
|
var uElevOn = gl.getUniformLocation(prog, 'uElevOn');
|
||||||
var uElevScale = gl.getUniformLocation(prog, 'uElevScale');
|
var uElevScale = gl.getUniformLocation(prog, 'uElevScale');
|
||||||
@@ -1268,6 +1295,8 @@ function init(canvas) {
|
|||||||
var terrainUwind = terrainProg ? gl.getUniformLocation(terrainProg, 'uWind') : null;
|
var terrainUwind = terrainProg ? gl.getUniformLocation(terrainProg, 'uWind') : null;
|
||||||
var terrainUcloudbot = terrainProg ? gl.getUniformLocation(terrainProg, 'uCloudBot') : null;
|
var terrainUcloudbot = terrainProg ? gl.getUniformLocation(terrainProg, 'uCloudBot') : null;
|
||||||
var terrainUcloudtop = terrainProg ? gl.getUniformLocation(terrainProg, 'uCloudTop') : null;
|
var terrainUcloudtop = terrainProg ? gl.getUniformLocation(terrainProg, 'uCloudTop') : null;
|
||||||
|
var terrainUcloudfollow = terrainProg ? gl.getUniformLocation(terrainProg, 'uCloudGroundFollow') : null;
|
||||||
|
var terrainUcloudclearance = terrainProg ? gl.getUniformLocation(terrainProg, 'uCloudClearanceUnits') : null;
|
||||||
var terrainUdensity = terrainProg ? gl.getUniformLocation(terrainProg, 'uDensity') : null;
|
var terrainUdensity = terrainProg ? gl.getUniformLocation(terrainProg, 'uDensity') : null;
|
||||||
var terrainUcloudshadow = terrainProg ? gl.getUniformLocation(terrainProg, 'uCloudShadowOn') : null;
|
var terrainUcloudshadow = terrainProg ? gl.getUniformLocation(terrainProg, 'uCloudShadowOn') : null;
|
||||||
var terrainUcsDens = terrainProg ? gl.getUniformLocation(terrainProg, 'uCloudShadowDensity') : null;
|
var terrainUcsDens = terrainProg ? gl.getUniformLocation(terrainProg, 'uCloudShadowDensity') : null;
|
||||||
@@ -1307,6 +1336,7 @@ function init(canvas) {
|
|||||||
uSpeed: uSpeed, uDensity: uDensity, uDayProgress: uDayProgress,
|
uSpeed: uSpeed, uDensity: uDensity, uDayProgress: uDayProgress,
|
||||||
uSunAlt: uSunAlt,
|
uSunAlt: uSunAlt,
|
||||||
uCloudTop: uCloudTop, uCloudBot: uCloudBot,
|
uCloudTop: uCloudTop, uCloudBot: uCloudBot,
|
||||||
|
uCloudGroundFollow: uCloudGroundFollow, uCloudClearanceUnits: uCloudClearanceUnits,
|
||||||
uCloudNearFadeStart: gl.getUniformLocation(prog, 'uCloudNearFadeStart'),
|
uCloudNearFadeStart: gl.getUniformLocation(prog, 'uCloudNearFadeStart'),
|
||||||
uCloudNearFadeEnd: gl.getUniformLocation(prog, 'uCloudNearFadeEnd'),
|
uCloudNearFadeEnd: gl.getUniformLocation(prog, 'uCloudNearFadeEnd'),
|
||||||
uElev: uElev, uElevOn: uElevOn, uElevScale: uElevScale, uTerrainExag: uTerrainExag,
|
uElev: uElev, uElevOn: uElevOn, uElevScale: uElevScale, uTerrainExag: uTerrainExag,
|
||||||
@@ -1372,6 +1402,7 @@ function init(canvas) {
|
|||||||
uStarAmb: terrainUstarAmb,
|
uStarAmb: terrainUstarAmb,
|
||||||
uTime: terrainUtime, uWind: terrainUwind, uCloudBot: terrainUcloudbot,
|
uTime: terrainUtime, uWind: terrainUwind, uCloudBot: terrainUcloudbot,
|
||||||
uCloudTop: terrainUcloudtop, uDensity: terrainUdensity,
|
uCloudTop: terrainUcloudtop, uDensity: terrainUdensity,
|
||||||
|
uCloudGroundFollow: terrainUcloudfollow, uCloudClearanceUnits: terrainUcloudclearance,
|
||||||
uCloudShadowOn: terrainUcloudshadow,
|
uCloudShadowOn: terrainUcloudshadow,
|
||||||
uCloudShadowDensity: terrainUcsDens,
|
uCloudShadowDensity: terrainUcsDens,
|
||||||
uCloudShadowFloor: terrainUcsFloor,
|
uCloudShadowFloor: terrainUcsFloor,
|
||||||
@@ -1553,6 +1584,11 @@ var TERRAIN_EXAG = (typeof TERRAIN_CFG.exaggeration === 'number' &&
|
|||||||
TERRAIN_CFG.exaggeration > 0) ? TERRAIN_CFG.exaggeration : 8;
|
TERRAIN_CFG.exaggeration > 0) ? TERRAIN_CFG.exaggeration : 8;
|
||||||
// metres -> world units, including the vertical exaggeration
|
// metres -> world units, including the vertical exaggeration
|
||||||
var ELEV_SCALE_UNITS = TERRAIN_EXAG / (KM_PER_TILE * 1000.0);
|
var ELEV_SCALE_UNITS = TERRAIN_EXAG / (KM_PER_TILE * 1000.0);
|
||||||
|
var CLOUD_GROUND_FOLLOW_CFG = (window.CONFIG && window.CONFIG.clouds && window.CONFIG.clouds.groundFollow) || {};
|
||||||
|
var CLOUD_GROUND_FOLLOW = CLOUD_GROUND_FOLLOW_CFG.enabled === true ? 1.0 : 0.0;
|
||||||
|
var CLOUD_CLEARANCE_M = (typeof CLOUD_GROUND_FOLLOW_CFG.clearanceM === 'number')
|
||||||
|
? Math.max(0, CLOUD_GROUND_FOLLOW_CFG.clearanceM) : 50;
|
||||||
|
var CLOUD_CLEARANCE_UNITS = CLOUD_CLEARANCE_M * ELEV_SCALE_UNITS;
|
||||||
|
|
||||||
var ORBIT_CFG = (window.CONFIG && window.CONFIG.orbit) || null;
|
var ORBIT_CFG = (window.CONFIG && window.CONFIG.orbit) || null;
|
||||||
var ORBIT_R = 0; // figure-8 half-diagonal (lobe tip reach) in tiles
|
var ORBIT_R = 0; // figure-8 half-diagonal (lobe tip reach) in tiles
|
||||||
@@ -7384,6 +7420,8 @@ window.clouds3dRender = function(target) {
|
|||||||
gl.uniform1f(s.uCloudBot, CLOUD_BOT);
|
gl.uniform1f(s.uCloudBot, CLOUD_BOT);
|
||||||
gl.uniform1f(s.uCloudNearFadeStart, CLOUD_NEAR_FADE_START);
|
gl.uniform1f(s.uCloudNearFadeStart, CLOUD_NEAR_FADE_START);
|
||||||
gl.uniform1f(s.uCloudNearFadeEnd, CLOUD_NEAR_FADE_END);
|
gl.uniform1f(s.uCloudNearFadeEnd, CLOUD_NEAR_FADE_END);
|
||||||
|
gl.uniform1f(s.uCloudGroundFollow, CLOUD_GROUND_FOLLOW);
|
||||||
|
gl.uniform1f(s.uCloudClearanceUnits, CLOUD_CLEARANCE_UNITS);
|
||||||
|
|
||||||
// Ground mosaic uniforms + periodic re-fetch trigger.
|
// Ground mosaic uniforms + periodic re-fetch trigger.
|
||||||
// uGroundCenter is the world position (X, Z) of the centre tile's
|
// uGroundCenter is the world position (X, Z) of the centre tile's
|
||||||
@@ -7656,6 +7694,8 @@ window.clouds3dRender = function(target) {
|
|||||||
gl.uniform3f(terr.uWind, _cloudWind[0], _cloudWind[1], _cloudWind[2]);
|
gl.uniform3f(terr.uWind, _cloudWind[0], _cloudWind[1], _cloudWind[2]);
|
||||||
gl.uniform1f(terr.uCloudBot, CLOUD_BOT);
|
gl.uniform1f(terr.uCloudBot, CLOUD_BOT);
|
||||||
gl.uniform1f(terr.uCloudTop, CLOUD_TOP);
|
gl.uniform1f(terr.uCloudTop, CLOUD_TOP);
|
||||||
|
gl.uniform1f(terr.uCloudGroundFollow, CLOUD_GROUND_FOLLOW);
|
||||||
|
gl.uniform1f(terr.uCloudClearanceUnits, CLOUD_CLEARANCE_UNITS);
|
||||||
gl.uniform1f(terr.uDensity, density);
|
gl.uniform1f(terr.uDensity, density);
|
||||||
gl.uniform1f(terr.uCloudShadowOn, ATMO_CLOUD_SHADOWS ? 1.0 : 0.0);
|
gl.uniform1f(terr.uCloudShadowOn, ATMO_CLOUD_SHADOWS ? 1.0 : 0.0);
|
||||||
gl.uniform1f(terr.uCloudShadowDensity, ATMO_CS_DENSITY);
|
gl.uniform1f(terr.uCloudShadowDensity, ATMO_CS_DENSITY);
|
||||||
|
|||||||
Reference in new issue
Block a user