17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-05 15:42:31 +01:00

Vulkan: parity with GL (P1–P7), shared GPU culling records (N6c), threaded command recording and render thread (M1, M4)

This commit is contained in:
2026-10-25 03:06:01 +01:00
parent 49f9df7d98
commit c48c58776a
146 changed files with 20748 additions and 4606 deletions

View File

@@ -1,20 +1,21 @@
// fog between the camera and a fragment, the api of shaders/ogl46/apply_fog.glsl. the fog volume and the weather of the opengl
// renderer come with the stage P2 of the plan; until then the fog is analytic: exponential with the distance, of the density of
// the draw (fog_density of the model block, a draw with 0 is not fogged), of the colour fog_color of the light buffer, turned
// towards the colour of the sun around its direction
// 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);
float amount = 1.0 - clamp(exp(-length(f_pos.xyz) * fog_density), 0.0, 1.0);
vec3 color = fog_color;
if ((lights_count >= 1U) && (lights[0].type == LIGHT_DIR))
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 sunward = pow(max(dot(normalize(f_pos.xyz), normalize(-lights[0].dir)), 0.0), 30.0);
color = mix(color, lights[0].color, sunward);
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 vec4(color * amount, 1.0 - amount);
return fog;
}
vec3 get_fog_color()

View File

@@ -0,0 +1,130 @@
#version 460 core
#define STAGE_COMPUTE 1
// what the cpu needs from the atmosphere, worked out from the sky-view table in one work group: the light arriving at the
// observer from every direction as spherical harmonics (the ground below the horizon lit by the sun and the sky), the
// transmittance towards the sun, and the average light of the sky above the horizon and over the whole sky.
// run after the sky-view table; the result is read back by the cpu a few frames later
#define LAYOUT_COMPUTE 1
#include "common.glsl"
#include "atmosphere_common.glsl"
layout(set = COMPUTE_SET, binding = 0) uniform sampler2D atmo_transmittance_lut;
layout(set = COMPUTE_SET, binding = 1) uniform sampler2D atmo_skyview_lut;
layout(local_size_x = ATMOSPHERE_AMBIENT_GROUP_SIZE) in;
layout(std430, set = COMPUTE_SET, binding = 2) writeonly buffer atmosphere_result_ssbo
{
vec4 result_radiance_sh[9];
vec4 result_sun_transmittance;
vec4 result_horizon;
vec4 result_average;
vec4 result_zenith_transmittance;
};
// directions of the upper hemisphere each invocation takes
const uint DIRECTIONS_PER_INVOCATION = 4U;
const uint DIRECTIONS = ATMOSPHERE_AMBIENT_GROUP_SIZE * DIRECTIONS_PER_INVOCATION;
// the sky up to this sine of the elevation is the horizon (about 6 degrees)
const float HORIZON_BAND = 0.1;
// partial sums: the 9 coefficients, the light on an upward facing surface, the horizon and its weight, the sky average
const int SUMS = 12;
shared vec4 shared_sums[ATMOSPHERE_AMBIENT_GROUP_SIZE];
void sh_basis(vec3 n, out float basis[9])
{
basis[0] = 0.282095;
basis[1] = 0.488603 * n.y;
basis[2] = 0.488603 * n.z;
basis[3] = 0.488603 * n.x;
basis[4] = 1.092548 * n.x * n.y;
basis[5] = 1.092548 * n.y * n.z;
basis[6] = 0.315392 * (3.0 * n.z * n.z - 1.0);
basis[7] = 1.092548 * n.x * n.z;
basis[8] = 0.546274 * (n.x * n.x - n.y * n.y);
}
// sum of the value of every invocation, valid in invocation 0
vec4 reduce(vec4 value)
{
uint index = gl_LocalInvocationID.x;
barrier();
shared_sums[index] = value;
barrier();
for (uint stride = ATMOSPHERE_AMBIENT_GROUP_SIZE / 2U; stride > 0U; stride >>= 1)
{
if (index < stride)
shared_sums[index] += shared_sums[index + stride];
barrier();
}
return shared_sums[0];
}
void main()
{
uint index = gl_LocalInvocationID.x;
vec3 sundirection = normalize(atmo_sun_direction.xyz);
vec3 sums[SUMS];
for (int i = 0; i < SUMS; ++i)
sums[i] = vec3(0.0);
// even spread over the upper hemisphere: a fibonacci spiral in the sine of the elevation
const float goldenangle = 2.39996323;
const float solidangle = 2.0 * ATMO_PI / float(DIRECTIONS);
for (uint k = 0U; k < DIRECTIONS_PER_INVOCATION; ++k)
{
uint i = index * DIRECTIONS_PER_INVOCATION + k;
float y = (float(i) + 0.5) / float(DIRECTIONS);
float r = sqrt(max(1.0 - y * y, 0.0));
float azimuth = goldenangle * float(i);
vec3 direction = vec3(r * cos(azimuth), y, r * sin(azimuth));
vec3 radiance = textureLod(atmo_skyview_lut, atmo_skyview_uv_world(direction, sundirection), 0.0).rgb;
float basis[9];
sh_basis(direction, basis);
for (int c = 0; c < 9; ++c)
sums[c] += radiance * (basis[c] * solidangle);
sums[9] += radiance * (y * solidangle);
if (y < HORIZON_BAND)
{
sums[10] += radiance;
sums[11] += vec3(1.0);
}
}
vec3 total[SUMS];
for (int i = 0; i < SUMS; ++i)
total[i] = reduce(vec4(sums[i], 0.0)).rgb;
if (index != 0U)
return;
// the transmittance towards the sun, zero once the planet is in the way
float radius = atmo_observer_radius;
vec3 origin = vec3(0.0, radius, 0.0);
vec3 suntransmittance = texture(atmo_transmittance_lut, atmo_transmittance_uv(radius, sundirection.y)).rgb;
if (atmo_ray_sphere_nearest(origin, sundirection, atmo_bottom_radius) >= 0.0)
suntransmittance = vec3(0.0);
// the ground, a lambertian surface lit by the sun and the sky, fills the lower hemisphere with uniform light. its
// spherical harmonics have only the constant term and the vertical one: for a uniform radiance L over the lower
// hemisphere, 0.282095 * 2 pi * L and 0.488603 * (-pi) * L
// the band 0 coefficient of the sky alone over the solid angle of the hemisphere
vec3 skyaverage = total[0] / (0.282095 * 2.0 * ATMO_PI);
vec3 irradiance = suntransmittance * max(sundirection.y, 0.0) + total[9];
vec3 groundradiance = atmo_ground.rgb / ATMO_PI * irradiance;
total[0] += groundradiance * (0.282095 * 2.0 * ATMO_PI);
total[1] += groundradiance * (0.488603 * -ATMO_PI);
for (int c = 0; c < 9; ++c)
result_radiance_sh[c] = vec4(total[c], 0.0);
result_sun_transmittance = vec4(suntransmittance, 0.0);
result_horizon = vec4(total[10] / max(total[11].x, 1.0), 0.0);
result_average = vec4(skyaverage, 0.0);
// what the sun high in the sky looks like at the observer, for the white balance
result_zenith_transmittance = vec4(texture(atmo_transmittance_lut, atmo_transmittance_uv(radius, 1.0)).rgb, 0.0);
}

View File

@@ -0,0 +1,194 @@
// physically based sky after S. Hillaire, "A Scalable and Production Ready Sky and Atmosphere Rendering Technique"
// (EGSR 2020): transmittance, multiple scattering and sky-view look-up tables. distances in kilometres, coefficients per
// kilometre. the planet is centred at the origin; the look-up tables are worked out in a frame with z up at the observer,
// the world (y up) is mapped to it by the callers. radiance is that of a sun of illuminance 1
layout(std140, set = PASS_SET, binding = ATMOSPHERE_UBO) uniform atmosphere_ubo
{
// rgb: rayleigh scattering, w: rayleigh scale height
vec4 atmo_rayleigh;
// x: mie scattering, y: mie extinction, z: mie scale height, w: mie phase asymmetry
vec4 atmo_mie;
// rgb: ozone absorption, w: altitude of the middle of the ozone layer
vec4 atmo_ozone;
// rgb: albedo of the ground, w: half width of the ozone layer
vec4 atmo_ground;
// world space direction towards the sun (y up)
vec4 atmo_sun_direction;
float atmo_bottom_radius;
float atmo_top_radius;
// distance of the observer from the centre of the planet
float atmo_observer_radius;
};
const float ATMO_PI = 3.14159265359;
struct atmo_medium_s
{
vec3 scattering;
vec3 extinction;
vec3 rayleigh_scattering;
vec3 mie_scattering;
};
atmo_medium_s atmo_sample_medium(float radius)
{
float height = max(radius - atmo_bottom_radius, 0.0);
float rayleighdensity = exp(-height / atmo_rayleigh.w);
float miedensity = exp(-height / atmo_mie.z);
float ozonedensity = max(0.0, 1.0 - abs(height - atmo_ozone.w) / atmo_ground.w);
atmo_medium_s medium;
medium.rayleigh_scattering = atmo_rayleigh.rgb * rayleighdensity;
medium.mie_scattering = vec3(atmo_mie.x * miedensity);
medium.scattering = medium.rayleigh_scattering + medium.mie_scattering;
medium.extinction = medium.rayleigh_scattering + vec3(atmo_mie.y * miedensity) + atmo_ozone.rgb * ozonedensity;
return medium;
}
// distance along a ray (unit direction) to the nearest point ahead of the origin where it meets the sphere, -1 if it does not
float atmo_ray_sphere_nearest(vec3 origin, vec3 direction, float radius)
{
float b = dot(origin, direction);
float c = dot(origin, origin) - radius * radius;
float discriminant = b * b - c;
if (discriminant < 0.0)
return -1.0;
float root = sqrt(discriminant);
float t0 = -b - root;
float t1 = -b + root;
if (t0 < 0.0 && t1 < 0.0)
return -1.0;
if (t0 < 0.0)
return t1;
if (t1 < 0.0)
return t0;
return min(t0, t1);
}
float atmo_rayleigh_phase(float cosangle)
{
return 3.0 / (16.0 * ATMO_PI) * (1.0 + cosangle * cosangle);
}
// Cornette-Shanks
float atmo_mie_phase(float cosangle)
{
float g = atmo_mie.w;
float k = 3.0 / (8.0 * ATMO_PI) * (1.0 - g * g) / (2.0 + g * g);
return k * (1.0 + cosangle * cosangle) / pow(max(1.0 + g * g - 2.0 * g * cosangle, 1e-4), 1.5);
}
// texel centres of a table of the given resolution span [0, 1] of the parameter
float atmo_unit_to_sub_uv(float u, float resolution)
{
return (u + 0.5 / resolution) * (resolution / (resolution + 1.0));
}
float atmo_sub_uv_to_unit(float u, float resolution)
{
return (u - 0.5 / resolution) * (resolution / (resolution - 1.0));
}
// transmittance table: x the distance to the top of the atmosphere along the view, y the height
vec2 atmo_transmittance_uv(float radius, float cosviewzenith)
{
float H = sqrt(max(0.0, atmo_top_radius * atmo_top_radius - atmo_bottom_radius * atmo_bottom_radius));
float rho = sqrt(max(0.0, radius * radius - atmo_bottom_radius * atmo_bottom_radius));
float discriminant = radius * radius * (cosviewzenith * cosviewzenith - 1.0) + atmo_top_radius * atmo_top_radius;
float d = max(0.0, -radius * cosviewzenith + sqrt(max(discriminant, 0.0)));
float dmin = atmo_top_radius - radius;
float dmax = rho + H;
return vec2((d - dmin) / max(dmax - dmin, 1e-6), rho / H);
}
void atmo_transmittance_params(vec2 uv, out float radius, out float cosviewzenith)
{
float H = sqrt(max(0.0, atmo_top_radius * atmo_top_radius - atmo_bottom_radius * atmo_bottom_radius));
float rho = H * uv.y;
radius = sqrt(rho * rho + atmo_bottom_radius * atmo_bottom_radius);
float dmin = atmo_top_radius - radius;
float dmax = rho + H;
float d = dmin + uv.x * (dmax - dmin);
cosviewzenith = (d == 0.0) ? 1.0 : (H * H - rho * rho - d * d) / (2.0 * radius * d);
cosviewzenith = clamp(cosviewzenith, -1.0, 1.0);
}
// multiple scattering table: x the cosine of the sun zenith angle, y the height
vec2 atmo_multiscattering_uv(float radius, float cossunzenith)
{
vec2 uv = vec2(cossunzenith * 0.5 + 0.5, (radius - atmo_bottom_radius) / (atmo_top_radius - atmo_bottom_radius));
return clamp(vec2(atmo_unit_to_sub_uv(uv.x, float(ATMOSPHERE_MULTISCATTERING_SIZE)), atmo_unit_to_sub_uv(uv.y, float(ATMOSPHERE_MULTISCATTERING_SIZE))), 0.0, 1.0);
}
// sky-view table: x the angle between the view and the sun around the zenith (sqrt spaced), y the angle of the view from
// the zenith, packed towards the horizon, which splits the table in the sky (lower half) and the ground (upper half)
vec2 atmo_skyview_uv(bool intersectsground, float cosviewzenith, float coslightview, float radius)
{
float vhorizon = sqrt(max(radius * radius - atmo_bottom_radius * atmo_bottom_radius, 0.0));
float cosbeta = vhorizon / radius;
float beta = acos(clamp(cosbeta, -1.0, 1.0));
float zenithhorizonangle = ATMO_PI - beta;
vec2 uv;
if (!intersectsground)
{
float coord = acos(clamp(cosviewzenith, -1.0, 1.0)) / zenithhorizonangle;
coord = 1.0 - coord;
coord = sqrt(max(coord, 0.0));
coord = 1.0 - coord;
uv.y = coord * 0.5;
}
else
{
float coord = (acos(clamp(cosviewzenith, -1.0, 1.0)) - zenithhorizonangle) / max(beta, 1e-6);
coord = sqrt(max(coord, 0.0));
uv.y = coord * 0.5 + 0.5;
}
uv.x = sqrt(clamp(-coslightview * 0.5 + 0.5, 0.0, 1.0));
return vec2(atmo_unit_to_sub_uv(uv.x, float(ATMOSPHERE_SKYVIEW_WIDTH)), atmo_unit_to_sub_uv(uv.y, float(ATMOSPHERE_SKYVIEW_HEIGHT)));
}
void atmo_skyview_params(vec2 uv, float radius, out float cosviewzenith, out float coslightview)
{
uv = vec2(atmo_sub_uv_to_unit(uv.x, float(ATMOSPHERE_SKYVIEW_WIDTH)), atmo_sub_uv_to_unit(uv.y, float(ATMOSPHERE_SKYVIEW_HEIGHT)));
uv = clamp(uv, 0.0, 1.0);
float vhorizon = sqrt(max(radius * radius - atmo_bottom_radius * atmo_bottom_radius, 0.0));
float cosbeta = vhorizon / radius;
float beta = acos(clamp(cosbeta, -1.0, 1.0));
float zenithhorizonangle = ATMO_PI - beta;
if (uv.y < 0.5)
{
float coord = 2.0 * uv.y;
coord = 1.0 - coord;
coord *= coord;
coord = 1.0 - coord;
cosviewzenith = cos(zenithhorizonangle * coord);
}
else
{
float coord = uv.y * 2.0 - 1.0;
coord *= coord;
cosviewzenith = cos(zenithhorizonangle + beta * coord);
}
float coord = uv.x * uv.x;
coslightview = -(coord * 2.0 - 1.0);
}
// the same mapping for a world space view direction and sun direction (y up), as seen by the observer
vec2 atmo_skyview_uv_world(vec3 viewdirection, vec3 sundirection)
{
float radius = atmo_observer_radius;
vec3 origin = vec3(0.0, 0.0, radius);
float cosviewzenith = viewdirection.y;
// angle between the view and the sun around the zenith
vec2 viewflat = viewdirection.xz;
vec2 sunflat = sundirection.xz;
float lengths = length(viewflat) * length(sunflat);
float coslightview = (lengths > 1e-6) ? clamp(dot(viewflat, sunflat) / lengths, -1.0, 1.0) : 1.0;
vec3 localview = vec3(sqrt(max(1.0 - cosviewzenith * cosviewzenith, 0.0)), 0.0, cosviewzenith);
bool intersectsground = atmo_ray_sphere_nearest(origin, localview, atmo_bottom_radius) >= 0.0;
return atmo_skyview_uv(intersectsground, cosviewzenith, coslightview, radius);
}

View File

@@ -0,0 +1,105 @@
// scattering along a ray through the atmosphere, see atmosphere_common.glsl. the includer declares the samplers
// atmo_transmittance_lut and (when multiple scattering is used) atmo_multiscattering_lut
// above the ground by this much (km), so the test for the shadow of the planet does not hit the point itself
const float ATMO_PLANET_RADIUS_OFFSET = 0.01;
struct atmo_scattering_s
{
// light scattered towards the origin
vec3 luminance;
// transmittance from the origin to the end of the ray
vec3 transmittance;
// light scattered once more, as if the light arriving at every point was 1 (multiple scattering table)
vec3 multiscattering_as_1;
};
vec3 atmo_transmittance_to_top(float radius, float cossunzenith)
{
return texture(atmo_transmittance_lut, atmo_transmittance_uv(radius, cossunzenith)).rgb;
}
// Origin and Direction (unit) in the frame of the planet. with Ground the light the ground reflects at the end of the ray
// is included. Physicalphase: rayleigh and mie phase functions, otherwise isotropic (multiple scattering table).
// Multiscattering: light scattered more than once comes from the multiple scattering table
atmo_scattering_s atmo_integrate(vec3 origin, vec3 direction, vec3 sundirection, bool ground, int samples, bool physicalphase, bool multiscattering)
{
atmo_scattering_s result;
result.luminance = vec3(0.0);
result.transmittance = vec3(1.0);
result.multiscattering_as_1 = vec3(0.0);
float tbottom = atmo_ray_sphere_nearest(origin, direction, atmo_bottom_radius);
float ttop = atmo_ray_sphere_nearest(origin, direction, atmo_top_radius);
float tmax;
if (tbottom < 0.0)
{
if (ttop < 0.0)
return result;
tmax = ttop;
}
else
{
tmax = (ttop > 0.0) ? min(ttop, tbottom) : tbottom;
}
float cosangle = dot(direction, sundirection);
const float uniformphase = 1.0 / (4.0 * ATMO_PI);
float miephase = physicalphase ? atmo_mie_phase(cosangle) : uniformphase;
float rayleighphase = physicalphase ? atmo_rayleigh_phase(cosangle) : uniformphase;
vec3 throughput = vec3(1.0);
float invsamples = 1.0 / float(samples);
for (int s = 0; s < samples; ++s)
{
// the steps grow with the distance: the medium near the observer matters most
float t0 = float(s) * invsamples;
float t1 = float(s + 1) * invsamples;
t0 = t0 * t0 * tmax;
t1 = t1 * t1 * tmax;
float dt = t1 - t0;
float t = t0 + dt * 0.3;
vec3 position = origin + t * direction;
float radius = length(position);
vec3 up = position / radius;
atmo_medium_s medium = atmo_sample_medium(radius);
vec3 extinction = max(medium.extinction, vec3(1e-7));
vec3 sampletransmittance = exp(-extinction * dt);
float cossunzenith = dot(sundirection, up);
vec3 suntransmittance = atmo_transmittance_to_top(radius, cossunzenith);
float earthshadow = (atmo_ray_sphere_nearest(position + up * ATMO_PLANET_RADIUS_OFFSET, sundirection, atmo_bottom_radius) >= 0.0) ? 0.0 : 1.0;
vec3 multiscatteredluminance = vec3(0.0);
#ifdef ATMO_MULTISCATTERING_LUT
if (multiscattering)
multiscatteredluminance = texture(atmo_multiscattering_lut, atmo_multiscattering_uv(radius, cossunzenith)).rgb;
#endif
vec3 phasescattering = medium.mie_scattering * miephase + medium.rayleigh_scattering * rayleighphase;
vec3 S = earthshadow * suntransmittance * phasescattering + multiscatteredluminance * medium.scattering;
// analytic integral over the step of the light scattered at the sample, attenuated on the way to its start
vec3 Sint = (S - S * sampletransmittance) / extinction;
result.luminance += throughput * Sint;
vec3 MSint = (medium.scattering - medium.scattering * sampletransmittance) / extinction;
result.multiscattering_as_1 += throughput * MSint;
throughput *= sampletransmittance;
}
if (ground && tbottom > 0.0 && tmax == tbottom)
{
vec3 position = origin + tbottom * direction;
float radius = length(position);
vec3 up = position / radius;
float cossunzenith = dot(sundirection, up);
vec3 suntransmittance = atmo_transmittance_to_top(radius, cossunzenith);
float NdotL = clamp(dot(up, sundirection), 0.0, 1.0);
result.luminance += suntransmittance * throughput * NdotL * atmo_ground.rgb / ATMO_PI;
}
result.transmittance = throughput;
return result;
}

View File

@@ -0,0 +1,72 @@
#version 460 core
#define STAGE_COMPUTE 1
// light scattered more than once, for the multiple scattering table (Hillaire 2020, section 5.5): for a point at a height
// lit by the sun at an angle, the second order scattering from 64 directions around it, summed as a geometric series of
// all the orders. one work group per texel, one invocation per direction. run after the transmittance table
#define LAYOUT_COMPUTE 1
#include "common.glsl"
#include "atmosphere_common.glsl"
layout(set = COMPUTE_SET, binding = 0) uniform sampler2D atmo_transmittance_lut;
#include "atmosphere_integrate.glsl"
layout(local_size_x = 64) in;
layout(set = COMPUTE_SET, binding = 1, rgba16f) writeonly uniform image2D multiscattering_target;
const int MULTISCATTERING_SAMPLES = 20;
const int SQRT_DIRECTIONS = 8;
shared vec3 shared_luminance[64];
shared vec3 shared_multiscattering[64];
void main()
{
ivec2 texel = ivec2(gl_WorkGroupID.xy);
uint index = gl_LocalInvocationID.x;
vec2 uv = (vec2(texel) + 0.5) / float(ATMOSPHERE_MULTISCATTERING_SIZE);
uv = vec2(atmo_sub_uv_to_unit(uv.x, float(ATMOSPHERE_MULTISCATTERING_SIZE)), atmo_sub_uv_to_unit(uv.y, float(ATMOSPHERE_MULTISCATTERING_SIZE)));
uv = clamp(uv, 0.0, 1.0);
float cossunzenith = uv.x * 2.0 - 1.0;
vec3 sundirection = vec3(0.0, sqrt(max(1.0 - cossunzenith * cossunzenith, 0.0)), cossunzenith);
float radius = atmo_bottom_radius + clamp(uv.y + 1e-4, 0.0, 1.0) * (atmo_top_radius - atmo_bottom_radius - 1e-3);
vec3 origin = vec3(0.0, 0.0, radius);
// directions spread evenly over the sphere
float a = (0.5 + float(index / uint(SQRT_DIRECTIONS))) / float(SQRT_DIRECTIONS);
float b = (0.5 + float(index % uint(SQRT_DIRECTIONS))) / float(SQRT_DIRECTIONS);
float theta = 2.0 * ATMO_PI * a;
float phi = acos(clamp(1.0 - 2.0 * b, -1.0, 1.0));
vec3 direction = vec3(cos(theta) * sin(phi), sin(theta) * sin(phi), cos(phi));
atmo_scattering_s result = atmo_integrate(origin, direction, sundirection, true, MULTISCATTERING_SAMPLES, false, false);
const float spheresolidangle = 4.0 * ATMO_PI;
const float directions = float(SQRT_DIRECTIONS * SQRT_DIRECTIONS);
shared_luminance[index] = result.luminance * spheresolidangle / directions;
shared_multiscattering[index] = result.multiscattering_as_1 * spheresolidangle / directions;
barrier();
for (uint stride = 32U; stride > 0U; stride >>= 1)
{
if (index < stride)
{
shared_luminance[index] += shared_luminance[index + stride];
shared_multiscattering[index] += shared_multiscattering[index + stride];
}
barrier();
}
if (index != 0U)
return;
const float isotropicphase = 1.0 / spheresolidangle;
vec3 inscatteredluminance = shared_luminance[0] * isotropicphase;
vec3 multiscatteringas1 = shared_multiscattering[0] * isotropicphase;
// every further order of scattering is the previous one times multiscatteringas1
vec3 luminance = inscatteredluminance / max(vec3(1.0) - multiscatteringas1, vec3(1e-3));
imageStore(multiscattering_target, texel, vec4(luminance, 1.0));
}

View File

@@ -0,0 +1,42 @@
#version 460 core
#define STAGE_COMPUTE 1
// light of the sky seen by the observer in every direction, for the sky-view table (atmosphere_common.glsl), which the
// sky, the ambient light and the fog colour are read from. one invocation per texel. run every frame the sky is drawn with
// the atmosphere, after the transmittance and multiple scattering tables
#define LAYOUT_COMPUTE 1
#include "common.glsl"
#include "atmosphere_common.glsl"
layout(set = COMPUTE_SET, binding = 0) uniform sampler2D atmo_transmittance_lut;
layout(set = COMPUTE_SET, binding = 1) uniform sampler2D atmo_multiscattering_lut;
#define ATMO_MULTISCATTERING_LUT
#include "atmosphere_integrate.glsl"
layout(local_size_x = 8, local_size_y = 8) in;
layout(set = COMPUTE_SET, binding = 2, rgba16f) writeonly uniform image2D skyview_target;
const int SKYVIEW_SAMPLES = 32;
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
if (texel.x >= int(ATMOSPHERE_SKYVIEW_WIDTH) || texel.y >= int(ATMOSPHERE_SKYVIEW_HEIGHT))
return;
vec2 uv = (vec2(texel) + 0.5) / vec2(ATMOSPHERE_SKYVIEW_WIDTH, ATMOSPHERE_SKYVIEW_HEIGHT);
float radius = atmo_observer_radius;
float cosviewzenith;
float coslightview;
atmo_skyview_params(uv, radius, cosviewzenith, coslightview);
float cossunzenith = clamp(atmo_sun_direction.y, -1.0, 1.0);
vec3 sundirection = normalize(vec3(sqrt(max(1.0 - cossunzenith * cossunzenith, 0.0)), 0.0, cossunzenith));
float sinviewzenith = sqrt(max(1.0 - cosviewzenith * cosviewzenith, 0.0));
vec3 direction = vec3(sinviewzenith * coslightview, sinviewzenith * sqrt(max(1.0 - coslightview * coslightview, 0.0)), cosviewzenith);
vec3 origin = vec3(0.0, 0.0, radius);
atmo_scattering_s result = atmo_integrate(origin, direction, sundirection, false, SKYVIEW_SAMPLES, true, true);
imageStore(skyview_target, texel, vec4(result.luminance, 1.0));
}

