diff --git a/client/shaders/extract_bloom/opengl_fragment.glsl b/client/shaders/extract_bloom/opengl_fragment.glsl index 36671b06c..b79911b9a 100644 --- a/client/shaders/extract_bloom/opengl_fragment.glsl +++ b/client/shaders/extract_bloom/opengl_fragment.glsl @@ -1,13 +1,28 @@ #define rendered texture0 +#define depthmap texture2 struct ExposureParams { float compensationFactor; }; uniform sampler2D rendered; +uniform sampler2D depthmap; + uniform mediump float bloomStrength; uniform ExposureParams exposureParams; +uniform vec3 sunPositionScreen; +uniform float sunBrightness; +uniform vec3 moonPositionScreen; +uniform float moonBrightness; + +uniform vec3 dayLight; +#ifdef ENABLE_DYNAMIC_SHADOWS +uniform vec3 v_LightDirection; +#else +const vec3 v_LightDirection = vec3(0.0, -1.0, 0.0); +#endif + #ifdef GL_ES varying mediump vec2 varTexCoord; #else @@ -18,6 +33,80 @@ centroid varying vec2 varTexCoord; varying float exposure; // linear exposure factor, see vertex shader #endif +const float far = 1000.; +const float near = 1.; +float mapDepth(float depth) +{ + return min(1., 1. / (1.00001 - depth) / far); +} + +float noise(vec3 uvd) { + return fract(dot(sin(uvd * vec3(13041.19699, 27723.29171, 61029.77801)), vec3(73137.11101, 37312.92319, 10108.89991))); +} + +float sampleVolumetricLight(vec2 uv, vec3 lightVec, float rawDepth) +{ + lightVec = 0.5 * lightVec / lightVec.z + 0.5; + const float samples = 30.; + float result = texture2D(depthmap, uv).r < 1. ? 0.0 : 1.0; + float bias = noise(vec3(uv, rawDepth)); + vec2 samplepos; + for (float i = 1.; i < samples; i++) { + samplepos = mix(uv, lightVec.xy, (i + bias) / samples); + if (min(samplepos.x, samplepos.y) > 0. && max(samplepos.x, samplepos.y) < 1.) + result += texture2D(depthmap, samplepos).r < 1. ? 0.0 : 1.0; + } + return result / samples; +} + +vec3 getDirectLightScatteringAtGround(vec3 v_LightDirection) +{ + // Based on talk at 2002 Game Developers Conference by Naty Hoffman and Arcot J. Preetham + const float beta_r0 = 1e-5; // Rayleigh scattering beta + + // These factors are calculated based on expected value of scattering factor of 1e-5 + // for Nitrogen at 532nm (green), 2e25 molecules/m3 in atmosphere + const vec3 beta_r0_l = vec3(3.3362176e-01, 8.75378289198826e-01, 1.95342379700656) * beta_r0; // wavelength-dependent scattering + + const float atmosphere_height = 15000.; // height of the atmosphere in meters + // sun/moon light at the ground level, after going through the atmosphere + return exp(-beta_r0_l * atmosphere_height / (1e-5 - dot(v_LightDirection, vec3(0., 1., 0.)))); +} + +vec3 applyVolumetricLight(vec3 color, vec2 uv, float rawDepth) +{ + vec3 lookDirection = normalize(vec3(uv.x * 2. - 1., uv.y * 2. - 1., rawDepth)); + vec3 lightSourceTint = vec3(1.0, 0.98, 0.4); + + const float boost = 4.0; + float brightness = 0.; + vec3 sourcePosition = vec3(-1., -1., -1); + + if (sunPositionScreen.z > 0. && sunBrightness > 0.) { + brightness = sunBrightness; + sourcePosition = sunPositionScreen; + } + else if (moonPositionScreen.z > 0. && moonBrightness > 0.) { + lightSourceTint = vec3(0.4, 0.9, 1.); + brightness = moonBrightness * 0.05; + sourcePosition = moonPositionScreen; + } + + float cameraDirectionFactor = pow(clamp(dot(sourcePosition, vec3(0., 0., 1.)), 0.0, 0.7), 2.5); + float viewAngleFactor = pow(max(0., dot(sourcePosition, lookDirection)), 8.); + + float lightFactor = brightness * sampleVolumetricLight(uv, sourcePosition, rawDepth) * + (0.05 * cameraDirectionFactor + 0.95 * viewAngleFactor); + + color = mix(color, boost * getDirectLightScatteringAtGround(v_LightDirection) * dayLight, lightFactor); + + // if (sunPositionScreen.z < 0.) + // color.rg += 1. - clamp(abs((2. * uv.xy - 1.) - sunPositionScreen.xy / sunPositionScreen.z) * 1000., 0., 1.); + // if (moonPositionScreen.z < 0.) + // color.rg += 1. - clamp(abs((2. * uv.xy - 1.) - moonPositionScreen.xy / moonPositionScreen.z) * 1000., 0., 1.); + return color; +} + void main(void) { vec2 uv = varTexCoord.st; @@ -31,5 +120,9 @@ void main(void) color *= exposure; #endif + float rawDepth = texture2D(depthmap, uv).r; + + color = applyVolumetricLight(color, uv, rawDepth); + gl_FragColor = vec4(color, 1.0); // force full alpha to avoid holes in the image. } diff --git a/src/client/game.cpp b/src/client/game.cpp index 6b5163616..ab1927eab 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -404,6 +404,10 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter CachedPixelShaderSetting m_bloom_radius_pixel; float m_bloom_radius; CachedPixelShaderSetting m_saturation_pixel; + CachedPixelShaderSetting m_sun_position_pixel; + CachedPixelShaderSetting m_sun_brightness_pixel; + CachedPixelShaderSetting m_moon_position_pixel; + CachedPixelShaderSetting m_moon_brightness_pixel; public: void onSettingsChange(const std::string &name) @@ -461,7 +465,11 @@ public: m_bloom_intensity_pixel("bloomIntensity"), m_bloom_strength_pixel("bloomStrength"), m_bloom_radius_pixel("bloomRadius"), - m_saturation_pixel("saturation") + m_saturation_pixel("saturation"), + m_sun_position_pixel("sunPositionScreen"), + m_sun_brightness_pixel("sunBrightness"), + m_moon_position_pixel("moonPositionScreen"), + m_moon_brightness_pixel("moonBrightness") { g_settings->registerChangedCallback("enable_fog", settingsCallback, this); g_settings->registerChangedCallback("exposure_compensation", settingsCallback, this); @@ -579,6 +587,51 @@ public: } float saturation = m_client->getEnv().getLocalPlayer()->getLighting().saturation; m_saturation_pixel.set(&saturation, services); + + // Map directional light to screen space + auto camera_node = m_client->getCamera()->getCameraNode(); + core::matrix4 transform = camera_node->getProjectionMatrix(); + transform *= camera_node->getViewMatrix(); + + if (m_sky->getSunVisible()) { + v3f sun_position = camera_node->getAbsolutePosition() + + 10000. * m_sky->getSunDirection(); + transform.transformVect(sun_position); + sun_position.normalize(); + + float sun_position_array[3] = { sun_position.X, sun_position.Y, sun_position.Z}; + m_sun_position_pixel.set(sun_position_array, services); + + float sun_brightness = rangelim(107.143f * m_sky->getSunDirection().dotProduct(v3f(0.f, 1.f, 0.f)), 0.f, 1.f); + m_sun_brightness_pixel.set(&sun_brightness, services); + } + else { + float sun_position_array[3] = { 0.f, 0.f, -1.f }; + m_sun_position_pixel.set(sun_position_array, services); + + float sun_brightness = 0.f; + m_sun_brightness_pixel.set(&sun_brightness, services); + } + + if (m_sky->getMoonVisible()) { + v3f moon_position = camera_node->getAbsolutePosition() + + 10000. * m_sky->getMoonDirection(); + transform.transformVect(moon_position); + moon_position.normalize(); + + float moon_position_array[3] = { moon_position.X, moon_position.Y, moon_position.Z}; + m_moon_position_pixel.set(moon_position_array, services); + + float moon_brightness = rangelim(107.143f * m_sky->getMoonDirection().dotProduct(v3f(0.f, 1.f, 0.f)), 0.f, 1.f); + m_moon_brightness_pixel.set(&moon_brightness, services); + } + else { + float moon_position_array[3] = { 0.f, 0.f, -1.f }; + m_moon_position_pixel.set(moon_position_array, services); + + float moon_brightness = 0.f; + m_moon_brightness_pixel.set(&moon_brightness, services); + } } void onSetMaterial(const video::SMaterial &material) override diff --git a/src/client/render/secondstage.cpp b/src/client/render/secondstage.cpp index f33f1975e..6a71f395e 100644 --- a/src/client/render/secondstage.cpp +++ b/src/client/render/secondstage.cpp @@ -171,7 +171,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep // get bright spots u32 shader_id = client->getShaderSource()->getShader("extract_bloom", TILE_MATERIAL_PLAIN, NDT_MESH); - RenderStep *extract_bloom = pipeline->addStep(shader_id, std::vector { TEXTURE_COLOR, TEXTURE_EXPOSURE_1 }); + RenderStep *extract_bloom = pipeline->addStep(shader_id, std::vector { TEXTURE_COLOR, TEXTURE_EXPOSURE_1, TEXTURE_DEPTH }); extract_bloom->setRenderSource(buffer); extract_bloom->setRenderTarget(pipeline->createOwned(buffer, TEXTURE_BLOOM)); source = TEXTURE_BLOOM;