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:
@@ -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
213
shaders/clouds_common.glsl
Normal 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
226
shaders/clouds_march.comp
Normal 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
121
shaders/clouds_noise.comp
Normal 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));
|
||||
}
|
||||
27
shaders/clouds_shadow.comp
Normal file
27
shaders/clouds_shadow.comp
Normal 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)));
|
||||
}
|
||||
65
shaders/clouds_temporal.comp
Normal file
65
shaders/clouds_temporal.comp
Normal 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
42
shaders/fog_horizon.comp
Normal 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
179
shaders/fog_inject.comp
Normal 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);
|
||||
}
|
||||
44
shaders/fog_integrate.comp
Normal file
44
shaders/fog_integrate.comp
Normal 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
48
shaders/fog_volume.glsl
Normal 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
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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
52
shaders/sky_weather.frag
Normal 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
26
shaders/stars.frag
Normal 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
41
shaders/stars.vert
Normal 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
163
shaders/weather_common.glsl
Normal 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
|
||||
Reference in New Issue
Block a user