View File

@@ -0,0 +1,43 @@
#version 460 core
#define STAGE_COMPUTE 1
// transmittance from a point in the atmosphere to its top, for the transmittance table (atmosphere_common.glsl).
// one invocation per texel. run when the parameters of the atmosphere change
#define LAYOUT_COMPUTE 1
#include "common.glsl"
#include "atmosphere_common.glsl"
layout(local_size_x = 8, local_size_y = 8) in;
layout(set = COMPUTE_SET, binding = 0, rgba16f) writeonly uniform image2D transmittance_target;
const int TRANSMITTANCE_SAMPLES = 40;
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
if (texel.x >= int(ATMOSPHERE_TRANSMITTANCE_WIDTH) || texel.y >= int(ATMOSPHERE_TRANSMITTANCE_HEIGHT))
return;
vec2 uv = (vec2(texel) + 0.5) / vec2(ATMOSPHERE_TRANSMITTANCE_WIDTH, ATMOSPHERE_TRANSMITTANCE_HEIGHT);
float radius;
float cosviewzenith;
atmo_transmittance_params(uv, radius, cosviewzenith);
vec3 origin = vec3(0.0, 0.0, radius);
vec3 direction = vec3(sqrt(max(1.0 - cosviewzenith * cosviewzenith, 0.0)), 0.0, cosviewzenith);
float tbottom = atmo_ray_sphere_nearest(origin, direction, atmo_bottom_radius);
float ttop = atmo_ray_sphere_nearest(origin, direction, atmo_top_radius);
float tmax = (tbottom > 0.0) ? ((ttop > 0.0) ? min(ttop, tbottom) : tbottom) : max(ttop, 0.0);
vec3 opticaldepth = vec3(0.0);
float dt = tmax / float(TRANSMITTANCE_SAMPLES);
for (int s = 0; s < TRANSMITTANCE_SAMPLES; ++s)
{
vec3 position = origin + (float(s) + 0.5) * dt * direction;
opticaldepth += atmo_sample_medium(length(position)).extinction * dt;
}
imageStore(transmittance_target, texel, vec4(exp(-opticaldepth), 1.0));
}

View File

@@ -0,0 +1,291 @@
// 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(set = PASS_SET, binding = CLOUD_SHAPE_TEX) uniform sampler3D cloud_shape_noise;
layout(set = PASS_SET, binding = CLOUD_DETAIL_TEX) uniform sampler3D cloud_detail_noise;
layout(set = PASS_SET, binding = CLOUD_COVERAGE_TEX) uniform sampler2D cloud_coverage_noise;
const float CLOUD_EARTH_RADIUS = 6360000.0;
// how much of the shape noise is left standing at the edge and in the core of a cloud area (cloud_layer_density)
const float CLOUD_FILL_EDGE = 0.45;
const float CLOUD_FILL_CORE = 0.85;
// how fast the density rises inside the edge of a cloud: above 1 the edges are crisper, the inside keeps its density
const float CLOUD_EDGE_SHARPNESS = 3.0;
// how far the base and the top of the single clouds stand off those of their layer, up or down, as shares of the depth of the layer
// and at most in metres: the clouds of a layer don't all end at one height, some reach a little out of it
// how high the base of a cloud curves up at its edge and how far into the cloud area (cloud_coverage: y) it does, and how much higher
// the base of a billow is between the cells of the shape noise than in their middles; shares of the height of the cloud
const float CLOUD_BASE_ROUND = 0.35;
const float CLOUD_BASE_ROUND_DEPTH = 0.3;
const float CLOUD_BASE_LUMPS = 0.08;
const float CLOUD_BASE_SHIFT = 0.07;
const float CLOUD_BASE_SHIFT_MAX = 100.0;
const float CLOUD_TOP_SHIFT = 0.15;
const float CLOUD_TOP_SHIFT_MAX = 400.0;
// the detail noise: its tile against the tile of the shape noise and how deep it eats into the edges (times the erosion of the
// genus and the tuning of the debug window, cloud_wind.w); the finer octave of the thin edges: its tile against the detail tile,
// how far the coarser octave pushes its lookup about (in its tiles), and the density below which it is used
const float CLOUD_DETAIL_SCALE = 6.0;
const float CLOUD_EROSION = 2.0;
const float CLOUD_FINE_SCALE = 3.7;
const float CLOUD_FINE_PUSH = 0.6;
const float CLOUD_FINE_BELOW = 0.6;
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));
}
// how far the base (x) and the top (y) of the clouds of a layer may stand off those of the layer, metres
vec2 cloud_layer_shift(cloud_layer_s layer)
{
float thickness = layer.heights.y - layer.heights.x;
return vec2(min(thickness * CLOUD_BASE_SHIFT, CLOUD_BASE_SHIFT_MAX), min(thickness * CLOUD_TOP_SHIFT, CLOUD_TOP_SHIFT_MAX));
}
// the lowest base (x) and the highest top (y) the clouds of a layer may reach
vec2 cloud_layer_bounds(cloud_layer_s layer)
{
vec2 shift = cloud_layer_shift(layer);
return vec2(layer.heights.x - shift.x, layer.heights.y + shift.y);
}
// 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). Shift: how
// far the base (x) and the top (y) of the cloud there stand off those of the layer, from the second and the third noise of the
// coverage texture, so the clouds which reach lower don't all reach higher too
vec2 cloud_coverage(cloud_layer_s layer, vec2 offset, out vec2 shift)
{
vec2 coords = (layer.extra.zw + cloud_noise_plane(layer, offset)) * layer.motion.z;
vec3 noise = textureLod(cloud_coverage_noise, coords + layer.extra.y, 0.0).rgb;
shift = (noise.yz * 2.0 - 1.0) * cloud_layer_shift(layer);
float coverage = layer.shape.x;
float threshold = 1.0 - coverage;
float mask = smoothstep(threshold - 0.06, threshold + 0.06, noise.x);
float depth = clamp((noise.x - threshold) / max(coverage, 0.05), 0.0, 1.0);
return vec2(mask, depth);
}
vec2 cloud_coverage(cloud_layer_s layer, vec2 offset)
{
vec2 shift;
return cloud_coverage(layer, offset, shift);
}
// 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];
vec2 bounds = cloud_layer_bounds(layer);
if (height <= bounds.x || height >= bounds.y)
return 0.0;
vec2 shift;
vec2 coverage = cloud_coverage(layer, position.xz, shift);
float bottom = layer.heights.x + shift.x;
float h = (height - bottom) / max(layer.heights.y + shift.y - bottom, 1.0);
if (h <= 0.0 || h >= 1.0)
return 0.0;
// 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 fall = 1.0 - smoothstep(top * layer.profile.y, top, h);
if (fall <= 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);
// the smaller cells of the worley octaves bite billows into the heaps of the perlin-worley noise; the octaves keep to a narrow
// band (clouds_noise.comp: WORLEY_FBM_LOW, WORLEY_FBM_HIGH), spread over 0-1 first
float worley = clamp((shape.g * 0.625 + shape.b * 0.25 + shape.a * 0.125 - 0.30) / 0.35, 0.0, 1.0);
// the base is not cut flat: it curves up towards the edge of the cloud into its sides, and every billow starts a little higher or
// lower; both as shares of the height of the cloud, so the small ones are rounded too and not cut away. the sheets (little
// erosion) keep a flatter base
float lift = (CLOUD_BASE_ROUND * (1.0 - smoothstep(0.0, CLOUD_BASE_ROUND_DEPTH, coverage.y)) + CLOUD_BASE_LUMPS * (1.0 - worley)) * top * clamp(layer.shape.w * 2.5, 0.0, 1.0);
float gradient = smoothstep(lift, lift + layer.profile.x, h) * fall;
if (gradient <= 0.0)
return 0.0;
float base = clamp(cloud_remap(shape.r, (1.0 - worley) * 0.35, 1.0, 0.0, 1.0), 0.0, 1.0);
// the sheets (little erosion) keep few holes, the heaps are made of lumps
base = mix(1.0, base, clamp(layer.shape.w * 2.5, 0.25, 1.0));
// inside its area a cloud is not a solid block: the lumps of the shape noise are cut out of it, the more the nearer its edge,
// so it breaks up into single heaps with rounded tops and gaps between them. the sheets keep their base above the cut, and a
// layer closing up (broken to overcast) keeps its lumps whole, so it has no more gaps than its coverage
float fill = coverage.x * mix(mix(CLOUD_FILL_EDGE, CLOUD_FILL_CORE, coverage.y), 1.0, smoothstep(0.5, 1.0, layer.shape.x));
float density = clamp(cloud_remap(base * gradient, 1.0 - fill, 1.0, 0.0, 1.0), 0.0, 1.0) * coverage.x;
if (detail && density > 0.0 && layer.shape.w > 0.0)
{
vec3 detailcoords = coords * CLOUD_DETAIL_SCALE;
vec4 small = textureLod(cloud_detail_noise, detailcoords, 0.0);
// spread over 0-1 as the worley octaves of the shape; left in their narrow band they would only thin the cloud evenly
float fbm = clamp((small.r * 0.625 + small.g * 0.25 + small.b * 0.125 - 0.30) / 0.35, 0.0, 1.0);
// the thin outer part of a cloud gets a finer octave too, its lookup pushed about by the coarser one, so the edges are frayed
// into small billows and torn bits rather than evenly rounded. the dense inside is never eaten into, so it is skipped there
float fineshare = 1.0 - smoothstep(CLOUD_FINE_BELOW * 0.5, CLOUD_FINE_BELOW, density);
if (fineshare > 0.0)
{
vec3 push = (small.gbr - 0.5) * CLOUD_FINE_PUSH;
vec4 fine = textureLod(cloud_detail_noise, detailcoords * CLOUD_FINE_SCALE + push, 0.0);
float finefbm = clamp((fine.r * 0.625 + fine.g * 0.25 + fine.b * 0.125 - 0.30) / 0.35, 0.0, 1.0);
fbm = clamp(mix(fbm, fbm * 0.65 + finefbm * 0.35, fineshare), 0.0, 1.0);
}
// wispy at the base, billowing at the top
float erosion = mix(fbm, 1.0 - fbm, clamp(h * 4.0, 0.0, 1.0));
density = clamp(cloud_remap(density, erosion * layer.shape.w * CLOUD_EROSION * cloud_wind.w, 1.0, 0.0, 1.0), 0.0, 1.0);
}
return min(density * CLOUD_EDGE_SHARPNESS, 1.0) * 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;
}
vec2 bounds = cloud_layer_bounds(layer);
float enter = max(bounds.x - height, 0.0) / sun.y;
float leave = (bounds.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);
}

View File

@@ -0,0 +1,295 @@
#version 460 core
#define STAGE_COMPUTE 1
// 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
#define LAYOUT_COMPUTE 1
#include "common.glsl"
#include "clouds_common.glsl"
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
layout(rgba16f, set = COMPUTE_SET, binding = 0) uniform writeonly image2D clouds_color;
layout(r32f, set = COMPUTE_SET, binding = 1) uniform writeonly image2D clouds_depth;
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
int mode;
ivec2 size;
// mode 0: clip space to view space of the camera, and view space to world axes
mat4 inverse_projection;
mat4 view_to_world;
int frame;
// x: samples through a layer at most, y: samples towards the sun, z: farthest distance (m), w: 1 with the detail noise
vec4 quality;
// mode 1: first row of the band and the rows in it
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),
// times the tuning of the debug window (weather_sun_color.w, weather_ambient.w). the sunlight three times that of the sunlit faces,
// set by eye in the simulator: the sunlit sides of real cumulus are several times brighter than the sky around them. the sky light
// about as the shaded faces get it: more of it greys the shaded sides of the clouds into a flat pastel
const float CLOUD_SUN_SCALE = 4.5;
const float CLOUD_AMBIENT_SCALE = 0.6;
// the ground under the clouds lights their bases back with this share of the light falling on it
const float CLOUD_GROUND_ALBEDO = 0.2;
// how fast the light of the sky fades into a cloud, per optical depth of the cloud below a point, or above it past CLOUD_SKY_REACH
const float CLOUD_AMBIENT_FADE = 0.05;
// the part of the cloud over a point looked at for the sky light held back (cloud_sky_depth), metres, how fast the light fades
// per optical depth there (less than the extinction: the light scattered many times gets through further), and the share left
// in the darkest places
const float CLOUD_SKY_REACH = 400.0;
const float CLOUD_SKY_FADE = 0.25;
const float CLOUD_SKY_FLOOR = 0.15;
// how dense a cloud has to be to scatter the sunlight back at full: the thin edges and wisps of the side lit by the sun, seen with the
// sun behind, are darker than its bulk (the "powder" effect: the light takes a few scatterings to turn back towards the view)
const float CLOUD_POWDER = 6.0;
// how many of the nearest steps towards the sun see the detail noise
const int CLOUD_LIGHT_DETAIL_STEPS = 1;
// orders of the scattering the sunlight is followed through (cloud_sunlight): the higher ones reach deep into a thick cloud, so its
// shaded sides and its inside are lit through the cloud too, not only by the sky, and a big cloud close by is not a flat grey
const int CLOUD_SCATTER_OCTAVES = 6;
// how much longer every next step towards the sun is than the one before: 1 + this / steps², so the first step is short where the
// light falls off fastest (about a 30th of the reach at 4 steps) and the steps get close to even when there are many of them
const float CLOUD_LIGHT_GROWTH = 24.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);
}
// the light of the sky a face looking straight up (Up 1) or down (-1) gets: the uniform ambient light and the directional part of
// the sky light (sky_ssbo, light_common.glsl: sky_ambient_directional) along the vertical. with the physical atmosphere it is bluer
// and brighter from above than from the sides, so the shaded sides of the clouds take the colour of the sky above them
vec3 cloud_sky_light(float up)
{
vec3 directional = sky_ambient_sh[1].rgb * (0.488603 * up) - sky_ambient_sh[6].rgb * 0.315392 - sky_ambient_sh[8].rgb * 0.546274;
return max(weather_ambient.rgb + directional * 1.3, vec3(0.0)) * (CLOUD_AMBIENT_SCALE * weather_ambient.w);
}
// light of the sun reaching a point inside a layer through the cloud towards it, with the light scattered many times
// (octaves after Wrenninge), and the phase of the view
float cloud_sunlight(int index, vec3 position, float cosangle, int steps, float powder, bool detail)
{
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;
// steps growing geometrically: near the point the shape matters, far only the bulk. with steps growing evenly the first one was a
// tenth of the reach at 4 steps, and the light fell off the smooth sides of the clouds in visible bands
float growth = 1.0 + CLOUD_LIGHT_GROWTH / float(steps * steps);
float step = reach * (growth - 1.0) / (pow(growth, float(steps)) - 1.0);
for (int s = 0; s < steps; s++)
{
vec3 point = position + sun * (travelled + 0.5 * step);
travelled += step;
// the nearest steps with the detail noise: the small billows shade one another, lit on top and grey beneath
depth += cloud_layer_density(index, point, cloud_height(point), detail && (s < CLOUD_LIGHT_DETAIL_STEPS)) * step;
step *= growth;
}
float light = 0.0;
float amount = 1.0;
float fade = 1.0;
float spread = 1.0;
for (int octave = 0; octave < CLOUD_SCATTER_OCTAVES; octave++)
{
light += amount * exp(-depth * fade) * cloud_phase(cosangle, spread) * (octave == 0 ? powder : 1.0);
amount *= 0.5;
fade *= 0.5;
spread *= 0.5;
}
return light;
}
// optical depth of the cloud over a point up to Reach above it, from Samples of the coarse density: under a lump and deep in a
// thick cloud the sky light is held back, at the tops and the thin edges it is not, so the shaded sides take the form of the cloud
float cloud_sky_depth(int index, vec3 position, float height, float reach, int samples)
{
float depth = 0.0;
for (int s = 0; s < samples; s++)
{
// up along the vertical: the height grows by the same distance (cloud_height bends only with the horizontal distance)
float up = reach * (float(s) + 0.5) / float(samples);
depth += cloud_layer_density(index, position + vec3(0.0, up, 0.0), height + up, false);
}
return depth * reach / float(samples);
}
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);
int skysteps = (lightsteps >= 4) ? 2 : 1;
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 bounds = cloud_layer_bounds(cloud_layers[i]);
vec2 segment = cloud_layer_segment(direction.y, bounds.x, bounds.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 = cloud_sky_light(1.0);
vec3 sunlight = weather_sun_color.rgb * (CLOUD_SUN_SCALE * weather_sun_color.w) * sunup;
// the bases are lit from below by the ground, which the sun lights through the gaps between the clouds
vec3 ambientbottom = (weather_sun_color.rgb * max(weather_sun_direction.y, 0.0) * sunup * fog_light.y + ambienttop) * CLOUD_GROUND_ALBEDO;
// seen against the sun the thin edges glow (the forward scattering); seen with the sun behind, they are dark
float powderview = clamp(1.0 - cosangle, 0.0, 1.0);
for (int k = 0; k < count && transmittance > 0.01; k++)
{
int index = order[k];
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 powder = mix(1.0, 1.0 - exp(-density / max(layer.shape.y, 1e-6) * CLOUD_POWDER), powderview);
float sunshare = (sunup > 0.0) ? cloud_sunlight(index, position, cosangle, lightsteps, powder, detail) : 0.0;
// the light of the sky reaches inside through the cloud above, that of the ground through the cloud below: the base of a
// thick cloud is darker than its top, and greyer, lit only by the ground and the sky at its sides
float column = layer.shape.y * layer.shape.x;
float top = cloud_layer_bounds(layer).y;
float reach = clamp(top - height, 0.0, CLOUD_SKY_REACH);
float sky = exp(-CLOUD_SKY_FADE * cloud_sky_depth(index, position, height, reach, skysteps) - CLOUD_AMBIENT_FADE * column * max(top - height - reach, 0.0));
float below = column * max(height - layer.heights.x, 0.0);
vec3 light = sunlight * sunshare + (ambienttop * mix(CLOUD_SKY_FLOOR, 1.0, sky) + ambientbottom * exp(-CLOUD_AMBIENT_FADE * below));
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));
}

View File

@@ -0,0 +1,141 @@
#version 460 core
#define STAGE_COMPUTE 1
// 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 b three perlin fbm, spread evenly over 0-1 so a threshold of 1 - c leaves a share c above it
#define LAYOUT_COMPUTE 1
#include "common.glsl"
#include "random.glsl"
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
layout(rgba8, set = COMPUTE_SET, binding = 0) uniform writeonly image3D noise_volume;
layout(rgba8, set = COMPUTE_SET, binding = 1) uniform writeonly image2D noise_plane;
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
int kind;
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;
}
// octaves of the coverage noise: it only places the clouds and gives their outline. it is the same at every height, so its finer
// octaves would stand as upright grooves down the sides of the clouds; their small shapes come from the 3d noises
const int COVERAGE_OCTAVES = 4;
// the worley fbm keeps about to this range (5th and 95th percentile 0.35 and 0.61)
const float WORLEY_FBM_LOW = 0.30;
const float WORLEY_FBM_HIGH = 0.65;
float remap(float value, float low, float high, float newlow, float newhigh)
{
return newlow + (value - low) / (high - low) * (newhigh - newlow);
}
// 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, COVERAGE_OCTAVES, 11U);
float second = perlin_fbm(position + vec3(0.0, 0.0, 0.5), 8, 5, 23U);
float third = perlin_fbm(position + vec3(0.0, 0.0, 0.25), 8, 5, 29U);
imageStore(noise_plane, texel.xy, vec4(even(first, 0.22), even(second, 0.22), even(third, 0.22), 1.0));
return;
}
if (texel.z >= size)
return;
vec3 position = (vec3(texel) + 0.5) / float(size);
if (kind == 0)
{
// both spread over 0-1 first: as sums of octaves they keep to a narrow band about the middle, and a cloud made of them
// would be an even block with no lumps. the cells then cut the perlin noise into rounded heaps: whole in their middles,
// only the highest perlin left between them
float low = even(perlin_fbm(position, 4, 4, 3U), 0.22);
float cells = worley_fbm(position, 4, 5U);
float spreadcells = clamp((cells - WORLEY_FBM_LOW) / (WORLEY_FBM_HIGH - WORLEY_FBM_LOW), 0.0, 1.0);
float perlinworley = clamp(remap(low, (1.0 - spreadcells) * 0.6, 1.0, 0.0, 1.0), 0.0, 1.0);
imageStore(noise_volume, texel, vec4(perlinworley, cells, worley_fbm(position, 8, 7U), worley_fbm(position, 16, 9U)));
}
else
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,34 @@
#version 460 core
#define STAGE_COMPUTE 1
// 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
#define LAYOUT_COMPUTE 1
#include "common.glsl"
#include "clouds_common.glsl"
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
layout(r16f, set = COMPUTE_SET, binding = 0) uniform writeonly image2D shadow;
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
int size;
// world x and z of the centre of the map relative to the camera, size of the map (metres)
vec3 placement;
// samples through a layer
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,72 @@
#version 460 core
#define STAGE_COMPUTE 1
// 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
#define LAYOUT_COMPUTE 1
#include "common.glsl"
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
layout(set = COMPUTE_SET, binding = 0) uniform sampler2D current_color;
layout(set = COMPUTE_SET, binding = 1) uniform sampler2D current_depth;
layout(set = COMPUTE_SET, binding = 2) uniform sampler2D history_color;
layout(rgba16f, set = COMPUTE_SET, binding = 3) uniform writeonly image2D result;
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
ivec2 size;
mat4 inverse_projection;
mat4 view_to_world;
// world axes to the clip space of the previous frame, the camera of the previous frame at the origin
mat4 previous_projection_view;
// where the camera was in the previous frame, relative to where it is (world, metres)
vec3 previous_camera;
// share of this frame in the result; 1 when there is no history
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));
}

View File

@@ -0,0 +1,48 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
// debug view of the light clusters: tints every pixel of the scene with the colour of the number of lights which
// were assigned to its cluster (see gl/cluster_grid.h), pixels of clusters without lights stay as they are.
// drawn over the output of the viewport, with the layout of the scene (the clusters of the pass set; the depth as the texture of
// a material)
layout(location = 14) in vec2 f_coords;
#include "common.glsl"
#pragma texture(depth_tex, 0, R)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D depth_tex;
layout(location = 0) out vec4 out_color;
const float TINT = 0.55;
// blue (few) through green to red (many)
vec3 ramp(float t)
{
return clamp(vec3(1.5) - abs(4.0 * t - vec3(3.0, 2.0, 1.0)), 0.0, 1.0);
}
void main()
{
// reverse z: 0 is the far plane at infinity, i.e. the sky, which has no cluster
float depth = texelFetch(depth_tex, ivec2(f_coords * vec2(textureSize(depth_tex, 0))), 0).r;
if (depth <= 0.0)
discard;
// depth is znear / w
float w = cluster_znear / depth;
int slice = clamp(int(floor(log(w) * cluster_slice_scale + cluster_slice_bias)), 0, CLUSTER_GRID_Z - 1);
ivec2 tile = clamp(ivec2(floor(f_coords * vec2(CLUSTER_GRID_X, CLUSTER_GRID_Y))), ivec2(0), ivec2(CLUSTER_GRID_X - 1, CLUSTER_GRID_Y - 1));
uint count = cluster_ranges[tile.x + CLUSTER_GRID_X * (tile.y + CLUSTER_GRID_Y * slice)].count;
if (count == 0U)
discard;
// a full cluster (lights beyond its capacity are dropped) is white; the rest goes by the logarithm of the count
vec3 color = (count >= uint(MAX_LIGHTS_PER_CLUSTER))
? vec3(1.0)
: ramp(log2(1.0 + float(count)) / log2(1.0 + float(MAX_LIGHTS_PER_CLUSTER)));
out_color = vec4(color, TINT);
}

View File

