17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-31 00:50:29 +01:00

Draw volumetric clouds, their shadows, a volumetric fog and catalogue stars from a new weather system in the GL 4.6 renderer

This commit is contained in:
2026-10-16 21:44:47 +02:00
parent d509775269
commit 2be85410ce
47 changed files with 5092 additions and 74 deletions

View File

@@ -1,30 +1,41 @@
// fog between the camera and a fragment, see fog_volume.glsl and weather_common.glsl. a draw with a fog_density of 0 is not
// fogged; one with another density than the regular one (the glare of lamps) gets the optical depth scaled by the ratio
#include <fog_volume.glsl>
// rgb: light the fog adds, a: what it lets through
vec4 fog_at_fragment()
{
if (fog_density <= 0.0)
return vec4(0.0, 0.0, 0.0, 1.0);
vec4 fog = fog_along_view(f_pos.xyz, gl_FragCoord.xy);
float scale = fog_density / max(fog_light.w, 1e-12);
if (abs(scale - 1.0) > 1e-3)
{
float transmittance = exp(log(max(fog.a, 1e-8)) * scale);
fog.rgb *= (1.0 - transmittance) / max(1.0 - fog.a, 1e-4);
fog.a = transmittance;
}
return fog;
}
vec3 get_fog_color()
{
vec3 fog_color_v = fog_color;
if (lights_count >= 1U && lights[0].type == LIGHT_DIR) {
float sun_amount = max(dot(normalize(f_pos.xyz), normalize(-lights[0].dir)), 0.0);
fog_color_v += lights[0].color * pow(sun_amount, 30.0);
}
return fog_color_v;
vec4 fog = fog_at_fragment();
return fog.rgb / max(1.0 - fog.a, 1e-4);
}
float get_fog_amount()
{
float z = length(f_pos.xyz);
// float fog_amount_v = 1.0 - z * fog_density; // linear
// float fog_amount_v = exp( -fog_density * z ); // exp
float fog_amount_v = exp2( -fog_density * fog_density * z * z * 1.442695 ); // exp2
fog_amount_v = 1.0 - clamp(fog_amount_v, 0.0, 1.0);
return fog_amount_v;
return 1.0 - fog_at_fragment().a;
}
vec3 apply_fog(vec3 color)
{
return mix(color, get_fog_color(), get_fog_amount());
vec4 fog = fog_at_fragment();
return color * fog.a + fog.rgb;
}
vec3 add_fog(vec3 color, float amount)
{
return color + get_fog_color() * get_fog_amount() * amount;
return color + fog_at_fragment().rgb * amount;
}

213
shaders/clouds_common.glsl Normal file
View File

@@ -0,0 +1,213 @@
// clouds of the weather (opengl46_weather): density of the layers of weather_common.glsl, made of three tiling noises.
// the shape noise (3d, clouds_noise.comp) gives the lumps of a cloud, the detail noise (3d) eats into their edges, the coverage
// noise (2d) spreads the clouds of a layer over the sky so they take up its coverage. positions are world offsets from the
// camera, in metres, y up; the earth curves away from the camera, so far clouds sink below the horizon
#include <weather_common.glsl>
layout(binding = CLOUD_SHAPE_TEX) uniform sampler3D cloud_shape_noise;
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;
float cloud_remap(float value, float low, float high, float newlow, float newhigh)
{
return newlow + (value - low) / max(high - low, 1e-5) * (newhigh - newlow);
}
// height of a point above the ground reference, the earth curving down with the square of the distance
float cloud_height(vec3 position)
{
return weather_camera.x + position.y - dot(position.xz, position.xz) * (0.5 / CLOUD_EARTH_RADIUS);
}
// distances along a ray (unit Direction from the camera) where it is at Height: the height goes as
// camera + up * t - (1 - up^2) / (2 R) * t^2. returns the number of crossings ahead (0-2), sorted into T
int cloud_ray_height(float up, float height, out vec2 t)
{
t = vec2(-1.0);
float a = (1.0 - up * up) * (0.5 / CLOUD_EARTH_RADIUS);
float b = -up;
float c = height - weather_camera.x;
// a t^2 + b t + c = 0
if (a < 1e-12)
{
if (abs(b) < 1e-8)
return 0;
float root = -c / b;
t = vec2(root, -1.0);
return root > 0.0 ? 1 : 0;
}
float discriminant = b * b - 4.0 * a * c;
if (discriminant < 0.0)
return 0;
float q = -0.5 * (b + (b >= 0.0 ? 1.0 : -1.0) * sqrt(discriminant));
float r0 = q / a;
float r1 = (abs(q) > 1e-12) ? c / q : r0;
vec2 roots = vec2(min(r0, r1), max(r0, r1));
if (roots.y <= 0.0)
return 0;
if (roots.x <= 0.0)
{
t = vec2(roots.y, -1.0);
return 1;
}
t = roots;
return 2;
}
// the first stretch of a ray within the heights of a layer, x to y; y <= x when it never gets there
vec2 cloud_layer_segment(float up, float base, float top)
{
vec2 tb, tt;
int nb = cloud_ray_height(up, base, tb);
int nt = cloud_ray_height(up, top, tt);
// the crossings in order; the ray goes in and out of the layer at each
float crossings[4];
int count = 0;
for (int i = 0; i < nb; i++)
crossings[count++] = tb[i];
for (int i = 0; i < nt; i++)
crossings[count++] = tt[i];
for (int i = 1; i < count; i++)
for (int j = i; j > 0 && crossings[j - 1] > crossings[j]; j--)
{
float swap = crossings[j];
crossings[j] = crossings[j - 1];
crossings[j - 1] = swap;
}
bool inside = (weather_camera.x >= base) && (weather_camera.x <= top);
float start = 0.0;
for (int i = 0; i < count; i++)
{
if (inside)
return vec2(start, crossings[i]);
inside = true;
start = crossings[i];
}
return inside ? vec2(start, 1e9) : vec2(0.0, -1.0);
}
// the position in the noise of a layer: the camera's place in it and the offset from the camera, stretched along the wind
vec2 cloud_noise_plane(cloud_layer_s layer, vec2 offset)
{
vec2 wind = cloud_wind.xy;
float along = dot(offset, wind);
vec2 across = offset - wind * along;
return across + wind * (along / max(layer.motion.w, 1.0));
}
// share of the layer filled at a place, x: the soft mask of the coverage, y: how far into a cloud area (0 at its edge)
vec2 cloud_coverage(cloud_layer_s layer, vec2 offset)
{
vec2 coords = (layer.extra.zw + cloud_noise_plane(layer, offset)) * layer.motion.z;
float noise = textureLod(cloud_coverage_noise, coords + layer.extra.y, 0.0).r;
float coverage = layer.shape.x;
float threshold = 1.0 - coverage;
float mask = smoothstep(threshold - 0.06, threshold + 0.06, noise);
float depth = clamp((noise - threshold) / max(coverage, 0.05), 0.0, 1.0);
return vec2(mask, depth);
}
// extinction (1/m) of a volumetric layer at a point; Detail eats the edges with the small noise
float cloud_layer_density(int index, vec3 position, float height, bool detail)
{
cloud_layer_s layer = cloud_layers[index];
float h = (height - layer.heights.x) * layer.heights.z;
if (h <= 0.0 || h >= 1.0)
return 0.0;
vec2 coverage = cloud_coverage(layer, position.xz);
// the anvil spreads the top of the storm clouds out of their area
if (layer.profile.w > 0.0)
{
float anvil = layer.profile.w * smoothstep(0.7, 0.92, h) * (1.0 - smoothstep(0.97, 1.0, h));
coverage.x = max(coverage.x, anvil * smoothstep(0.0, 0.5, coverage.y + 0.35));
coverage.y = max(coverage.y, anvil * 0.6);
}
if (coverage.x <= 0.0)
return 0.0;
// the tops of the single clouds are of different heights, the larger ones (deeper in their area) the taller
float top = mix(1.0 - layer.profile.z, 1.0, coverage.y);
float gradient = smoothstep(0.0, layer.profile.x, h) * (1.0 - smoothstep(top * layer.profile.y, top, h));
if (gradient <= 0.0)
return 0.0;
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 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;
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;
// 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);
}
return density * layer.shape.y;
}
// the same without the detail, cheaper, for the shadows and the fog
float cloud_density_coarse(vec3 position)
{
float height = cloud_height(position);
float density = 0.0;
for (int i = 0; i < int(weather_camera.w); i++)
{
if (cloud_layers[i].extra.x > 0.5)
continue;
density += cloud_layer_density(i, position, height, false);
}
return density;
}
// optical depth of a flat layer (cirrus and the like) where a ray crosses its middle, Up the vertical part of the ray there
float cloud_flat_depth(int index, vec3 position)
{
cloud_layer_s layer = cloud_layers[index];
vec2 coverage = cloud_coverage(layer, position.xz);
if (coverage.x <= 0.0)
return 0.0;
vec2 plane = layer.motion.xy + cloud_noise_plane(layer, position.xz);
vec2 coords = plane * layer.shape.z;
// strands and ripples out of slices of the noise
vec4 shape = textureLod(cloud_shape_noise, vec3(coords, 0.37 + layer.extra.y), 0.0);
vec4 small = textureLod(cloud_detail_noise, vec3(coords * 4.0, 0.61), 0.0);
float strands = clamp(cloud_remap(shape.r * 0.7 + small.r * 0.3, 0.35 * layer.shape.w + (1.0 - coverage.x) * 0.5, 1.0, 0.0, 1.0), 0.0, 1.0);
return strands * coverage.x * layer.shape.y;
}
// the shadow of the clouds at a point: what they let through towards the key light. Steps per layer
float cloud_transmittance_to_sun(vec3 position, int steps)
{
vec3 sun = weather_sun_direction.xyz;
if (sun.y <= 0.01)
return fog_light.y;
float height = cloud_height(position);
float depth = 0.0;
for (int i = 0; i < int(weather_camera.w); i++)
{
cloud_layer_s layer = cloud_layers[i];
if (layer.extra.x > 0.5)
{
float middle = 0.5 * (layer.heights.x + layer.heights.y);
if (middle > height)
depth += cloud_flat_depth(i, position + sun * ((middle - height) / sun.y)) / sun.y;
continue;
}
float enter = max(layer.heights.x - height, 0.0) / sun.y;
float leave = (layer.heights.y - height) / sun.y;
if (leave <= 0.0)
continue;
float step = (leave - enter) / float(steps);
for (int s = 0; s < steps; s++)
{
vec3 point = position + sun * (enter + step * (float(s) + 0.5));
depth += cloud_layer_density(i, point, cloud_height(point), false) * step;
}
}
return exp(-depth);
}

