From 0dae1f0b9f0dfd43881ef43a3de7102e67611f20 Mon Sep 17 00:00:00 2001 From: Hirek193 Date: Sat, 17 Oct 2026 19:56:35 +0200 Subject: [PATCH] Give the fog volume a slice of the fog without its clouds for the sky --- rendering/opengl46/opengl46weather.cpp | 4 ++- shaders/fog_integrate.comp | 42 +++++++++++++++++++++++--- shaders/fog_volume.glsl | 33 ++++++-------------- 3 files changed, 49 insertions(+), 30 deletions(-) diff --git a/rendering/opengl46/opengl46weather.cpp b/rendering/opengl46/opengl46weather.cpp index 754fe8c6..b9772246 100644 --- a/rendering/opengl46/opengl46weather.cpp +++ b/rendering/opengl46/opengl46weather.cpp @@ -456,7 +456,8 @@ void opengl46_weather::prepare_view(glm::ivec2 const &Size, quality_level const m_fog_grid = Level.fog_grid; for (auto &scattering : m_fog_scattering) scattering = create_texture(GL_TEXTURE_3D, GL_RGBA16F, m_fog_grid, GL_LINEAR, GL_CLAMP_TO_EDGE); - m_fog_integrated = create_texture(GL_TEXTURE_3D, GL_RGBA16F, m_fog_grid, GL_LINEAR, GL_CLAMP_TO_EDGE); + // one slice more: the fog through the whole volume without the clouds, which the sky takes (fog_integrate.comp) + m_fog_integrated = create_texture(GL_TEXTURE_3D, GL_RGBA16F, m_fog_grid + glm::ivec3{0, 0, 1}, GL_LINEAR, GL_CLAMP_TO_EDGE); m_previous_fog.valid = false; } m_view_level = Level; @@ -558,6 +559,7 @@ void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const glBindImageTexture(0, m_fog_integrated, 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_RGBA16F); glProgramUniform3i(*m_integrate_program, 0, m_fog_grid.x, m_fog_grid.y, m_fog_grid.z); glProgramUniformMatrix4fv(*m_integrate_program, 1, 1, GL_FALSE, glm::value_ptr(inverseprojection)); + glProgramUniformMatrix4fv(*m_integrate_program, 2, 1, GL_FALSE, glm::value_ptr(viewtoworld)); glProgramUniform4fv(*m_integrate_program, 3, 1, glm::value_ptr(slicing)); m_integrate_program->dispatch(groups(m_fog_grid.x), groups(m_fog_grid.y), 1); glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); diff --git a/shaders/fog_integrate.comp b/shaders/fog_integrate.comp index a824217f..f1e462d2 100644 --- a/shaders/fog_integrate.comp +++ b/shaders/fog_integrate.comp @@ -3,20 +3,38 @@ // (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 +// 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 #include +#include layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in; layout(binding = CLOUD_HISTORY_TEX) uniform sampler3D fog_scattering; +layout(binding = CLOUD_SHAPE_TEX) uniform sampler3D cloud_shape_noise; layout(rgba16f, binding = 0) uniform writeonly image3D fog_integrated; layout(location = 0) uniform ivec3 grid; layout(location = 1) uniform mat4 inverse_projection; +layout(location = 2) uniform mat4 view_to_world; // x: near, y: far, z: slices / log((far + FOG_VOLUME_BIAS) / (near + FOG_VOLUME_BIAS)) layout(location = 3) uniform 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); @@ -30,6 +48,10 @@ void main() 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++) { @@ -39,14 +61,24 @@ void main() 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); - cell.rgb = 0.25 * (textureLod(fog_scattering, vec3(coords + vec2(-0.5, -0.5) * texel, depthcoord), 0.0).rgb - + textureLod(fog_scattering, vec3(coords + vec2(0.5, -0.5) * texel, depthcoord), 0.0).rgb - + textureLod(fog_scattering, vec3(coords + vec2(-0.5, 0.5) * texel, depthcoord), 0.0).rgb - + textureLod(fog_scattering, vec3(coords + vec2(0.5, 0.5) * texel, depthcoord), 0.0).rgb); + 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)); } diff --git a/shaders/fog_volume.glsl b/shaders/fog_volume.glsl index 20a58781..b202ace6 100644 --- a/shaders/fog_volume.glsl +++ b/shaders/fog_volume.glsl @@ -1,6 +1,7 @@ // 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 +// 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 @@ -46,22 +47,17 @@ vec4 fog_along_view(vec3 view, vec2 fragcoord) 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), max(slice, 0.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); - vec4 volume = fog_volume_at(coords, (slices - 0.5) / 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); } -// shares of the light the clouds in the volume hold back between which its fog over the sky gives way to the fog without the clouds. -// low: the clouds of the volume (without the detail, so a little wider than those drawn) leave no light rim along their thin edges; -// the lower end above what the cells of the volume and the fog along the ray differ by on their own -const float FOG_SKY_CLOUD_LOW = 0.004; -const float FOG_SKY_CLOUD_HIGH = 0.03; - // 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 +// 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); @@ -69,19 +65,8 @@ vec4 fog_sky(vec3 viewdirection, vec2 fragcoord) 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); - float far = fog_volume_target.z / -view.z; - // the volume holds the low clouds within its reach (fog_inject.comp), for what stands in them; over the sky they are drawn by the - // ray march (clouds_march.comp) already. laid on a second time at the size of the cells they would show blurred and grey, with a - // light rim, and cut into bands where the slices cross them. where the volume lets through less than the fog without the clouds, - // that fog is taken instead - if (weather_camera.z > 0.5) - { - vec4 clear = fog_analytic(direction, 0.0, far, false); - float clouds = smoothstep(FOG_SKY_CLOUD_LOW, FOG_SKY_CLOUD_HIGH, 1.0 - volume.a / max(clear.a, 1e-4)); - volume = mix(volume, clear, clouds); - } - vec4 rest = fog_analytic(direction, far, 200000.0, true); + 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); }