@@ -6,10 +6,14 @@
//
// the pipeline layout is picked by a define before the include of this file:
// (none) the passes of the scene (materials, shadows, picking, lines, sprites, sky, particles): set PASS_SET with
// the blocks of the scene, of the draws, of the instances, the lights, the clusters and the shadow,
// environment and headlight maps; set MATERIAL_SET with the textures of the material
// LAYOUT_POST the post-processing and the copies of whole images: set POST_SET pushed with the images read, the
// parameters in the push constants (param stands for pc.data)
// the blocks of the scene, of the draws, of the instances, the lights, the clusters, the shadow,
// environment and headlight maps, the light of the sky, the weather, the shadows of the lamps and the depth
// of the scene; set MATERIAL_SET with the textures of the material
// LAYOUT_COMPUTE the compute kernels (vk14::compute_kernel): set PASS_SET as above (the block of the scene, the lights, the
// clusters, the light of the sky; the weather and the atmosphere with their includes), set COMPUTE_SET
// pushed with the bindings the kernel declares from 0 on, its parameters in push constants of its own
// LAYOUT_POST the post-processing and the copies of whole images: set POST_SET pushed with the images read and the
// exposure, the parameters in the push constants (param stands for pc.data)
// LAYOUT_CLUSTER the compute passes of the light clusters: set 0 pushed at the CL_* bindings
// LAYOUT_UI the user interface: none of the above, its own push constants
//
@@ -40,7 +44,22 @@
#define SHADOW_TEX 7
#define ENV_TEX 8
#define HEADLIGHT_TEX 9
#define PASS_BINDING_COUNT 10
#define SKY_SSBO 10
#define ATMOSPHERE_UBO 11
#define SKY_VIEW_TEX 12
#define ENV_GGX_TEX 13
#define WEATHER_UBO 14
#define WEATHER_HORIZON_UBO 15
#define FOG_VOLUME_TEX 16
#define CLOUD_SHADOW_TEX 17
#define CLOUD_SHAPE_TEX 18
#define CLOUD_DETAIL_TEX 19
#define CLOUD_COVERAGE_TEX 20
#define LOCAL_SHADOW_TEX 21
#define LOCAL_SHADOW_LAMPS_TEX 22
#define HEADLIGHT_SHADOW_TEX 23
#define SCENE_DEPTH_TEX 24
#define PASS_BINDING_COUNT 25
#define MATERIAL_TEXTURE_FIRST 0
#define CL_CLUSTER_UBO 0
#define CL_AABB_SSBO 1
@@ -50,6 +69,9 @@
#define CL_LIGHT_SSBO 5
#define POST_SET 0
#define POST_TEXTURE_FIRST 0
#define POST_TEXTURES 4
#define POST_EXPOSURE_SSBO 4
#define COMPUTE_SET 1
#define PUSH_CONSTANTS_SIZE 112
#define SPEC_OUTPUT_GAMMA 0
#define SPEC_SHADOWMAP_ENABLED 1
@@ -69,11 +91,35 @@
#define MAX_LIGHTS_PER_CLUSTER 64
#define CLUSTER_BUILD_GROUP_SIZE 64
#define CLUSTER_CULL_GROUP_SIZE 64
#define ENVMAP_GGX_LEVELS 7U
#define ENVMAP_GGX_ROUGHNESS_MIN 0.200000
#define ENVMAP_GGX_GROUP_SIZE 8
#define EXPOSURE_HISTOGRAM_BINS 256
#define EXPOSURE_HISTOGRAM_GROUP_SIZE 16
#define ATMOSPHERE_TRANSMITTANCE_WIDTH 256
#define ATMOSPHERE_TRANSMITTANCE_HEIGHT 64
#define ATMOSPHERE_MULTISCATTERING_SIZE 32
#define ATMOSPHERE_SKYVIEW_WIDTH 192
#define ATMOSPHERE_SKYVIEW_HEIGHT 108
#define ATMOSPHERE_AMBIENT_GROUP_SIZE 256
#define MAX_CLOUD_LAYERS 4
#define FOG_HORIZON_SAMPLES 32
#define WEATHER_GROUP_SIZE 8
#define FOG_VOLUME_BIAS 10.000000
#define INSTANCE_CULL_GROUP_SIZE 64
#define INSTANCE_OFFSETS_GROUP_SIZE 1024
#define INSTANCE_COMMAND_GROUP_SIZE 64
#define INSTANCE_COUNTER_HEADER 8
#define INSTANCE_JOB_SLEEPERS 1U
#define HIZ_GROUP_SIZE 8
#define SHADOW_FIT_GROUP_SIZE 16
#define LOCAL_SHADOW_NEAR 0.150000
#define PARTICLE_GROUP_SIZE 64
#endif
#include "settings.glsl"
#ifndef LAYOUT_UI
#if !defined(LAYOUT_UI) && !defined(LAYOUT_COMPUTE)
// vk14::draw_constants, PUSH_CONSTANTS_SIZE bytes, visible in every stage. draw: the element of model_draws[] the draw reads
// (the passes of the scene); flags and data: the parameters of the special shaders, each says what it takes from them
layout(push_constant) uniform draw_constants_block
@@ -171,8 +217,9 @@ layout(std430, set = 0, binding = CL_LIGHT_SSBO) readonly buffer light_ssbo
uint lights_count;
// 1 if the shading takes the lights from the list of the cluster of a fragment, see cluster_ubo
uint lights_clustered;
// not used by the shaders of this renderer yet (the sky ambient light and the prefiltered environment map come later)
// scale of the directional part of the sky ambient light, see sky_ssbo
float sky_ambient_factor;
// 1 if rough reflections come from the prefiltered environment map
uint env_ggx;
light_s lights[];
@@ -183,6 +230,16 @@ layout(std430, set = 0, binding = CL_LIGHT_SSBO) readonly buffer light_ssbo
// the post-processing reads the parameters the opengl renderer gives it in the model block from the push constants
#define param pc.data
// exposure worked out by exposure_average.comp, gl::exposure_data
layout(std430, set = POST_SET, binding = POST_EXPOSURE_SSBO) readonly buffer exposure_ssbo
{
uint histogram[EXPOSURE_HISTOGRAM_BINS];
float adapted_luminance;
float exposure;
float average_luminance;
uint valid;
};
#elif !defined(LAYOUT_UI)
// the block of the scene, gl::scene_ubs (every member kept, the layout is the one of the c++ struct)
@@ -204,7 +261,8 @@ layout(std140, set = PASS_SET, binding = SCENE_UBO) uniform scene_ubo
vec4 shadow_fit;
};
// the model block of a draw, gl::model_ubs (256 bytes), the one of the draw picked by pc.draw. the names below stand for its
#ifndef LAYOUT_COMPUTE
// the model block of a draw, gl::model_ubs (256 bytes), the one of the draw picked by pc.draw (MODEL_DRAW). the names below stand for its
// fields, so the shaders read them as the uniform block of the opengl renderer
struct model_draw_s
{
@@ -225,15 +283,32 @@ layout(std430, set = PASS_SET, binding = DRAW_SSBO) readonly buffer model_draw_s
model_draw_s model_draws[];
};
#define modelview model_draws[pc.draw].modelview
#define modelviewnormal model_draws[pc.draw].modelviewnormal
#define param model_draws[pc.draw].param
#define future model_draws[pc.draw].future
#define opacity model_draws[pc.draw].opacity
#define emission model_draws[pc.draw].emission
#define fog_density model_draws[pc.draw].fog_density
#define alpha_mult model_draws[pc.draw].alpha_mult
#define shadow_tone model_draws[pc.draw].shadow_tone
#ifdef MODEL_DRAWS
// programs of the multi draws of the instances drawn from the gpu culling: the blocks of the draws of a multi draw follow one
// another from the one of pc.draw, picked by the number of the draw in it. the fragment stage gets the block from the vertex
// stage (which writes f_model_draw)
#if defined(STAGE_VERTEX)
layout(location = 11) flat out uint f_model_draw;
#define MODEL_DRAW (pc.draw + uint(gl_DrawID))
#elif defined(STAGE_FRAGMENT)
layout(location = 11) flat in uint f_model_draw;
#define MODEL_DRAW f_model_draw
#else
#define MODEL_DRAW pc.draw
#endif
#else
#define MODEL_DRAW pc.draw
#endif
#define modelview model_draws[MODEL_DRAW].modelview
#define modelviewnormal model_draws[MODEL_DRAW].modelviewnormal
#define param model_draws[MODEL_DRAW].param
#define future model_draws[MODEL_DRAW].future
#define opacity model_draws[MODEL_DRAW].opacity
#define emission model_draws[MODEL_DRAW].emission
#define fog_density model_draws[MODEL_DRAW].fog_density
#define alpha_mult model_draws[MODEL_DRAW].alpha_mult
#define shadow_tone model_draws[MODEL_DRAW].shadow_tone
// per-instance modelview matrices of the instanced draws, element 0 the identity. a draw which isn't instanced is drawn with
// firstInstance 0, an instanced one from its base instance on, with modelview set to the submodel-local chain (computed from
@@ -242,6 +317,7 @@ layout(std430, set = PASS_SET, binding = INSTANCE_SSBO) readonly buffer instance
{
mat4 instance_modelview[];
};
#endif
// the light buffer of the pass, gl::light_data
layout(std430, set = PASS_SET, binding = LIGHT_SSBO) readonly buffer light_ssbo
@@ -255,13 +331,21 @@ layout(std430, set = PASS_SET, binding = LIGHT_SSBO) readonly buffer light_ssbo
uint lights_count;
// 1 if the shading takes the lights from the list of the cluster of a fragment, see cluster_ubo
uint lights_clustered;
// not used by the shaders of this renderer yet (the sky ambient light and the prefiltered environment map come later)
// scale of the directional part of the sky ambient light, see sky_ssbo
float sky_ambient_factor;
// 1 if rough reflections come from the prefiltered environment map
uint env_ggx;
light_s lights[];
};
// directional part of the sky ambient light: real spherical harmonics (bands 1 and 2, band 0 is ambient) of the light
// a surface facing a world space direction gets, in units of ambient. see gl::sky_data
layout(std430, set = PASS_SET, binding = SKY_SSBO) readonly buffer sky_ssbo
{
vec4 sky_ambient_sh[9];
};
// the grid of the light clusters of the pass, gl::cluster_ubs; the compute passes fill the lists, shading only reads them
layout(std140, set = PASS_SET, binding = CLUSTER_UBO) uniform cluster_ubo
{

View File

@@ -0,0 +1,10 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
// depth prepass of materials which never discard a fragment (no alpha test)
layout(location = 2) in vec2 f_coord;
void main()
{
}

View File

@@ -0,0 +1,26 @@
#version 460 core
#define STAGE_VERTEX 1
#include "common.glsl"
// depth prepass of the instances drawn from the gpu culling (opengl46_renderer::Render_Instances_Indirect()).
// the position is worked out the way vertex.vert of the materials does it, and declared invariant in both, so the colour
// pass which follows gets the same depth for the same surface
layout(location = 0) in vec3 v_vert;
layout(location = 2) in vec2 v_coord;
invariant gl_Position;
layout(location = 2) out vec2 f_coord;
void main()
{
mat4 mv = effective_modelview();
vec4 position = mv * vec4(v_vert, 1.0);
gl_Position = projection * position;
f_coord = v_coord;
#ifdef MODEL_DRAWS
// which block of the multi draw the fragments read (common.glsl)
f_model_draw = MODEL_DRAW;
#endif
}

View File

@@ -0,0 +1,18 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
// depth prepass of materials whose colour shader tests the alpha of the diffuse texture against the opacity of the
// material (mat_default.frag and the like). the test here is stricter by one step of 8-bit alpha: a fragment which writes
// depth here is never discarded by the colour pass, which would leave a hole with nothing drawn behind it
layout(location = 2) in vec2 f_coord;
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
void main()
{
if (texture(diffuse, f_coord).a < opacity + 1.0 / 255.0)
discard;
}

View File

@@ -0,0 +1,107 @@
#version 460 core
#define STAGE_COMPUTE 1
// prefilters the environment map with the GGX lobe for rough reflections (split sum approximation, Karis 2013), one level
// of the prefiltered map per dispatch. filtered importance sampling (Krivanek, Colbert 2008): each sample reads the mipmap
// of the source whose texels are about as large as the solid angle the sample stands for, so a few dozen samples give a
// smooth result. run when an update of the environment map is complete
#define LAYOUT_COMPUTE 1
#include "common.glsl"
layout(local_size_x = ENVMAP_GGX_GROUP_SIZE, local_size_y = ENVMAP_GGX_GROUP_SIZE) in;
layout(set = COMPUTE_SET, binding = 0, rgba16f) writeonly uniform imageCube prefilter_target;
// the plain map with its box filtered mipmaps, on the unit gl::program binds envmap to
layout(set = COMPUTE_SET, binding = 1) uniform samplerCube envmap;
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
// perceptual roughness of the level being written
float roughness;
// size of the level being written, and of the first level of the source
int target_size;
float source_size;
int sample_count;
};
const float PI = 3.14159265359;
// direction through the centre of a texel of a cube map face, in the order and orientation of GL_TEXTURE_CUBE_MAP_POSITIVE_X..
vec3 cube_direction(ivec3 texel, int size)
{
vec2 uv = (vec2(texel.xy) + 0.5) / float(size) * 2.0 - 1.0;
switch (texel.z)
{
case 0: return vec3(1.0, -uv.y, -uv.x);
case 1: return vec3(-1.0, -uv.y, uv.x);
case 2: return vec3(uv.x, 1.0, uv.y);
case 3: return vec3(uv.x, -1.0, -uv.y);
case 4: return vec3(uv.x, -uv.y, 1.0);
default: return vec3(-uv.x, -uv.y, -1.0);
}
}
vec2 hammersley(uint i, uint n)
{
uint bits = bitfieldReverse(i);
return vec2(float(i) / float(n), float(bits) * 2.3283064365386963e-10);
}
// half vector around N for the GGX distribution of linear roughness a
vec3 importance_sample_ggx(vec2 xi, float a, vec3 N)
{
float phi = 2.0 * PI * xi.x;
float costheta = sqrt((1.0 - xi.y) / (1.0 + (a * a - 1.0) * xi.y));
float sintheta = sqrt(max(1.0 - costheta * costheta, 0.0));
vec3 H = vec3(sintheta * cos(phi), sintheta * sin(phi), costheta);
vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
vec3 tangentx = normalize(cross(up, N));
vec3 tangenty = cross(N, tangentx);
return tangentx * H.x + tangenty * H.y + N * H.z;
}
float distribution_ggx(float NdotH, float a)
{
float a2 = a * a;
float d = NdotH * NdotH * (a2 - 1.0) + 1.0;
return a2 / (PI * d * d);
}
void main()
{
ivec3 texel = ivec3(gl_GlobalInvocationID);
if (texel.x >= target_size || texel.y >= target_size)
return;
vec3 N = normalize(cube_direction(texel, target_size));
// the view is taken along the normal, which is what the split sum approximation assumes
vec3 V = N;
float a = max(roughness * roughness, 1e-3);
float texelsolidangle = 4.0 * PI / (6.0 * source_size * source_size);
vec3 sum = vec3(0.0);
float weight = 0.0;
uint count = uint(sample_count);
for (uint i = 0U; i < count; ++i)
{
vec3 H = importance_sample_ggx(hammersley(i, count), a, N);
float VdotH = dot(V, H);
vec3 L = 2.0 * VdotH * H - V;
float NdotL = dot(N, L);
if (NdotL <= 0.0)
continue;
float NdotH = max(dot(N, H), 0.0);
// pdf of L: D * NdotH / (4 * VdotH)
float pdf = distribution_ggx(NdotH, a) * NdotH / max(4.0 * VdotH, 1e-4);
float samplesolidangle = 1.0 / (float(count) * max(pdf, 1e-6));
float level = max(0.5 * log2(samplesolidangle / texelsolidangle) + 1.0, 0.0);
vec3 color = textureLod(envmap, L, level).rgb;
if (any(isnan(color)) || any(isinf(color)))
color = vec3(0.0);
sum += max(color, vec3(0.0)) * NdotL;
weight += NdotL;
}
imageStore(prefilter_target, texel, vec4(sum / max(weight, 1e-4), 1.0));
}

View File

@@ -1,4 +1,6 @@
layout(set = PASS_SET, binding = ENV_TEX) uniform samplerCube envmap;
// the same map prefiltered with the GGX lobe (envmap_prefilter.comp), used when env_ggx is 1
layout(set = PASS_SET, binding = ENV_GGX_TEX) uniform samplerCube envmap_ggx;
vec3 envmap_color( vec3 normal )
{
@@ -45,10 +47,27 @@ vec3 envmap_color_lod(vec3 fragnormal, float lod)
}
}
// reflection of the surroundings in a surface of the given perceptual roughness (0 mirror, 1 matte): the plain map, its mipmaps
// (box filtered) standing for the roughness. the prefiltered GGX map of the opengl renderer (env_ggx) comes in a later stage
// reflection of the surroundings in a surface of the given perceptual roughness (0 mirror, 1 matte)
vec3 envmap_color_rough(vec3 fragnormal, float roughness)
{
// the plain map, its mipmaps (box filtered) standing for the roughness
const float MAX_REFLECTION_LOD = 8.0;
return envmap_color_lod(fragnormal, roughness * MAX_REFLECTION_LOD);
vec3 boxcolor = envmap_color_lod(fragnormal, roughness * MAX_REFLECTION_LOD);
if (ENVMAP_ENABLED)
{
if (env_ggx == 0U || roughness <= ENVMAP_GGX_ROUGHNESS_MIN)
return boxcolor;
vec3 refvec = reflect(f_pos.xyz, fragnormal);
refvec = vec3(inv_view * vec4(refvec, 0.0));
float level = (roughness - ENVMAP_GGX_ROUGHNESS_MIN) / (1.0 - ENVMAP_GGX_ROUGHNESS_MIN) * float(ENVMAP_GGX_LEVELS - 1U);
vec3 ggxcolor = textureLod(envmap_ggx, refvec, level).rgb;
if (any(isnan(ggxcolor)) || any(isinf(ggxcolor))) ggxcolor = vec3(0.0);
ggxcolor = max(ggxcolor, vec3(0.0));
// the prefiltered map takes over over the first tenth of the roughness above its lowest level
return mix(boxcolor, ggxcolor, clamp((roughness - ENVMAP_GGX_ROUGHNESS_MIN) * 10.0, 0.0, 1.0));
}
else
{
return boxcolor;
}
}

View File

@@ -0,0 +1,86 @@
#version 460 core
#define STAGE_COMPUTE 1
// exposure from the luminance histogram (exposure_histogram.comp): the average log2 luminance of the pixels between the
// low and high percentile (the darkest and brightest parts of the image do not move the exposure), adapted over time to,
// faster towards the brighter. the exposure brings the adapted luminance to the key, within the limits. one work group,
// one invocation per bin; the histogram is cleared for the next frame
#define LAYOUT_COMPUTE 1
#include "common.glsl"
layout(local_size_x = EXPOSURE_HISTOGRAM_BINS) in;
layout(std430, set = COMPUTE_SET, binding = 0) buffer exposure_ssbo
{
uint histogram[EXPOSURE_HISTOGRAM_BINS];
float adapted_luminance;
float exposure;
float average_luminance;
uint valid;
};
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
// x: log2 of the lowest luminance binned, y: log2 luminance per bin
vec2 log_range;
// x: time since the last frame (s), y: rate of adaptation to a darker image, z: to a brighter one (1/s), w: key
vec4 adaptation;
// lowest and highest exposure (multipliers)
vec2 exposure_range;
};
const float LOW_PERCENTILE = 0.4;
const float HIGH_PERCENTILE = 0.95;
shared float shared_counts[EXPOSURE_HISTOGRAM_BINS];
void main()
{
uint index = gl_LocalInvocationIndex;
shared_counts[index] = float(histogram[index]);
histogram[index] = 0U;
barrier();
if (index != 0U)
return;
float total = 0.0;
for (uint bin = 1U; bin < EXPOSURE_HISTOGRAM_BINS; ++bin)
total += shared_counts[bin];
// an all black image (or none) leaves the exposure as it was
if (total < 1.0)
return;
float low = total * LOW_PERCENTILE;
float high = total * HIGH_PERCENTILE;
float counted = 0.0;
float logsum = 0.0;
float weight = 0.0;
for (uint bin = 1U; bin < EXPOSURE_HISTOGRAM_BINS; ++bin)
{
float count = shared_counts[bin];
// the part of the bin between the percentiles
float inside = max(min(counted + count, high) - max(counted, low), 0.0);
counted += count;
float binluminance = log_range.x + (float(bin - 1U) + 0.5) * log_range.y;
logsum += inside * binluminance;
weight += inside;
}
float averagelog = (weight > 0.0) ? logsum / weight : log_range.x;
average_luminance = exp2(averagelog);
if (valid == 0U || !(adapted_luminance > 0.0))
{
adapted_luminance = average_luminance;
valid = 1U;
}
else
{
float adaptedlog = log2(adapted_luminance);
float rate = (averagelog < adaptedlog) ? adaptation.y : adaptation.z;
adaptedlog = mix(adaptedlog, averagelog, 1.0 - exp(-adaptation.x * rate));
adapted_luminance = exp2(adaptedlog);
}
exposure = clamp(adaptation.w / adapted_luminance, exposure_range.x, exposure_range.y);
}

View File

@@ -0,0 +1,56 @@
#version 460 core
#define STAGE_COMPUTE 1
// luminance histogram of the image for the automatic exposure: every second pixel in both directions, binned by the log2
// of its luminance. bin 0 takes the black pixels, which the average leaves out. exposure_average.comp reads and clears it
#define LAYOUT_COMPUTE 1
#include "common.glsl"
layout(local_size_x = EXPOSURE_HISTOGRAM_GROUP_SIZE, local_size_y = EXPOSURE_HISTOGRAM_GROUP_SIZE) in;
layout(set = COMPUTE_SET, binding = 0) uniform sampler2D hdr_image;
layout(std430, set = COMPUTE_SET, binding = 1) buffer exposure_ssbo
{
uint histogram[EXPOSURE_HISTOGRAM_BINS];
float adapted_luminance;
float exposure;
float average_luminance;
uint valid;
};
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
// x: log2 of the lowest luminance binned, y: number of bins per unit of log2 luminance
vec2 log_range;
};
shared uint shared_histogram[EXPOSURE_HISTOGRAM_BINS];
void main()
{
uint index = gl_LocalInvocationIndex;
shared_histogram[index] = 0U;
barrier();
ivec2 pixel = ivec2(gl_GlobalInvocationID.xy) * 2;
ivec2 size = textureSize(hdr_image, 0);
if (pixel.x < size.x && pixel.y < size.y)
{
vec3 color = texelFetch(hdr_image, pixel, 0).rgb;
float luminance = dot(color, vec3(0.2126, 0.7152, 0.0722));
uint bin = 0U;
// also false for a NaN
if (luminance > 1e-6)
{
float position = clamp((log2(luminance) - log_range.x) * log_range.y, 0.0, float(EXPOSURE_HISTOGRAM_BINS - 2U));
bin = 1U + uint(position);
}
atomicAdd(shared_histogram[bin], 1U);
}
barrier();
if (shared_histogram[index] != 0U)
atomicAdd(histogram[index], shared_histogram[index]);
}

View File

@@ -0,0 +1,49 @@
#version 460 core
#define STAGE_COMPUTE 1
// 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
#define LAYOUT_COMPUTE 1
#include "common.glsl"
#include "atmosphere_common.glsl"
layout(local_size_x = FOG_HORIZON_SAMPLES) in;
layout(set = PASS_SET, binding = SKY_VIEW_TEX) uniform sampler2D sky_view_lut;
layout(std430, set = COMPUTE_SET, binding = 0) buffer weather_scratch
{
vec4 weather_words[];
};
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
// first vec4 of fog_horizon in the weather buffer
int offset;
// x: share of the atmosphere, y: its brightness, z: desaturation by the overcast, w: brightest the colour gets
vec4 params;
vec3 white_balance;
// the colour of the old fog, which the atmosphere replaces by its share
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);
}

View File