226
shaders/clouds_march.comp Normal file
View File

@@ -0,0 +1,226 @@
// clouds of the weather ray marched through their layers (clouds_common.glsl), into an image the sky pass lays over the sky
// (sky_weather.frag). mode 0: the view of the main camera at a part of the resolution, smoothed over frames by
// clouds_temporal.comp; mode 1: a panorama around the camera (azimuth across, elevation up), a band of rows per frame, for the
// environment map and the other views. rgb: the light of the clouds with the fog in front of them, a: what they let through
// of the sky behind; the depth image gets the distance of the clouds, for the reprojection
#include <common>
#include <clouds_common.glsl>
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
layout(rgba16f, binding = 0) uniform writeonly image2D clouds_color;
layout(r32f, binding = 1) uniform writeonly image2D clouds_depth;
layout(location = 0) uniform int mode;
layout(location = 1) uniform ivec2 size;
// mode 0: clip space to view space of the camera, and view space to world axes
layout(location = 2) uniform mat4 inverse_projection;
layout(location = 3) uniform mat4 view_to_world;
layout(location = 4) uniform int frame;
// x: samples through a layer at most, y: samples towards the sun, z: farthest distance (m), w: 1 with the detail noise
layout(location = 5) uniform vec4 quality;
// mode 1: first row of the band and the rows in it
layout(location = 6) uniform ivec2 rows;
// lowest elevation of the panorama (radians); its rows go with the square of the height, denser at the horizon
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;
float henyey_greenstein(float cosangle, float g)
{
// times 4 pi: 1 for an even scattering
float g2 = g * g;
return (1.0 - g2) / pow(max(1.0 + g2 - 2.0 * g * cosangle, 1e-4), 1.5);
}
float cloud_phase(float cosangle, float scale)
{
return mix(henyey_greenstein(cosangle, 0.8 * scale), henyey_greenstein(cosangle, -0.25 * scale), 0.3);
}
// 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)
{
vec3 sun = weather_sun_direction.xyz;
cloud_layer_s layer = cloud_layers[index];
float thickness = layer.heights.y - layer.heights.x;
float reach = min(thickness, 1500.0);
float depth = 0.0;
float travelled = 0.0;
for (int s = 0; s < steps; s++)
{
// growing steps: near the point the shape matters, far only the bulk
float step = reach * (float(s) + 1.0) / float(steps * (steps + 1) / 2);
vec3 point = position + sun * (travelled + 0.5 * step);
travelled += step;
depth += cloud_layer_density(index, point, cloud_height(point), false) * step;
}
float light = 0.0;
float amount = 1.0;
float fade = 1.0;
float spread = 1.0;
for (int octave = 0; octave < 3; octave++)
{
light += amount * exp(-depth * fade) * cloud_phase(cosangle, spread);
amount *= 0.5;
fade *= 0.5;
spread *= 0.5;
}
return light;
}
float interleaved_noise(vec2 pixel)
{
return fract(52.9829189 * fract(dot(pixel, vec2(0.06711056, 0.00583715))));
}
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
vec3 direction;
if (mode == 0)
{
if (any(greaterThanEqual(texel, size)))
return;
vec2 ndc = (vec2(texel) + 0.5) / vec2(size) * 2.0 - 1.0;
// reverse z: the near plane at 1
vec4 near = inverse_projection * vec4(ndc, 1.0, 1.0);
direction = normalize(mat3(view_to_world) * normalize(near.xyz / near.w));
}
else
{
texel.y += rows.x;
if (texel.x >= size.x || texel.y >= min(size.y, rows.x + rows.y))
return;
vec2 coords = (vec2(texel) + 0.5) / vec2(size);
float elevation = PANORAMA_LOW + (0.5 * WEATHER_PI - PANORAMA_LOW) * coords.y * coords.y;
float azimuth = coords.x * 2.0 * WEATHER_PI;
direction = vec3(cos(elevation) * sin(azimuth), sin(elevation), cos(elevation) * cos(azimuth));
}
vec3 sun = weather_sun_direction.xyz;
float cosangle = dot(direction, sun);
float sunup = weather_sun_direction.w;
float maxdistance = quality.z;
int maxsteps = int(quality.x);
int lightsteps = int(quality.y);
bool detail = quality.w > 0.5;
float jitter = fract(interleaved_noise(vec2(texel)) + float(frame) * 0.618034);
vec3 scattered = vec3(0.0);
float transmittance = 1.0;
float depthsum = 0.0;
float weightsum = 0.0;
int layers = int(weather_camera.w);
// the stretches of the volumetric layers along the ray, nearest first
vec2 segments[MAX_CLOUD_LAYERS];
int order[MAX_CLOUD_LAYERS];
int count = 0;
for (int i = 0; i < layers; i++)
{
if (cloud_layers[i].extra.x > 0.5)
continue;
vec2 segment = cloud_layer_segment(direction.y, cloud_layers[i].heights.x, cloud_layers[i].heights.y);
segment.y = min(segment.y, maxdistance);
if (segment.y <= segment.x)
continue;
int at = count++;
segments[at] = segment;
order[at] = i;
for (int j = at; j > 0 && segments[j - 1].x > segments[j].x; j--)
{
vec2 swapsegment = segments[j];
segments[j] = segments[j - 1];
segments[j - 1] = swapsegment;
int swaporder = order[j];
order[j] = order[j - 1];
order[j - 1] = swaporder;
}
}
vec3 ambienttop = weather_ambient.rgb * CLOUD_AMBIENT_SCALE;
vec3 sunlight = weather_sun_color.rgb * CLOUD_SUN_SCALE * sunup;
for (int k = 0; k < count && transmittance > 0.01; k++)
{
int index = order[k];
cloud_layer_s layer = cloud_layers[index];
vec2 segment = segments[k];
float span = segment.y - segment.x;
float thickness = layer.heights.y - layer.heights.x;
int steps = clamp(int(float(maxsteps) * span / (thickness * 4.0)), 8, maxsteps);
float step = span / float(steps);
for (int s = 0; s < steps; s++)
{
float t = segment.x + (float(s) + jitter) * step;
vec3 position = direction * t;
float height = cloud_height(position);
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 through = exp(-density * step);
vec3 inscattered = light * (1.0 - through);
scattered += transmittance * inscattered;
float weight = transmittance * (1.0 - through);
depthsum += t * weight;
weightsum += weight;
transmittance *= through;
if (transmittance < 0.01)
break;
}
}
// the thin high layers, past the others
for (int i = 0; i < layers && transmittance > 0.01; i++)
{
cloud_layer_s layer = cloud_layers[i];
if (layer.extra.x < 0.5)
continue;
vec2 crossing;
if (cloud_ray_height(direction.y, 0.5 * (layer.heights.x + layer.heights.y), crossing) == 0)
continue;
float t = crossing.x;
if (t > maxdistance * 2.0)
continue;
vec3 position = direction * t;
// the slant of the ray through the sheet where it crosses it, the earth curving away
float slope = max(abs(direction.y - (1.0 - direction.y * direction.y) * t / CLOUD_EARTH_RADIUS), 0.03);
float depth = cloud_flat_depth(i, position) / slope;
if (depth <= 0.0)
continue;
float through = exp(-depth);
// ice crystals scatter forwards strongly
vec3 light = sunlight * henyey_greenstein(cosangle, 0.6) * 0.8 + ambienttop;
scattered += transmittance * light * (1.0 - through);
float weight = transmittance * (1.0 - through);
depthsum += t * weight;
weightsum += weight;
transmittance *= through;
}
// the haze the atmosphere has in the sky already lies between the camera and the clouds too; the rest of the fog goes over
// the clouds and the sky alike when they are laid over it (sky_weather.frag)
float span_distance = (weightsum > 1e-5) ? depthsum / weightsum : maxdistance;
if (weightsum > 1e-5)
{
vec4 fog = fog_analytic_with(direction, 0.0, span_distance, min(fog_haze.x, fog_haze.z), 0.0);
scattered = scattered * fog.a + fog.rgb * (1.0 - transmittance);
}
if (any(isnan(scattered)) || isnan(transmittance))
{
scattered = vec3(0.0);
transmittance = 1.0;
}
imageStore(clouds_color, texel, vec4(scattered, transmittance));
if (mode == 0)
imageStore(clouds_depth, texel, vec4(span_distance));
}

