diff --git a/vendor/clouds3d.js b/vendor/clouds3d.js index ead3a51..6fefd59 100644 --- a/vendor/clouds3d.js +++ b/vendor/clouds3d.js @@ -85,6 +85,9 @@ var FRAG_SRC = [ 'uniform float uSunAlt;', 'uniform float uCloudTop;', // cloud deck upper edge (world Y) 'uniform float uCloudBot;', // cloud deck lower edge (world Y) + 'uniform float uCloudEdgeSoft;', + 'uniform float uCloudHorizonSoft;', + 'uniform float uCloudMaxMarch;', 'uniform float uCloudNearFadeStart;', 'uniform float uCloudNearFadeEnd;', 'uniform sampler2D uElev;', // terrarium RGB height grid, same UV as uGround @@ -219,6 +222,14 @@ var FRAG_SRC = [ 'vec2 d = xz - uCamPos.xz;', 'return dot(d, d) / (2.0 * max(1.0, uEarthRadius));', '}', + 'float cloudVerticalProfile(float alt) {', + 'float pad = max(0.0, uCloudEdgeSoft);', + 'float bot = uCloudBot - pad;', + 'float top = uCloudTop + pad;', + 'float h = (alt - bot) / max(0.01, top - bot);', + 'if (h <= 0.0 || h >= 1.0) return 0.0;', + 'return smoothstep(0.0, 0.25, h) * smoothstep(1.0, 0.75, h);', + '}', 'float cloudDensity(vec3 p, float baseThresh, float soft) {', 'float py = p.y + earthDrop(p.xz);', 'vec3 cp = vec3(p.x, py, p.z) + uWind * uTime;', @@ -230,11 +241,7 @@ var FRAG_SRC = [ '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);', '}', - // Cloud layer between uCloudBot and uCloudTop, densest in the - // middle (bell curve). The layer follows the Earth curvature. - 'float h = clamp((py - uCloudBot) / (uCloudTop - uCloudBot), 0.0, 1.0);', - 'float profile = 4.0 * h * (1.0 - h); // bell curve', - 'profile = max(profile, 0.0);', + 'float profile = cloudVerticalProfile(py);', 'return smoothstep(baseThresh, baseThresh + 0.34 + soft * 0.28, n) * profile;', '}', // Star field: project ray onto a far sphere, hash-grid for @@ -421,9 +428,7 @@ var FRAG_SRC = [ '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);', '}', - 'float h = clamp((alt - uCloudBot) / max(0.01, uCloudTop - uCloudBot), 0.0, 1.0);', - 'float profile = 4.0 * h * (1.0 - h);', - 'profile = max(profile, 0.0);', + 'float profile = cloudVerticalProfile(alt);', 'return smoothstep(baseThresh, baseThresh + 0.34 + soft * 0.28, n) * profile;', '}', 'float hazeColumnToDistance(vec3 rd, float dist) {', @@ -677,14 +682,17 @@ var FRAG_SRC = [ 'if (uMode == 1) {', 'if (uCloudsOn < 0.5) { gl_FragColor = vec4(0.0); return; }', 'float camAlt = uCamPos.y;', - 'vec2 top = cloudLayerRoots(uCloudTop, rd);', - 'vec2 bot = cloudLayerRoots(uCloudBot, rd);', + 'float pad = max(0.0, uCloudEdgeSoft);', + 'float softTop = uCloudTop + pad;', + 'float softBot = uCloudBot - pad;', + 'vec2 top = cloudLayerRoots(softTop, rd);', + 'vec2 bot = cloudLayerRoots(softBot, rd);', 'float tEnter = 1e9;', 'float tExit = -1e9;', - 'if (camAlt >= uCloudBot && camAlt <= uCloudTop) {', + 'if (camAlt >= softBot && camAlt <= softTop) {', 'tEnter = 0.0;', 'tExit = max(maxPositiveRoot(top), maxPositiveRoot(bot));', - '} else if (camAlt > uCloudTop) {', + '} else if (camAlt > softTop) {', 'tEnter = minPositiveRoot(top);', 'tExit = maxPositiveRoot(bot);', 'if (tExit < 0.0) tExit = maxPositiveRoot(top);', @@ -694,6 +702,9 @@ var FRAG_SRC = [ 'if (tExit < 0.0) tExit = maxPositiveRoot(bot);', '}', 'if (tEnter > 1e8 || tExit <= tEnter) { gl_FragColor = vec4(0.0); return; }', + 'float naturalInterval = tExit - tEnter;', + 'float horizonFade = smoothstep(0.0, max(0.1, uCloudHorizonSoft), naturalInterval);', + 'if (horizonFade <= 0.001) { gl_FragColor = vec4(0.0); return; }', 'vec2 uv = gl_FragCoord.xy / uResolution;', 'if (uTerrainDistOk > 0.5) {', 'vec4 distTex = texture2D(uTerrainDistTex, uv);', @@ -702,13 +713,14 @@ var FRAG_SRC = [ 'float dist = distNorm * max(1.0, uFarPlane);', 'vec3 pT = uCamPos + rd * dist;', 'float terrainAlt = (pT.y + earthDrop(pT.xz)) / max(1.0, uTerrainExag);', - 'if (terrainAlt > uCloudTop + 0.05) { gl_FragColor = vec4(0.0); return; }', + 'if (terrainAlt > softTop + 0.05) { gl_FragColor = vec4(0.0); return; }', 'tExit = min(tExit, dist - 0.02);', '}', '}', - 'tExit = min(tExit, tEnter + 30.0);', + 'float maxMarch = max(1.0, uCloudMaxMarch);', + 'tExit = min(tExit, tEnter + maxMarch);', 'if (tExit <= tEnter) { gl_FragColor = vec4(0.0); return; }', - 'float stepSize = (tExit - tEnter) / 24.0;', + 'float stepSize = (tExit - tEnter) / 48.0;', 'float t = tEnter + stepSize * 0.5;', 'float coverage = clamp(uDensity, 0.0, 1.0);', 'float baseThresh = mix(0.62, 0.20, coverage);', @@ -722,11 +734,11 @@ var FRAG_SRC = [ 'vec3 p = uCamPos + rd * t;', 'p += rd * (ign(gl_FragCoord.xy + vec2(float(i) * 7.13, float(i) * 3.71)) - 0.5) * stepSize * 0.25;', 'float alt = p.y + earthDrop(p.xz);', - 'if (alt > uCloudTop + 0.1 || alt < uCloudBot - 0.1) break;', + 'if (alt > softTop + 0.1 || alt < softBot - 0.1) break;', 'float soft = smoothstep(4.0, 20.0, t);', 'float d = cloudDensitySpherical(p, alt, baseThresh, soft);', 'if (d > 0.02) {', - 'float lit = clamp((alt - uCloudBot) / max(0.01, uCloudTop - uCloudBot), 0.0, 1.0);', + 'float lit = clamp((alt - softBot) / max(0.01, softTop - softBot), 0.0, 1.0);', 'vec3 albC = uCloudAlbedo * mix(0.78, 1.0, lit);', 'float selfLit = clamp(1.0 - d * 0.8, 0.18, 1.0);', 'vec3 direct = albC * uSunColor * uSunInt * phase * sunUp * selfLit;', @@ -734,7 +746,8 @@ var FRAG_SRC = [ 'vec3 moonC = albC * uMoonColor * (0.35 + 0.65 * max(0.0, dot(rd, uMoonDir))) * uMoonInt * 3.0;', 'vec3 cloudCol = direct + ambC + moonC;', 'float nearFade = smoothstep(uCloudNearFadeStart, uCloudNearFadeEnd, t);', - 'float alpha = clamp(d * stepSize * 3.50 * densityGain * nearFade, 0.0, 1.0);', + 'float marchFade = 1.0 - smoothstep(maxMarch * 0.75, maxMarch, t - tEnter);', + 'float alpha = clamp(d * stepSize * 3.50 * densityGain * nearFade * horizonFade * marchFade, 0.0, 1.0);', 'cloudSum += cloudCol * alpha * trans;', 'trans *= (1.0 - alpha);', 'if (trans < 0.04) break;', @@ -877,6 +890,14 @@ var CLOUD_SAMPLER_GLSL = [ 'float sq = sqrt(disc);', 'return vec2((-b - sq) / (2.0 * a), (-b + sq) / (2.0 * a));', '}', + 'float cloudVerticalProfile(float alt) {', + 'float pad = max(0.0, uCloudEdgeSoft);', + 'float bot = uCloudBot - pad;', + 'float top = uCloudTop + pad;', + 'float h = (alt - bot) / max(0.01, top - bot);', + 'if (h <= 0.0 || h >= 1.0) return 0.0;', + 'return smoothstep(0.0, 0.25, h) * smoothstep(1.0, 0.75, h);', + '}', 'float cloudDensity(vec3 p, float baseThresh, float soft) {', 'float py = p.y + earthDrop(p.xz);', 'vec3 cp = vec3(p.x, py, p.z) + uWind * uTime;', @@ -888,9 +909,7 @@ var CLOUD_SAMPLER_GLSL = [ '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);', '}', - 'float h = clamp((py - uCloudBot) / (uCloudTop - uCloudBot), 0.0, 1.0);', - 'float profile = 4.0 * h * (1.0 - h);', - 'profile = max(profile, 0.0);', + 'float profile = cloudVerticalProfile(py);', 'return smoothstep(baseThresh, baseThresh + 0.34 + soft * 0.28, n) * profile;', '}', ].join('\n'); @@ -915,6 +934,7 @@ var TERRAIN_FRAG_SRC = [ 'uniform vec3 uWind;', // cloud advection vector, world units / s 'uniform float uCloudBot;', 'uniform float uCloudTop;', + 'uniform float uCloudEdgeSoft;', 'uniform float uDensity;', // cloud coverage, same as the sky pass 'uniform float uCloudShadowOn;', 'uniform float uCloudShadowDensity;', // optical depth per density sample @@ -1146,6 +1166,9 @@ function init(canvas) { var uSunAlt = gl.getUniformLocation(prog, 'uSunAlt'); var uCloudTop = gl.getUniformLocation(prog, 'uCloudTop'); var uCloudBot = gl.getUniformLocation(prog, 'uCloudBot'); + var uCloudEdgeSoft = gl.getUniformLocation(prog, 'uCloudEdgeSoft'); + var uCloudHorizonSoft = gl.getUniformLocation(prog, 'uCloudHorizonSoft'); + var uCloudMaxMarch = gl.getUniformLocation(prog, 'uCloudMaxMarch'); var uElev = gl.getUniformLocation(prog, 'uElev'); var uElevOn = gl.getUniformLocation(prog, 'uElevOn'); var uElevScale = gl.getUniformLocation(prog, 'uElevScale'); @@ -1269,6 +1292,7 @@ function init(canvas) { var terrainUwind = terrainProg ? gl.getUniformLocation(terrainProg, 'uWind') : null; var terrainUcloudbot = terrainProg ? gl.getUniformLocation(terrainProg, 'uCloudBot') : null; var terrainUcloudtop = terrainProg ? gl.getUniformLocation(terrainProg, 'uCloudTop') : null; + var terrainUcloudEdgeSoft = terrainProg ? gl.getUniformLocation(terrainProg, 'uCloudEdgeSoft') : null; var terrainUdensity = terrainProg ? gl.getUniformLocation(terrainProg, 'uDensity') : null; var terrainUcloudshadow = terrainProg ? gl.getUniformLocation(terrainProg, 'uCloudShadowOn') : null; var terrainUcsDens = terrainProg ? gl.getUniformLocation(terrainProg, 'uCloudShadowDensity') : null; @@ -1308,6 +1332,7 @@ function init(canvas) { uSpeed: uSpeed, uDensity: uDensity, uDayProgress: uDayProgress, uSunAlt: uSunAlt, uCloudTop: uCloudTop, uCloudBot: uCloudBot, + uCloudEdgeSoft: uCloudEdgeSoft, uCloudHorizonSoft: uCloudHorizonSoft, uCloudMaxMarch: uCloudMaxMarch, uCloudNearFadeStart: gl.getUniformLocation(prog, 'uCloudNearFadeStart'), uCloudNearFadeEnd: gl.getUniformLocation(prog, 'uCloudNearFadeEnd'), uElev: uElev, uElevOn: uElevOn, uElevScale: uElevScale, uTerrainExag: uTerrainExag, @@ -1372,7 +1397,7 @@ function init(canvas) { uDistancePassOn: terrainUDistancePass, uFarPlane: terrainUFarPlane, uStarAmb: terrainUstarAmb, uTime: terrainUtime, uWind: terrainUwind, uCloudBot: terrainUcloudbot, - uCloudTop: terrainUcloudtop, uDensity: terrainUdensity, + uCloudTop: terrainUcloudtop, uCloudEdgeSoft: terrainUcloudEdgeSoft, uDensity: terrainUdensity, uCloudShadowOn: terrainUcloudshadow, uCloudShadowDensity: terrainUcsDens, uCloudShadowFloor: terrainUcsFloor, @@ -1505,6 +1530,13 @@ var CLOUD_NEAR_FADE_CFG = (window.CONFIG && window.CONFIG.clouds && window.CONFI var CLOUD_NEAR_FADE_START = (typeof CLOUD_NEAR_FADE_CFG.start === 'number') ? Math.max(0, CLOUD_NEAR_FADE_CFG.start) : 0.35; var CLOUD_NEAR_FADE_END = Math.max(CLOUD_NEAR_FADE_START + 0.1, (typeof CLOUD_NEAR_FADE_CFG.end === 'number') ? CLOUD_NEAR_FADE_CFG.end : 2.5); var CLOUDS_ON = !(window.CONFIG && window.CONFIG.clouds && window.CONFIG.clouds.enabled === false); +var CLOUD_SOFT_EDGES = !(window.CONFIG && window.CONFIG.clouds && window.CONFIG.clouds.softEdges === false); +var CLOUD_EDGE_SOFT = CLOUD_SOFT_EDGES && (typeof window.CONFIG.clouds.edgeSoftness === 'number') + ? Math.max(0.0, Math.min(1.0, window.CONFIG.clouds.edgeSoftness)) : (CLOUD_SOFT_EDGES ? 0.18 : 0.0); +var CLOUD_HORIZON_SOFT = CLOUD_SOFT_EDGES && (typeof window.CONFIG.clouds.horizonSoftness === 'number') + ? Math.max(0.1, Math.min(40.0, window.CONFIG.clouds.horizonSoftness)) : (CLOUD_SOFT_EDGES ? 8.0 : 0.1); +var CLOUD_MAX_MARCH_UNITS = (window.CONFIG && typeof window.CONFIG.clouds.maxMarchUnits === 'number') + ? Math.max(10.0, Math.min(300.0, window.CONFIG.clouds.maxMarchUnits)) : 30.0; var CLOUD_TERRAIN_OCCLUSION = !(window.CONFIG && window.CONFIG.clouds && window.CONFIG.clouds.terrainOcclusion === false); var CLOUD_WIND_CFG = (window.CONFIG && window.CONFIG.clouds && window.CONFIG.clouds.wind) || {}; var CLOUD_WIND_SCALE = (typeof CLOUD_WIND_CFG.speedScale === 'number') ? Math.max(0, CLOUD_WIND_CFG.speedScale) : 80; @@ -7384,6 +7416,9 @@ window.clouds3dRender = function(target) { gl.uniform1f(s.uSpeed, speed); gl.uniform1f(s.uCloudTop, CLOUD_TOP); gl.uniform1f(s.uCloudBot, CLOUD_BOT); + gl.uniform1f(s.uCloudEdgeSoft, CLOUD_EDGE_SOFT); + gl.uniform1f(s.uCloudHorizonSoft, CLOUD_HORIZON_SOFT); + gl.uniform1f(s.uCloudMaxMarch, CLOUD_MAX_MARCH_UNITS); gl.uniform1f(s.uCloudNearFadeStart, CLOUD_NEAR_FADE_START); gl.uniform1f(s.uCloudNearFadeEnd, CLOUD_NEAR_FADE_END); @@ -7658,6 +7693,7 @@ window.clouds3dRender = function(target) { gl.uniform3f(terr.uWind, _cloudWind[0], _cloudWind[1], _cloudWind[2]); gl.uniform1f(terr.uCloudBot, CLOUD_BOT); gl.uniform1f(terr.uCloudTop, CLOUD_TOP); + gl.uniform1f(terr.uCloudEdgeSoft, CLOUD_EDGE_SOFT); gl.uniform1f(terr.uDensity, density); gl.uniform1f(terr.uCloudShadowOn, ATMO_CLOUD_SHADOWS ? 1.0 : 0.0); gl.uniform1f(terr.uCloudShadowDensity, ATMO_CS_DENSITY);