@@ -0,0 +1,323 @@
#version 460 core
#define STAGE_COMPUTE 1
// 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 point every frame, each cell going through
// the points from a place of its own, so the volume doesn't flicker as a whole, and the blending smooths what a cell holds over
// its whole size: a beam doesn't swim over the grid of the cells as the camera moves and turns. what the cell takes the lights
// from doesn't depend on the point (the clusters of the middle of the cell)
// rgb: light scattered towards the camera per metre, a: extinction per metre
#define LAYOUT_COMPUTE 1
#include "common.glsl"
#include "clouds_common.glsl"
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
layout(rgba16f, set = COMPUTE_SET, binding = 0) uniform writeonly image3D fog_scattering;
layout(set = COMPUTE_SET, binding = 1) uniform sampler3D fog_history;
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;
// the parameters of the kernel (a block of the ring of the frame: more of them than the push constants hold)
layout(std140, set = COMPUTE_SET, binding = 2) uniform kernel_block
{
ivec3 grid;
mat4 inverse_projection;
mat4 view_to_world;
// x: near, y: far, z: slices / log((far + FOG_VOLUME_BIAS) / (near + FOG_VOLUME_BIAS)), w: where in its slice the cell is
// sampled this frame (0-1)
vec4 slicing;
// world axes to the clip space of the previous frame, the camera of the previous frame at the origin
mat4 previous_projection_view;
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
vec4 options;
// the light of the cab lamp, which lights no fog (the air of the cab is clear), or -1
int cablamp;
// x: near, y: slices / log((far + FOG_VOLUME_BIAS) / (near + FOG_VOLUME_BIAS)) of the previous frame
vec2 previous_slicing;
// where in its cell the cell is sampled this frame across the view (0-1), as slicing.w is along the depth
vec2 jitter;
};
// ray of the view space through a point of the view (0-1), one metre of the depth long
vec3 view_ray(vec2 coords)
{
vec4 near = inverse_projection * vec4(coords * 2.0 - 1.0, 1.0, 1.0);
vec3 ray = near.xyz / near.w;
return ray / -ray.z;
}
// view depth at a place along the slices: slice number and the part of the slice
float slice_depth(float slice)
{
return (slicing.x + FOG_VOLUME_BIAS) * exp(slice / slicing.z) - FOG_VOLUME_BIAS;
}
// where in the cell it is sampled this frame, 0-1 on each axis: the point of the sequence of the frames moved by a value of
// the cell's own (pcg3d hash), the middle without the blending
vec3 cell_offset(ivec3 cell)
{
if (options.x >= 1.0)
return vec3(0.5);
uvec3 v = uvec3(cell) * 1664525U + 1013904223U;
v.x += v.y * v.z;
v.y += v.z * v.x;
v.z += v.x * v.y;
v ^= v >> 16U;
v.x += v.y * v.z;
v.y += v.z * v.x;
v.z += v.x * v.y;
return fract(vec3(jitter, slicing.w) + vec3(v >> 8U) * (1.0 / 16777216.0));
}
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;
for (; cascade < MAX_CASCADES; cascade++)
{
vec4 lightpos = lightview[cascade] * vec4(position, 1.0);
vec3 coords = lightpos.xyz / lightpos.w;
if (all(greaterThanEqual(coords.xy, vec2(0.0))) && all(lessThanEqual(coords.xy, vec2(1.0))))
{
if (coords.z < 0.0)
return 0.0;
return texture(shadowmap, vec4(coords.xy, float(cascade), coords.z + 0.0001 * float(cascade + 1U)));
}
// outside the map: lit, unless the cascades are fitted to the depth of the view (gfx.shadow.sdsm), whose maps cover
// only the area of the surfaces seen; then the first further cascade whose map holds the point
if (shadow_fit.x <= 0.0)
return 0.0;
}
return 0.0;
}
else
{
return 0.0;
}
}
// 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);
}
// light of a lamp or of the beam of headlights scattered towards the camera, the average per metre along a segment of the ray
// of a cell (start, unit direction, length; view space). point samples of a lamp next to the ray change many times over with
// where in the cell they fall (the light falls off with the square of the distance), so the falloff of a lamp is integrated
// along the segment and only the rest of its light (the cone, the edge of its reach, the phase) is sampled, at two points the
// integral spreads by itself (equi-angular sampling, Kulla & Fajardo 2012). next to the lamp the light is spread over the size
// of a cell: the distance of the lamp from the ray grows by the width of the cell there, and the cone of a spot light starts
// that far behind the lamp. the point sampled moves within the cell from frame to frame, and the ray of the cell would pass at
// almost no distance from the lamp in one frame and a cell away in the next, above the tip of the cone and below it. Part:
// where between the two halves of the segment the points are (0-1)
vec3 lamp_segment(light_s lamp, light_s cone, vec3 start, vec3 direction, float span, float width, float part);
vec3 lamp_scattering(light_s lamp, vec3 start, vec3 direction, float span, float cellwidth, float part)
{
vec3 sum = vec3(0.0);
if (lamp.type == LIGHT_HEADLIGHTS)
{
// the beam falls off slowly, two points spread evenly along the segment
for (int i = 0; i < 2; i++)
{
vec3 position = start + direction * (span * (float(i) + part) * 0.5);
sum += lamp_light(lamp, position) * henyey_greenstein(dot(normalize(position - lamp.pos), -direction), 0.4);
}
return sum * 0.5;
}
float closest = dot(lamp.pos - start, direction);
vec3 nearest = start + direction * closest;
float width = max(cellwidth * max(-nearest.z, 0.0), 1e-3);
light_s cone = lamp;
if (lamp.type == LIGHT_SPOT)
cone.pos -= normalize(lamp.dir) * width;
if (dot(lamp.pos - nearest, lamp.pos - nearest) < 9.0 * width * width)
{
// a lamp within a few cells of the ray: the light of the cell is taken along four rays spread over the cell, so it
// doesn't depend on which side of the lamp the ray of this frame passes, and the points along the rays are staggered,
// eight along the cell, where the cone changes the most (its tip)
vec3 across = normalize(cross(direction, vec3(0.0, 1.0, 0.0) + vec3(1e-4, 0.0, 0.0)));
vec3 up = cross(across, direction);
for (int k = 0; k < 4; k++)
{
vec2 shift = (vec2(float(k & 1), float(k >> 1)) - 0.5) * 0.5 * width;
sum += lamp_segment(lamp, cone, start + across * shift.x + up * shift.y, direction, span, width, fract(part + 0.25 * float(k)));
}
return sum * 0.25;
}
return lamp_segment(lamp, cone, start, direction, span, width, part);
}
// lamp_scattering() along one ray: Cone the lamp with the tip of its cone moved behind it, Width the width of the cell at the lamp
vec3 lamp_segment(light_s lamp, light_s cone, vec3 start, vec3 direction, float span, float width, float part)
{
vec3 sum = vec3(0.0);
float closest = dot(lamp.pos - start, direction);
vec3 nearest = start + direction * closest;
float reach = sqrt(dot(lamp.pos - nearest, lamp.pos - nearest) + width * width);
float first = atan(-closest, reach);
float last = atan(span - closest, reach);
for (int i = 0; i < 2; i++)
{
vec3 position = start + direction * (closest + reach * tan(mix(first, last, (float(i) + part) * 0.5)));
vec3 tolight = cone.pos - position;
// the light without its falloff, which the integral holds
vec3 fromlamp = position - lamp.pos;
sum += lamp_light(cone, position) * max(dot(tolight, tolight), 0.01) * henyey_greenstein(dot(fromlamp, -direction) * inversesqrt(max(dot(fromlamp, fromlamp), 1e-8)), 0.4);
}
return sum * (0.5 * (last - first) / (reach * max(span, 1e-4)));
}
void main()
{
ivec3 cell = ivec3(gl_GlobalInvocationID);
if (any(greaterThanEqual(cell, grid)))
return;
vec3 offset = cell_offset(cell);
vec2 coords = (vec2(cell.xy) + offset.xy) / vec2(grid.xy);
float depth = slice_depth(float(cell.z) + offset.z);
vec3 ray = view_ray(coords);
vec3 view = ray * depth;
// the segment of the ray through the cell, for the light which changes within the cell
float raylength = length(ray);
vec3 raydirection = ray / raylength;
float front = slice_depth(float(cell.z));
vec3 start = ray * front;
float span = (slice_depth(float(cell.z) + 1.0) - front) * raylength;
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)
{
// 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);
// 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)
if (options.y > 0.0 && lights_clustered != 0U && cluster_valid != 0U)
{
vec2 middle = (vec2(cell.xy) + 0.5) / vec2(grid.xy);
ivec2 tile = clamp(ivec2(middle * vec2(CLUSTER_GRID_X, CLUSTER_GRID_Y)), ivec2(0), ivec2(CLUSTER_GRID_X - 1, CLUSTER_GRID_Y - 1));
float back = front + span / raylength;
int first = clamp(int(floor(cluster_slice_bias + log(max(front, 1e-4)) * cluster_slice_scale)), 0, CLUSTER_GRID_Z - 1);
int last = clamp(int(floor(cluster_slice_bias + log(max(back, 1e-4)) * cluster_slice_scale)), first, min(first + 3, CLUSTER_GRID_Z - 1));
float cellwidth = length(view_ray(coords + vec2(1.0 / float(grid.x), 0.0)) - ray);
vec3 lamps = vec3(0.0);
for (int slice = first; slice <= last; slice++)
{
float partfront = (slice == first) ? front : exp((float(slice) - cluster_slice_bias) / cluster_slice_scale);
float partback = (slice == last) ? back : exp((float(slice + 1) - cluster_slice_bias) / cluster_slice_scale);
float partspan = max(partback - partfront, 0.0) * raylength;
if (partspan <= 0.0)
continue;
vec3 partstart = ray * partfront;
cluster_range_s range = cluster_ranges[uint(tile.x + CLUSTER_GRID_X * (tile.y + CLUSTER_GRID_Y * slice))];
vec3 partlamps = vec3(0.0);
for (uint i = 0U; i < range.count; i++)
{
uint index = cluster_light_indices[range.offset + i];
if (int(index) == cablamp)
continue;
partlamps += lamp_scattering(lights[index], partstart, raydirection, partspan, cellwidth, offset.z);
}
lamps += partlamps * (partspan / max(span, 1e-4));
}
light += lamps * options.y;
}
result = vec4(light * fog_haze.w * extinction, extinction);
}
// the previous frame, where the middle of the cell was then: the cells of the history hold what the cells were on the whole,
// not at the point they were sampled at
float share = options.x;
if (share < 1.0)
{
vec3 centre = mat3(view_to_world) * (view_ray((vec2(cell.xy) + 0.5) / vec2(grid.xy)) * slice_depth(float(cell.z) + 0.5));
vec4 clip = previous_projection_view * vec4(centre - previous_camera, 1.0);
if (clip.w > 0.0)
{
float previousslice = log(max((clip.w + FOG_VOLUME_BIAS) / (previous_slicing.x + FOG_VOLUME_BIAS), 1e-6)) * previous_slicing.y;
vec3 previous = vec3(clip.xy / clip.w * 0.5 + 0.5, previousslice / 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,91 @@
#version 460 core
#define STAGE_COMPUTE 1
// 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. the light of the cells is taken with the light of their neighbours across the view, a 3x3 tent filter
// (four fetches of the linear filter): every cell is sampled at a point of its own in a frame, and what is left of that after
// the blending over frames differs from cell to cell, which the filter evens out. the history keeps the cells unfiltered.
// past the last slice one more: the fog all the way through the volume without the low clouds it holds, for the sky
#define LAYOUT_COMPUTE 1
#include "common.glsl"
#include "weather_common.glsl"
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
layout(set = COMPUTE_SET, binding = 0) uniform sampler3D fog_scattering;
layout(set = PASS_SET, binding = CLOUD_SHAPE_TEX) uniform sampler3D cloud_shape_noise;
layout(rgba16f, set = COMPUTE_SET, binding = 1) uniform writeonly image3D fog_integrated;
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
ivec3 grid;
mat4 inverse_projection;
mat4 view_to_world;
// x: near, y: far, z: slices / log((far + FOG_VOLUME_BIAS) / (near + FOG_VOLUME_BIAS))
vec4 slicing;
};
// extinction (1/m) of the haze and the ground fog at a point, made as fog_inject.comp makes them
float fog_clear_extinction(vec3 world)
{
float height = weather_camera.x + world.y;
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);
}
return haze + ground;
}
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;
// the same without the clouds: over the sky the ray march draws them (clouds_march.comp), and the volume laid over it would draw
// those within its reach a second time, blurred to the size of the cells and cut into bands by the slices
vec3 clearlight = vec3(0.0);
float cleartransmittance = 1.0;
float front = slicing.x;
for (int slice = 0; slice < grid.z; slice++)
{
float back = (slicing.x + FOG_VOLUME_BIAS) * exp(float(slice + 1) / slicing.z) - FOG_VOLUME_BIAS;
float span = (back - front) * stretch;
front = back;
vec4 cell = texelFetch(fog_scattering, ivec3(column, slice), 0);
float depthcoord = (float(slice) + 0.5) / float(grid.z);
vec2 texel = 1.0 / vec2(grid.xy);
vec4 filtered = 0.25 * (textureLod(fog_scattering, vec3(coords + vec2(-0.5, -0.5) * texel, depthcoord), 0.0)
+ textureLod(fog_scattering, vec3(coords + vec2(0.5, -0.5) * texel, depthcoord), 0.0)
+ textureLod(fog_scattering, vec3(coords + vec2(-0.5, 0.5) * texel, depthcoord), 0.0)
+ textureLod(fog_scattering, vec3(coords + vec2(0.5, 0.5) * texel, depthcoord), 0.0));
cell.rgb = filtered.rgb;
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));
// the haze and the ground fog of the cell, at its middle, and their light as that of the cell per extinction, the lamps and the
// shadows with it; what is left of the extinction is the clouds. the light per extinction from the filtered cells, light and
// extinction alike: the filtered light over the extinction of the cell alone would bring in the light of a cloud next to it
float middle = (slicing.x + FOG_VOLUME_BIAS) * exp((float(slice) + 0.5) / slicing.z) - FOG_VOLUME_BIAS;
float clear = min(fog_clear_extinction(mat3(view_to_world) * (ray / -ray.z * middle)), extinction);
float clearthrough = exp(-clear * span);
clearlight += cleartransmittance * (filtered.rgb / max(filtered.a, 1e-7)) * (1.0 - clearthrough);
cleartransmittance *= clearthrough;
}
imageStore(fog_integrated, ivec3(column, grid.z), vec4(clearlight, cleartransmittance));
}

View File

@@ -0,0 +1,73 @@
// 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. the volume holds one slice more than fog_volume_params.w:
// the fog through all of it without the clouds, for the sky
#ifndef FOG_VOLUME_GLSL
#define FOG_VOLUME_GLSL
#include "weather_common.glsl"
layout(set = PASS_SET, binding = FOG_VOLUME_TEX) uniform sampler3D fog_volume;
// the volume at a point of the view (0-1) and a coordinate along the slices, filtered across the view with a cubic B-spline
// (four fetches of the linear filter): a cell is several pixels wide, and the linear filter alone draws the shafts of light and
// the edges of shadows in the fog as steps
vec4 fog_volume_at(vec2 coords, float slice)
{
vec2 size = vec2(textureSize(fog_volume, 0).xy);
vec2 texel = coords * size - 0.5;
vec2 base = floor(texel);
vec2 f = texel - base;
vec2 f2 = f * f;
vec2 f3 = f2 * f;
vec2 w0 = (1.0 - 3.0 * f + 3.0 * f2 - f3) / 6.0;
vec2 w1 = (4.0 - 6.0 * f2 + 3.0 * f3) / 6.0;
vec2 w2 = (1.0 + 3.0 * f + 3.0 * f2 - 3.0 * f3) / 6.0;
vec2 w3 = f3 / 6.0;
vec2 g0 = w0 + w1;
vec2 g1 = w2 + w3;
vec2 h0 = (base - 0.5 + w1 / g0) / size;
vec2 h1 = (base + 1.5 + w3 / g1) / size;
return g0.y * (g0.x * textureLod(fog_volume, vec3(h0.x, h0.y, slice), 0.0) + g1.x * textureLod(fog_volume, vec3(h1.x, h0.y, slice), 0.0))
+ g1.y * (g0.x * textureLod(fog_volume, vec3(h0.x, h1.y, slice), 0.0) + g1.x * textureLod(fog_volume, vec3(h1.x, h1.y, slice), 0.0));
}
// 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 plus FOG_VOLUME_BIAS; 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_BIAS) / (fog_volume_params.y + FOG_VOLUME_BIAS), 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), fog_volume_at(coords, 0.5 / (slices + 1.0)), max(slice, 0.0));
if (slice < slices)
return fog_volume_at(coords, (slice - 0.5) / (slices + 1.0));
vec4 volume = fog_volume_at(coords, (slices - 0.5) / (slices + 1.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. the volume
// is taken without the low clouds it holds (its extra slice): the ray march draws them over the sky already
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 = fog_volume_at(fragcoord * fog_volume_target.xy, (slices + 0.5) / (slices + 1.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

49
shaders/vk14/grass.frag Normal file
View File

@@ -0,0 +1,49 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
// grass of the heightmap terrain (opengl46_grass, grass.vert): the blades lit like the rest of the scene
layout(location = 0) in vec3 f_normal;
layout(location = 3) in vec4 f_pos;
layout(location = 12) in vec3 f_albedo;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
#include "common.glsl"
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
void main()
{
vec3 fragnormal = normalize(f_normal);
glossiness = phong_glossiness(6.0);
metalic = 0.0;
vec3 fragcolor = apply_lights(ambient, fragnormal, f_albedo, 0.0, 0.2, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), 1.0);
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
}

247
shaders/vk14/grass.vert Normal file
View File

@@ -0,0 +1,247 @@
#version 460 core
#define STAGE_VERTEX 1
// grass of the heightmap terrain (opengl46_grass, vk14::grass_field): a blade made of the numbers of its vertex and its instance.
//
// the instances of a draw stand in a tile of a chunk. a blade takes its place in the tile from a sequence which spreads
// any number of its first members evenly (R2, shifted for each tile), so drawing fewer of them thins the grass out
// evenly; a blade picks its kind by the densities painted there, and gets its height, its turn and its lean from its own
// numbers, so it's the same in every frame. blades of no kind, in the holes, or past the share drawn at their distance are
// collapsed into a point outside the view. the vertices go up the blade in pairs, the last one is its tip.
// the chunk is drawn relative to its corner: x, z in metres from the corner, y the height as it is
#include "common.glsl"
// the parameters of the draw (the uniforms of the opengl renderer): those of the tile and of the frame in the push constants,
// the kinds of the chunk in a table (vk14::grass_field)
// x, z: corner of the tile in the chunk, metres; y: side of the tile; w: blades of the tile where its densest kind is painted fully
#define grass_tile pc.data[0]
// x: the share of the densest kind at the densest place of the tile (the blades of the tile are counted for it); y: segments
// of a blade; z, w: number of the tile in the world
#define grass_part pc.data[1]
// the camera relative to the corner of the chunk (y as it is)
#define grass_camera pc.data[2]
// x: distance the grass starts to thin out at, for speed; y: distance the fading starts at (gfx.grass.distance); z: where the
// grass ends (gfx.grass.fadedistance)
#define grass_lod pc.data[3]
// x, y: direction of the wind (x, z of the world); z: its strength, 0..1; w: time, seconds
#define grass_wind pc.data[4]
// the time of the year: x: how dry the grass is, y: how fresh (spring), z: how grown (share of the height)
#define grass_season pc.data[5]
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D grass_heights; // heights of the grid points, R32F
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D grass_density; // densities of the kinds, RGBA8
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 2) uniform sampler2D grass_holes; // holes cut in the ground, R8
// the table of the chunk, RGBA32F, 4 x 5: a column for each kind; the rows: colour at the root and at the tip (linear rgb),
// height, its variation, width, lean; the colour dried out (linear rgb) and how much it follows the seasons; in the last row
// the share of the blades of each kind against the densest one of the chunk (x) and the chunk (y: side, metres, cells of its
// grid along a side, grass samples along a side less one)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 3) uniform sampler2D grass_kinds;
vec4 grass_base[4];
vec4 grass_tip[4];
vec4 grass_shape[4];
vec4 grass_dry[4];
vec4 grass_share;
vec4 grass_chunk;
void read_kinds()
{
for (int i = 0; i < 4; ++i)
{
grass_base[i] = texelFetch(grass_kinds, ivec2(i, 0), 0);
grass_tip[i] = texelFetch(grass_kinds, ivec2(i, 1), 0);
grass_shape[i] = texelFetch(grass_kinds, ivec2(i, 2), 0);
grass_dry[i] = texelFetch(grass_kinds, ivec2(i, 3), 0);
}
grass_share = texelFetch(grass_kinds, ivec2(0, 4), 0);
grass_chunk = texelFetch(grass_kinds, ivec2(1, 4), 0);
}
layout(location = 0) out vec3 f_normal;
layout(location = 3) out vec4 f_pos;
layout(location = 12) out vec3 f_albedo;
layout(location = 4) out vec4 f_clip_pos;
layout(location = 5) out vec4 f_clip_future_pos;
layout(location = 7) out vec4 UserData;
uint hash(uint x)
{
x ^= x >> 16;
x *= 0x7feb352dU;
x ^= x >> 15;
x *= 0x846ca68bU;
x ^= x >> 16;
return x;
}
float random(inout uint state)
{
state = hash(state);
return float(state >> 8) * (1.0 / 16777216.0);
}
// the share of the blades drawn at specified distance for speed: fewer further away, each standing for more of them
float lod_thinning(float distance)
{
return distance <= grass_lod.x ? 1.0 : pow(grass_lod.x / distance, 1.5);
}
// the share of the blades drawn: the above, and fewer and fewer over the fading distance, where the grass really gets
// sparser; the same as lod_share() of opengl46grass.cpp
float lod_share(float distance)
{
return lod_thinning(distance) * (1.0 - smoothstep(grass_lod.y, grass_lod.z, distance));
}
// height of the ground at a point of the chunk and its normal, the way the full grid is triangulated
float ground(vec2 point, out vec3 normal)
{
float cells = grass_chunk.y;
float spacing = grass_chunk.x / cells;
vec2 place = clamp(point / spacing, vec2(0.0), vec2(cells));
ivec2 cell = min(ivec2(place), ivec2(int(cells) - 1));
vec2 t = place - vec2(cell);
float h00 = texelFetch(grass_heights, cell, 0).r;
float h10 = texelFetch(grass_heights, cell + ivec2(1, 0), 0).r;
float h01 = texelFetch(grass_heights, cell + ivec2(0, 1), 0).r;
float h11 = texelFetch(grass_heights, cell + ivec2(1, 1), 0).r;
// the quads are cut along the diagonal from (x0, z1) to (x1, z0), as the mesh is made
if (t.x + t.y <= 1.0)
{
normal = normalize(vec3(-(h10 - h00) / spacing, 1.0, -(h01 - h00) / spacing));
return h00 + t.x * (h10 - h00) + t.y * (h01 - h00);
}
normal = normalize(vec3(-(h11 - h01) / spacing, 1.0, -(h11 - h10) / spacing));
return h11 + (1.0 - t.x) * (h01 - h11) + (1.0 - t.y) * (h10 - h11);
}
void collapse()
{
gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
f_normal = vec3(0.0, 1.0, 0.0);
f_pos = vec4(0.0);
f_albedo = vec3(0.0);
f_clip_pos = gl_Position;
f_clip_future_pos = gl_Position;
UserData = vec4(0.0);
}
void main()
{
read_kinds();
int blade = gl_InstanceIndex;
// the numbers of the blade, drawn in the same order whatever becomes of them
uint state = hash(uint(blade) * 0x9E3779B9U ^ hash(uint(int(grass_part.z)) * 0x85EBCA6BU ^ hash(uint(int(grass_part.w)) + 0x27D4EB2FU)));
uint tilestate = hash(uint(int(grass_part.z)) * 0x85EBCA6BU ^ hash(uint(int(grass_part.w)) + 0x165667B1U));
vec2 shift = vec2(random(tilestate), random(tilestate));
vec2 jitter = vec2(random(state), random(state)) - 0.5;
float pick = random(state);
float heightrandom = random(state) * 2.0 - 1.0;
float widthrandom = random(state);
float turnrandom = random(state) * 6.2831853;
float leanrandom = random(state) * 6.2831853;
float leanamount = random(state);
float shade = random(state);
float phase = random(state);
float early = random(state);
// place: R2 spreads the first blades evenly at any count, a little jitter breaks its lattice
vec2 spot = fract(shift + float(blade) * vec2(0.7548776662466927, 0.5698402909980532));
vec2 point = grass_tile.xy + fract(spot + jitter * (0.7 / sqrt(grass_tile.w))) * grass_tile.z;
if (any(lessThan(point, vec2(0.0))) || any(greaterThanEqual(point, vec2(grass_chunk.x))))
{
collapse();
return;
}
vec2 local = point / grass_chunk.x;
float samples = grass_chunk.z;
vec4 density = texture(grass_density, (local * samples + 0.5) / (samples + 1.0));
float holeside = float(textureSize(grass_holes, 0).x);
float hole = texture(grass_holes, (local * (holeside - 1.0) + 0.5) / holeside).r;
// the kind: the blades of the tile are shared among the kinds by their densities here
vec4 cover = density * grass_share / max(grass_part.x, 1e-3);
float covered = cover.x + cover.y + cover.z + cover.w;
if (covered > 1.0)
cover /= covered;
int kind = -1;
float sum = 0.0;
for (int i = 0; i < 4; ++i)
{
sum += cover[i];
if (pick < sum)
{
kind = i;
break;
}
}
vec3 groundnormal;
vec3 root = vec3(point.x, ground(point, groundnormal), point.y);
float distance = length(root - grass_camera.xyz);
float share = lod_share(distance);
float rank = (float(blade) + 0.5) / grass_tile.w;
if (kind < 0 || hole > 0.5 || rank >= share || distance > grass_lod.z)
{
collapse();
return;
}
vec4 shape = grass_shape[kind];
float height = shape.x * (1.0 + shape.y * heightrandom);
// the last of the blades drawn at the distance grow in, and the grass fades out at the end of its reach
height *= 1.0 - smoothstep(share * 0.8, share, rank);
// lower in winter, growing in spring
float seasonal = grass_dry[kind].a;
height *= mix(1.0, grass_season.z, seasonal);
// where the paint thins out the blades get shorter
height *= mix(0.5, 1.0, clamp(density[kind] * 1.5, 0.0, 1.0));
// the blades drawn further away stand for more of them, so they're wider
float width = shape.z * (0.8 + 0.4 * widthrandom) / sqrt(lod_thinning(distance));
// the face of the blade turns towards the camera further away, so the blades don't vanish edge on
vec2 facing = vec2(cos(turnrandom), sin(turnrandom));
vec2 toview = grass_camera.xz - root.xz;
toview = toview / max(length(toview), 1e-3);
facing = mix(facing, toview, smoothstep(grass_lod.x * 0.5, grass_lod.x * 2.0, distance));
facing = facing / max(length(facing), 1e-3);
vec2 across = vec2(-facing.y, facing.x);
// the lean of the blade and the wind: a steady push along it, with gusts running across the field, and a flutter
vec2 lean = vec2(cos(leanrandom), sin(leanrandom)) * (shape.w * (0.4 + 0.6 * leanamount));
float gust = 0.5 + 0.5 * sin(grass_wind.w * 0.9 - dot(root.xz, grass_wind.xy) * 0.12);
float flutter = sin(grass_wind.w * (2.0 + phase) + phase * 6.2831853);
vec2 sway = grass_wind.xy * grass_wind.z * (0.15 + 0.45 * gust + 0.08 * flutter);
vec2 bend = lean + sway;
// a long lean takes some of the height
float bent = min(length(bend), 0.9);
int segments = int(grass_part.y);
int level = min(gl_VertexIndex / 2, segments);
float side = (gl_VertexIndex >= segments * 2) ? 0.0 : ((gl_VertexIndex & 1) == 0 ? -0.5 : 0.5);
float t = float(level) / float(segments);
float halfwidth = width * (1.0 - pow(t, 1.5));
vec2 xz = root.xz + across * (side * halfwidth) + bend * (height * t * t);
float y = root.y - 0.02 + height * t * (1.0 - 0.35 * bent * t);
vec3 position = vec3(xz.x, y, xz.y);
// lit mostly as the ground under it, which keeps a field of blades from shimmering
vec3 normal = normalize(groundnormal * 0.8 + vec3(facing.x, 0.0, facing.y) * (side == 0.0 ? 0.0 : 0.25) + vec3(0.0, 0.3, 0.0));
// the seasons: some blades dry out (and green up) earlier than others, so a field turns gradually; a dry blade takes the
// dry colour, darker at its root. in spring the green is fresher
float dry = clamp(grass_season.x * seasonal * (0.6 + 0.8 * early), 0.0, 1.0);
vec3 bottom = mix(grass_base[kind].rgb, grass_dry[kind].rgb * 0.45, dry);
vec3 top = mix(grass_tip[kind].rgb, grass_dry[kind].rgb, dry);
top *= mix(vec3(1.0), vec3(1.08, 1.22, 0.85), grass_season.y * seasonal * (1.0 - dry));
vec3 albedo = mix(bottom, top, t) * (0.85 + 0.3 * shade);
// the lower part of the blades is shaded by the others
albedo *= mix(0.7, 1.0, t);
f_pos = modelview * vec4(position, 1.0);
f_normal = normalize(modelviewnormal * normal);
f_albedo = albedo;
f_clip_pos = projection * f_pos;
f_clip_future_pos = (projection * future) * f_pos;
gl_Position = f_clip_pos;
UserData = vec4(0.0);
}

View File

@@ -0,0 +1,58 @@
#version 460 core
#define STAGE_COMPUTE 1
#define LAYOUT_COMPUTE 1
#include "common.glsl"
// smooths the ambient occlusion of gtao_main.comp: the average of the 4x4 pixels around each one, which takes out the
// repeating 4x4 pattern of the slices, with the pixels at a clearly different distance left out so the occlusion does
// not leak across the edges of objects. one invocation per pixel of the half size target
layout(local_size_x = 8, local_size_y = 8) in;
layout(set = COMPUTE_SET, binding = 0) uniform sampler2D depth_tex;
layout(set = COMPUTE_SET, binding = 1) uniform sampler2D ao_tex;
layout(set = COMPUTE_SET, binding = 2, r8) writeonly uniform image2D ao_target;
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
mat4 inv_projection;
};
// relative difference of the distance at which a neighbour stops counting
const float DEPTH_TOLERANCE = 0.05;
float view_distance(ivec2 texel)
{
ivec2 full_size = textureSize(depth_tex, 0);
float depth = texelFetch(depth_tex, min(texel * 2, full_size - 1), 0).r;
if (depth <= 0.0)
return 1e30;
vec2 uv = (vec2(texel * 2) + 0.5) / vec2(full_size);
vec4 position = inv_projection * vec4(uv * 2.0 - 1.0, depth, 1.0);
return length(position.xyz / position.w);
}
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = imageSize(ao_target);
if (texel.x >= size.x || texel.y >= size.y)
return;
float distance = view_distance(texel);
float sum = 0.0;
float weights = 0.0;
for (int y = -2; y < 2; ++y)
{
for (int x = -2; x < 2; ++x)
{
ivec2 sampletexel = clamp(texel + ivec2(x, y), ivec2(0), size - 1);
float sampledistance = view_distance(sampletexel);
float weight = clamp(1.0 - abs(sampledistance - distance) / (DEPTH_TOLERANCE * distance), 0.0, 1.0);
sum += texelFetch(ao_tex, sampletexel, 0).r * weight;
weights += weight;
}
}
// the pixel itself is in the window with the full weight, so weights >= 1
imageStore(ao_target, texel, vec4(sum / weights));
}