121
shaders/clouds_noise.comp Normal file
View File

@@ -0,0 +1,121 @@
// the tiling noises the clouds and the fog are made of, made once at the start (opengl46_weather::init).
// kind 0, the shape (3d): r perlin-worley (perlin fbm with the worley cells carved in), g b a worley fbm of rising frequency;
// kind 1, the detail (3d): r g b worley fbm of rising frequency;
// kind 2, the coverage (2d): r g two perlin fbm, spread evenly over 0-1 so a threshold of 1 - c leaves a share c above it
#include <common>
#include <random.glsl>
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
layout(rgba8, binding = 0) uniform writeonly image3D noise_volume;
layout(rgba8, binding = 1) uniform writeonly image2D noise_plane;
layout(location = 0) uniform int kind;
layout(location = 1) uniform int size;
vec3 hash3(ivec3 cell, int period, uint seed)
{
uvec3 wrapped = uvec3((cell % period + period) % period);
uint h = Hash(uvec3(wrapped.x + seed * 977U, wrapped.y, wrapped.z));
uint h2 = Hash(h + 0x68bc21ebU);
uint h3 = Hash(h2 + 0x02e5be93U);
return vec3(float(h & 0xffffU), float(h2 & 0xffffU), float(h3 & 0xffffU)) / 65535.0;
}
// gradient noise repeating every Period cells, -1..1 about
float perlin(vec3 position, int period, uint seed)
{
ivec3 cell = ivec3(floor(position));
vec3 local = fract(position);
vec3 fade = local * local * local * (local * (local * 6.0 - 15.0) + 10.0);
float corners[8];
for (int i = 0; i < 8; i++)
{
ivec3 offset = ivec3(i & 1, (i >> 1) & 1, (i >> 2) & 1);
vec3 gradient = normalize(hash3(cell + offset, period, seed) * 2.0 - 1.0 + 1e-4);
corners[i] = dot(gradient, local - vec3(offset));
}
float x0 = mix(corners[0], corners[1], fade.x);
float x1 = mix(corners[2], corners[3], fade.x);
float x2 = mix(corners[4], corners[5], fade.x);
float x3 = mix(corners[6], corners[7], fade.x);
return mix(mix(x0, x1, fade.y), mix(x2, x3, fade.y), fade.z) * 1.6;
}
// inverted distance to the nearest of the points scattered one per cell, 0-1, repeating every Period cells
float worley(vec3 position, int period, uint seed)
{
ivec3 cell = ivec3(floor(position));
vec3 local = fract(position);
float nearest = 1.0;
for (int z = -1; z <= 1; z++)
for (int y = -1; y <= 1; y++)
for (int x = -1; x <= 1; x++)
{
ivec3 offset = ivec3(x, y, z);
vec3 point = vec3(offset) + hash3(cell + offset, period, seed);
nearest = min(nearest, length(point - local));
}
return 1.0 - clamp(nearest, 0.0, 1.0);
}
float perlin_fbm(vec3 position, int frequency, int octaves, uint seed)
{
float sum = 0.0;
float amplitude = 0.5;
float total = 0.0;
for (int i = 0; i < octaves; i++)
{
sum += perlin(position * float(frequency), frequency, seed + uint(i)) * amplitude;
total += amplitude;
amplitude *= 0.5;
frequency *= 2;
}
return sum / total;
}
float worley_fbm(vec3 position, int frequency, uint seed)
{
return worley(position * float(frequency), frequency, seed) * 0.625
+ worley(position * float(frequency * 2), frequency * 2, seed + 1U) * 0.25
+ worley(position * float(frequency * 4), frequency * 4, seed + 2U) * 0.125;
}
float remap(float value, float low, float high, float newlow, float newhigh)
{
return newlow + (value - low) / (high - low) * (newhigh - newlow);
}
// a sum of noises is about normally distributed; this spreads it about evenly over 0-1
float even(float value, float spread)
{
return clamp(0.5 + 0.5 * tanh(value / spread * 1.1), 0.0, 1.0);
}
void main()
{
ivec3 texel = ivec3(gl_GlobalInvocationID);
if (any(greaterThanEqual(texel.xy, ivec2(size))))
return;
if (kind == 2)
{
vec3 position = vec3((vec2(texel.xy) + 0.5) / float(size), 0.0);
float first = perlin_fbm(position, 4, 6, 11U);
float second = perlin_fbm(position + vec3(0.0, 0.0, 0.5), 8, 5, 23U);
imageStore(noise_plane, texel.xy, vec4(even(first, 0.22), even(second, 0.22), 0.0, 1.0));
return;
}
if (texel.z >= size)
return;
vec3 position = (vec3(texel) + 0.5) / float(size);
if (kind == 0)
{
float low = perlin_fbm(position, 4, 4, 3U) * 0.5 + 0.5;
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);
imageStore(noise_volume, texel, vec4(perlinworley, cells, worley_fbm(position, 8, 7U), worley_fbm(position, 16, 9U)));
}
else
imageStore(noise_volume, texel, vec4(worley_fbm(position, 4, 13U), worley_fbm(position, 8, 15U), worley_fbm(position, 16, 17U), 1.0));
}

