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

Shade the sky and the sun by the scenery around the camera instead of the track environment flags

This commit is contained in:
2026-10-27 16:02:36 +01:00
parent 9dde5b0e5e
commit 2a876befb0
35 changed files with 2351 additions and 55 deletions

View File

@@ -109,6 +109,8 @@
#define SCENE_AO_TEX 26
#define CAB_PROBE_TEX 27
#define CAB_PROBE_ALBEDO_TEX 28
#define SKY_OCCLUSION_HEIGHT_TEX 29
#define SKY_OCCLUSION_TEX 30
#define CAB_PROBE_SSBO_BINDING 17
#define CAB_PROBE_SH_SIZE 16
#define CAB_PROBE_SH_GROUP_SIZE 64
@@ -329,6 +331,10 @@ layout(std140, binding = SCENE_UBO_BINDING) uniform scene_ubo
vec4 cab_probe_lamp;
vec4 cab_probe_lamp_color;
vec4 cab_probe_lamp_shadow;
// the occlusion of the sky and of the sun by the scenery around the camera, see gl::scene_ubs
vec4 sky_occlusion_origin;
vec4 sky_occlusion_params;
vec4 sky_occlusion_center;
};
#ifdef STAGE_VERTEX

View File

@@ -12,6 +12,7 @@
#include "common.glsl"
#include "clouds_common.glsl"
#include "sky_occlusion.glsl"
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
@@ -253,14 +254,16 @@ void main()
{
vec3 direction = normalize(world);
// the light of the sky and the sun, held back by the shadows
float sunlit = cloud_shadow(world);
// the scenery around holds back the sky and the key light (tunnels, under bridges: gfx.skyvisibility.fog)
vec2 skyoccl = (sky_occlusion_origin.w > 1.5) ? sky_occlusion_world(world, vec3(0.0)) : vec2(1.0);
float sunlit = cloud_shadow(world) * skyoccl.y;
if (options.z > 0.5 && sunlit > 0.0)
{
// two points of the segment, one in each half: a shaft of light no longer comes and goes with the one point sampled
float shadow = sun_shadow(start + raydirection * (span * 0.5 * offset.z)) + sun_shadow(start + raydirection * (span * 0.5 * (offset.z + 1.0)));
sunlit *= 1.0 - 0.5 * shadow;
}
vec3 light = fog_whiten(fog_color_world(direction), (ground + clouds) / extinction) * fog_light_factor(sunlit);
vec3 light = fog_whiten(fog_color_world(direction), (ground + clouds) / extinction) * ((1.0 - fog_light.x) * skyoccl.x + fog_light.x * sunlit);
// lamps and headlights of the clusters the segment of the cell goes through, each part of the segment with the lights of
// its own cluster. the lights of the cluster of the point sampled would change with the point, frame to frame, where the
// cell reaches into two clusters which don't hold the same lights (a full cluster leaves the least important out)

View File

@@ -24,6 +24,7 @@ layout(std430, binding = CAB_PROBE_SSBO_BINDING) readonly buffer cab_probe_ssbo
#include "ssr.glsl"
#include "conversion.glsl"
#include "weather_common.glsl"
#include "sky_occlusion.glsl"
float glossiness = 1.0;
float metalic = 0.0;
@@ -860,6 +861,10 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref
// attenuated by AMBIENT_SCALE - this only dims the indirect term.
fragcolor *= AMBIENT_SCALE;
}
// the scenery around holds back the sky (x) and the key light (y): tunnels, the ground under bridges and roofs, cuttings. not
// the light of the lamps and the headlights, nor of the cab lamp bounced around the cab, which come in below
vec2 skyoccl = sky_occlusion_world(mat3(inv_view) * f_pos.xyz, normalize(mat3(inv_view) * fragnormal));
fragcolor *= skyoccl.x;
vec3 emissioncolor = vec3(emission);
vec3 view_dir = normalize(-f_pos.xyz);
@@ -919,7 +924,8 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref
vec4 scenecolor = vec4(0.0);
if (!matte)
{
envcolor = reflection_surroundings(fragnormal, env_roughness);
// the sky the surface would reflect is held back as the sky which lights it
envcolor = reflection_surroundings(fragnormal, env_roughness) * skyoccl.x;
// inside the cab the cab is what the surroundings are, where the probe holds it
if (cabprobe && (cab_probe_params.y > 0.0))
envcolor = cab_probe_reflection(envcolor, fragnormal, env_roughness, cabambient);
@@ -942,6 +948,7 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref
// the clouds hold back the sun (the moon at night) where their shadow falls
float cloudsun = cloud_shadow(mat3(inv_view) * f_pos.xyz);
// Sun HDR scale -> SUN_DIFFUSE_SCALE, the same for the diffuse and the specular light
cloudsun *= skyoccl.y;
vec3 sunlight = lights[0].color * (SUN_DIFFUSE_SCALE * lights[0].intensity * cloudsun);
float shadow = 0.0;

View File

@@ -8,6 +8,8 @@
#ifndef PARTICLE_LIGHT_GLSL
#define PARTICLE_LIGHT_GLSL
#include "sky_occlusion.glsl"
layout(binding = SHADOW_TEX) uniform sampler2DArrayShadow shadowmap;
layout(binding = HEADLIGHT_TEX) uniform sampler2D headlightmap;
layout(binding = HEADLIGHT_SHADOW_TEX) uniform sampler2DArrayShadow headlight_shadowmap;
@@ -112,10 +114,12 @@ uint particle_cluster(vec3 position)
vec3 particle_light(vec3 position, vec3 world, float forward)
{
vec3 tocamera = -normalize(position);
vec3 light = ambient * PARTICLE_AMBIENT_SCALE;
// the scenery around holds back the sky and the key light (tunnels, bridges, roofs, gfx::sky_occlusion)
vec2 skyoccl = sky_occlusion_world(world, vec3(0.0));
vec3 light = ambient * (PARTICLE_AMBIENT_SCALE * skyoccl.x);
if (lights_count > 0U)
{
float sunlit = cloud_shadow(world) * (1.0 - particle_sun_shadow(position));
float sunlit = cloud_shadow(world) * skyoccl.y * (1.0 - particle_sun_shadow(position));
if (sunlit > 0.0)
light += lights[0].color * (0.5 * PARTICLE_SUN_SCALE * lights[0].intensity * sunlit * particle_phase(dot(normalize(lights[0].dir), tocamera), forward));
}

View File

@@ -64,6 +64,8 @@ void main()
vec3 walls = abs(world) / (0.5 * size);
opacity *= 1.0 - smoothstep(0.7, 1.0, max(max(walls.x, walls.y), walls.z));
opacity *= smoothstep(0.15, 0.6, length(world));
// none under the roofs, the bridges, in the tunnels
opacity *= 1.0 - sky_occlusion_cover(world);
if (opacity <= 0.002)
return;
vec3 tail = -move * timing.y;

View File

@@ -0,0 +1,73 @@
// the occlusion of the sky and of the sun by the scenery around the camera (gfx.skyvisibility, gfx::sky_occlusion, the track T):
// toroidal maps of the square around the camera, read texel by texel. SKY_OCCLUSION_HEIGHT_TEX holds the height of the top of the
// scenery over a texel (the highest surface, world space), SKY_OCCLUSION_TEX for the texel: x the share of the sky a surface facing
// up sees above the top, y the same under the ceiling (the vault of a tunnel, the deck of a bridge), z the height above the top over
// which the key light (the sun, the moon) comes, w the height above the top under which it comes in under the ceilings.
// the same as gfx::sky_occlusion::sample() of rendering/common/skyocclusion.cpp, which the cpu works out the light around the
// vehicles with: a change here goes there as well
#ifndef SKY_OCCLUSION_GLSL
#define SKY_OCCLUSION_GLSL
layout(binding = SKY_OCCLUSION_HEIGHT_TEX) uniform sampler2D sky_occlusion_height;
layout(binding = SKY_OCCLUSION_TEX) uniform sampler2D sky_occlusion_map;
// a point this far below the top of a texel is under it, fully this much further; half the height over which the key light comes
// in at the edge of its shadow
const float SKY_OCCLUSION_COVER_START = 0.75;
const float SKY_OCCLUSION_COVER_SPAN = 1.0;
const float SKY_OCCLUSION_SUN_SOFTNESS = 0.6;
// the share of the sky (x) and of the key light (y) a point gets, 0..1. World: its offset from the camera along the world axes,
// Worldnormal: the normal of its surface in the world space (zero for a point in the air), the texels are read a little in front of
// the surface (a wall reads the ground in front of it, not the roof above it)
vec2 sky_occlusion_world(vec3 world, vec3 worldnormal)
{
if (sky_occlusion_origin.w <= 0.0)
return vec2(1.0);
float fade = clamp((max(abs(world.x - sky_occlusion_center.x), abs(world.z - sky_occlusion_center.y)) - sky_occlusion_params.z) / max(sky_occlusion_params.w, 1e-3), 0.0, 1.0);
if (fade >= 1.0)
return vec2(1.0);
float texel = sky_occlusion_params.x / sky_occlusion_params.y;
vec3 point = world + worldnormal * (0.75 * texel);
vec2 coords = (point.xz + sky_occlusion_origin.xy) / texel - 0.5;
vec2 base = floor(coords);
vec2 f = coords - base;
vec2 uv = (base + 1.0) / sky_occlusion_params.y;
// the four texels around the point, each with its own top: under one of them a point gets what is under it, above it what is
// above, and the four are blended as bilinear filtering would
vec4 tops = textureGather(sky_occlusion_height, uv, 0);
vec4 skytop = textureGather(sky_occlusion_map, uv, 0);
vec4 skyunder = textureGather(sky_occlusion_map, uv, 1);
vec4 litabove = textureGather(sky_occlusion_map, uv, 2);
vec4 litbelow = textureGather(sky_occlusion_map, uv, 3);
float height = point.y + sky_occlusion_origin.z;
vec4 cover = clamp((tops - vec4(height) - SKY_OCCLUSION_COVER_START) / SKY_OCCLUSION_COVER_SPAN, 0.0, 1.0);
vec4 sky = mix(skytop, skyunder, cover);
vec4 suntop = smoothstep(vec4(-SKY_OCCLUSION_SUN_SOFTNESS), vec4(SKY_OCCLUSION_SUN_SOFTNESS), vec4(height) - (tops + litabove));
vec4 sununder = smoothstep(vec4(-SKY_OCCLUSION_SUN_SOFTNESS), vec4(SKY_OCCLUSION_SUN_SOFTNESS), (tops + litbelow) - vec4(height));
vec4 sun = mix(suntop, sununder, cover);
// the order of the texels of a gather: (0, 1), (1, 1), (1, 0), (0, 0)
vec4 weights = vec4((1.0 - f.x) * f.y, f.x * f.y, f.x * (1.0 - f.y), (1.0 - f.x) * (1.0 - f.y));
return mix(vec2(dot(sky, weights), dot(sun, weights)), vec2(1.0), fade);
}
// how much of a point in the air is under the scenery (0 in the open, 1 under a roof, a bridge, in a tunnel), for what falls from
// the sky. World: its offset from the camera along the world axes
float sky_occlusion_cover(vec3 world)
{
if (sky_occlusion_origin.w <= 0.0)
return 0.0;
float fade = clamp((max(abs(world.x - sky_occlusion_center.x), abs(world.z - sky_occlusion_center.y)) - sky_occlusion_params.z) / max(sky_occlusion_params.w, 1e-3), 0.0, 1.0);
if (fade >= 1.0)
return 0.0;
float texel = sky_occlusion_params.x / sky_occlusion_params.y;
vec2 coords = (world.xz + sky_occlusion_origin.xy) / texel - 0.5;
vec2 base = floor(coords);
vec2 f = coords - base;
vec4 tops = textureGather(sky_occlusion_height, (base + 1.0) / sky_occlusion_params.y, 0);
vec4 cover = clamp((tops - vec4(world.y + sky_occlusion_origin.z) - SKY_OCCLUSION_COVER_START) / SKY_OCCLUSION_COVER_SPAN, 0.0, 1.0);
vec4 weights = vec4((1.0 - f.x) * f.y, f.x * f.y, f.x * (1.0 - f.y), (1.0 - f.x) * (1.0 - f.y));
return dot(cover, weights) * (1.0 - fade);
}
#endif

View File

@@ -65,7 +65,9 @@
#define CAB_PROBE_TEX 28
#define CAB_PROBE_ALBEDO_TEX 29
#define CAB_PROBE_SSBO 30
#define PASS_BINDING_COUNT 31
#define SKY_OCCLUSION_HEIGHT_TEX 31
#define SKY_OCCLUSION_TEX 32
#define PASS_BINDING_COUNT 33
#define MATERIAL_TEXTURE_FIRST 0
#define CL_CLUSTER_UBO 0
#define CL_AABB_SSBO 1
@@ -296,6 +298,10 @@ layout(std140, set = PASS_SET, binding = SCENE_UBO) uniform scene_ubo
vec4 cab_probe_lamp;
vec4 cab_probe_lamp_color;
vec4 cab_probe_lamp_shadow;
// the occlusion of the sky and of the sun by the scenery around the camera, see gl::scene_ubs
vec4 sky_occlusion_origin;
vec4 sky_occlusion_params;
vec4 sky_occlusion_center;
};
#ifndef LAYOUT_COMPUTE

View File

@@ -13,6 +13,7 @@
#define LAYOUT_COMPUTE 1
#include "common.glsl"
#include "clouds_common.glsl"
#include "sky_occlusion.glsl"
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
@@ -258,14 +259,16 @@ void main()
{
vec3 direction = normalize(world);
// the light of the sky and the sun, held back by the shadows
float sunlit = cloud_shadow(world);
// the scenery around holds back the sky and the key light (tunnels, under bridges: gfx.skyvisibility.fog)
vec2 skyoccl = (sky_occlusion_origin.w > 1.5) ? sky_occlusion_world(world, vec3(0.0)) : vec2(1.0);
float sunlit = cloud_shadow(world) * skyoccl.y;
if (options.z > 0.5 && sunlit > 0.0)
{
// two points of the segment, one in each half: a shaft of light no longer comes and goes with the one point sampled
float shadow = sun_shadow(start + raydirection * (span * 0.5 * offset.z)) + sun_shadow(start + raydirection * (span * 0.5 * (offset.z + 1.0)));
sunlit *= 1.0 - 0.5 * shadow;
}
vec3 light = fog_whiten(fog_color_world(direction), (ground + clouds) / extinction) * fog_light_factor(sunlit);
vec3 light = fog_whiten(fog_color_world(direction), (ground + clouds) / extinction) * ((1.0 - fog_light.x) * skyoccl.x + fog_light.x * sunlit);
// lamps and headlights of the clusters the segment of the cell goes through, each part of the segment with the lights of
// its own cluster. the lights of the cluster of the point sampled would change with the point, frame to frame, where the
// cell reaches into two clusters which don't hold the same lights (a full cluster leaves the least important out)

View File

@@ -28,6 +28,7 @@ layout(std430, set = PASS_SET, binding = CAB_PROBE_SSBO) readonly buffer cab_pro
#include "ssr.glsl"
#include "conversion.glsl"
#include "weather_common.glsl"
#include "sky_occlusion.glsl"
float glossiness = 1.0;
float metalic = 0.0;
@@ -873,6 +874,10 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref
// attenuated by AMBIENT_SCALE - this only dims the indirect term.
fragcolor *= AMBIENT_SCALE;
}
// the scenery around holds back the sky (x) and the key light (y): tunnels, the ground under bridges and roofs, cuttings. not
// the light of the lamps and the headlights, nor of the cab lamp bounced around the cab, which come in below
vec2 skyoccl = sky_occlusion_world(mat3(inv_view) * f_pos.xyz, normalize(mat3(inv_view) * fragnormal));
fragcolor *= skyoccl.x;
vec3 emissioncolor = vec3(emission);
vec3 view_dir = normalize(-f_pos.xyz);
@@ -932,7 +937,8 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref
vec4 scenecolor = vec4(0.0);
if (!matte)
{
envcolor = reflection_surroundings(fragnormal, env_roughness);
// the sky the surface would reflect is held back as the sky which lights it
envcolor = reflection_surroundings(fragnormal, env_roughness) * skyoccl.x;
// inside the cab the cab is what the surroundings are, where the probe holds it
if (cabprobe && (cab_probe_params.y > 0.0))
envcolor = cab_probe_reflection(envcolor, fragnormal, env_roughness, cabambient);
@@ -956,6 +962,7 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref
// the clouds hold back the sun (the moon at night) where their shadow falls
float cloudsun = cloud_shadow(mat3(inv_view) * f_pos.xyz);
// Sun HDR scale -> SUN_DIFFUSE_SCALE, the same for the diffuse and the specular light
cloudsun *= skyoccl.y;
vec3 sunlight = lights[0].color * (SUN_DIFFUSE_SCALE * lights[0].intensity * cloudsun);
float shadow = 0.0;

View File

@@ -8,6 +8,8 @@
#ifndef PARTICLE_LIGHT_GLSL
#define PARTICLE_LIGHT_GLSL
#include "sky_occlusion.glsl"
layout(set = PASS_SET, binding = SHADOW_TEX) uniform sampler2DArrayShadow shadowmap;
layout(set = PASS_SET, binding = HEADLIGHT_TEX) uniform sampler2D headlightmap;
layout(set = PASS_SET, binding = HEADLIGHT_SHADOW_TEX) uniform sampler2DArrayShadow headlight_shadowmap;
@@ -112,10 +114,12 @@ uint particle_cluster(vec3 position)
vec3 particle_light(vec3 position, vec3 world, float forward)
{
vec3 tocamera = -normalize(position);
vec3 light = ambient * PARTICLE_AMBIENT_SCALE;
// the scenery around holds back the sky and the key light (tunnels, bridges, roofs, gfx::sky_occlusion)
vec2 skyoccl = sky_occlusion_world(world, vec3(0.0));
vec3 light = ambient * (PARTICLE_AMBIENT_SCALE * skyoccl.x);
if (lights_count > 0U)
{
float sunlit = cloud_shadow(world) * (1.0 - particle_sun_shadow(position));
float sunlit = cloud_shadow(world) * skyoccl.y * (1.0 - particle_sun_shadow(position));
if (sunlit > 0.0)
light += lights[0].color * (0.5 * PARTICLE_SUN_SCALE * lights[0].intensity * sunlit * particle_phase(dot(normalize(lights[0].dir), tocamera), forward));
}

View File

@@ -71,6 +71,8 @@ void main()
vec3 walls = abs(world) / (0.5 * size);
opacity *= 1.0 - smoothstep(0.7, 1.0, max(max(walls.x, walls.y), walls.z));
opacity *= smoothstep(0.15, 0.6, length(world));
// none under the roofs, the bridges, in the tunnels
opacity *= 1.0 - sky_occlusion_cover(world);
if (opacity <= 0.002)
return;
vec3 tail = -move * timing.y;

View File

@@ -0,0 +1,73 @@
// the occlusion of the sky and of the sun by the scenery around the camera (gfx.skyvisibility, gfx::sky_occlusion, the track T):
// toroidal maps of the square around the camera, read texel by texel. SKY_OCCLUSION_HEIGHT_TEX holds the height of the top of the
// scenery over a texel (the highest surface, world space), SKY_OCCLUSION_TEX for the texel: x the share of the sky a surface facing
// up sees above the top, y the same under the ceiling (the vault of a tunnel, the deck of a bridge), z the height above the top over
// which the key light (the sun, the moon) comes, w the height above the top under which it comes in under the ceilings.
// the same as gfx::sky_occlusion::sample() of rendering/common/skyocclusion.cpp, which the cpu works out the light around the
// vehicles with: a change here goes there as well
#ifndef SKY_OCCLUSION_GLSL
#define SKY_OCCLUSION_GLSL
layout(set = PASS_SET, binding = SKY_OCCLUSION_HEIGHT_TEX) uniform sampler2D sky_occlusion_height;
layout(set = PASS_SET, binding = SKY_OCCLUSION_TEX) uniform sampler2D sky_occlusion_map;
// a point this far below the top of a texel is under it, fully this much further; half the height over which the key light comes
// in at the edge of its shadow
const float SKY_OCCLUSION_COVER_START = 0.75;
const float SKY_OCCLUSION_COVER_SPAN = 1.0;
const float SKY_OCCLUSION_SUN_SOFTNESS = 0.6;
// the share of the sky (x) and of the key light (y) a point gets, 0..1. World: its offset from the camera along the world axes,
// Worldnormal: the normal of its surface in the world space (zero for a point in the air), the texels are read a little in front of
// the surface (a wall reads the ground in front of it, not the roof above it)
vec2 sky_occlusion_world(vec3 world, vec3 worldnormal)
{
if (sky_occlusion_origin.w <= 0.0)
return vec2(1.0);
float fade = clamp((max(abs(world.x - sky_occlusion_center.x), abs(world.z - sky_occlusion_center.y)) - sky_occlusion_params.z) / max(sky_occlusion_params.w, 1e-3), 0.0, 1.0);
if (fade >= 1.0)
return vec2(1.0);
float texel = sky_occlusion_params.x / sky_occlusion_params.y;
vec3 point = world + worldnormal * (0.75 * texel);
vec2 coords = (point.xz + sky_occlusion_origin.xy) / texel - 0.5;
vec2 base = floor(coords);
vec2 f = coords - base;
vec2 uv = (base + 1.0) / sky_occlusion_params.y;
// the four texels around the point, each with its own top: under one of them a point gets what is under it, above it what is
// above, and the four are blended as bilinear filtering would
vec4 tops = textureGather(sky_occlusion_height, uv, 0);
vec4 skytop = textureGather(sky_occlusion_map, uv, 0);
vec4 skyunder = textureGather(sky_occlusion_map, uv, 1);
vec4 litabove = textureGather(sky_occlusion_map, uv, 2);
vec4 litbelow = textureGather(sky_occlusion_map, uv, 3);
float height = point.y + sky_occlusion_origin.z;
vec4 cover = clamp((tops - vec4(height) - SKY_OCCLUSION_COVER_START) / SKY_OCCLUSION_COVER_SPAN, 0.0, 1.0);
vec4 sky = mix(skytop, skyunder, cover);
vec4 suntop = smoothstep(vec4(-SKY_OCCLUSION_SUN_SOFTNESS), vec4(SKY_OCCLUSION_SUN_SOFTNESS), vec4(height) - (tops + litabove));
vec4 sununder = smoothstep(vec4(-SKY_OCCLUSION_SUN_SOFTNESS), vec4(SKY_OCCLUSION_SUN_SOFTNESS), (tops + litbelow) - vec4(height));
vec4 sun = mix(suntop, sununder, cover);
// the order of the texels of a gather: (0, 1), (1, 1), (1, 0), (0, 0)
vec4 weights = vec4((1.0 - f.x) * f.y, f.x * f.y, f.x * (1.0 - f.y), (1.0 - f.x) * (1.0 - f.y));
return mix(vec2(dot(sky, weights), dot(sun, weights)), vec2(1.0), fade);
}
// how much of a point in the air is under the scenery (0 in the open, 1 under a roof, a bridge, in a tunnel), for what falls from
// the sky. World: its offset from the camera along the world axes
float sky_occlusion_cover(vec3 world)
{
if (sky_occlusion_origin.w <= 0.0)
return 0.0;
float fade = clamp((max(abs(world.x - sky_occlusion_center.x), abs(world.z - sky_occlusion_center.y)) - sky_occlusion_params.z) / max(sky_occlusion_params.w, 1e-3), 0.0, 1.0);
if (fade >= 1.0)
return 0.0;
float texel = sky_occlusion_params.x / sky_occlusion_params.y;
vec2 coords = (world.xz + sky_occlusion_origin.xy) / texel - 0.5;
vec2 base = floor(coords);
vec2 f = coords - base;
vec4 tops = textureGather(sky_occlusion_height, (base + 1.0) / sky_occlusion_params.y, 0);
vec4 cover = clamp((tops - vec4(world.y + sky_occlusion_origin.z) - SKY_OCCLUSION_COVER_START) / SKY_OCCLUSION_COVER_SPAN, 0.0, 1.0);
vec4 weights = vec4((1.0 - f.x) * f.y, f.x * f.y, f.x * (1.0 - f.y), (1.0 - f.x) * (1.0 - f.y));
return dot(cover, weights) * (1.0 - fade);
}
#endif