160
shaders/vk14/gtao_main.comp Normal file
View File

@@ -0,0 +1,160 @@
#version 460 core
#define STAGE_COMPUTE 1
#define LAYOUT_COMPUTE 1
#include "common.glsl"
// ground truth ambient occlusion (Jimenez et al. 2016, "Practical realtime strategies for accurate indirect occlusion";
// integration as in Intel XeGTAO) at half the resolution of the image. for every pixel, a few slices through the
// hemisphere around the view direction; in each the horizons on both sides are searched in the depth buffer and the
// cosine weighted visible arc between them is integrated against the normal rebuilt from the depth.
// one invocation per pixel of the half size target. the pattern of the slice directions and steps repeats every 4x4
// pixels; gtao_denoise.comp averages it out
layout(local_size_x = 8, local_size_y = 8) in;
// depth of the scene at full resolution: reversed, 1 at the near plane, 0 at infinity (the sky)
layout(set = COMPUTE_SET, binding = 0) uniform sampler2D depth_tex;
layout(set = COMPUTE_SET, binding = 1, r8) writeonly uniform image2D ao_target;
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
mat4 inv_projection;
// x: radius in metres, y: projection[0][0] times half the width of the target in pixels
vec2 params;
// the row of the projection which gives the clip w of a view space position
vec4 clipw_row;
// x: number of slices, y: steps on each side of a slice
ivec2 quality;
};
const float PI = 3.14159265;
const float HALF_PI = 1.57079633;
// share of the radius over which the weight of an occluder falls to nothing
const float FALLOFF_RANGE = 0.615;
// depth of the pixel of the half size target: the top left one of the four full size pixels it covers, so the
// application (postfx_ssao_apply.frag) finds the same depth for it
float half_depth(ivec2 texel)
{
ivec2 full_size = textureSize(depth_tex, 0);
return texelFetch(depth_tex, min(texel * 2, full_size - 1), 0).r;
}
vec3 view_position(ivec2 texel, float depth)
{
ivec2 full_size = textureSize(depth_tex, 0);
vec2 uv = (vec2(texel * 2) + 0.5) / vec2(full_size);
vec4 position = inv_projection * vec4(uv * 2.0 - 1.0, depth, 1.0);
return position.xyz / position.w;
}
// 4x4 ordered pattern, values 0..15
const float BAYER[16] = float[](0.0, 8.0, 2.0, 10.0, 12.0, 4.0, 14.0, 6.0, 3.0, 11.0, 1.0, 9.0, 15.0, 7.0, 13.0, 5.0);
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = imageSize(ao_target);
if (texel.x >= size.x || texel.y >= size.y)
return;
float depth = half_depth(texel);
if (depth <= 0.0)
{
imageStore(ao_target, texel, vec4(1.0));
return;
}
vec3 position = view_position(texel, depth);
vec3 view = normalize(-position);
// normal from the neighbours: on each axis the one closer in depth, so edges of objects do not bend it
vec3 left = view_position(texel - ivec2(1, 0), half_depth(texel - ivec2(1, 0)));
vec3 right = view_position(texel + ivec2(1, 0), half_depth(texel + ivec2(1, 0)));
vec3 down = view_position(texel - ivec2(0, 1), half_depth(texel - ivec2(0, 1)));
vec3 up = view_position(texel + ivec2(0, 1), half_depth(texel + ivec2(0, 1)));
vec3 dx = (abs(right.z - position.z) < abs(position.z - left.z)) ? right - position : position - left;
vec3 dy = (abs(up.z - position.z) < abs(position.z - down.z)) ? up - position : position - down;
vec3 normal = normalize(cross(dx, dy));
if (dot(normal, view) < 0.0)
normal = -normal;
float radius = params.x;
float clipw = dot(clipw_row, vec4(position, 1.0));
float screenradius = params.y * radius / max(clipw, 1e-6);
// smaller than a pixel: nothing to find
if (screenradius < 1.0)
{
imageStore(ao_target, texel, vec4(1.0));
return;
}
screenradius = min(screenradius, 256.0);
float falloffrange = FALLOFF_RANGE * radius;
float falloffmul = -1.0 / falloffrange;
float falloffadd = (radius - falloffrange) / falloffrange + 1.0;
int pattern = (texel.x & 3) + 4 * (texel.y & 3);
float slicenoise = (BAYER[pattern] + 0.5) / 16.0;
float stepnoise = fract(float(pattern) * 0.618034 + 0.25);
int slices = max(quality.x, 1);
int steps = max(quality.y, 1);
float visibility = 0.0;
for (int slice = 0; slice < slices; ++slice)
{
float angle = (float(slice) + slicenoise) * PI / float(slices);
vec2 omega = vec2(cos(angle), sin(angle));
vec3 direction = vec3(omega, 0.0);
vec3 orthodirection = direction - dot(direction, view) * view;
vec3 axis = normalize(cross(orthodirection, view));
vec3 projectednormal = normal - axis * dot(normal, axis);
float projectedlength = length(projectednormal);
if (projectedlength < 1e-4)
continue;
float signnormal = sign(dot(orthodirection, projectednormal));
float cosnormal = clamp(dot(projectednormal, view) / projectedlength, 0.0, 1.0);
float n = signnormal * acos(cosnormal);
float lowcos0 = cos(n + HALF_PI);
float lowcos1 = cos(n - HALF_PI);
float horizoncos0 = lowcos0;
float horizoncos1 = lowcos1;
for (int step = 0; step < steps; ++step)
{
// denser near the pixel, where the occluders matter most
float s = (float(step) + stepnoise) / float(steps);
s = s * s;
vec2 offset = omega * max(s * screenradius, float(step) + 1.0);
for (int side = 0; side < 2; ++side)
{
ivec2 sampletexel = texel + ivec2(round(side == 0 ? offset : -offset));
if (any(lessThan(sampletexel, ivec2(0))) || any(greaterThanEqual(sampletexel, size)))
continue;
float sampledepth = half_depth(sampletexel);
if (sampledepth <= 0.0)
continue;
vec3 delta = view_position(sampletexel, sampledepth) - position;
float distance = length(delta);
if (distance < 1e-5)
continue;
float horizoncos = dot(delta / distance, view);
float weight = clamp(distance * falloffmul + falloffadd, 0.0, 1.0);
if (side == 0)
horizoncos0 = max(horizoncos0, mix(lowcos0, horizoncos, weight));
else
horizoncos1 = max(horizoncos1, mix(lowcos1, horizoncos, weight));
}
}
float h0 = -acos(clamp(horizoncos1, -1.0, 1.0));
float h1 = acos(clamp(horizoncos0, -1.0, 1.0));
h0 = n + clamp(h0 - n, -HALF_PI, HALF_PI);
h1 = n + clamp(h1 - n, -HALF_PI, HALF_PI);
float arc0 = (cosnormal + 2.0 * h0 * sin(n) - cos(2.0 * h0 - n)) * 0.25;
float arc1 = (cosnormal + 2.0 * h1 * sin(n) - cos(2.0 * h1 - n)) * 0.25;
visibility += projectedlength * (arc0 + arc1);
}
visibility = clamp(visibility / float(slices), 0.0, 1.0);
imageStore(ao_target, texel, vec4(visibility));
}

View File

@@ -0,0 +1,46 @@
#version 460 core
#define STAGE_COMPUTE 1
// one level of the depth pyramid of the occlusion test of the instances (the hi-z of the renderers): each texel holds the farthest
// depth of the texels of the level below it which it covers. the depth is reversed (1 near, 0 far), so the farthest is
// the smallest. the first level is made from the resolved depth of the scene, the others from the level below.
// a level halves the one below, rounding down; when that one has an odd size, the last row and column of the new level
// take its last row and column as well, so that nothing is left out
#define LAYOUT_COMPUTE 1
#include "common.glsl"
layout(local_size_x = HIZ_GROUP_SIZE, local_size_y = HIZ_GROUP_SIZE) in;
layout(set = COMPUTE_SET, binding = 0) uniform sampler2D source;
layout(r32f, set = COMPUTE_SET, binding = 1) writeonly uniform image2D target;
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
// xy: size of the source level, zw: size of the target level
ivec4 sizes;
// x: level of the source read
ivec4 params;
};
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(texel, sizes.zw)))
return;
ivec2 first = min(2 * texel, sizes.xy - 1);
ivec2 last = min(2 * texel + 1, sizes.xy - 1);
if (texel.x == sizes.z - 1)
last.x = sizes.x - 1;
if (texel.y == sizes.w - 1)
last.y = sizes.y - 1;
float farthest = 1.0;
for (int y = first.y; y <= last.y; ++y)
{
for (int x = first.x; x <= last.x; ++x)
farthest = min(farthest, texelFetch(source, ivec2(x, y), params.x).r);
}
imageStore(target, texel, vec4(farthest));
}

View File

@@ -0,0 +1,291 @@
#version 460 core
#define STAGE_COMPUTE 1
// culls the model instances of the buckets queued in a render pass and assigns them their lod bands, with the tests
// gfx::scene_walk::Render_Instanced() makes on the cpu. one invocation per record of a queued bucket. the sleepers of the
// tracks queued come as buckets too, with the tests of gfx::scene_walk::Render_Sleepers().
// stage 0 counts the instances which pass the tests, per entry (lod band of a group); after instance_cull_offsets.comp
// turned the counts into the first slots of the entries, stage 1 writes the modelview matrices of the instances drawn first
// there, in no particular order, and stage 2 (two phases) those of the instances drawn second after them.
// with the occlusion test on, an instance whose bounding sphere lies whole behind the depth already drawn in the pass
// (the farthest depth of the pyramid over the part of the view it takes, see hiz_build.comp) is left out as well.
// in two phases (the second phase of the culling) stage 1 takes only the instances seen in the previous frame,
// which are drawn, the pyramid is made again with them in it, and stage 2 tests the others against it; it also keeps for
// the next frame which instances it found in view. an instance seen before but hidden behind the depth of the first
// pyramid is hidden behind the second one as well (it only holds nearer depth), so each instance is drawn at most once,
// and everything left out is hidden behind something drawn
#define LAYOUT_COMPUTE 1
#include "common.glsl"
layout(local_size_x = INSTANCE_CULL_GROUP_SIZE) in;
// scene::basic_cell::instance_record
struct instance_record
{
vec3 position; // relative to the origin of the bucket
float radius;
float rangesquaredmin;
float rangesquaredmax;
uint flags;
uint visible; // the instance was seen in the previous frame (written by stage 2, 0 when the cpu sends the record)
vec4 rotationscale[3];
};
struct instance_cull_job
{
vec3 origin;
uint first;
vec3 distanceorigin;
uint count;
uint group;
uint candidatestart;
uint flags;
uint pad;
};
struct instance_cull_group
{
uint lodfirst;
uint lodcount;
uint entryfirst;
uint pad;
};
layout(std430, set = COMPUTE_SET, binding = 0) buffer instance_record_ssbo
{
instance_record records[];
};
layout(std430, set = COMPUTE_SET, binding = 1) readonly buffer instance_job_ssbo
{
instance_cull_job jobs[];
};
layout(std430, set = COMPUTE_SET, binding = 2) readonly buffer instance_group_ssbo
{
instance_cull_group groups[];
};
layout(std430, set = COMPUTE_SET, binding = 3) readonly buffer instance_lod_ssbo
{
float lodbounds[];
};
layout(std430, set = COMPUTE_SET, binding = 4) buffer instance_counter_ssbo
{
uint counters[];
};
layout(std430, set = COMPUTE_SET, binding = 5) writeonly buffer instance_output_ssbo
{
mat4 modelviews[];
};
// the parameters of the kernel (a block of the ring of the frame: more of them than the push constants hold)
layout(std140, set = COMPUTE_SET, binding = 7) uniform kernel_block
{
// rotation of the view of the pass: the instances are placed relative to its camera
mat4 view;
// planes of the frustum of the pass, relative to its camera
vec4 planes[6];
// x: scale of the squared distance, y: added to it past the range test, z: range of the scaled distance before the
// addition (reflections), w: squared draw range
vec4 distance_params;
// x: zoom factor
vec4 size_params;
// occlusion test: projection of the pass, x: on, y: levels of the pyramid, zw: size of its first level
mat4 hiz_projection;
ivec4 hiz_params;
// xy: size of the depth the pyramid was made from, pixels
vec4 hiz_screen;
// x: two phases, stage 1 takes only the instances seen in the previous frame
uvec4 phases;
};
// the parameters which change from stage to stage (push constants). x: jobs, y: records in all jobs, z: entries, w: stage
layout(push_constant) uniform kernel_constants
{
uvec4 counts;
};
// depth pyramid, farthest (smallest, reversed) depth per texel
layout(set = COMPUTE_SET, binding = 6) uniform sampler2D hiz;
const uint INSTANCE_VISIBLE = 1u;
const uint INSTANCE_INSTANCEABLE = 2u;
// smallest part of the view an instance has to take
const float MIN_SCREEN_SIZE = 0.003;
// header of the counters, see gl::INSTANCE_COUNTER_HEADER
const uint COUNTER_OCCLUDED = 3u;
const uint COUNTER_FIRST = 4u;
// the job which holds the given record: the last one starting at it or before it
uint find_job(uint candidate)
{
uint low = 0u;
uint high = counts.x - 1u;
while (low < high)
{
uint middle = (low + high + 1u) / 2u;
if (jobs[middle].candidatestart <= candidate)
low = middle;
else
high = middle - 1u;
}
return low;
}
// whether a sphere (view space) lies whole behind the depth in the pyramid over the rectangle of the view it takes
bool occluded(vec3 center, float radius)
{
vec2 low = vec2(1.0);
vec2 high = vec2(-1.0);
float nearest = 0.0;
for (int corner = 0; corner < 8; ++corner)
{
vec3 offset = vec3(((corner & 1) != 0) ? radius : -radius, ((corner & 2) != 0) ? radius : -radius, ((corner & 4) != 0) ? radius : -radius);
vec4 clip = hiz_projection * vec4(center + offset, 1.0);
// reaches the plane of the camera: no rectangle to test
if (clip.w <= 1e-3)
return false;
vec3 ndc = clip.xyz / clip.w;
low = min(low, ndc.xy);
high = max(high, ndc.xy);
nearest = max(nearest, ndc.z);
}
if (any(greaterThan(low, vec2(1.0))) || any(lessThan(high, vec2(-1.0))) || (nearest >= 1.0))
return false;
// pixels of the rectangle, then texels of the first level: each covers 2x2 pixels
ivec2 size = hiz_params.zw;
ivec2 pixelfirst = ivec2(clamp((low * 0.5 + 0.5) * hiz_screen.xy, vec2(0.0), hiz_screen.xy - 1.0));
ivec2 pixellast = ivec2(clamp((high * 0.5 + 0.5) * hiz_screen.xy, vec2(0.0), hiz_screen.xy - 1.0));
ivec2 first = min(pixelfirst / 2, size - 1);
ivec2 last = min(pixellast / 2, size - 1);
// the level where the rectangle takes at most 2x2 texels
int level = 0;
while ((level < hiz_params.y - 1) && any(greaterThan((last >> level) - (first >> level), ivec2(1))))
++level;
ivec2 levelsize = max(size >> level, ivec2(1));
first = min(first >> level, levelsize - 1);
last = min(last >> level, levelsize - 1);
float farthest = 1.0;
for (int y = first.y; y <= last.y; ++y)
{
for (int x = first.x; x <= last.x; ++x)
farthest = min(farthest, texelFetch(hiz, ivec2(x, y), level).r);
}
return nearest < farthest;
}
// the tests of the cpu (Render_Instanced(), for sleepers Render_Sleepers()), all but the occlusion one: the entry of the instance, or false when it's left out.
// Location: relative to the camera of the pass
bool passes(instance_cull_job job, instance_record record, out vec3 location, out uint entry)
{
location = job.origin + record.position;
entry = 0u;
// instances hidden, or drawn on their own after getting animations or skins from events
if ((record.flags & (INSTANCE_VISIBLE | INSTANCE_INSTANCEABLE)) != (INSTANCE_VISIBLE | INSTANCE_INSTANCEABLE))
return false;
for (int plane = 0; plane < 6; ++plane)
{
if (dot(planes[plane].xyz, location) + planes[plane].w <= -record.radius)
return false;
}
float distancesquared;
if ((job.flags & INSTANCE_JOB_SLEEPERS) != 0u)
{
// the sleepers of a track, as Render_Sleepers() takes them: the lod by the plain distance from the camera of the pass
distancesquared = dot(location, location);
}
else
{
vec3 offset = job.distanceorigin + record.position;
distancesquared = dot(offset, offset) * distance_params.x;
if (distancesquared > distance_params.z)
return false;
distancesquared += distance_params.y;
if ((distancesquared < record.rangesquaredmin) || (distancesquared >= record.rangesquaredmax))
return false;
if (distancesquared > distance_params.w)
return false;
if (record.radius * record.radius * size_params.x / distancesquared < MIN_SCREEN_SIZE * MIN_SCREEN_SIZE)
return false;
}
// band: the number of lod bounds the distance reached, like upper_bound over the sorted bounds
instance_cull_group group = groups[job.group];
uint band = 0u;
for (uint bound = 0u; bound < group.lodcount; ++bound)
{
if (lodbounds[group.lodfirst + bound] <= distancesquared)
band = bound + 1u;
}
entry = group.entryfirst + band;
return true;
}
void write(uint entry, instance_record record, vec3 location, instance_cull_job job)
{
uint slot = atomicAdd(counters[INSTANCE_COUNTER_HEADER + counts.z + entry], 1u);
if ((job.flags & INSTANCE_JOB_SLEEPERS) != 0u)
{
// in the order Render_Sleepers() multiplies them (the view moved to the origin of the track, then the transform of the
// sleeper), which gives the very same matrix: the other order rounds differently, enough to shift the edges of sleepers
// sunk in the ballast by a pixel here and there
mat4 originview = view * mat4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(job.origin, 1.0));
modelviews[slot] = originview * mat4(record.rotationscale[0], record.rotationscale[1], record.rotationscale[2], vec4(record.position, 1.0));
return;
}
modelviews[slot] = view * mat4(record.rotationscale[0], record.rotationscale[1], record.rotationscale[2], vec4(location, 1.0));
}
void main()
{
// the dispatch can be two dimensional when the records don't fit in the work group count limit of one
uint candidate = (gl_WorkGroupID.y * gl_NumWorkGroups.x + gl_WorkGroupID.x) * INSTANCE_CULL_GROUP_SIZE + gl_LocalInvocationIndex;
if (candidate >= counts.y)
return;
instance_cull_job job = jobs[find_job(candidate)];
uint index = job.first + (candidate - job.candidatestart);
instance_record record = records[index];
vec3 location;
uint entry;
bool passed = passes(job, record, location, entry);
if (counts.w == 0u)
{
// room in the output for every instance which passes, whatever the occlusion test says
if (passed)
atomicAdd(counters[INSTANCE_COUNTER_HEADER + entry], 1u);
return;
}
bool seen = (record.visible != 0u);
if (counts.w == 1u)
{
// the ones seen in the previous frame first, the others wait for stage 2
if ((false == passed) || ((phases.x != 0u) && (false == seen)))
return;
if ((hiz_params.x != 0) && occluded((view * vec4(location, 1.0)).xyz, record.radius))
{
atomicAdd(counters[COUNTER_OCCLUDED], 1u);
return;
}
write(entry, record, location, job);
if (phases.x != 0u)
atomicAdd(counters[COUNTER_FIRST], 1u);
return;
}
// stage 2, against the pyramid with the instances drawn first in it
bool inview = passed && (false == occluded((view * vec4(location, 1.0)).xyz, record.radius));
if (passed && (false == seen))
{
if (inview)
write(entry, record, location, job);
else
atomicAdd(counters[COUNTER_OCCLUDED], 1u);
}
uint visible = (inview ? 1u : 0u);
if (visible != record.visible)
records[index].visible = visible;
}

View File

@@ -0,0 +1,69 @@
#version 460 core
#define STAGE_COMPUTE 1
// fills in the instances of the indirect draw commands of the entries, after instance_cull.comp wrote the modelview
// matrices of the instances drawn. one invocation per command. the commands are kept three times over: the first copy
// draws the instances of stage 1, the second all of them, the third those of stage 2 (two phases), each from its first
// slot in the output on. stage 0 (after stage 1): the instance count of the first copy is how far stage 1 advanced the
// cursor of its entry, the base instance the first slot of the entry. stage 1 (after stage 2): the other two copies take
// the command of the first one, the second with the instances of both stages, the third with the ones past the first copy
#define LAYOUT_COMPUTE 1
#include "common.glsl"
layout(local_size_x = INSTANCE_COMMAND_GROUP_SIZE) in;
layout(std430, set = COMPUTE_SET, binding = 0) readonly buffer instance_counter_ssbo
{
uint counters[];
};
// gl::instance_draw_command, eight uints each: count, instance count, first, base vertex (base instance of an array
// draw), base instance, entry, indexed, unused
layout(std430, set = COMPUTE_SET, binding = 1) buffer instance_command_ssbo
{
uint commands[];
};
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
// x: commands (of one copy), y: entries, z: stage
uvec4 counts;
};
void set_instances(uint field, uint count, uint base)
{
commands[field + 1u] = count;
if (commands[field + 6u] != 0u)
commands[field + 4u] = base;
else
commands[field + 3u] = base;
}
void main()
{
uint command = gl_GlobalInvocationID.x;
if (command >= counts.x)
return;
uint field = command * 8u;
uint entry = commands[field + 5u];
uint first = counters[INSTANCE_COUNTER_HEADER + 2u * counts.y + entry];
uint count = counters[INSTANCE_COUNTER_HEADER + counts.y + entry] - first;
if (counts.z == 0u)
{
set_instances(field, count, first);
return;
}
uint drawnfirst = commands[field + 1u];
uint all = (counts.x + command) * 8u;
uint second = (2u * counts.x + command) * 8u;
for (uint value = 0u; value < 8u; ++value)
{
commands[all + value] = commands[field + value];
commands[second + value] = commands[field + value];
}
set_instances(all, count, first);
set_instances(second, count - drawnfirst, first + drawnfirst);
}

View File