View File

@@ -0,0 +1,27 @@
// the shadow the clouds cast on the ground: for points of the ground reference around the camera, what the clouds let through
// towards the key light. the shading (light_common.glsl) and the fog (fog_inject.comp) look it up along the ray of the sun,
// see cloud_shadow() in weather_common.glsl
#include <common>
#include <clouds_common.glsl>
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
layout(r16f, binding = 0) uniform writeonly image2D shadow;
layout(location = 1) uniform int size;
// world x and z of the centre of the map relative to the camera, size of the map (metres)
layout(location = 2) uniform vec3 placement;
// samples through a layer
layout(location = 3) uniform int steps;
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(texel, ivec2(size))))
return;
vec2 offset = placement.xy + ((vec2(texel) + 0.5) / float(size) - 0.5) * placement.z;
// the ground reference, the earth curving away
vec3 ground = vec3(offset.x, -weather_camera.x + dot(offset, offset) * (0.5 / CLOUD_EARTH_RADIUS), offset.y);
imageStore(shadow, texel, vec4(cloud_transmittance_to_sun(ground, steps)));
}

View File

@@ -0,0 +1,65 @@
// the clouds of the main view smoothed over frames: the image of this frame (clouds_march.comp, ray started at another
// distance every frame) blended into the one of the previous frames, found where the clouds were by the distance of the
// clouds, the rotation and the move of the camera. the history is held to the range of the neighbourhood of this frame,
// so moving clouds and a turning camera don't leave trails
#include <common>
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
layout(binding = CLOUD_TEX) uniform sampler2D current_color;
layout(binding = CLOUD_SHAPE_TEX) uniform sampler2D current_depth;
layout(binding = CLOUD_HISTORY_TEX) uniform sampler2D history_color;
layout(rgba16f, binding = 0) uniform writeonly image2D result;
layout(location = 1) uniform ivec2 size;
layout(location = 2) uniform mat4 inverse_projection;
layout(location = 3) uniform mat4 view_to_world;
// world axes to the clip space of the previous frame, the camera of the previous frame at the origin
layout(location = 4) uniform mat4 previous_projection_view;
// where the camera was in the previous frame, relative to where it is (world, metres)
layout(location = 5) uniform vec3 previous_camera;
// share of this frame in the result; 1 when there is no history
layout(location = 6) uniform float blend;
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(texel, size)))
return;
vec4 current = texelFetch(current_color, texel, 0);
if (blend >= 1.0)
{
imageStore(result, texel, current);
return;
}
// the range of the neighbourhood
vec4 low = current;
vec4 high = current;
for (int y = -1; y <= 1; y++)
for (int x = -1; x <= 1; x++)
{
vec4 neighbour = texelFetch(current_color, clamp(texel + ivec2(x, y), ivec2(0), size - 1), 0);
low = min(low, neighbour);
high = max(high, neighbour);
}
vec2 ndc = (vec2(texel) + 0.5) / vec2(size) * 2.0 - 1.0;
vec4 near = inverse_projection * vec4(ndc, 1.0, 1.0);
vec3 direction = normalize(mat3(view_to_world) * normalize(near.xyz / near.w));
float span_distance = texelFetch(current_depth, texel, 0).r;
vec3 position = direction * span_distance - previous_camera;
vec4 clip = previous_projection_view * vec4(position, 1.0);
float share = blend;
vec4 history = current;
if (clip.w > 0.0)
{
vec2 coords = clip.xy / clip.w * 0.5 + 0.5;
if (all(greaterThanEqual(coords, vec2(0.0))) && all(lessThanEqual(coords, vec2(1.0))))
history = clamp(textureLod(history_color, coords, 0.0), low, high);
else
share = 1.0;
}
else
share = 1.0;
imageStore(result, texel, mix(history, current, share));
}

42
shaders/fog_horizon.comp Normal file
View File

@@ -0,0 +1,42 @@
// colours of the fog towards the horizon by the angle from the azimuth of the sun, from the sky-view table of the atmosphere,
// written into the weather buffer (weather_common.glsl: fog_horizon). the far fog then has the colour of the sky behind it.
// the atmosphere blends into the old sky as opengl46_sky does it, by its share, white balance and brightness
#include <common>
#include <atmosphere_common.glsl>
layout(local_size_x = FOG_HORIZON_SAMPLES) in;
layout(binding = SKY_VIEW_TEX) uniform sampler2D sky_view_lut;
layout(std430, binding = WEATHER_SCRATCH_SSBO_BINDING) buffer weather_scratch
{
vec4 weather_words[];
};
// first vec4 of fog_horizon in the weather buffer
layout(location = 0) uniform int offset;
// x: share of the atmosphere, y: its brightness, z: desaturation by the overcast, w: brightest the colour gets
layout(location = 1) uniform vec4 params;
layout(location = 2) uniform vec3 white_balance;
// the colour of the old fog, which the atmosphere replaces by its share
layout(location = 3) uniform vec3 old_color;
void main()
{
uint index = gl_LocalInvocationID.x;
float angle = float(index) / float(FOG_HORIZON_SAMPLES - 1) * ATMO_PI;
vec3 sun = normalize(atmo_sun_direction.xyz);
vec2 flat_sun = (dot(sun.xz, sun.xz) > 1e-8) ? normalize(sun.xz) : vec2(1.0, 0.0);
vec2 side = vec2(-flat_sun.y, flat_sun.x);
vec2 flat_view = flat_sun * cos(angle) + side * sin(angle);
// a little above the horizon, where the far fog meets the sky
vec3 direction = normalize(vec3(flat_view.x, 0.035, flat_view.y));
vec3 color = textureLod(sky_view_lut, atmo_skyview_uv_world(direction, sun), 0.0).rgb * white_balance * params.y;
color = min(color, vec3(params.w));
float luminance = dot(color, vec3(0.2126, 0.7152, 0.0722));
color = mix(color, vec3(luminance), params.z);
if (any(isnan(color)))
color = old_color;
weather_words[offset + int(index)] = vec4(mix(old_color, color, params.x), 1.0);
}

179
shaders/fog_inject.comp Normal file
View File

