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:
@@ -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()
|
||||
|
||||
130
shaders/vk14/atmosphere_ambient.comp
Normal file
130
shaders/vk14/atmosphere_ambient.comp
Normal 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);
|
||||
}
|
||||
194
shaders/vk14/atmosphere_common.glsl
Normal file
194
shaders/vk14/atmosphere_common.glsl
Normal 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);
|
||||
}
|
||||
105
shaders/vk14/atmosphere_integrate.glsl
Normal file
105
shaders/vk14/atmosphere_integrate.glsl
Normal 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;
|
||||
}
|
||||
72
shaders/vk14/atmosphere_multiscattering.comp
Normal file
72
shaders/vk14/atmosphere_multiscattering.comp
Normal 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));
|
||||
}
|
||||
42
shaders/vk14/atmosphere_skyview.comp
Normal file
42
shaders/vk14/atmosphere_skyview.comp
Normal 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));
|
||||
}
|
||||
43
shaders/vk14/atmosphere_transmittance.comp
Normal file
43
shaders/vk14/atmosphere_transmittance.comp
Normal 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));
|
||||
}
|
||||
291
shaders/vk14/clouds_common.glsl
Normal file
291
shaders/vk14/clouds_common.glsl
Normal 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);
|
||||
}
|
||||
295
shaders/vk14/clouds_march.comp
Normal file
295
shaders/vk14/clouds_march.comp
Normal 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));
|
||||
}
|
||||
141
shaders/vk14/clouds_noise.comp
Normal file
141
shaders/vk14/clouds_noise.comp
Normal 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));
|
||||
}
|
||||
34
shaders/vk14/clouds_shadow.comp
Normal file
34
shaders/vk14/clouds_shadow.comp
Normal 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)));
|
||||
}
|
||||
72
shaders/vk14/clouds_temporal.comp
Normal file
72
shaders/vk14/clouds_temporal.comp
Normal 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));
|
||||
}
|
||||
48
shaders/vk14/cluster_heatmap.frag
Normal file
48
shaders/vk14/cluster_heatmap.frag
Normal 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);
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
10
shaders/vk14/depthprepass.frag
Normal file
10
shaders/vk14/depthprepass.frag
Normal 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()
|
||||
{
|
||||
}
|
||||
26
shaders/vk14/depthprepass.vert
Normal file
26
shaders/vk14/depthprepass.vert
Normal 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
|
||||
}
|
||||
18
shaders/vk14/depthprepass_alpha.frag
Normal file
18
shaders/vk14/depthprepass_alpha.frag
Normal 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;
|
||||
}
|
||||
107
shaders/vk14/envmap_prefilter.comp
Normal file
107
shaders/vk14/envmap_prefilter.comp
Normal 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));
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
86
shaders/vk14/exposure_average.comp
Normal file
86
shaders/vk14/exposure_average.comp
Normal 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);
|
||||
}
|
||||
56
shaders/vk14/exposure_histogram.comp
Normal file
56
shaders/vk14/exposure_histogram.comp
Normal 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]);
|
||||
}
|
||||
49
shaders/vk14/fog_horizon.comp
Normal file
49
shaders/vk14/fog_horizon.comp
Normal 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);
|
||||
}
|
||||
323
shaders/vk14/fog_inject.comp
Normal file
323
shaders/vk14/fog_inject.comp
Normal 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);
|
||||
}
|
||||
91
shaders/vk14/fog_integrate.comp
Normal file
91
shaders/vk14/fog_integrate.comp
Normal 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));
|
||||
}
|
||||
73
shaders/vk14/fog_volume.glsl
Normal file
73
shaders/vk14/fog_volume.glsl
Normal 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
49
shaders/vk14/grass.frag
Normal 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
247
shaders/vk14/grass.vert
Normal 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);
|
||||
}
|
||||
58
shaders/vk14/gtao_denoise.comp
Normal file
58
shaders/vk14/gtao_denoise.comp
Normal 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
160
shaders/vk14/gtao_main.comp
Normal 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));
|
||||
}
|
||||
46
shaders/vk14/hiz_build.comp
Normal file
46
shaders/vk14/hiz_build.comp
Normal 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));
|
||||
}
|
||||
291
shaders/vk14/instance_cull.comp
Normal file
291
shaders/vk14/instance_cull.comp
Normal 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;
|
||||
}
|
||||
69
shaders/vk14/instance_cull_commands.comp
Normal file
69
shaders/vk14/instance_cull_commands.comp
Normal 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);
|
||||
}
|
||||
62
shaders/vk14/instance_cull_offsets.comp
Normal file
62
shaders/vk14/instance_cull_offsets.comp
Normal 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];
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
4
shaders/vk14/mat_terrain.frag
Normal file
4
shaders/vk14/mat_terrain.frag
Normal file
@@ -0,0 +1,4 @@
|
||||
#version 460 core
|
||||
#define STAGE_FRAGMENT 1
|
||||
#include "common.glsl"
|
||||
#include "mat_terrain.glsl"
|
||||
158
shaders/vk14/mat_terrain.glsl
Normal file
158
shaders/vk14/mat_terrain.glsl
Normal 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);
|
||||
}
|
||||
}
|
||||
12
shaders/vk14/mat_terrain_holes.frag
Normal file
12
shaders/vk14/mat_terrain_holes.frag
Normal 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"
|
||||
96
shaders/vk14/particle_common.glsl
Normal file
96
shaders/vk14/particle_common.glsl
Normal 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
|
||||
147
shaders/vk14/particle_light.glsl
Normal file
147
shaders/vk14/particle_light.glsl
Normal 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
|
||||
24
shaders/vk14/particle_soft.glsl
Normal file
24
shaders/vk14/particle_soft.glsl
Normal 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
|
||||
84
shaders/vk14/particles_precipitation.comp
Normal file
84
shaders/vk14/particles_precipitation.comp
Normal 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));
|
||||
}
|
||||
54
shaders/vk14/particles_precipitation.frag
Normal file
54
shaders/vk14/particles_precipitation.frag
Normal 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);
|
||||
}
|
||||
}
|
||||
61
shaders/vk14/particles_precipitation.vert
Normal file
61
shaders/vk14/particles_precipitation.vert
Normal 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;
|
||||
}
|
||||
50
shaders/vk14/particles_smoke.frag
Normal file
50
shaders/vk14/particles_smoke.frag
Normal 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);
|
||||
}
|
||||
}
|
||||
40
shaders/vk14/particles_smoke.vert
Normal file
40
shaders/vk14/particles_smoke.vert
Normal 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;
|
||||
}
|
||||
65
shaders/vk14/particles_smoke_prepare.comp
Normal file
65
shaders/vk14/particles_smoke_prepare.comp
Normal 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));
|
||||
}
|
||||
102
shaders/vk14/particles_smoke_simulate.comp
Normal file
102
shaders/vk14/particles_smoke_simulate.comp
Normal 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;
|
||||
}
|
||||
80
shaders/vk14/postfx_bloom_down.frag
Normal file
80
shaders/vk14/postfx_bloom_down.frag
Normal 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);
|
||||
}
|
||||
33
shaders/vk14/postfx_bloom_up.frag
Normal file
33
shaders/vk14/postfx_bloom_up.frag
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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[]
|
||||
(
|
||||
|
||||
133
shaders/vk14/shadow_fit_bounds.comp
Normal file
133
shaders/vk14/shadow_fit_bounds.comp
Normal 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];
|
||||
}
|
||||
}
|
||||
72
shaders/vk14/shadow_fit_depth.comp
Normal file
72
shaders/vk14/shadow_fit_depth.comp
Normal 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);
|
||||
}
|
||||
}
|
||||
31
shaders/vk14/shadowcube.vert
Normal file
31
shaders/vk14/shadowcube.vert
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
63
shaders/vk14/sky_atmosphere.frag
Normal file
63
shaders/vk14/sky_atmosphere.frag
Normal 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);
|
||||
}
|
||||
}
|
||||
64
shaders/vk14/sky_weather.frag
Normal file
64
shaders/vk14/sky_weather.frag
Normal 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
35
shaders/vk14/stars.frag
Normal 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
43
shaders/vk14/stars.vert
Normal 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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
172
shaders/vk14/weather_common.glsl
Normal file
172
shaders/vk14/weather_common.glsl
Normal 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
|
||||
Reference in New Issue
Block a user