@@ -0,0 +1,62 @@
#version 460 core
#define STAGE_COMPUTE 1
// turns the counts of instances of the entries, written by stage 0 of instance_cull.comp, into the first slots of the
// entries in the output (cursors, which stages 1 and 2 advance as they write, and the same first slots kept for
// instance_cull_commands.comp), and puts the total in the header.
// a single work group: each invocation sums a run of the entries, then the sums are scanned in shared memory
#define LAYOUT_COMPUTE 1
#include "common.glsl"
layout(local_size_x = INSTANCE_OFFSETS_GROUP_SIZE) in;
layout(std430, set = COMPUTE_SET, binding = 0) buffer instance_counter_ssbo
{
uint counters[];
};
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
// x: entries
uvec4 counts;
};
shared uint sums[INSTANCE_OFFSETS_GROUP_SIZE];
void main()
{
uint entries = counts.x;
uint invocation = gl_LocalInvocationIndex;
uint run = (entries + INSTANCE_OFFSETS_GROUP_SIZE - 1u) / INSTANCE_OFFSETS_GROUP_SIZE;
uint begin = min(invocation * run, entries);
uint end = min(begin + run, entries);
uint sum = 0u;
for (uint entry = begin; entry < end; ++entry)
sum += counters[INSTANCE_COUNTER_HEADER + entry];
sums[invocation] = sum;
memoryBarrierShared();
barrier();
// inclusive scan of the sums
for (uint step = 1u; step < INSTANCE_OFFSETS_GROUP_SIZE; step *= 2u)
{
uint added = (invocation >= step ? sums[invocation - step] : 0u);
memoryBarrierShared();
barrier();
sums[invocation] += added;
memoryBarrierShared();
barrier();
}
uint offset = sums[invocation] - sum;
for (uint entry = begin; entry < end; ++entry)
{
counters[INSTANCE_COUNTER_HEADER + entries + entry] = offset;
counters[INSTANCE_COUNTER_HEADER + 2u * entries + entry] = offset;
offset += counters[INSTANCE_COUNTER_HEADER + entry];
}
if (invocation == INSTANCE_OFFSETS_GROUP_SIZE - 1u)
counters[1] = sums[invocation];
}

View File

@@ -1,13 +1,21 @@
// stage D of the vulkan renderer: the sun with its cascaded shadows, the environment map, the headlights and the lamps (in the
// clusters or not). the cube shadow maps of the lamps, the shadow maps of the headlights, the contact shadows, the shadows of
// the clouds, the directional sky ambient light and the prefiltered environment map of shaders/ogl46/light_common.glsl come
// with the later stages; the code below is the one of the opengl renderer with those left out, it shades as that one does
// with them off
// the shading of the scene, shaders/ogl46/light_common.glsl with the bindings of the pass set of the vulkan renderer
// (rendering/vulkan14/vulkan14bindings.h): the sun with its cascaded shadows and the shadows of the clouds, the environment map
// with its prefiltered levels, the headlights with their beams and shadow maps, the lamps (in the clusters or not) with their
// cube shadow maps, the contact shadows and the directional ambient light of the sky
layout(set = PASS_SET, binding = SHADOW_TEX) uniform sampler2DArrayShadow shadowmap;
layout(set = PASS_SET, binding = HEADLIGHT_TEX) uniform sampler2D headlightmap;
// cube shadow maps of the lamps, see opengl46_local_shadows
layout(set = PASS_SET, binding = LOCAL_SHADOW_TEX) uniform samplerCubeArrayShadow local_shadowmap;
// the same for the lamps of scenery models, the lights with linear 1
layout(set = PASS_SET, binding = LOCAL_SHADOW_LAMPS_TEX) uniform samplerCubeArrayShadow local_shadowmap_lamps;
// shadow maps of the headlights of vehicles, see gl::scene_ubs::headlight_shadow
layout(set = PASS_SET, binding = HEADLIGHT_SHADOW_TEX) uniform sampler2DArrayShadow headlight_shadowmap;
// depth of the opaque scene drawn before the colour, for the contact shadows (gl::scene_ubs::contact_shadow_params)
layout(set = PASS_SET, binding = SCENE_DEPTH_TEX) uniform sampler2D scene_depth;
#include "envmapping.glsl"
#include "conversion.glsl"
#include "weather_common.glsl"
float glossiness = 1.0;
float metalic = 0.0;
@@ -40,11 +48,80 @@ float length2(vec3 v)
return dot(v, v);
}
// share of the sun (the moon) held back before the fragment by the shadow map, 0..1
// how many more lamps and headlights the fragment traces contact shadows to, see add_headlights()
int contact_shadow_budget = 0;
// contact shadow of a light in direction Light_dir (view space, towards the light), 0..1: whether the depth of the scene drawn
// before the colour (scene_depth) holds a surface between the fragment and a point up to Length metres towards the light. it
// catches what the shadow maps are too coarse for (rails on the sleepers, bolts, edges of panels, where a thing stands on the
// ground), and things too small or near for them. only what is in the depth casts it, and only within the image.
// the ray starts a pixel and a half off the visible side of the surface: the depth there, sampled anywhere within a pixel of
// the projected point, then stays behind the ray on a flat surface, so it doesn't shadow itself. a surface nearer than the
// ray by more than the thickness is taken for something in front of the ray, not something it goes through.
// the shadow fades over the last part of the ray (it doesn't end with an edge) and over the last quarter of the range
float contact_shadow(vec3 light_dir, float raylength, float steps)
{
float range = contact_shadow_params.y;
vec3 position = f_pos.xyz;
float fade = 1.0 - smoothstep(range * 0.75, range, length(position));
if ((fade <= 0.0) || (raylength <= 0.0) || (steps < 1.0))
return 0.0;
vec3 normal = normalize(f_normal);
if (dot(normal, position) > 0.0)
normal = -normal;
if (dot(normal, light_dir) <= 0.0)
return 0.0;
vec2 size = vec2(textureSize(scene_depth, 0));
// the projection is perspective with the depth n / w (reverse z, infinite far plane): w is the distance along the axis
// of the view, and a depth d is a surface at n / d
float near = projection[3][2];
float distance = (projection * vec4(position, 1.0)).w;
float pixel = 2.0 * distance / (abs(projection[1][1]) * size.y);
vec4 start = projection * vec4(position + normal * (pixel * 1.5), 1.0);
vec4 ray = projection * vec4(light_dir * raylength, 0.0);
float thickness = 0.3 + pixel * 4.0;
float bias = pixel * 0.5;
// a different start along the ray in neighbouring pixels: the steps give noise instead of bands
float jitter = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));
for (float i = 0.0; i < steps; i += 1.0)
{
float t = (i + jitter) / steps;
vec4 clip = start + ray * t;
if (clip.w <= near)
break;
vec2 coords = (clip.xy / clip.w) * 0.5 + 0.5;
if (any(lessThan(coords, vec2(0.0))) || any(greaterThan(coords, vec2(1.0))))
break;
float depth = texelFetch(scene_depth, min(ivec2(coords * size), ivec2(size) - 1), 0).r;
// nothing drawn there (the sky)
if (depth <= 0.0)
continue;
float nearer = clip.w - near / depth;
if ((nearer > bias) && (nearer < thickness))
return fade * (1.0 - smoothstep(0.6, 1.0, t));
}
return 0.0;
}
// contact shadow of a lamp or headlight Distance metres away, whose light reaches the fragment with Strength, for the first
// few lamps of a fragment (contact_shadow_lamps.x) which light it noticeably. the ray goes at most half way to the light,
// so the lamp doesn't shadow with its own fitting
float contact_shadow_lamp(vec3 light_dir, float distance, float strength)
{
if ((contact_shadow_budget <= 0) || (strength < 0.005))
return 0.0;
--contact_shadow_budget;
return contact_shadow(light_dir, min(contact_shadow_params.x, distance * 0.5), contact_shadow_params.w);
}
// share of the sun (the moon) held back before the fragment by the shadow map and the contact shadow, 0..1
float calc_shadow_map();
float calc_shadow()
{
return calc_shadow_map();
float shadow = calc_shadow_map();
if ((contact_shadow_params.x > 0.0) && (shadow < 1.0))
shadow = max(shadow, contact_shadow(normalize(-lights[0].dir), contact_shadow_params.x, contact_shadow_params.z));
return shadow;
}
float calc_shadow_map()
@@ -295,6 +372,52 @@ vec2 calc_dir_light(light_s light, vec3 fragnormal)
return calc_light(light_dir, fragnormal);
}
// samples of the soft shadows of the lamps and headlights: a disc, rotated per pixel
const vec2 SHADOW_DISC[8] = vec2[](vec2(-0.326, -0.406), vec2(-0.840, -0.074), vec2(-0.696, 0.457), vec2(-0.203, 0.621),
vec2(0.962, -0.195), vec2(0.473, -0.480), vec2(0.519, 0.767), vec2(0.185, -0.893));
float shadow_disc_angle()
{
return 6.2831853 * fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));
}
// share of the beam of headlights with a shadow map (Map, headlight_weights.w - 1 of the light) held back before the fragment,
// 0..1. the map was drawn from the projector of the beam: x and y of the projection across it, z / w the depth (near plane over
// the distance along the axis, as the reverse z of the scene), w the distance. the comparison gives 1 where the map holds a
// value at least as large as the reference: a surface closer to the projector
float headlight_beam_shadow(uint map, vec3 fragnormal, vec3 light_dir)
{
mat4 tomap = headlight_shadow[map];
vec4 params = headlight_shadow_params[map];
// a texel of the map is as large as this here. the point is moved along the normal by about a texel, and the reference towards
// the projector by a part of one. only the back faces are in the map, which leaves the lit faces little to shadow themselves with
float texel = params.x * max((tomap * vec4(f_pos.xyz, 1.0)).w, 0.0);
float NdotL = clamp(dot(fragnormal, light_dir), 0.0, 1.0);
vec4 position = tomap * vec4(f_pos.xyz + fragnormal * (texel * (0.5 + 1.5 * (1.0 - NdotL))), 1.0);
if (position.w <= 0.0)
return 0.0;
vec2 coords = position.xy / position.w;
if (any(lessThan(coords, vec2(0.0))) || any(greaterThan(coords, vec2(1.0))))
return 0.0;
// nearer to the projector than its near plane (the vehicle itself) the reference is past anything in the map
float reference = position.z / max(position.w - texel * 1.5, 1e-3);
float layer = float(map);
float radius = params.y;
if (radius <= 0.0)
return texture(headlight_shadowmap, vec4(coords, layer, reference));
float angle = shadow_disc_angle();
vec2 rotation = vec2(cos(angle), sin(angle));
float spread = radius / float(textureSize(headlight_shadowmap, 0).x);
int count = (radius > 1.5) ? 8 : 4;
float shadow = 0.0;
for (int i = 0; i < count; i++)
{
vec2 offset = vec2(SHADOW_DISC[i].x * rotation.x - SHADOW_DISC[i].y * rotation.y, SHADOW_DISC[i].x * rotation.y + SHADOW_DISC[i].y * rotation.x) * spread;
shadow += texture(headlight_shadowmap, vec4(coords + offset, layer, reference));
}
return shadow / float(count);
}
// dpdx and dpdy are the change of the view space position of the fragment from a pixel to the next one. the beam texture is
// sampled with gradients worked out from them instead of the ones of the hardware: those are undefined where the fragments of
// a quad do not go through the same lights, which is what happens at the edges of the clusters
@@ -327,6 +450,12 @@ vec2 calc_headlights(light_s light, vec3 fragnormal, vec3 dpdx, vec3 dpdy)
vec2 dbeamdy = (dheadlightposdy.xy - beamcoords * dheadlightposdy.w) / headlightpos.w;
vec3 lights = textureGrad(headlightmap, beamcoords, dbeamdx, dbeamdy).rgb * light.headlight_weights.rgb;
float lightintensity = max(max(lights.r, lights.g), lights.b);
// headlights with a shadow map: what is in the way holds the beam back
if ((light.headlight_weights.w > 0.5) && (lightintensity > 0.0) && (part.x > 0.0))
lightintensity *= 1.0 - headlight_beam_shadow(uint(light.headlight_weights.w - 0.5), fragnormal, light_dir);
if ((contact_shadow_budget > 0) && (lightintensity > 0.0) && (part.x > 0.0))
lightintensity *= 1.0 - contact_shadow_lamp(light_dir, distance,
part.x * atten * lightintensity * light.intensity * max(light.color.r, max(light.color.g, light.color.b)));
// x the diffuse light, y the light the specular reflection takes (specular_light() brings its own n.l)
return vec2(part.x, 1.0) * (atten * lightintensity);
}
@@ -334,10 +463,59 @@ vec2 calc_headlights(light_s light, vec3 fragnormal, vec3 dpdx, vec3 dpdy)
// closest distance (squared) the light of a lamp is computed for: nearer the inverse square would grow without a limit
const float LOCAL_LIGHT_MIN_DISTANCE2 = 0.01;
// one comparison with the cube shadow map at the point of the space of the map, moved towards the lamp by Bias (metres). the
// map is in the array of the scenery lamps when Lamps, in the one of the cab lamp otherwise
float local_light_shadow_tap(vec3 position, float layer, float near, float bias, bool lamps)
{
vec3 axes = abs(position);
float major = max(axes.x, max(axes.y, axes.z));
float reference = near / max(major - bias, near);
return lamps ? texture(local_shadowmap_lamps, vec4(position, layer), reference) : texture(local_shadowmap, vec4(position, layer), reference);
}
// share of the light of a lamp with a cube shadow map (headlight_weights.x is its layer) which reaches the fragment, 0..1.
// the map holds n / d of the closest surface, d the distance along the axis of the face, n the near plane: the farther, the
// smaller (as the depth of the scene, reverse z). the comparison gives 1 where the map holds a value at least as large as
// the reference, i.e. a surface at most as far: in shadow
float local_light_shadow(light_s light, vec3 fragnormal)
{
// the space of the map: origin at the lamp, axes of the model the shadows are cast by
vec3 position = (light.headlight_projection * vec4(f_pos.xyz, 1.0)).xyz;
vec3 normal = normalize(mat3(light.headlight_projection) * fragnormal);
float distance = length(position);
// a texel of the map is as large as this here. the point is moved along the normal by about a texel, which keeps a surface
// from shadowing itself at grazing angles, and the reference is moved towards the lamp by a part of a texel
float texel = light.headlight_weights.z * distance;
vec3 tolamp = -position / max(distance, 1e-4);
float NdotL = clamp(dot(normal, tolamp), 0.0, 1.0);
position += normal * (texel * (0.5 + 1.5 * (1.0 - NdotL)));
float bias = texel * 1.5;
float layer = light.headlight_weights.x;
float radius = light.headlight_weights.w;
bool lamps = (light.linear > 0.5);
if (radius <= 0.0)
return local_light_shadow_tap(position, layer, light.headlight_weights.y, bias, lamps);
// a disc of samples in the plane of the surface, each compared with its own distance: points of the surface itself, which
// samples spread across the direction to the lamp would not be at grazing angles. rotated per pixel to turn the banding into noise
vec3 side = normalize(cross(normal, (abs(normal.y) < 0.99) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0)));
vec3 up = cross(normal, side);
float angle = shadow_disc_angle();
vec2 rotation = vec2(cos(angle), sin(angle));
float spread = texel * radius;
int count = (radius > 1.5) ? 8 : 4;
float shadow = 0.0;
for (int i = 0; i < count; i++)
{
vec2 offset = vec2(SHADOW_DISC[i].x * rotation.x - SHADOW_DISC[i].y * rotation.y, SHADOW_DISC[i].x * rotation.y + SHADOW_DISC[i].y * rotation.x) * spread;
shadow += local_light_shadow_tap(position + side * offset.x + up * offset.y, layer, light.headlight_weights.y, bias, lamps);
}
return shadow / float(count);
}
// light of a lamp (point or spot light: the lamps of models and vehicles, the cab lamp) reaching the fragment, x the diffuse
// part and y the light the specular reflection takes (specular_light() brings its own n.l). it falls off with the square of the distance, and smoothly to nothing at the range of the light,
// 1 / sqrt(quadratic), past which the culling of the clusters does not take it. a spot light fades from its inner cone to the
// outer one
// outer one. a lamp with a shadow map is held back by what is between it and the fragment
vec2 calc_local_light(light_s light, vec3 fragnormal)
{
vec3 tolight = light.pos - f_pos.xyz;
@@ -347,6 +525,11 @@ vec2 calc_local_light(light_s light, vec3 fragnormal)
float atten = edge * edge / max(distance2, LOCAL_LIGHT_MIN_DISTANCE2);
if (light.type == LIGHT_SPOT)
atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-light_dir, normalize(light.dir)));
if ((atten > 0.0) && (light.headlight_weights.x >= 0.0) && (dot(fragnormal, light_dir) > 0.0))
atten *= 1.0 - local_light_shadow(light, fragnormal);
if ((atten > 0.0) && (contact_shadow_budget > 0))
atten *= 1.0 - contact_shadow_lamp(light_dir, sqrt(distance2),
atten * max(dot(fragnormal, light_dir), 0.0) * light.intensity * max(light.color.r, max(light.color.g, light.color.b)));
if (atten <= 0.0)
return vec2(0.0);
return vec2(max(dot(fragnormal, light_dir), 0.0), 1.0) * atten;
@@ -384,6 +567,7 @@ void add_local_lights(vec3 fragnormal, inout vec3 diffuse, inout vec3 specular)
// worked out before the loops, which the fragments of a quad do not leave together
vec3 dpdx = dFdx(f_pos.xyz);
vec3 dpdy = dFdy(f_pos.xyz);
contact_shadow_budget = (contact_shadow_params.x > 0.0) ? int(contact_shadow_lamps.x) : 0;
if (lights_clustered != 0U)
{
@@ -452,6 +636,31 @@ vec3 material_diffuse()
return (value > 0.0 ? diffusecolor * (param[1].x / value) : vec3(0.0));
}
// directional part of the sky ambient light (sky_ssbo, bands 1 and 2 of the spherical harmonics) for a world space direction
vec3 sky_ambient_directional(vec3 n)
{
return sky_ambient_sh[1].rgb * (0.488603 * n.y)
+ sky_ambient_sh[2].rgb * (0.488603 * n.z)
+ sky_ambient_sh[3].rgb * (0.488603 * n.x)
+ sky_ambient_sh[4].rgb * (1.092548 * n.x * n.y)
+ sky_ambient_sh[5].rgb * (1.092548 * n.y * n.z)
+ sky_ambient_sh[6].rgb * (0.315392 * (3.0 * n.z * n.z - 1.0))
+ sky_ambient_sh[7].rgb * (1.092548 * n.x * n.z)
+ sky_ambient_sh[8].rgb * (0.546274 * (n.x * n.x - n.y * n.y));
}
// adds the directional part of the sky light to the ambient light the material starts from (the uniform ambient, or a share
// of it, like the water takes). the same share of the directional part is added, so with the directional part at zero
// (gfx.ambient.sh off) the result is what it always was
vec3 add_sky_ambient(vec3 basecolor, vec3 fragnormal)
{
if (sky_ambient_factor == 0.0)
return basecolor;
float share = dot(basecolor, vec3(1.0)) / max(dot(ambient, vec3(1.0)), 1e-4);
vec3 worldnormal = normalize(mat3(inv_view) * fragnormal);
return max(basecolor + sky_ambient_directional(worldnormal) * (sky_ambient_factor * share), vec3(0.0));
}
// lights the surface: the ambient light, the sun (the moon), the headlights and lamps and the reflection of the surroundings.
// the units are the ones of the ambient light, the irradiance over pi: a white lambertian surface lit by 1 of it shows 1.
// Texturecolor is the albedo (the base colour of a metal), Specularity the specular level of a dielectric (0.5 is the
@@ -471,6 +680,8 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref
}
else
{
// the sky lights the faces turned up and towards the bright part of the sky more than the ones turned to the ground
fragcolor = add_sky_ambient(fragcolor, fragnormal);
#ifndef NO_WEATHERING
float weathered = (param[2].w > 0.0 ? clamp(param[2].w * UserData.x, 0.0, 1.0) : 0.0);
float tinted = (param[2].w < 0.0 ? clamp(-param[2].w * UserData.x, 0.0, 1.0) : 0.0);
@@ -541,8 +752,8 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref
// 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(NdotL, SUN_NDOTL_SHARPNESS);
// the shadows of the clouds come with the weather (stage P2): nothing holds the sun back yet
float cloudsun = 1.0;
// 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
vec3 sunlight = lights[0].color * (SUN_DIFFUSE_SCALE * lights[0].intensity * cloudsun);

View File

@@ -0,0 +1,4 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
#include "mat_terrain.glsl"

View File

@@ -0,0 +1,158 @@
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 8) in mat3 f_tbn;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
#include "common.glsl"
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
// heightmap terrain: up to 8 materials (layers) painted over a chunk, blended by their weights.
// the textures of all layers of the terrain are kept in texture arrays; the table of the chunk tells which layers it has:
// row 0, per layer: index in the arrays, 1 / metres the textures repeat at, has normal map, has specular/gloss map
// row 1, per layer: reflection, cosine and sine of the angle the textures are turned by
// f_coord holds metres from the corner of the pack of chunks, along x and -z
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, one)
#pragma param(glossiness, 1, 3, 1, glossiness)
// corner of the chunk (x, z) in texture coordinates, 1 / size of the chunk, paint samples along a side less one
#pragma param(placement, 2, 0, 4, zero)
#pragma texture(opaque, 0, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D opaque;
#pragma texture(weights0, 1, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D weights0;
#pragma texture(weights1, 2, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 2) uniform sampler2D weights1;
#pragma texture(layerdiffuse, 3, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 3) uniform sampler2DArray layerdiffuse;
#pragma texture(layernormal, 4, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 4) uniform sampler2DArray layernormal;
#pragma texture(layerspecgloss, 5, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 5) uniform sampler2DArray layerspecgloss;
#pragma texture(layertable, 6, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 6) uniform sampler2D layertable;
#define NORMALMAP
// param[2] holds the placement of the chunk, not the weathering
#define NO_WEATHERING
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
void main()
{
// place within the chunk, 0..1 along x and z, and the paint samples around it
vec2 local = clamp(vec2(f_coord.x - param[2].x, -f_coord.y - param[2].y) * param[2].z, 0.0, 1.0);
#if TERRAIN_HOLES
// the openings cut in the ground (mat_terrain_holes.frag): the mask led between its samples, cut over the half.
// discarded at the end, the neighbours of the fragment still need it for their derivatives
float holeside = float(textureSize(holes, 0).x);
bool cut = (texture(holes, (local * (holeside - 1.0) + 0.5) / holeside).r > 0.5);
#endif
float samples = param[2].w;
vec2 paintcoord = (local * samples + 0.5) / (samples + 1.0);
vec4 w0 = texture(weights0, paintcoord);
vec4 w1 = texture(weights1, paintcoord);
float weights[8] = float[8](w0.r, w0.g, w0.b, w0.a, w1.r, w1.g, w1.b, w1.a);
float painted = dot(w0, vec4(1.0)) + dot(w1, vec4(1.0));
// the layers are sampled only where they're painted, so the gradients are taken here, where every pixel gets them
vec2 coorddx = dFdx(f_coord);
vec2 coorddy = dFdy(f_coord);
vec3 albedo = vec3(0.0);
vec3 normal = vec3(0.0);
float reflectivity = 0.0;
float specularity = 0.0;
float gloss = 0.0;
float metal = 0.0;
float total = 0.0;
for (int i = 0; i < 8; ++i)
{
// a chunk without weights shows its first layer
float weight = (painted < 0.004 ? (i == 0 ? 1.0 : 0.0) : weights[i]);
if (weight < 0.004)
continue;
vec4 info = texelFetch(layertable, ivec2(i, 0), 0);
vec4 extra = texelFetch(layertable, ivec2(i, 1), 0);
// turned counterclockwise seen from above; f_coord runs along x and -z, so that's clockwise in it
vec2 turn = (extra.y == 0.0 && extra.z == 0.0) ? vec2(1.0, 0.0) : extra.yz;
mat2 rotation = mat2(turn.x, -turn.y, turn.y, turn.x);
vec3 coord = vec3(rotation * f_coord * info.y, info.x);
vec2 dx = rotation * coorddx * info.y;
vec2 dy = rotation * coorddy * info.y;
albedo += textureGrad(layerdiffuse, coord, dx, dy).rgb * weight;
vec3 layernormal_ts = vec3(0.0, 0.0, 1.0);
float layerreflection = extra.x;
if (info.z > 0.5)
{
vec4 normalmap = textureGrad(layernormal, coord, dx, dy);
layernormal_ts.xy = normalmap.rg * 2.0 - 1.0;
layernormal_ts.z = sqrt(1.0 - clamp(dot(layernormal_ts.xy, layernormal_ts.xy), 0.0, 1.0));
// the normal map leans along the turned texture; turned back to the axes of the terrain
layernormal_ts.xy = layernormal_ts.xy * rotation;
layerreflection *= normalmap.a;
}
normal += layernormal_ts * weight;
if (info.w > 0.5)
{
vec4 specgloss = textureGrad(layerspecgloss, coord, dx, dy);
specularity += specgloss.r * weight;
gloss += specgloss.g * weight;
metal += specgloss.b * weight;
}
else
{
// a layer without a specgloss map: a dielectric of the glossiness: of the material (a blinn-phong exponent)
specularity += 0.5 * weight;
gloss += (1.0 - phong_roughness(abs(param[1].w))) * weight;
}
reflectivity += layerreflection * weight;
total += weight;
}
total = max(total, 0.004);
albedo /= total;
specularity /= total;
metal /= total;
reflectivity = param[1].z * reflectivity / total;
glossiness = (gloss / total) * abs(param[1].w);
metalic = metal;
vec3 fragnormal = normalize(f_tbn * normalize(normal));
vec3 fragcolor = ambient;
fragcolor = apply_lights(fragcolor, fragnormal, albedo, reflectivity, specularity, shadow_tone);
#if TERRAIN_HOLES
if (cut)
discard;
#endif
vec4 color = vec4(apply_fog(fragcolor), alpha_mult);
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
}

View File