@@ -0,0 +1,179 @@
// the fog volume of the main view, first pass: in every cell of a grid laid over the view (slices spaced exponentially in the
// depth), how dense the fog is and how much light it scatters towards the camera. the density: the haze, the patches of the
// ground fog, the low clouds; the light: the colour the fog has towards the horizon (weather_common.glsl), less what the
// shadows of the sun and of the clouds hold back, and the lamps and headlights of the light clusters. blended with the
// previous frame, found where the cell was then; the cells are sampled at another depth every frame.
// rgb: light scattered towards the camera per metre, a: extinction per metre
#include <common>
#include <clouds_common.glsl>
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
layout(rgba16f, binding = 0) uniform writeonly image3D fog_scattering;
layout(binding = CLOUD_HISTORY_TEX) uniform sampler3D fog_history;
#if SHADOWMAP_ENABLED
uniform sampler2DArrayShadow shadowmap;
#endif
uniform sampler2D headlightmap;
layout(binding = HEADLIGHT_SHADOW_TEX) uniform sampler2DArrayShadow headlight_shadowmap;
layout(location = 0) uniform ivec3 grid;
layout(location = 1) uniform mat4 inverse_projection;
layout(location = 2) uniform mat4 view_to_world;
// x: near, y: far, z: slices / log(far / near), w: where in its slice the cell is sampled this frame (0-1)
layout(location = 3) uniform vec4 slicing;
// world axes to the clip space of the previous frame, the camera of the previous frame at the origin
layout(location = 4) uniform mat4 previous_projection_view;
layout(location = 5) uniform vec3 previous_camera;
// x: share of this frame (1 without history), y: brightness of the lamps in the fog (0: none), z: 1 when the sun casts shadows
// into the fog, w: 1 when the headlights cast shadows into it
layout(location = 6) uniform vec4 options;
// the light of the cab lamp, which lights no fog (the air of the cab is clear), or -1
layout(location = 7) uniform int cablamp;
// x: near, y: slices / log(far / near) of the previous frame
layout(location = 8) uniform vec2 previous_slicing;
float henyey_greenstein(float cosangle, float g)
{
float g2 = g * g;
return (1.0 - g2) / pow(max(1.0 + g2 - 2.0 * g * cosangle, 1e-4), 1.5);
}
// share of the sunlight the shadow map holds back at a point of the view space, one comparison
float sun_shadow(vec3 position)
{
#if SHADOWMAP_ENABLED
float span_distance = dot(position, position);
uint cascade;
for (cascade = 0U; cascade < MAX_CASCADES; cascade++)
if (span_distance <= cascade_end[cascade])
break;
if (cascade >= MAX_CASCADES)
return 0.0;
vec4 lightpos = lightview[cascade] * vec4(position, 1.0);
vec3 coords = lightpos.xyz / lightpos.w;
if (coords.z < 0.0 || any(lessThan(coords.xy, vec2(0.0))) || any(greaterThan(coords.xy, vec2(1.0))))
return 0.0;
return texture(shadowmap, vec4(coords.xy, float(cascade), coords.z + 0.0001 * float(cascade + 1U)));
#else
return 0.0;
#endif
}
// light of a lamp or of the beam of headlights at a point of the view space, before the phase
vec3 lamp_light(light_s light, vec3 position)
{
if (light.type == LIGHT_HEADLIGHTS)
{
vec4 projected = light.headlight_projection * vec4(position, 1.0);
vec3 coords = projected.xyz / projected.w;
if (coords.z < 0.0 || coords.z > 1.0 || any(lessThan(coords.xy, vec2(0.0))) || any(greaterThan(coords.xy, vec2(1.0))))
return vec3(0.0);
float span_distance = length(light.pos - position);
float atten = 1.0 / (1.0 + light.linear * span_distance + light.quadratic * span_distance * span_distance);
atten *= mix(1.0, 0.0, clamp((coords.z - 0.998) * 500.0, 0.0, 1.0));
vec3 beam = textureLod(headlightmap, coords.xy, 1.0).rgb * light.headlight_weights.rgb;
float intensity = max(max(beam.r, beam.g), beam.b);
if ((options.w > 0.5) && (light.headlight_weights.w > 0.5) && (intensity > 0.0))
{
// the shadow map of the beam, as in light_common.glsl without the offset along a normal
uint map = uint(light.headlight_weights.w - 0.5);
vec4 shadowpos = headlight_shadow[map] * vec4(position, 1.0);
if (shadowpos.w > 0.0)
{
vec2 shadowcoords = shadowpos.xy / shadowpos.w;
if (all(greaterThanEqual(shadowcoords, vec2(0.0))) && all(lessThanEqual(shadowcoords, vec2(1.0))))
{
float texel = headlight_shadow_params[map].x * shadowpos.w;
float reference = shadowpos.z / max(shadowpos.w - texel * 1.5, 1e-3);
intensity *= 1.0 - texture(headlight_shadowmap, vec4(shadowcoords, float(map), reference));
}
}
}
return light.color * (atten * intensity * light.intensity);
}
vec3 tolight = light.pos - position;
float distance2 = dot(tolight, tolight);
float edge = clamp(1.0 - distance2 * distance2 * (light.quadratic * light.quadratic), 0.0, 1.0);
float atten = edge * edge / max(distance2, 0.01);
if (light.type == LIGHT_SPOT)
atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-tolight * inversesqrt(max(distance2, 1e-8)), normalize(light.dir)));
return light.color * (atten * light.intensity);
}
void main()
{
ivec3 cell = ivec3(gl_GlobalInvocationID);
if (any(greaterThanEqual(cell, grid)))
return;
vec2 coords = (vec2(cell.xy) + 0.5) / vec2(grid.xy);
vec4 near = inverse_projection * vec4(coords * 2.0 - 1.0, 1.0, 1.0);
vec3 ray = near.xyz / near.w;
ray /= -ray.z;
float depth = slicing.x * exp((float(cell.z) + slicing.w) / slicing.z);
vec3 view = ray * depth;
vec3 world = mat3(view_to_world) * view;
float height = weather_camera.x + world.y;
// density
float haze = fog_haze.x * exp(-fog_haze.y * max(height, 0.0));
float ground = fog_ground.x * exp(-fog_ground.y * max(height - fog_ground.z, 0.0));
if (ground > 0.0 && fog_ground.w > 0.0)
{
float patches = textureLod(cloud_shape_noise, (world + fog_noise.xyz) * fog_noise.w, 0.0).g;
ground *= mix(1.0, clamp((patches - 0.3) * 2.6, 0.0, 2.2), fog_ground.w);
}
float clouds = cloud_density_coarse(world);
float extinction = haze + ground + clouds;
vec4 result = vec4(0.0);
if (extinction > 1e-7)
{
vec3 direction = normalize(world);
// the light of the sky and the sun, held back by the shadows
float sunlit = cloud_shadow(world);
if (options.z > 0.5 && sunlit > 0.0)
sunlit *= 1.0 - sun_shadow(view);
vec3 light = fog_whiten(fog_color_world(direction), (ground + clouds) / extinction) * fog_light_factor(sunlit);
// lamps and headlights of the cluster of the cell
if (options.y > 0.0 && lights_clustered != 0U && cluster_valid != 0U)
{
ivec2 tile = clamp(ivec2(coords * vec2(CLUSTER_GRID_X, CLUSTER_GRID_Y)), ivec2(0), ivec2(CLUSTER_GRID_X - 1, CLUSTER_GRID_Y - 1));
int slice = clamp(int(floor(cluster_slice_bias + log(depth) * cluster_slice_scale)), 0, CLUSTER_GRID_Z - 1);
cluster_range_s range = cluster_ranges[uint(tile.x + CLUSTER_GRID_X * (tile.y + CLUSTER_GRID_Y * slice))];
vec3 tocamera = -normalize(view);
vec3 lamps = vec3(0.0);
for (uint i = 0U; i < range.count; i++)
{
uint index = cluster_light_indices[range.offset + i];
if (int(index) == cablamp)
continue;
light_s lamp = lights[index];
vec3 incoming = lamp_light(lamp, view);
if (dot(incoming, incoming) <= 0.0)
continue;
lamps += incoming * henyey_greenstein(dot(normalize(view - lamp.pos), tocamera), 0.4);
}
light += lamps * options.y;
}
result = vec4(light * fog_haze.w * extinction, extinction);
}
// the previous frame, where the cell was then
float share = options.x;
if (share < 1.0)
{
vec4 clip = previous_projection_view * vec4(world - previous_camera, 1.0);
if (clip.w > 0.0)
{
vec3 previous = vec3(clip.xy / clip.w * 0.5 + 0.5, log(max(clip.w / previous_slicing.x, 1e-6)) * previous_slicing.y / float(grid.z));
if (all(greaterThanEqual(previous, vec3(0.0))) && all(lessThanEqual(previous, vec3(1.0))))
result = mix(textureLod(fog_history, previous, 0.0), result, share);
}
}
if (any(isnan(result)))
result = vec4(0.0);
imageStore(fog_scattering, cell, result);
}

View File

