diff --git a/docs/opengl46-renderer.md b/docs/opengl46-renderer.md index b6e86436..bc9e7e07 100644 --- a/docs/opengl46-renderer.md +++ b/docs/opengl46-renderer.md @@ -196,7 +196,9 @@ are no longer loaded. - **Clouds** are ray marched through layers of the genera of aviation reports (cumulus, cumulonimbus, stratus, cirrus...), each with its own shape, height and depth, moving with the wind. The main view gets them at a fraction of its resolution and blended over frames; reflections and the other views read a panorama of the sky with them, renewed in bands over a few - frames. Low clouds hide the hills and the far scenery they are in. + frames. Low clouds hide the hills and the far scenery they are in. They are lit by the sun through themselves (dark bellies, + edges glowing against the sun, thin wisps darker with the sun behind), by the sky above them in its colour (with the + directional ambient light, `gfx.ambient.sh`) and from below by the ground. - **Cloud shadows**: the clouds cast their shadows on the ground, the models, the vehicles and into the fog, from a map around the camera along the sun (or the moon). With them on, the overcast no longer dims the sun and softens its shadows as a whole: the sun is dimmed where the clouds are, and shines in the gaps. diff --git a/shaders/clouds_common.glsl b/shaders/clouds_common.glsl index 804b9422..2cd09a1f 100644 --- a/shaders/clouds_common.glsl +++ b/shaders/clouds_common.glsl @@ -9,6 +9,11 @@ layout(binding = CLOUD_DETAIL_TEX) uniform sampler3D cloud_detail_noise; layout(binding = CLOUD_COVERAGE_TEX) uniform sampler2D cloud_coverage_noise; const float CLOUD_EARTH_RADIUS = 6360000.0; +// how much of the shape noise is left standing at the edge and in the core of a cloud area (cloud_layer_density) +const float CLOUD_FILL_EDGE = 0.45; +const float CLOUD_FILL_CORE = 0.85; +// how fast the density rises inside the edge of a cloud: above 1 the edges are crisper, the inside keeps its density +const float CLOUD_EDGE_SHARPNESS = 2.0; float cloud_remap(float value, float low, float high, float newlow, float newhigh) { @@ -134,20 +139,27 @@ float cloud_layer_density(int index, vec3 position, float height, bool detail) vec2 plane = layer.motion.xy + cloud_noise_plane(layer, position.xz); vec3 coords = vec3(plane.x, height + layer.extra.y * 4096.0, plane.y) * layer.shape.z; vec4 shape = textureLod(cloud_shape_noise, coords, 0.0); - float worley = shape.g * 0.625 + shape.b * 0.25 + shape.a * 0.125; - float base = clamp(cloud_remap(shape.r, worley - 1.0, 1.0, 0.0, 1.0), 0.0, 1.0); + // the smaller cells of the worley octaves bite billows into the heaps of the perlin-worley noise; the octaves keep to a narrow + // band (clouds_noise.comp: WORLEY_FBM_LOW, WORLEY_FBM_HIGH), spread over 0-1 first + float worley = clamp((shape.g * 0.625 + shape.b * 0.25 + shape.a * 0.125 - 0.30) / 0.35, 0.0, 1.0); + float base = clamp(cloud_remap(shape.r, (1.0 - worley) * 0.35, 1.0, 0.0, 1.0), 0.0, 1.0); // the sheets (little erosion) keep few holes, the heaps are made of lumps base = mix(1.0, base, clamp(layer.shape.w * 2.5, 0.25, 1.0)); - float density = clamp(cloud_remap(base * gradient, 1.0 - coverage.x, 1.0, 0.0, 1.0), 0.0, 1.0) * coverage.x; + // inside its area a cloud is not a solid block: the lumps of the shape noise are cut out of it, the more the nearer its edge, + // so it breaks up into single heaps with rounded tops and gaps between them. the sheets keep their base above the cut, and a + // layer closing up (broken to overcast) keeps its lumps whole, so it has no more gaps than its coverage + float fill = coverage.x * mix(mix(CLOUD_FILL_EDGE, CLOUD_FILL_CORE, coverage.y), 1.0, smoothstep(0.5, 1.0, layer.shape.x)); + float density = clamp(cloud_remap(base * gradient, 1.0 - fill, 1.0, 0.0, 1.0), 0.0, 1.0) * coverage.x; if (detail && density > 0.0 && layer.shape.w > 0.0) { vec4 small = textureLod(cloud_detail_noise, coords * 6.0, 0.0); - float fbm = small.r * 0.625 + small.g * 0.25 + small.b * 0.125; + // spread over 0-1 as the worley octaves of the shape; left in their narrow band they would only thin the cloud evenly + float fbm = clamp((small.r * 0.625 + small.g * 0.25 + small.b * 0.125 - 0.30) / 0.35, 0.0, 1.0); // wispy at the base, billowing at the top float erosion = mix(fbm, 1.0 - fbm, clamp(h * 4.0, 0.0, 1.0)); - density = clamp(cloud_remap(density, erosion * layer.shape.w * 0.5, 1.0, 0.0, 1.0), 0.0, 1.0); + density = clamp(cloud_remap(density, erosion * layer.shape.w * 0.7, 1.0, 0.0, 1.0), 0.0, 1.0); } - return density * layer.shape.y; + return min(density * CLOUD_EDGE_SHARPNESS, 1.0) * layer.shape.y; } // the same without the detail, cheaper, for the shadows and the fog diff --git a/shaders/clouds_march.comp b/shaders/clouds_march.comp index fcf9365e..2b3e055c 100644 --- a/shaders/clouds_march.comp +++ b/shaders/clouds_march.comp @@ -28,6 +28,13 @@ const float PANORAMA_LOW = -0.1; // scales of the light of the clouds against the shading of the surfaces (light_common.glsl: SUN_DIFFUSE_SCALE, AMBIENT_SCALE) const float CLOUD_SUN_SCALE = 1.5; const float CLOUD_AMBIENT_SCALE = 1.0; +// the ground under the clouds lights their bases back with this share of the light falling on it +const float CLOUD_GROUND_ALBEDO = 0.2; +// how fast the light of the sky fades into a cloud, per optical depth of the cloud above (or below) a point +const float CLOUD_AMBIENT_FADE = 0.05; +// how dense a cloud has to be to scatter the sunlight back at full: the thin edges and wisps of the side lit by the sun, seen with the +// sun behind, are darker than its bulk (the "powder" effect: the light takes a few scatterings to turn back towards the view) +const float CLOUD_POWDER = 6.0; float henyey_greenstein(float cosangle, float g) { @@ -41,9 +48,18 @@ float cloud_phase(float cosangle, float scale) return mix(henyey_greenstein(cosangle, 0.8 * scale), henyey_greenstein(cosangle, -0.25 * scale), 0.3); } +// the light of the sky a face looking straight up (Up 1) or down (-1) gets: the uniform ambient light and the directional part of +// the sky light (sky_ssbo, light_common.glsl: sky_ambient_directional) along the vertical. with the physical atmosphere it is bluer +// and brighter from above than from the sides, so the shaded sides of the clouds take the colour of the sky above them +vec3 cloud_sky_light(float up) +{ + vec3 directional = sky_ambient_sh[1].rgb * (0.488603 * up) - sky_ambient_sh[6].rgb * 0.315392 - sky_ambient_sh[8].rgb * 0.546274; + return max(weather_ambient.rgb + directional * 1.3, vec3(0.0)) * CLOUD_AMBIENT_SCALE; +} + // light of the sun reaching a point inside a layer through the cloud towards it, with the light scattered many times // (octaves after Wrenninge), and the phase of the view -float cloud_sunlight(int index, vec3 position, float cosangle, int steps) +float cloud_sunlight(int index, vec3 position, float cosangle, int steps, float powder) { vec3 sun = weather_sun_direction.xyz; cloud_layer_s layer = cloud_layers[index]; @@ -65,7 +81,7 @@ float cloud_sunlight(int index, vec3 position, float cosangle, int steps) float spread = 1.0; for (int octave = 0; octave < 3; octave++) { - light += amount * exp(-depth * fade) * cloud_phase(cosangle, spread); + light += amount * exp(-depth * fade) * cloud_phase(cosangle, spread) * (octave == 0 ? powder : 1.0); amount *= 0.5; fade *= 0.5; spread *= 0.5; @@ -143,8 +159,12 @@ void main() } } - vec3 ambienttop = weather_ambient.rgb * CLOUD_AMBIENT_SCALE; + vec3 ambienttop = cloud_sky_light(1.0); vec3 sunlight = weather_sun_color.rgb * CLOUD_SUN_SCALE * sunup; + // the bases are lit from below by the ground, which the sun lights through the gaps between the clouds + vec3 ambientbottom = (weather_sun_color.rgb * max(weather_sun_direction.y, 0.0) * sunup * fog_light.y + ambienttop) * CLOUD_GROUND_ALBEDO; + // seen against the sun the thin edges glow (the forward scattering); seen with the sun behind, they are dark + float powderview = clamp(1.0 - cosangle, 0.0, 1.0); for (int k = 0; k < count && transmittance > 0.01; k++) { int index = order[k]; @@ -162,11 +182,14 @@ void main() float density = cloud_layer_density(index, position, height, detail); if (density <= 0.0) continue; - float h = clamp((height - layer.heights.x) * layer.heights.z, 0.0, 1.0); - float sunshare = (sunup > 0.0) ? cloud_sunlight(index, position, cosangle, lightsteps) : 0.0; - // the light of the sky reaches inside through the cloud above: the base of a thick cloud is darker than its top - float above = layer.shape.y * layer.shape.x * (layer.heights.y - height); - vec3 light = sunlight * sunshare + ambienttop * mix(0.35, 1.0, h) * mix(0.25, 1.0, exp(-0.05 * above)); + float powder = mix(1.0, 1.0 - exp(-density / max(layer.shape.y, 1e-6) * CLOUD_POWDER), powderview); + float sunshare = (sunup > 0.0) ? cloud_sunlight(index, position, cosangle, lightsteps, powder) : 0.0; + // the light of the sky reaches inside through the cloud above, that of the ground through the cloud below: the base of a + // thick cloud is darker than its top, and greyer, lit only by the ground and the sky at its sides + float column = layer.shape.y * layer.shape.x; + float above = column * (layer.heights.y - height); + float below = column * (height - layer.heights.x); + vec3 light = sunlight * sunshare + (ambienttop * mix(0.15, 1.0, exp(-CLOUD_AMBIENT_FADE * above)) + ambientbottom * exp(-CLOUD_AMBIENT_FADE * below)) * powder; float through = exp(-density * step); vec3 inscattered = light * (1.0 - through); scattered += transmittance * inscattered; diff --git a/shaders/clouds_noise.comp b/shaders/clouds_noise.comp index 21459ac4..b32c290e 100644 --- a/shaders/clouds_noise.comp +++ b/shaders/clouds_noise.comp @@ -82,6 +82,10 @@ float worley_fbm(vec3 position, int frequency, uint seed) + worley(position * float(frequency * 4), frequency * 4, seed + 2U) * 0.125; } +// the worley fbm keeps about to this range (5th and 95th percentile 0.35 and 0.61) +const float WORLEY_FBM_LOW = 0.30; +const float WORLEY_FBM_HIGH = 0.65; + float remap(float value, float low, float high, float newlow, float newhigh) { return newlow + (value - low) / (high - low) * (newhigh - newlow); @@ -111,9 +115,13 @@ void main() vec3 position = (vec3(texel) + 0.5) / float(size); if (kind == 0) { - float low = perlin_fbm(position, 4, 4, 3U) * 0.5 + 0.5; + // both spread over 0-1 first: as sums of octaves they keep to a narrow band about the middle, and a cloud made of them + // would be an even block with no lumps. the cells then cut the perlin noise into rounded heaps: whole in their middles, + // only the highest perlin left between them + float low = even(perlin_fbm(position, 4, 4, 3U), 0.22); float cells = worley_fbm(position, 4, 5U); - float perlinworley = clamp(remap(low, cells - 1.0, 1.0, 0.0, 1.0), 0.0, 1.0); + float spreadcells = clamp((cells - WORLEY_FBM_LOW) / (WORLEY_FBM_HIGH - WORLEY_FBM_LOW), 0.0, 1.0); + float perlinworley = clamp(remap(low, (1.0 - spreadcells) * 0.6, 1.0, 0.0, 1.0), 0.0, 1.0); imageStore(noise_volume, texel, vec4(perlinworley, cells, worley_fbm(position, 8, 7U), worley_fbm(position, 16, 9U))); } else diff --git a/shaders/weather_common.glsl b/shaders/weather_common.glsl index af513b8d..5dfbf1ff 100644 --- a/shaders/weather_common.glsl +++ b/shaders/weather_common.glsl @@ -98,11 +98,13 @@ vec2 fog_optical_depth(float origin, float up, float near, float far, float haze vec3 fog_color_world(vec3 direction) { vec3 sun = weather_sun_direction.xyz; - vec2 flat_view = direction.xz; - vec2 flat_sun = sun.xz; + // the light of the haze goes with the angle from the sun, not with the azimuth alone: the table is taken at the point of the + // horizon as far from the sun as the view is. level views get the colour of their azimuth as before; a view high up over the + // sun no longer gets the glow of the horizon under it (a column of light standing over a low sun) + float sunflat = length(sun.xz); float cosangle = 1.0; - if (dot(flat_view, flat_view) > 1e-8 && dot(flat_sun, flat_sun) > 1e-8) - cosangle = dot(normalize(flat_view), normalize(flat_sun)); + if (sunflat > 1e-4) + cosangle = clamp(dot(normalize(direction), sun) / sunflat, -1.0, 1.0); float position = acos(clamp(cosangle, -1.0, 1.0)) / WEATHER_PI * float(FOG_HORIZON_SAMPLES - 1); int index = int(floor(position)); vec3 color = mix(fog_horizon[clamp(index, 0, FOG_HORIZON_SAMPLES - 1)].rgb, fog_horizon[clamp(index + 1, 0, FOG_HORIZON_SAMPLES - 1)].rgb, fract(position));