@@ -0,0 +1,12 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
// heightmap terrain with holes cut in it, for the portals of tunnels and the like (opengl46_terrain_materials): the cells cut
// out entirely are left out of the mesh, the rest of the openings is cut here. the mask of the holes covers the chunk, 255
// where the ground is cut; the fragments where the value led between its samples is over the half are discarded
#define TERRAIN_HOLES 1
#pragma texture(holes, 7, R)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 7) uniform sampler2D holes;
#include "mat_terrain.glsl"

View File

@@ -0,0 +1,96 @@
// storage of the particles of the gpu (gfx::gpu_particles, gl::particle_* in gl/ubo.h) for the compute passes; a kernel which
// draws defines PARTICLE_COMMAND_BINDING and PARTICLE_OUTPUT_BINDING, the bindings of its pushed set the command and the draws
// are at
#ifndef PARTICLE_COMMON_GLSL
#define PARTICLE_COMMON_GLSL
// a particle of smoke; it's dead (or its place was never used) when its opacity is 0 or less
struct smoke_particle_s
{
vec4 position; // xyz: metres from the origin of the particles (world axes), w: rotation, radians
vec4 velocity; // xyz: m/s, w: size of the billboard, metres
vec4 state; // x: opacity, y: age, seconds
};
// a source of smoke as the last update of the simulation left it (smoke_source::gpu_state)
struct smoke_source_s
{
uvec4 pool; // x: first place of the source in the storage, y: number of its places, z: place of the first particle born in the update, w: particles born
vec4 location; // xyz: emitter at the update, from the origin
vec4 previous; // xyz: emitter at the update before, from the origin
vec4 launch; // xy: inclination, radians, zw: speed, m/s
vec4 initial; // xy: size, zw: opacity
vec4 sizechange; // x: per second, y: min, z: max, w: per metre the particle goes through the air (spread:)
vec4 opacitychange; // x: per second, y: min, z: max
vec4 motion; // x: rise, y: damping (per second, of the vertical velocity), z: least vertical velocity
vec4 settling; // x: velocity the particles settle at, m/s upwards, y: drag, per second, z: 1 when they settle (in place of motion), w: seconds they take to show up (fade_in:)
vec4 color; // rgb: colour of the smoke, lit by the lights of the pass
vec4 oldcolor; // rgb: colour lit as the cpu does it, without the lights (gfx.smoke.lighting off)
};
// what a draw of a particle reads, as attributes of an instance
struct particle_draw_s
{
vec4 place; // xyz: metres from the camera (world axes), w: size (smoke) or width (rain, snow)
vec4 shape; // x: rotation (smoke); xyz: from the head of the streak to its tail (rain, snow)
vec4 color; // rgb: lit colour, a: opacity
};
#ifdef PARTICLE_OUTPUT_BINDING
// the indirect draw command of the particles; the compute passes count the instances
layout(std430, set = COMPUTE_SET, binding = PARTICLE_COMMAND_BINDING) buffer particle_command_ssbo
{
uint particle_draw_vertices;
uint particle_draw_instances;
uint particle_draw_first;
uint particle_draw_base;
};
layout(std430, set = COMPUTE_SET, binding = PARTICLE_OUTPUT_BINDING) writeonly buffer particle_output_ssbo
{
particle_draw_s particle_draws[];
};
#endif
// pcg hash
uint particle_hash(uint value)
{
uint state = value * 747796405U + 2891336453U;
uint word = ((state >> ((state >> 28U) + 4U)) ^ state) * 277803737U;
return (word >> 22U) ^ word;
}
// next of a sequence of random numbers 0-1
float particle_random(inout uint state)
{
state = particle_hash(state);
return float(state >> 8U) * (1.0 / 16777216.0);
}
// whether a sphere (centre from the camera along the world axes) is within the planes of the view
bool particle_visible(vec4 planes[5], vec3 centre, float radius)
{
for (int i = 0; i < 5; i++)
if (dot(planes[i].xyz, centre) + planes[i].w < -radius)
return false;
return true;
}
#ifdef PARTICLE_OUTPUT_BINDING
// adds a particle to the draw; Capacity: the places of the storage of the draws
void particle_emit(vec3 place, float size, vec3 shape, vec3 color, float opacity, uint capacity)
{
uint index = atomicAdd(particle_draw_instances, 1U);
if (index >= capacity)
{
atomicAdd(particle_draw_instances, 0xFFFFFFFFU);
return;
}
particle_draws[index].place = vec4(place, size);
particle_draws[index].shape = vec4(shape, 0.0);
particle_draws[index].color = vec4(color, opacity);
}
#endif
#endif

View File

@@ -0,0 +1,147 @@
// light of the particles of the gpu (gfx::gpu_particles: smoke, rain, snow) at a point of the view space of the pass, as a
// particle takes it as a whole, at its middle: the ambient light, the sun (the moon at night) held back by the shadow map and
// by the clouds, and the lamps and headlights of the light cluster the point is in (the first lights of lights[] without the
// clusters). each light is scattered towards the camera by the phase of the particle, Forward: 0 evenly, towards 1 mostly
// onwards. the scale is the one of the shading of the surfaces (light_common.glsl): a white particle takes as much light as
// a white face turned half way to it. for the compute passes; needs <common> and <weather_common.glsl>
#ifndef PARTICLE_LIGHT_GLSL
#define PARTICLE_LIGHT_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;
// AMBIENT_SCALE and SUN_DIFFUSE_SCALE of light_common.glsl
const float PARTICLE_AMBIENT_SCALE = 0.65;
const float PARTICLE_SUN_SCALE = 1.5;
// Henyey-Greenstein, 1 for every angle when G is 0
float particle_phase(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 (as fog_inject.comp)
float particle_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;
for (; cascade < MAX_CASCADES; cascade++)
{
vec4 lightpos = lightview[cascade] * vec4(position, 1.0);
vec3 coords = lightpos.xyz / lightpos.w;
if (all(greaterThanEqual(coords.xy, vec2(0.0))) && all(lessThanEqual(coords.xy, vec2(1.0))))
{
if (coords.z < 0.0)
return 0.0;
return texture(shadowmap, vec4(coords.xy, float(cascade), coords.z + 0.0001 * float(cascade + 1U)));
}
// outside the map: lit, unless the cascades are fitted to the depth of the view (gfx.shadow.sdsm), whose maps cover only
// the area of the surfaces seen; then the first further cascade whose map holds the point
if (shadow_fit.x <= 0.0)
return 0.0;
}
return 0.0;
}
else
{
return 0.0;
}
}
// light of a lamp or of the beam of headlights reaching a point of the view space (as fog_inject.comp; the beams with their
// shadow maps, the lamps without)
vec3 particle_lamp(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 ((light.headlight_weights.w > 0.5) && (intensity > 0.0))
{
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);
}
// the light cluster of a point of the view space in front of the camera, as fragment_cluster() of light_common.glsl finds it for
// the fragment drawn there
uint particle_cluster(vec3 position)
{
vec4 clip = cluster_projection * vec4(position, 1.0);
vec2 screen = clip.xy / clip.w * 0.5 + 0.5;
ivec2 tile = clamp(ivec2(screen * 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(max(clip.w, 1e-4)) * cluster_slice_scale)), 0, CLUSTER_GRID_Z - 1);
return uint(tile.x + CLUSTER_GRID_X * (tile.y + CLUSTER_GRID_Y * slice));
}
// Position: of the particle in the view space; World: its offset from the camera along the world axes
vec3 particle_light(vec3 position, vec3 world, float forward)
{
vec3 tocamera = -normalize(position);
vec3 light = ambient * PARTICLE_AMBIENT_SCALE;
if (lights_count > 0U)
{
float sunlit = cloud_shadow(world) * (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));
}
vec3 lamps = vec3(0.0);
if (lights_clustered != 0U)
{
if ((cluster_valid != 0U) && (position.z < 0.0))
{
cluster_range_s range = cluster_ranges[particle_cluster(position)];
for (uint i = 0U; i < range.count; i++)
{
light_s lamp = lights[cluster_light_indices[range.offset + i]];
vec3 fromlamp = position - lamp.pos;
lamps += particle_lamp(lamp, position) * particle_phase(dot(fromlamp, tocamera) * inversesqrt(max(dot(fromlamp, fromlamp), 1e-8)), forward);
}
}
}
else
{
for (uint i = 1U; i < lights_count; i++)
{
vec3 fromlamp = position - lights[i].pos;
lamps += particle_lamp(lights[i], position) * particle_phase(dot(fromlamp, tocamera) * inversesqrt(max(dot(fromlamp, fromlamp), 1e-8)), forward);
}
}
return light + lamps * 0.5;
}
#endif

View File

@@ -0,0 +1,24 @@
// soft particles (gfx::gpu_particles): a particle fades where it nears what is behind it, instead of being cut by it.
// the resolved depth of the scene drawn so far (SCENE_DEPTH_TEX of the pass set), reversed with the far plane at infinity: the distance
// along the axis of the view is the near plane over the depth, and the sky, which writes no depth, stays at 0
#ifndef PARTICLE_SOFT_GLSL
#define PARTICLE_SOFT_GLSL
layout(set = PASS_SET, binding = SCENE_DEPTH_TEX) uniform sampler2D particle_scene_depth;
// share of the particle left at the fragment, 0-1: Range (metres) is how far in front of the scene it fades out over, 0 for none.
// Fragdistance: of the fragment along the axis of the view
float particle_soft_fade(float range, float fragdistance)
{
if (range <= 0.0)
return 1.0;
float depth = texelFetch(particle_scene_depth, ivec2(gl_FragCoord.xy), 0).r;
if (depth <= 0.0)
return 1.0;
float scene = projection[3][2] / depth;
float share = clamp((scene - fragdistance) / range, 0.0, 1.0);
return share * share * (3.0 - 2.0 * share);
}
#endif

View File

@@ -0,0 +1,84 @@
#version 460 core
#define STAGE_COMPUTE 1
// rain and snow of the gpu (gfx::gpu_particles), in every colour pass which draws them: a thread per drop or flake. the
// particles fill three boxes around the camera, a small dense one and two larger ones, each repeated along the world axes:
// a particle keeps its place in the box, moved by how far the wind took them all and how far it fell, so nothing is kept
// from frame to frame. what the camera sees is lit (particle_light.glsl) and drawn as a streak from where it is to where
// it was a moment ago, seen from the moving camera (particles_precipitation.vert)
#define LAYOUT_COMPUTE 1
#include "common.glsl"
#include "weather_common.glsl"
#include "particle_light.glsl"
#define PARTICLE_COMMAND_BINDING 0
#define PARTICLE_OUTPUT_BINDING 1
#include "particle_common.glsl"
layout(local_size_x = PARTICLE_GROUP_SIZE) in;
// the parameters of the kernel (a block of the ring of the frame: more of them than the push constants hold)
layout(std140, set = COMPUTE_SET, binding = 2) uniform kernel_block
{
// x, y: first particles of the second and of the third box, z: particles, w: 1 for snow, 0 for rain
uvec4 counts;
// xyz: how far the wind took the particles less where the camera is, in each box (metres, 0 to the size of the box); w: how
// far they fell in it, 0 to 16 sizes of the box
vec4 offsets[3];
// xyz: size of each box, metres; w unused
vec4 sizes;
// xyz: velocity of the wind less the one of the camera, m/s; w: speed of the fall
vec4 velocity;
// x: seconds (for the flutter of the flakes), y: how long a streak lasts, seconds, z: opacity, w: places for the draws
vec4 timing;
// the world axes around the camera to the view space of the pass
mat4 view;
// y: how much a particle scatters the light onwards, z: brightness
vec4 options;
vec4 planes[5];
};
void main()
{
uint index = gl_GlobalInvocationID.x;
if (index >= counts.z)
return;
uint box = (index < counts.x) ? 0U : (index < counts.y) ? 1U : 2U;
float size = sizes[box];
bool snow = counts.w != 0U;
uint state = particle_hash(index * 3U + 0x9E3779B9U);
vec3 home = vec3(particle_random(state), particle_random(state), particle_random(state)) * size;
// the speed of the fall is (16 + k) / 16 of the common one, so a whole number of sizes of the box (16 of them) of the common
// fall is a whole number of them for every particle, and the fall can be kept within 16 sizes
float pace = (16.0 + floor(particle_random(state) * 7.0) - 3.0) / 16.0;
float width = snow ? mix(0.004, 0.012, particle_random(state)) : mix(0.0012, 0.0025, particle_random(state));
vec3 move = vec3(velocity.xyz) + vec3(0.0, -velocity.w * pace, 0.0);
vec3 place = home + offsets[box].xyz - vec3(0.0, offsets[box].w * pace, 0.0);
if (snow)
{
// the flakes flutter about their way
float amplitude = mix(0.1, 0.35, particle_random(state));
vec2 frequency = mix(vec2(1.5), vec2(5.0), vec2(particle_random(state), particle_random(state)));
vec2 phase = vec2(particle_random(state), particle_random(state)) * 6.2831853;
vec2 angle = frequency * timing.x + phase;
place.xz += amplitude * sin(angle);
move.xz += amplitude * frequency * cos(angle);
}
// within the box around the camera
vec3 world = mod(place, size) - 0.5 * size;
float opacity = timing.z;
// fading at the walls of the box, where the particles wrap around, and right at the camera
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));
if (opacity <= 0.002)
return;
vec3 tail = -move * timing.y;
float streak = length(tail);
if (streak > 3.0)
tail *= 3.0 / streak;
if (false == particle_visible(planes, world + 0.5 * tail, 0.5 * length(tail) + width))
return;
vec3 color = particle_light((view * vec4(world, 1.0)).xyz, world, options.y);
particle_emit(world, width, tail, color * options.z, opacity, uint(timing.w));
}

View File

@@ -0,0 +1,54 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
// rain and snow of the gpu (gfx::gpu_particles, particles_precipitation.vert): a streak rounded at its ends, fainter towards
// its tail, in the colour the compute pass lit it with, faded in front of the scene, fogged
layout(location = 3) in vec4 f_pos;
layout(location = 13) in vec4 f_color;
layout(location = 17) in vec4 f_streak;
layout(location = 16) in float f_soft;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
#include "common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
#include "particle_soft.glsl"
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
void main()
{
// distance from the axis of the streak, in its radii
float along = clamp(f_streak.y, 0.0, f_streak.w);
float offaxis = length(vec2(f_streak.x, f_streak.y - along)) / max(f_streak.z, 1e-6);
float alpha = f_color.a * (1.0 - smoothstep(0.35, 1.0, offaxis));
alpha *= mix(1.0, 0.4, along / max(f_streak.w, 1e-6));
alpha *= particle_soft_fade(f_soft, -f_pos.z);
if (alpha <= 0.0)
discard;
vec4 color = vec4(apply_fog(f_color.rgb), alpha);
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;
out_motion = vec4(a - b, 0.0f, alpha * alpha_mult);
}
}

View File

@@ -0,0 +1,61 @@
#version 460 core
#define STAGE_VERTEX 1
#include "common.glsl"
// rain and snow of the gpu (gfx::gpu_particles): a streak per drop or flake the compute pass added to the draw, from where
// it is to where it was a moment ago as the moving camera saw it, turned to face the camera, rounded at its ends. a particle
// thinner than a pixel and a half is drawn that wide and as much fainter, so the distant ones don't flicker
layout(location = 0) in vec4 v_place; // xyz: the head, metres from the camera (world axes), w: width
layout(location = 1) in vec4 v_shape; // xyz: from the head to the tail
layout(location = 2) in vec4 v_color; // rgb: lit colour, a: opacity
layout(location = 3) out vec4 f_pos;
layout(location = 13) out vec4 f_color;
// x: across the streak, y: along it from the head, metres; z: its radius, w: its length
layout(location = 17) out vec4 f_streak;
layout(location = 16) out float f_soft;
layout(location = 4) out vec4 f_clip_pos;
layout(location = 5) out vec4 f_clip_future_pos;
#include "common.glsl"
// x: metres it fades out over in front of the scene, 0: none
#define particle_soft pc.data[0]
// x: width of a pixel one metre in front of the camera, metres
#define particle_pixel pc.data[1]
void main()
{
vec3 head = (modelview * vec4(v_place.xyz, 1.0)).xyz;
vec3 axis = mat3(modelview) * v_shape.xyz;
float span = length(axis);
vec3 direction = (span > 1e-5) ? axis / span : vec3(0.0, -1.0, 0.0);
vec3 middle = head + 0.5 * axis;
float width = v_place.w;
float alpha = v_color.a;
float least = 1.5 * particle_pixel.x * max(-middle.z, 1e-3);
if (width < least)
{
alpha *= width / least;
width = least;
}
float radius = 0.5 * width;
vec3 side = cross(direction, normalize(-middle));
float sidelength = length(side);
side = (sidelength > 1e-4) ? side / sidelength : vec3(1.0, 0.0, 0.0);
// a triangle strip of four corners: across, along
vec2 corner = vec2(float(gl_VertexIndex & 1), float(gl_VertexIndex >> 1));
float across = (corner.x * 2.0 - 1.0) * radius;
float along = mix(-radius, span + radius, corner.y);
f_pos = vec4(head + direction * along + side * across, 1.0);
f_clip_pos = projection * f_pos;
f_clip_future_pos = (projection * future) * f_pos;
gl_Position = f_clip_pos;
f_streak = vec4(across, along, radius, span);
f_color = vec4(v_color.rgb, alpha);
f_soft = particle_soft.x;
}

View File

@@ -0,0 +1,50 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
// the smoke of the gpu (gfx::gpu_particles, particles_smoke.vert): the texture of the smoke in the colour the compute pass
// lit the particle with, faded in front of the scene, fogged
layout(location = 13) in vec4 f_color;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 16) in float f_soft;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
#pragma texture(tex1, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D tex1;
#include "common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
#include "particle_soft.glsl"
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
void main()
{
vec4 tex_color = texture(tex1, f_coord) * f_color;
tex_color.a *= particle_soft_fade(f_soft, -f_pos.z);
if (POSTFX_ENABLED)
{
out_color = vec4(apply_fog(tex_color.rgb), tex_color.a);
}
else
{
out_color = tonemap(vec4(apply_fog(tex_color.rgb), tex_color.a));
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;
out_motion = vec4(a - b, 0.0f, tex_color.a * alpha_mult);
}
}

View File

@@ -0,0 +1,40 @@
#version 460 core
#define STAGE_VERTEX 1
#include "common.glsl"
// the smoke of the gpu (gfx::gpu_particles): a billboard per particle the compute pass added to the draw, turned about the
// axis of the view by the rotation of the particle (as opengl46_particles builds them on the cpu)
layout(location = 0) in vec4 v_place; // xyz: metres from the camera (world axes), w: size
layout(location = 1) in vec4 v_shape; // x: rotation, radians
layout(location = 2) in vec4 v_color; // rgb: lit colour, a: opacity
layout(location = 3) out vec4 f_pos;
layout(location = 13) out vec4 f_color;
layout(location = 2) out vec2 f_coord;
// metres in front of the scene the particle fades out over, see particle_soft.glsl
layout(location = 16) out float f_soft;
layout(location = 4) out vec4 f_clip_pos;
layout(location = 5) out vec4 f_clip_future_pos;
#include "common.glsl"
// x: share of the size of the particle it fades out over in front of the scene (gfx.particles.soft), 0: none
#define particle_soft pc.data[0]
void main()
{
// a triangle strip of four corners
vec2 corner = vec2(float(gl_VertexIndex & 1), float(gl_VertexIndex >> 1)) - 0.5;
float c = cos(v_shape.x);
float s = sin(v_shape.x);
vec2 offset = vec2(c * corner.x - s * corner.y, s * corner.x + c * corner.y) * v_place.w;
f_pos = modelview * vec4(v_place.xyz, 1.0) + vec4(offset, 0.0, 0.0);
f_clip_pos = projection * f_pos;
f_clip_future_pos = (projection * future) * f_pos;
gl_Position = f_clip_pos;
f_coord = corner + 0.5;
f_color = v_color;
f_soft = particle_soft.x * 0.5 * v_place.w;
}

View File

@@ -0,0 +1,65 @@
#version 460 core
#define STAGE_COMPUTE 1
// the smoke of the gpu (gfx::gpu_particles), in every colour pass which draws it: a thread per place of the storage. the
// living particles the view sees are lit (particle_light.glsl) and added to the draw (particles_smoke.vert)
#define LAYOUT_COMPUTE 1
#include "common.glsl"
#include "weather_common.glsl"
#include "particle_light.glsl"
#define PARTICLE_COMMAND_BINDING 3
#define PARTICLE_OUTPUT_BINDING 4
#include "particle_common.glsl"
layout(local_size_x = PARTICLE_GROUP_SIZE) in;
layout(std430, set = COMPUTE_SET, binding = 0) readonly buffer particle_ssbo
{
smoke_particle_s particles[];
};
layout(std430, set = COMPUTE_SET, binding = 1) readonly buffer particle_source_ssbo
{
smoke_source_s sources[];
};
layout(std430, set = COMPUTE_SET, binding = 2) readonly buffer particle_owner_ssbo
{
uint owners[];
};
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
uint places;
// xyz: the origin of the particles less the camera, metres along the world axes
vec3 origin;
// the world axes around the camera to the view space of the pass
mat4 view;
// x: 1 when the smoke is lit by the lights of the pass, y: how much it scatters the light onwards, z: places for the draws
vec4 options;
// planes of the view (world axes around the camera): a point is inside when dot(xyz, point) + w >= 0
vec4 planes[5];
};
void main()
{
uint place = gl_GlobalInvocationID.x;
if (place >= places)
return;
smoke_particle_s particle = particles[place];
if (particle.state.x <= 0.0)
return;
vec3 world = particle.position.xyz + origin;
float size = particle.velocity.w;
// the corners of the billboard turned anyhow
if (false == particle_visible(planes, world, size * 0.7072))
return;
smoke_source_s source = sources[owners[place]];
vec3 color = source.oldcolor.rgb;
if (options.x > 0.5)
color = source.color.rgb * particle_light((view * vec4(world, 1.0)).xyz, world, options.y);
float opacity = clamp(particle.state.x, 0.0, 1.0);
// showing up after the birth (smoke_source::fade_in())
if (source.settling.w > 0.0)
opacity *= smoothstep(0.0, source.settling.w, particle.state.y);
particle_emit(world, size, vec3(particle.position.w, 0.0, 0.0), color, opacity, uint(options.z));
}

View File

@@ -0,0 +1,102 @@
#version 460 core
#define STAGE_COMPUTE 1
// the smoke of the gpu (gfx::gpu_particles), once per update of the simulation: a thread per place of the storage. the
// particles the sources had born in the update take the places after the ones of the last update (each source keeps its
// places as a ring, the oldest give way), spread along the way the emitter went since the update before; the living ones
// change as smoke_source::update() changes them on the cpu
#define LAYOUT_COMPUTE 1
#include "common.glsl"
#include "particle_common.glsl"
layout(local_size_x = PARTICLE_GROUP_SIZE) in;
layout(std430, set = COMPUTE_SET, binding = 0) buffer particle_ssbo
{
smoke_particle_s particles[];
};
layout(std430, set = COMPUTE_SET, binding = 1) readonly buffer particle_source_ssbo
{
smoke_source_s sources[];
};
// the source of every place
layout(std430, set = COMPUTE_SET, binding = 2) readonly buffer particle_owner_ssbo
{
uint owners[];
};
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
uint places;
// x: seconds of the update, yzw: added to the positions, as the origin moved
vec4 step;
// xyz: the wind, m/s; w unused
vec4 wind;
// x: number of the update, for the random numbers
uint seed;
};
// smoke_source::update( Particle ); false when the particle is dead
bool advance(inout smoke_particle_s particle, smoke_source_s source, float seconds)
{
particle.state.x = clamp(particle.state.x + source.opacitychange.x * seconds, source.opacitychange.y, source.opacitychange.z);
if (particle.state.x <= 0.0)
return false;
particle.velocity.w = clamp(particle.velocity.w + source.sizechange.x * seconds, source.sizechange.y, source.sizechange.z);
// the jet widens with the way it goes
if (source.sizechange.w > 0.0)
particle.velocity.w = min(particle.velocity.w + source.sizechange.w * length(particle.velocity.xyz) * seconds, source.sizechange.z);
if (source.settling.z > 0.5)
{
// the drag of the air takes the particle towards the velocity it settles at
particle.velocity.xyz += (vec3(0.0, source.settling.x, 0.0) - particle.velocity.xyz) * (1.0 - exp(-source.settling.y * seconds));
}
else
{
// crude smoke dispersion
particle.velocity.y += source.motion.x * particle.velocity.y * seconds;
particle.velocity.y -= source.motion.y * particle.velocity.y * seconds;
particle.velocity.y = max(source.motion.z, particle.velocity.y);
}
particle.position.xyz += particle.velocity.xyz * seconds;
particle.position.xyz += 0.1 * particle.state.y * wind.xyz * seconds;
particle.state.y += seconds;
return true;
}
void main()
{
uint place = gl_GlobalInvocationID.x;
if (place >= places)
return;
smoke_source_s source = sources[owners[place]];
smoke_particle_s particle = particles[place];
particle.position.xyz += step.yzw;
uint size = source.pool.y;
uint order = (place - source.pool.x + size - source.pool.z) % size;
if (order < source.pool.w)
{
// born in the update: smoke_source::particle_emitter::initialize()
uint state = particle_hash(place ^ particle_hash(seed));
float polar = mix(source.launch.x, source.launch.y, particle_random(state));
float azimuth = radians(mix(-180.0, 180.0, particle_random(state)));
vec3 direction = vec3(-sin(polar) * sin(azimuth), cos(polar), sin(polar) * cos(azimuth));
particle.velocity.xyz = direction * mix(source.launch.z, source.launch.w, particle_random(state));
particle.position.w = radians(mix(0.0, 360.0, particle_random(state)));
particle.velocity.w = mix(source.initial.x, source.initial.y, particle_random(state));
particle.state = vec4(mix(source.initial.z, source.initial.w, particle_random(state)), 0.0, 0.0, 0.0);
// the births spread over the update: where the emitter was then, and the time since
float share = (float(order) + 0.5) / float(source.pool.w);
particle.position.xyz = mix(source.previous.xyz, source.location.xyz, share);
if (false == advance(particle, source, step.x * (1.0 - share)))
particle.state.x = 0.0;
}
else if (particle.state.x > 0.0)
{
if (false == advance(particle, source, step.x))
particle.state.x = 0.0;
}
particles[place] = particle;
}