@@ -0,0 +1,44 @@
// the fog volume of the main view, second pass: the light and extinction of the cells (fog_inject.comp) added up along the rays
// of the view, front to back, into what the fog adds and lets through from the camera to the far side of every slice
// (apply_fog.glsl). the light of a cell is integrated over its length as Hillaire (2015) does it, so it doesn't grow with
// the size of the cell
#include <common>
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
layout(binding = CLOUD_HISTORY_TEX) uniform sampler3D fog_scattering;
layout(rgba16f, binding = 0) uniform writeonly image3D fog_integrated;
layout(location = 0) uniform ivec3 grid;
layout(location = 1) uniform mat4 inverse_projection;
// x: near, y: far, z: slices / log(far / near)
layout(location = 3) uniform vec4 slicing;
void main()
{
ivec2 column = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(column, grid.xy)))
return;
vec2 coords = (vec2(column) + 0.5) / vec2(grid.xy);
vec4 near = inverse_projection * vec4(coords * 2.0 - 1.0, 1.0, 1.0);
vec3 ray = near.xyz / near.w;
// metres along the ray per metre of the depth
float stretch = length(ray / -ray.z);
vec3 light = vec3(0.0);
float transmittance = 1.0;
float front = slicing.x;
for (int slice = 0; slice < grid.z; slice++)
{
float back = slicing.x * exp(float(slice + 1) / slicing.z);
float span = (back - front) * stretch;
front = back;
vec4 cell = texelFetch(fog_scattering, ivec3(column, slice), 0);
float extinction = max(cell.a, 1e-7);
float through = exp(-extinction * span);
light += transmittance * cell.rgb * ((1.0 - through) / extinction);
transmittance *= through;
imageStore(fog_integrated, ivec3(column, slice), vec4(light, transmittance));
}
}

48
shaders/fog_volume.glsl Normal file
View File

@@ -0,0 +1,48 @@
// fog between the camera and a point of the view space (weather_common.glsl): from the fog volume of the main view
// (fog_inject.comp, fog_integrate.comp) up to its far end and along the ray past it, or along the ray all the way in the
// other passes. Fragcoord: the pixel of the target the point is seen at
#ifndef FOG_VOLUME_GLSL
#define FOG_VOLUME_GLSL
#include <weather_common.glsl>
layout(binding = FOG_VOLUME_TEX) uniform sampler3D fog_volume;
// rgb: light the fog adds, a: what it lets through
vec4 fog_along_view(vec3 view, vec2 fragcoord)
{
float span_distance = length(view);
if (span_distance <= 0.0)
return vec4(0.0, 0.0, 0.0, 1.0);
vec3 direction = mat3(inv_view) * (view / span_distance);
if (fog_volume_params.x < 0.5)
return fog_analytic(direction, 0.0, span_distance, false);
// slices are spaced exponentially in the view depth; slice k holds the fog up to its far side
float depth = max(-view.z, 1e-4);
float slices = fog_volume_params.w;
float slice = log(max(depth / fog_volume_params.y, 1e-6)) * fog_volume_params.z;
vec2 coords = fragcoord * fog_volume_target.xy;
if (slice < 1.0)
return mix(vec4(0.0, 0.0, 0.0, 1.0), textureLod(fog_volume, vec3(coords, 0.5 / slices), 0.0), max(slice, 0.0));
if (slice < slices)
return textureLod(fog_volume, vec3(coords, (slice - 0.5) / slices), 0.0);
vec4 volume = textureLod(fog_volume, vec3(coords, (slices - 0.5) / slices), 0.0);
vec4 rest = fog_analytic(direction, fog_volume_target.z * span_distance / depth, span_distance, false);
return vec4(volume.rgb + volume.a * rest.rgb, volume.a * rest.a);
}
// fog in front of the sky in a view space direction: through the fog volume to its far end, then along the ray without the
// haze the atmosphere draws already. the far fog of the volume and the fog over the sky behind it are the same then
vec4 fog_sky(vec3 viewdirection, vec2 fragcoord)
{
vec3 view = normalize(viewdirection);
vec3 direction = mat3(inv_view) * view;
if (fog_volume_params.x < 0.5 || view.z > -1e-3)
return fog_analytic(direction, 0.0, 200000.0, true);
float slices = fog_volume_params.w;
vec4 volume = textureLod(fog_volume, vec3(fragcoord * fog_volume_target.xy, (slices - 0.5) / slices), 0.0);
vec4 rest = fog_analytic(direction, fog_volume_target.z / -view.z, 200000.0, true);
return vec4(volume.rgb + volume.a * rest.rgb, volume.a * rest.a);
}
#endif

View File