View File

@@ -0,0 +1,80 @@
#version 460 core
#define STAGE_FRAGMENT 1
#define LAYOUT_POST 1
#include "common.glsl"
// one step down the bloom chain: a 13 tap filter (Jimenez, "Next generation post processing in Call of Duty: Advanced
// Warfare", 2014) from a level twice as large. the first step, from the image itself, weighs the groups of taps by their
// brightness (Karis average) so single bright pixels do not flicker, and applies the threshold.
// param[0].x: 1 for the first step, param[0].y: threshold (0: none), param[0].z: soft knee
layout(location = 14) in vec2 f_coords;
layout(location = 0) out vec4 out_color;
#pragma texture(source_tex, 0, RGB)
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 0) uniform sampler2D source_tex;
vec3 tap(vec2 uv)
{
vec3 color = texture(source_tex, uv).rgb;
if (any(isnan(color)) || any(isinf(color)))
return vec3(0.0);
return clamp(color, vec3(0.0), vec3(65000.0));
}
float karis_weight(vec3 color)
{
return 1.0 / (1.0 + dot(color, vec3(0.2126, 0.7152, 0.0722)));
}
void main()
{
vec2 texel = 1.0 / vec2(textureSize(source_tex, 0));
vec2 uv = f_coords;
vec3 a = tap(uv + texel * vec2(-2.0, 2.0));
vec3 b = tap(uv + texel * vec2(0.0, 2.0));
vec3 c = tap(uv + texel * vec2(2.0, 2.0));
vec3 d = tap(uv + texel * vec2(-2.0, 0.0));
vec3 e = tap(uv);
vec3 f = tap(uv + texel * vec2(2.0, 0.0));
vec3 g = tap(uv + texel * vec2(-2.0, -2.0));
vec3 h = tap(uv + texel * vec2(0.0, -2.0));
vec3 i = tap(uv + texel * vec2(2.0, -2.0));
vec3 j = tap(uv + texel * vec2(-1.0, 1.0));
vec3 k = tap(uv + texel * vec2(1.0, 1.0));
vec3 l = tap(uv + texel * vec2(-1.0, -1.0));
vec3 m = tap(uv + texel * vec2(1.0, -1.0));
vec3 color;
if (param[0].x > 0.5)
{
vec3 g0 = (j + k + l + m) * 0.25;
vec3 g1 = (a + b + d + e) * 0.25;
vec3 g2 = (b + c + e + f) * 0.25;
vec3 g3 = (d + e + g + h) * 0.25;
vec3 g4 = (e + f + h + i) * 0.25;
float w0 = 0.5 * karis_weight(g0);
float w1 = 0.125 * karis_weight(g1);
float w2 = 0.125 * karis_weight(g2);
float w3 = 0.125 * karis_weight(g3);
float w4 = 0.125 * karis_weight(g4);
color = (g0 * w0 + g1 * w1 + g2 * w2 + g3 * w3 + g4 * w4) / (w0 + w1 + w2 + w3 + w4);
float threshold = param[0].y;
if (threshold > 0.0)
{
// quadratic soft knee below the threshold
float knee = max(threshold * param[0].z, 1e-4);
float brightness = max(color.r, max(color.g, color.b));
float soft = clamp(brightness - threshold + knee, 0.0, 2.0 * knee);
soft = soft * soft / (4.0 * knee);
color *= max(soft, brightness - threshold) / max(brightness, 1e-4);
}
}
else
{
color = e * 0.125 + (a + c + g + i) * 0.03125 + (b + d + f + h) * 0.0625 + (j + k + l + m) * 0.125;
}
out_color = vec4(color, 1.0);
}

View File

@@ -0,0 +1,33 @@
#version 460 core
#define STAGE_FRAGMENT 1
#define LAYOUT_POST 1
#include "common.glsl"
// one step up the bloom chain: the level below (half as large, with all the levels under it already added) blurred up with
// a 3x3 tent filter and added to this level of the way down
layout(location = 14) in vec2 f_coords;
layout(location = 0) out vec4 out_color;
#pragma texture(lower_tex, 0, RGB)
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 0) uniform sampler2D lower_tex;
#pragma texture(current_tex, 1, RGB)
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 1) uniform sampler2D current_tex;
void main()
{
vec2 texel = 1.0 / vec2(textureSize(lower_tex, 0));
vec2 uv = f_coords;
vec3 sum = texture(lower_tex, uv).rgb * 4.0;
sum += (texture(lower_tex, uv + texel * vec2(0.0, 1.0)).rgb
+ texture(lower_tex, uv + texel * vec2(-1.0, 0.0)).rgb
+ texture(lower_tex, uv + texel * vec2(1.0, 0.0)).rgb
+ texture(lower_tex, uv + texel * vec2(0.0, -1.0)).rgb) * 2.0;
sum += texture(lower_tex, uv + texel * vec2(-1.0, 1.0)).rgb
+ texture(lower_tex, uv + texel * vec2(1.0, 1.0)).rgb
+ texture(lower_tex, uv + texel * vec2(-1.0, -1.0)).rgb
+ texture(lower_tex, uv + texel * vec2(1.0, -1.0)).rgb;
out_color = vec4(texture(current_tex, uv).rgb + sum / 16.0, 1.0);
}

View File

@@ -1,6 +1,5 @@
#version 460 core
#define STAGE_FRAGMENT 1
#define LAYOUT_POST 1
#include "common.glsl"
// applies the ambient occlusion of the half size target to the image at full size, as a multiplier: drawn with the
// blending dst * src over the image (opengl46_ao::apply). the four nearest pixels of the occlusion are weighed by how
@@ -8,22 +7,21 @@
// 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.
// the fog in front of a surface hides its occlusion as it hides the surface: the analytic fog of apply_fog.glsl, the fog
// volume comes with the stage P2.
// the parameters are in the push constants (loose uniforms in the opengl renderer): pc.data[0..3] the columns of the inverse
// of the projection (inv_projection), pc.data[4] params, pc.data[5].x the density of the fog of the scene (fog_density of the
// regular draws, 0: no fog), pc.data[5].yzw unused
// the fog in front of a surface hides its occlusion as it hides the surface (fog_volume.glsl): drawn in the layout of the passes of
// the scene, with the pass set of the colour pass and the block of its weather. the parameters are in the push constants (loose
// uniforms in the opengl renderer): pc.data[0..3] the columns of the inverse of the projection, pc.data[4] params
layout(location = 14) in vec2 f_coords;
#include "fog_volume.glsl"
layout(location = 0) out vec4 out_color;
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 0) uniform sampler2D depth_tex;
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 1) uniform sampler2D ao_tex;
// the textures of the draw
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D depth_tex;
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D ao_tex;
#define inv_projection mat4(pc.data[0], pc.data[1], pc.data[2], pc.data[3])
#define params pc.data[4]
#define scene_fog_density pc.data[5].x
vec3 view_position(ivec2 pixel, float depth)
{
@@ -93,7 +91,6 @@ void main()
ao = multi_bounce(ao, params.z);
}
// what the fog lets through of the surface is what is left to occlude
float transmittance = (scene_fog_density > 0.0) ? clamp(exp(-distance * scene_fog_density), 0.0, 1.0) : 1.0;
ao = mix(1.0, ao, transmittance);
ao = mix(1.0, ao, fog_along_view(view_position(pixel, depth), gl_FragCoord.xy).a);
out_color = vec4(vec3(ao), 1.0);
}

View File

@@ -13,16 +13,13 @@ layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 0) uniform sampler2D tex1;
#pragma texture(bloom_tex, 1, RGB)
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 1) uniform sampler2D bloom_tex;
// the automatic exposure (exposure_ssbo of the opengl renderer, exposure_average.comp) comes in a later stage: the fixed
// exposure multiplier alone
const float exposure = 0.0;
#include "tonemapping.glsl"
#include "dither.glsl"
// param is pc.data of the push constants (the model block in the opengl renderer).
// param[0].x: share of the bloom in the image (0: no bloom, the texture is not read), param[0].y: 1 if the automatic
// exposure is applied (there is none yet, so it has no effect), param[0].z: fixed exposure multiplier, param[0].w: scale of
// exposure is applied (exposure_ssbo of common.glsl, worked out by exposure_average.comp), param[0].z: fixed exposure multiplier, param[0].w: scale of
// the bloom texture (it holds the sum of the levels of the chain), param[1].x: 1 when this is the last pass before the
// window, which takes the dither
void main()

View File

@@ -1,6 +1,9 @@
#version 460 core
#define STAGE_VERTEX 1
// the full screen quad of the post-processing, or with QUAD_SCENE of a pass of the scene (the layout of the scene)
#ifndef QUAD_SCENE
#define LAYOUT_POST 1
#endif
#include "common.glsl"
const vec2 vert[4] = vec2[]
(

View File

@@ -0,0 +1,133 @@
#version 460 core
#define STAGE_COMPUTE 1
// the cascades of the sun shadows fitted to the depth of the main view (gfx::shadow_fit), second pass: the distances
// from the nearest to the farthest pixel which isn't the sky (shadow_fit_depth.comp) split between the cascades, and for each
// cascade the box of the pixels it takes, in the space of the light. the cpu makes the maps of the cascades of a frame or
// two later cover the boxes: the camera has moved since, so a cascade takes the pixels a little past its distances as well,
// and the shading goes to the next cascade with a point outside the map of its own (light_common.glsl)
#define LAYOUT_COMPUTE 1
#include "common.glsl"
layout(local_size_x = SHADOW_FIT_GROUP_SIZE, local_size_y = SHADOW_FIT_GROUP_SIZE) in;
layout(set = COMPUTE_SET, binding = 0) uniform sampler2D scene_depth;
layout(std430, set = COMPUTE_SET, binding = 1) buffer shadow_fit_ssbo
{
uint measurement;
uint samples;
// positive floats, as bits
uint depthmin;
uint depthmax;
float splits[4];
uint counts[4];
// per cascade the lowest x, y, z, unused, then the highest x, y, z, unused, as shadow_fit_order() gives them
uint bounds[MAX_CASCADES * 8U];
};
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
// the inverse of the projection of the view
mat4 inv_projection;
// rotation from the view space to the space of the light: x and y across the maps, z along the light
mat4 view_to_light;
// x: the farthest distance the cascades may reach (the one of the fixed cascades), y: the nearest one the first may start
// from, z: the least one the last reaches, w: the share of equal ratios in the splits, the rest in equal lengths
vec4 split_params;
// how far past its distances a cascade takes the pixels, x + y * distance metres
vec4 margin_params;
};
shared uint shared_bounds[MAX_CASCADES * 8U];
shared uint shared_counts[MAX_CASCADES];
// a float as a uint which orders the same way, also the negative ones
uint shadow_fit_order(float value)
{
uint bits = floatBitsToUint(value);
return ((bits & 0x80000000U) != 0U) ? ~bits : (bits | 0x80000000U);
}
// the distances the cascades go from and to. both ends are moved out to the next step of 1/8 of a power of two, so that
// the cascades change only now and then with the pixels coming and going
void shadow_fit_splits(out float result[MAX_CASCADES + 1U])
{
float rangemax = split_params.x;
float far = clamp(uintBitsToFloat(depthmax), split_params.z, rangemax);
far = min(exp2(ceil(log2(far) * 8.0) / 8.0), rangemax);
float near = clamp(uintBitsToFloat(depthmin), split_params.y, far * 0.25);
near = exp2(floor(log2(near) * 8.0) / 8.0);
result[0] = near;
result[MAX_CASCADES] = far;
for (uint cascade = 1U; cascade < MAX_CASCADES; ++cascade)
{
float share = float(cascade) / float(MAX_CASCADES);
result[cascade] = mix(near + (far - near) * share, near * pow(far / near, share), split_params.w);
}
}
void main()
{
uint index = gl_LocalInvocationIndex;
if (index < MAX_CASCADES * 8U)
shared_bounds[index] = ((index & 4U) == 0U) ? 0xFFFFFFFFU : 0U;
if (index < MAX_CASCADES)
shared_counts[index] = 0U;
barrier();
float cascadesplits[MAX_CASCADES + 1U];
shadow_fit_splits(cascadesplits);
ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = textureSize(scene_depth, 0);
if ((samples > 0U) && all(lessThan(pixel, size)))
{
float depth = texelFetch(scene_depth, pixel, 0).r;
if (depth > 0.0)
{
vec2 ndc = (vec2(pixel) + 0.5) / vec2(size) * 2.0 - 1.0;
vec4 position = inv_projection * vec4(ndc, depth, 1.0);
vec3 view = position.xyz / position.w;
float distance = length(view);
vec3 light = mat3(view_to_light) * view;
uvec3 ordered = uvec3(shadow_fit_order(light.x), shadow_fit_order(light.y), shadow_fit_order(light.z));
for (uint cascade = 0U; cascade < MAX_CASCADES; ++cascade)
{
// the end of a cascade has the padding of opengl46_renderer::Render_pass(), MAX_CASCADES - cascade metres
float low = (cascade == 0U) ? 0.0 : cascadesplits[cascade] - (margin_params.x + margin_params.y * cascadesplits[cascade]);
float high = cascadesplits[cascade + 1U] + float(MAX_CASCADES - cascade) + margin_params.x + margin_params.y * cascadesplits[cascade + 1U];
if ((distance < low) || (distance > high))
continue;
uint first = cascade * 8U;
atomicAdd(shared_counts[cascade], 1U);
atomicMin(shared_bounds[first + 0U], ordered.x);
atomicMin(shared_bounds[first + 1U], ordered.y);
atomicMin(shared_bounds[first + 2U], ordered.z);
atomicMax(shared_bounds[first + 4U], ordered.x);
atomicMax(shared_bounds[first + 5U], ordered.y);
atomicMax(shared_bounds[first + 6U], ordered.z);
}
}
}
barrier();
if ((index < MAX_CASCADES * 8U) && ((index & 3U) != 3U))
{
if ((index & 4U) == 0U)
{
if (shared_bounds[index] != 0xFFFFFFFFU)
atomicMin(bounds[index], shared_bounds[index]);
}
else if (shared_bounds[index] != 0U)
atomicMax(bounds[index], shared_bounds[index]);
}
if ((index < MAX_CASCADES) && (shared_counts[index] > 0U))
atomicAdd(counts[index], shared_counts[index]);
if (all(equal(gl_GlobalInvocationID.xy, uvec2(0U))))
{
for (uint split = 0U; split <= MAX_CASCADES; ++split)
splits[split] = cascadesplits[split];
}
}

View File

@@ -0,0 +1,72 @@
#version 460 core
#define STAGE_COMPUTE 1
// the cascades of the sun shadows fitted to the depth of the main view (gfx::shadow_fit), first pass: how many pixels of
// the resolved depth of the view aren't the sky, and the nearest and the farthest of their distances from the camera.
// the depth is reversed with the far plane at infinity: the sky, which writes no depth, stays at 0.
// shadow_fit_bounds.comp splits the distances between the cascades
#define LAYOUT_COMPUTE 1
#include "common.glsl"
layout(local_size_x = SHADOW_FIT_GROUP_SIZE, local_size_y = SHADOW_FIT_GROUP_SIZE) in;
layout(set = COMPUTE_SET, binding = 0) uniform sampler2D scene_depth;
layout(std430, set = COMPUTE_SET, binding = 1) buffer shadow_fit_ssbo
{
uint measurement;
uint samples;
// positive floats, as bits
uint depthmin;
uint depthmax;
float splits[4];
uint counts[4];
uint bounds[MAX_CASCADES * 8U];
};
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
// the inverse of the projection of the view
mat4 inv_projection;
};
shared uint shared_samples;
shared uint shared_min;
shared uint shared_max;
void main()
{
if (gl_LocalInvocationIndex == 0U)
{
shared_samples = 0U;
// infinity
shared_min = 0x7F800000U;
shared_max = 0U;
}
barrier();
ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = textureSize(scene_depth, 0);
if (all(lessThan(pixel, size)))
{
float depth = texelFetch(scene_depth, pixel, 0).r;
if (depth > 0.0)
{
vec2 ndc = (vec2(pixel) + 0.5) / vec2(size) * 2.0 - 1.0;
vec4 position = inv_projection * vec4(ndc, depth, 1.0);
uint distance = floatBitsToUint(length(position.xyz / position.w));
atomicAdd(shared_samples, 1U);
atomicMin(shared_min, distance);
atomicMax(shared_max, distance);
}
}
barrier();
if ((gl_LocalInvocationIndex == 0U) && (shared_samples > 0U))
{
atomicAdd(samples, shared_samples);
atomicMin(depthmin, shared_min);
atomicMax(depthmax, shared_max);
}
}

View File

@@ -0,0 +1,31 @@
#version 460 core
#extension GL_EXT_multiview : require
#define STAGE_VERTEX 1
// draws the geometry into the six faces of a cube shadow map of a lamp at once, a view of the multiview rendering per face (the
// geometry shader shadowcube.geom of the opengl renderer). the modelview takes the geometry to the space of the map, with the lamp at
// the origin. the faces go in the order of the faces of a cube map (+x, -x, +y, -y, +z, -z), each looks the way glm::lookAt() with
// the usual up vectors of cube maps sets it, with a 90 degree projection of infinite reach and reversed depth like the one of the
// scene: the depth is near / d, d the distance along the axis of the face. the near plane of the map, LOCAL_SHADOW_NEAR: closer
// surfaces (the lamp itself) cast no shadows
#include "common.glsl"
layout(location = 0) in vec3 v_vert;
layout(location = 2) in vec2 v_coord;
layout(location = 2) out vec2 f_coord;
// right, up and forward axes of the faces
const vec3 face_right[6] = vec3[](vec3(0.0, 0.0, -1.0), vec3(0.0, 0.0, 1.0), vec3(1.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), vec3(-1.0, 0.0, 0.0));
const vec3 face_up[6] = vec3[](vec3(0.0, -1.0, 0.0), vec3(0.0, -1.0, 0.0), vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, -1.0), vec3(0.0, -1.0, 0.0), vec3(0.0, -1.0, 0.0));
const vec3 face_forward[6] = vec3[](vec3(1.0, 0.0, 0.0), vec3(-1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, -1.0, 0.0), vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, -1.0));
void main()
{
vec3 position = (effective_modelview() * vec4(v_vert, 1.0f)).xyz;
int face = int(gl_ViewIndex);
gl_Position = vec4(dot(position, face_right[face]), dot(position, face_up[face]), LOCAL_SHADOW_NEAR, dot(position, face_forward[face]));
f_coord = v_coord;
#ifdef MODEL_DRAWS
// which block of the multi draw the fragments read (common.glsl)
f_model_draw = MODEL_DRAW;
#endif
}

View File

@@ -11,4 +11,8 @@ void main()
{
gl_Position = (projection * effective_modelview()) * vec4(v_vert, 1.0f);
f_coord = v_coord;
#ifdef MODEL_DRAWS
// which block of the multi draw the fragments read (common.glsl)
f_model_draw = MODEL_DRAW;
#endif
}

View File

@@ -0,0 +1,63 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
// the sky dome drawn with the light of the physical atmosphere (atmosphere_common.glsl): the sky-view table looked up in
// the direction of each fragment. the colour of the old dome (vertex colours, see color.frag) is kept for the night,
// which the atmosphere does not cover (no moon, no stars), and the two are blended by the height of the sun.
// param[0].x: share of the atmosphere, param[0].y: brightness of the atmosphere, param[0].z: how much the overcast flattens
// the sky, param[0].w: luminance the flattened sky tends to. param[1].rgb: white balance of the atmosphere
layout(location = 13) in vec3 f_color;
layout(location = 3) in vec4 f_pos;
#include "atmosphere_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
#include "sky_shoulder.glsl"
layout(set = PASS_SET, binding = SKY_VIEW_TEX) uniform sampler2D sky_view_lut;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
// the brightest the sky gets, a guard against the circumsolar peak of the mie scattering blowing up the bloom
const float SKY_RADIANCE_MAX = 64.0;
void main()
{
vec3 domecolor = sky_shoulder(pow(max(f_color.rgb, vec3(0.0)), vec3(2.2)));
// the dome is drawn around the camera with the rotation of the view only, so the view space position is the direction
vec3 direction = normalize(mat3(inv_view) * f_pos.xyz);
// the bottom of the dome reaches a little under the horizon, where the table holds the dark air in front of the ground;
// it gets the light of the horizon instead, as the old dome did
direction = normalize(vec3(direction.x, max(direction.y, 0.002), direction.z));
vec3 sundirection = normalize(atmo_sun_direction.xyz);
vec3 atmosphere = textureLod(sky_view_lut, atmo_skyview_uv_world(direction, sundirection), 0.0).rgb;
atmosphere *= param[1].rgb * param[0].y;
// an overcast sky is greyer and more even than the clear one the atmosphere describes
vec3 overcast = vec3(param[0].w) * (0.75 + 0.5 * clamp(direction.y, 0.0, 1.0));
atmosphere = mix(atmosphere, overcast, param[0].z);
atmosphere = min(atmosphere, vec3(SKY_RADIANCE_MAX));
vec3 col = mix(domecolor, atmosphere, param[0].x);
if (any(isnan(col)) || any(isinf(col)))
col = domecolor;
if (POSTFX_ENABLED)
{
out_color = vec4(apply_fog(col), 1.0f);
}
else
{
out_color = tonemap(vec4(apply_fog(col), 1.0f));
}
if (MOTIONBLUR_ENABLED)
{
out_motion = vec4(0.0f);
}
}

View File

@@ -0,0 +1,64 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
// 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
layout(location = 14) in vec2 f_coords;
#include "fog_volume.glsl"
#include "tonemapping.glsl"
// the clouds of the view or the panorama, the first texture of the draw
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D clouds;
// the clip space to the view space of the camera of the pass (pc.data[0-3]), the view space to world axes (the rotation of the
// block of the scene of the sky pass), the mode (pc.data[4].x)
#define inverse_projection mat4(pc.data[0], pc.data[1], pc.data[2], pc.data[3])
#define view_to_world inv_view
#define mode int(pc.data[4].x)
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
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);
}
if (MOTIONBLUR_ENABLED)
{
out_motion = vec4(0.0);
}
}

35
shaders/vk14/stars.frag Normal file
View File

@@ -0,0 +1,35 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
// a star: a soft point added to the sky
layout(location = 13) in vec3 f_color;
#include "tonemapping.glsl"
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
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);
}
if (MOTIONBLUR_ENABLED)
{
out_motion = vec4(0.0);
}
}

43
shaders/vk14/stars.vert Normal file
View File

@@ -0,0 +1,43 @@
#version 460 core
#define STAGE_VERTEX 1
#include "common.glsl"
// 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
// equatorial frame to world axes (x west, y up, z north), and world axes to the view: the columns of their rotations in
// pc.data[0-2] and pc.data[3-5]
#define equatorial_to_world mat3(pc.data[0].xyz, pc.data[1].xyz, pc.data[2].xyz)
#define world_to_view mat3(pc.data[3].xyz, pc.data[4].xyz, pc.data[5].xyz)
// x: brightness of a star of magnitude 0, y: seconds, z: size of the points in pixels (param[0] of the draw block)
#define params param[0]
layout(location = 13) 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);
}

View File

@@ -52,4 +52,8 @@ void main()
// TangentViewPos = TBN * vec3(0.0, 0.0, 0.0);
TangentFragPos = TBN * f_pos.xyz;
UserData = v_userdata;
#ifdef MODEL_DRAWS
// which block of the multi draw the fragments read (common.glsl)
f_model_draw = MODEL_DRAW;
#endif
}

View File

@@ -0,0 +1,172 @@
// weather of the vulkan renderer (the stage P2), shaders/ogl46/weather_common.glsl with the bindings of the pass set: the block
// of the weather of the pass (gl::weather_ubs without the colours of the horizon, which are a block of their own, written by
// fog_horizon.comp or by the cpu): 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, set = PASS_SET, binding = WEATHER_UBO) 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: scale of the sunlight of the clouds
vec4 weather_sun_color;
// rgb: ambient light of the sky above; w: scale of the sky light of the clouds
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, w: scale of the erosion of the
// edges of the clouds by the detail noise
vec4 cloud_wind;
cloud_layer_s cloud_layers[MAX_CLOUD_LAYERS];
};
layout(std140, set = PASS_SET, binding = WEATHER_HORIZON_UBO) uniform weather_horizon_ubo
{
// 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(set = PASS_SET, 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;
// the light of the haze goes with the angle from the sun, not with the azimuth alone: the table is taken at the point of the
// horizon as far from the sun as the view is. level views get the colour of their azimuth as before; a view high up over the
// sun no longer gets the glow of the horizon under it (a column of light standing over a low sun)
float sunflat = length(sun.xz);
float cosangle = 1.0;
if (sunflat > 1e-4)
cosangle = clamp(dot(normalize(direction), sun) / sunflat, -1.0, 1.0);
float position = acos(clamp(cosangle, -1.0, 1.0)) / WEATHER_PI * float(FOG_HORIZON_SAMPLES - 1);
int index = int(floor(position));
vec3 color = mix(fog_horizon[clamp(index, 0, FOG_HORIZON_SAMPLES - 1)].rgb, fog_horizon[clamp(index + 1, 0, FOG_HORIZON_SAMPLES - 1)].rgb, fract(position));
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