@@ -11,6 +11,7 @@ layout(binding = HEADLIGHT_SHADOW_TEX) uniform sampler2DArrayShadow headlight_sh
#include <envmapping.glsl>
#include <conversion.glsl>
#include <weather_common.glsl>
float glossiness = 1.0;
float metalic = 0.0;
@@ -510,7 +511,9 @@ vec3 apply_lights(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float refl
// panels, vehicle bodies and terrain reads as a clear edge rather
// than a soft Lambertian ramp. Tunable via SUN_NDOTL_SHARPNESS.
float sun_NdotL = pow(sunlight.x, SUN_NDOTL_SHARPNESS);
float diffuseamount = sun_NdotL * lights[0].intensity;
// the clouds hold back the sun (the moon at night) where their shadow falls
float cloudsun = cloud_shadow(mat3(inv_view) * f_pos.xyz);
float diffuseamount = sun_NdotL * lights[0].intensity * cloudsun;
float shadow1 = 0.0;
if (shadowtone < 1.0)
@@ -523,7 +526,7 @@ vec3 apply_lights(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float refl
fragcolor = add_headlights(fragcolor, fragnormal);
float specularamount = sunlight.y * param[1].y * specularity * lights[0].intensity
float specularamount = sunlight.y * param[1].y * specularity * lights[0].intensity * cloudsun
* clamp(1.0 - shadowtone, 0.0, 1.0);
if (shadowtone < 1.0)
specularamount *= clamp(1.0 - shadow1, 0.0, 1.0);

View File

@@ -62,15 +62,17 @@ vec3 apply_lights_sunless(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, fl
// surfaces (uses SUN_NDOTL_SHARPNESS from light_common.glsl).
float sun_NdotL = pow(sunlight.x, SUN_NDOTL_SHARPNESS);
float diffuseamount = sun_NdotL * lights[0].intensity;
// the clouds hold back the sun where their shadow falls, as the shadow map does
float cloudsun = cloud_shadow(mat3(inv_view) * f_pos.xyz);
fragcolor += envcolor * reflectivity;
float specularamount = (sunlight.y * param[1].y * specularity) * lights[0].intensity;
float specularamount = (sunlight.y * param[1].y * specularity) * lights[0].intensity * cloudsun;
glossiness = abs(param[1].w);
fragcolor = add_headlights(fragcolor, fragnormal);
if (shadowtone < 1.0)
{
float shadow = calc_shadow();
float shadow = max(calc_shadow(), 1.0 - cloudsun);
specularamount *= clamp(1.0 - shadow, 0.0, 1.0);
fragcolor = mix(fragcolor, fragcolor * shadowtone, clamp(diffuseamount * shadow + specularamount, 0.0, 1.0));
}

View File

@@ -72,14 +72,16 @@ vec3 apply_lights_sunless(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, fl
// surfaces (uses SUN_NDOTL_SHARPNESS from light_common.glsl).
float sun_NdotL = pow(sunlight.x, SUN_NDOTL_SHARPNESS);
float diffuseamount = sun_NdotL * lights[0].intensity;
// the clouds hold back the sun where their shadow falls, as the shadow map does
float cloudsun = cloud_shadow(mat3(inv_view) * f_pos.xyz);
float specularamount = sunlight.y * param[1].y * specularity * lights[0].intensity;
float specularamount = sunlight.y * param[1].y * specularity * lights[0].intensity * cloudsun;
fragcolor = add_headlights(fragcolor, fragnormal);
if (shadowtone < 1.0)
{
float shadow = calc_shadow();
float shadow = max(calc_shadow(), 1.0 - cloudsun);
specularamount *= clamp(1.0 - shadow, 0.0, 1.0);
fragcolor = mix(fragcolor, fragcolor * shadowtone, clamp(diffuseamount * shadow, 0.0, 1.0));
}

View File

@@ -3,9 +3,13 @@
// close they are and by how close their distance is to that of the pixel, so the occlusion does not spill over the
// edges of objects. each pixel of the occlusion stands for the top left one of the four full size pixels it covers
// params.x: 0 ground truth ambient occlusion, 1 the older ssao (its own fixed curve)
// params.y: strength, a power of the occlusion (1 = as computed); params.z: albedo assumed by the multiple bounce term
// params.y: strength, a power of the occlusion (1 = as computed); params.z: albedo assumed by the multiple bounce term.
// the fog in front of a surface hides its occlusion as it hides the surface (fog_volume.glsl)
in vec2 f_coords;
#include <common>
#include <fog_volume.glsl>
layout(location = 0) out vec4 out_color;
layout(binding = 0) uniform sampler2D depth_tex;
@@ -14,12 +18,17 @@ layout(binding = 1) uniform sampler2D ao_tex;
layout(location = 0) uniform mat4 inv_projection;
layout(location = 1) uniform vec4 params;
float view_distance(ivec2 pixel, float depth)
vec3 view_position(ivec2 pixel, float depth)
{
ivec2 full_size = textureSize(depth_tex, 0);
vec2 uv = (vec2(pixel) + 0.5) / vec2(full_size);
vec4 position = inv_projection * vec4(uv * 2.0 - 1.0, depth, 1.0);
return length(position.xyz / position.w);
return position.xyz / position.w;
}
float view_distance(ivec2 pixel, float depth)
{
return length(view_position(pixel, depth));
}
// light which comes back after more than one bounce off surfaces of the given albedo, fit of Jimenez et al. 2016
@@ -76,5 +85,7 @@ void main()
ao = pow(ao, params.y);
ao = multi_bounce(ao, params.z);
}
// what the fog lets through of the surface is what is left to occlude
ao = mix(1.0, ao, fog_along_view(view_position(pixel, depth), gl_FragCoord.xy).a);
out_color = vec4(vec3(ao), 1.0);
}

52
shaders/sky_weather.frag Normal file
View File

@@ -0,0 +1,52 @@
// laid over the sky after the dome, the stars, the sun and the moon (opengl46_weather::draw_sky): the clouds (clouds_march.comp)
// and the fog in front of them, which hides the sky and the clouds towards the horizon. blended with (1, source alpha): the colour is
// what the clouds and the fog add, the alpha what they let through of the sky behind.
// mode 0: no clouds, 1: the clouds of the main view (an image over the screen), 2: the panorama of the clouds around the camera
in vec2 f_coords;
#include <common>
#include <fog_volume.glsl>
#include <tonemapping.glsl>
layout(binding = CLOUD_TEX) uniform sampler2D clouds;
layout(location = 0) uniform mat4 inverse_projection;
layout(location = 1) uniform mat4 view_to_world;
layout(location = 2) uniform int mode;
layout(location = 0) out vec4 out_color;
#if MOTIONBLUR_ENABLED
layout(location = 1) out vec4 out_motion;
#endif
// as in clouds_march.comp
const float PANORAMA_LOW = -0.1;
void main()
{
vec4 near = inverse_projection * vec4(f_coords * 2.0 - 1.0, 1.0, 1.0);
vec3 direction = normalize(mat3(view_to_world) * normalize(near.xyz / near.w));
vec4 cloud = vec4(0.0, 0.0, 0.0, 1.0);
if (mode == 1)
cloud = textureLod(clouds, f_coords, 0.0);
else if (mode == 2)
{
float elevation = asin(clamp(direction.y, -1.0, 1.0));
float v = sqrt(clamp((elevation - PANORAMA_LOW) / (0.5 * WEATHER_PI - PANORAMA_LOW), 0.0, 1.0));
float u = atan(direction.x, direction.z) / (2.0 * WEATHER_PI);
cloud = textureLod(clouds, vec2(fract(u), v), 0.0);
}
// the sky and the clouds are far: the fog all the way, without the haze the atmosphere has already (the clouds got it)
vec4 fog = fog_sky(near.xyz / near.w, gl_FragCoord.xy);
vec3 color = fog.rgb + fog.a * cloud.rgb;
float through = fog.a * cloud.a;
#if POSTFX_ENABLED
out_color = vec4(color, through);
#else
out_color = vec4(tonemap(vec4(color, 1.0)).rgb, through);
#endif
#if MOTIONBLUR_ENABLED
out_motion = vec4(0.0);
#endif
}

26
shaders/stars.frag Normal file
View File

@@ -0,0 +1,26 @@
// a star: a soft point added to the sky
in vec3 f_color;
#include <common>
#include <tonemapping.glsl>
layout(location = 0) out vec4 out_color;
#if MOTIONBLUR_ENABLED
layout(location = 1) out vec4 out_motion;
#endif
void main()
{
vec2 offset = gl_PointCoord - 0.5;
float spot = exp(-dot(offset, offset) * 14.0);
vec3 color = f_color * spot;
#if POSTFX_ENABLED
out_color = vec4(color, 0.0);
#else
out_color = vec4(tonemap(vec4(color, 1.0)).rgb, 0.0);
#endif
#if MOTIONBLUR_ENABLED
out_motion = vec4(0.0);
#endif
}

41
shaders/stars.vert Normal file
View File

@@ -0,0 +1,41 @@
// the stars of the catalogue (environment/starcatalog.h) as points: turned from the equatorial frame to the sky of the observer
// by the sidereal time and the latitude, dimmed through the air towards the horizon and twinkling there
layout(location = 0) in vec4 v_star; // xyz: direction in the equatorial frame (z to the north pole), w: visual magnitude
layout(location = 1) in vec4 v_color; // rgb: colour of the star, w: a random phase of the twinkling
#include <common>
// equatorial frame to world axes (x west, y up, z north), and world axes to the view
layout(location = 0) uniform mat4 equatorial_to_world;
layout(location = 1) uniform mat4 world_to_view;
// x: brightness of a star of magnitude 0, y: seconds, z: size of the points in pixels
layout(location = 2) uniform vec4 params;
out vec3 f_color;
void main()
{
vec3 world = mat3(equatorial_to_world) * v_star.xyz;
float elevation = world.y;
if (elevation < -0.01)
{
// below the horizon: out of the view
gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
f_color = vec3(0.0);
return;
}
// air mass after Kasten and Young, 0.25 magnitude of extinction per air mass
float altitude = max(degrees(asin(clamp(elevation, 0.0, 1.0))), 0.0);
float airmass = 1.0 / (max(elevation, 0.0) + 0.50572 * pow(altitude + 6.07995, -1.6364));
float magnitude = v_star.w + 0.25 * airmass;
// low stars twinkle
float twinkle = 1.0 + 0.35 * clamp((airmass - 1.0) * 0.25, 0.0, 1.0) * sin(params.y * (7.0 + 5.0 * v_color.w) + v_color.w * 40.0);
float flux = pow(10.0, -0.4 * magnitude) * params.x * twinkle;
f_color = v_color.rgb * flux;
vec3 view = mat3(world_to_view) * world;
gl_Position = projection * vec4(view, 1.0);
// at the far end of the depth (reverse z)
gl_Position.z = 0.0;
gl_PointSize = params.z * clamp(1.0 + 0.4 * log(1.0 + flux / max(params.x * 0.01, 1e-6)), 1.0, 2.5);
}

163
shaders/weather_common.glsl Normal file
View File

@@ -0,0 +1,163 @@
// weather of the opengl 4.6 renderer (opengl46_weather, gl::weather_ubs): fog, clouds and their shadows.
// world axes around the camera: the positions here are world space offsets from the camera, in metres, y up; heights are
// above the ground reference of the weather. the fog is a haze whose density falls exponentially with the height and a fog
// lying on the ground, both of a constant density below the reference (in the valleys), lit with the colour the atmosphere
// has at the horizon in the direction of the view (fog_horizon), less the part of the sunlight held back by shadows
#ifndef WEATHER_COMMON_GLSL
#define WEATHER_COMMON_GLSL
struct cloud_layer_s
{
// x: base, y: top (metres above the ground reference), z: 1 / (top - base), w: genus (environment/weather.h)
vec4 heights;
// x: coverage 0-1, y: extinction at density 1 (1/m), or the optical depth of a flat layer, z: 1 / (size of the shape noise
// tile), w: erosion by the detail noise
vec4 shape;
// x: share of the depth over which the density rises from the base, y: share from which it falls towards the top,
// z: variation of the height of the tops, w: spread of the top (anvil)
vec4 profile;
// xy: position of the camera in the shape noise (metres, world x and z, wrapped to the tile), z: 1 / (size of the coverage
// noise tile), w: stretch along the wind
vec4 motion;
// x: 1 for a flat layer, y: offset of the layer in the noise (seed), zw: position of the camera in the coverage noise
vec4 extra;
};
layout(std140, binding = WEATHER_UBO_BINDING) uniform weather_ubo
{
// x: extinction of the haze at the ground reference (1/m), y: 1 / its scale height, z: extinction the sky is not fogged
// with (the atmosphere has that haze already), w: albedo of the fog
vec4 fog_haze;
// x: extinction of the ground fog (1/m), y: 1 / scale height of its top, z: height its density starts to fall at, w: patchiness
vec4 fog_ground;
// xyz: position of the camera in the noise of the fog (metres, moved by the wind), w: 1 / size of the patches
vec4 fog_noise;
// x: share of the light of the fog which comes from the sun (and is held back by shadows), y: transmittance of the clouds
// for the sun on average, z: how much the dense ground fog is whitened, w: fog density of a regular draw (fog_density
// of a draw over it scales the optical depth)
vec4 fog_light;
// x: 1 when the fog volume holds the fog of the pass, y: near, z: slices / log(far / near), w: slices
vec4 fog_volume_params;
// xy: 1 / size of the target of the pass (pixels), z: far end of the volume (view depth), w: share of the old sun glow
vec4 fog_volume_target;
// x: height of the camera above the ground reference, y: seconds of simulation, z: 1 when the clouds are drawn, w: layers
vec4 weather_camera;
// xyz: world space direction towards the key light (the sun, or the moon at night), w: 1 when it is above the horizon
vec4 weather_sun_direction;
// rgb: colour of the key light as the shading gets it, without the shadow of the clouds; w: unused
vec4 weather_sun_color;
// rgb: ambient light of the sky above; w: unused
vec4 weather_ambient;
// xy: centre of the cloud shadow map (world x and z relative to the camera), z: 1 / its size (metres), w: 1 when it holds shadows
vec4 cloud_shadow_params;
// xy: world x and z of the direction the wind at the clouds blows to (unit), z: its speed, m/s
vec4 cloud_wind;
cloud_layer_s cloud_layers[MAX_CLOUD_LAYERS];
// colour of the fog towards the horizon by the angle from the azimuth of the sun, 0 to 180 degrees, in the units of the
// scene. filled on the gpu from the sky-view table of the atmosphere
vec4 fog_horizon[FOG_HORIZON_SAMPLES];
};
layout(binding = CLOUD_SHADOW_TEX) uniform sampler2D cloud_shadow_map;
const float WEATHER_PI = 3.14159265359;
// integral of the density of an exponential layer from the reference up to Height, the density being constant below Shift and
// falling with Rate (1 / scale height) above it
float fog_column(float height, float extinction, float rate, float shift)
{
float above = height - shift;
if (above <= 0.0)
return extinction * height;
return extinction * (shift + (1.0 - exp(-rate * above)) / rate);
}
// optical depth of the fog along a ray from the height Origin (above the reference), direction with the vertical part Up,
// between the distances Near and Far, for the haze and the ground fog of the given extinctions at the reference: x of the
// haze, y of the ground fog
vec2 fog_optical_depth(float origin, float up, float near, float far, float haze, float ground)
{
float span = far - near;
if (span <= 0.0)
return vec2(0.0);
float h0 = origin + up * near;
float h1 = origin + up * far;
if (abs(up) * span < 0.5)
{
// nearly level: the density in the middle
float h = 0.5 * (h0 + h1);
return vec2(haze * exp(-fog_haze.y * max(h, 0.0)), ground * exp(-fog_ground.y * max(h - fog_ground.z, 0.0))) * span;
}
float hazedepth = fog_column(h1, haze, fog_haze.y, 0.0) - fog_column(h0, haze, fog_haze.y, 0.0);
float grounddepth = fog_column(h1, ground, fog_ground.y, fog_ground.z) - fog_column(h0, ground, fog_ground.y, fog_ground.z);
return max(vec2(hazedepth, grounddepth) / up, vec2(0.0));
}
// light of the fog in a world space direction: the colour of the horizon the atmosphere has that way, the old sun glow where
// the old sky is used. the dense ground fog is whiter than the air
vec3 fog_color_world(vec3 direction)
{
vec3 sun = weather_sun_direction.xyz;
vec2 flat_view = direction.xz;
vec2 flat_sun = 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));
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));
if (fog_volume_target.w > 0.0)
color += weather_sun_color.rgb * pow(max(dot(direction, sun), 0.0), 30.0) * fog_volume_target.w;
return color;
}
// the light the fog takes from the sun, 0-1: the share the shadows may hold back is scaled by Sunlit
float fog_light_factor(float sunlit)
{
return (1.0 - fog_light.x) + fog_light.x * sunlit;
}
// the colour of the fog whitened by the share Ground of the dense ground fog in it
vec3 fog_whiten(vec3 color, float ground)
{
float luminance = dot(color, vec3(0.2126, 0.7152, 0.0722));
return mix(color, vec3(luminance), clamp(ground, 0.0, 1.0) * fog_light.z);
}
// fog along a ray of the view between two distances, of the haze and the ground fog of the given extinctions: rgb the light it
// adds, a what it lets through
vec4 fog_analytic_with(vec3 direction, float near, float far, float haze, float ground)
{
vec2 depth = fog_optical_depth(weather_camera.x, direction.y, near, far, haze, ground);
float total = depth.x + depth.y;
float transmittance = exp(-total);
vec3 light = fog_whiten(fog_color_world(direction), depth.y / max(total, 1e-6)) * fog_light_factor(fog_light.y) * fog_haze.w;
return vec4(light * (1.0 - transmittance), transmittance);
}
// fog along a ray of the view between two distances. Sky leaves out the haze the atmosphere draws in the sky already
vec4 fog_analytic(vec3 direction, float near, float far, bool sky)
{
return fog_analytic_with(direction, near, far, sky ? max(fog_haze.x - fog_haze.z, 0.0) : fog_haze.x, fog_ground.x);
}
// share of the key light the clouds let through to a point, World its offset from the camera
float cloud_shadow(vec3 world)
{
if (cloud_shadow_params.w < 0.5)
return 1.0;
vec3 sun = weather_sun_direction.xyz;
if (sun.y < 0.02)
return fog_light.y;
// the map holds the shadow on the ground reference: down the ray of the sun to it
float height = weather_camera.x + world.y;
vec2 ground = world.xz - sun.xz * (height / sun.y);
vec2 coords = (ground - cloud_shadow_params.xy) * cloud_shadow_params.z + 0.5;
// towards the edges of the map the shadow fades into the average
vec2 edge = abs(coords - 0.5) * 2.0;
float fade = smoothstep(0.8, 1.0, max(edge.x, edge.y));
float shadow = textureLod(cloud_shadow_map, coords, 0.0).r;
return mix(shadow, fog_light.y, fade);
}
#endif