From 04f0d545da000dc86a5c9895110f77b3bfecbf9f Mon Sep 17 00:00:00 2001
From: x2048 <codeforsmile@gmail.com>
Date: Mon, 9 Oct 2023 11:58:58 -0700
Subject: [PATCH] Initial implementation of 'Godrays'

---
 .../extract_bloom/opengl_fragment.glsl        | 93 +++++++++++++++++++
 src/client/game.cpp                           | 55 ++++++++++-
 src/client/render/secondstage.cpp             |  2 +-
 3 files changed, 148 insertions(+), 2 deletions(-)

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<float> m_bloom_radius_pixel;
 	float m_bloom_radius;
 	CachedPixelShaderSetting<float> m_saturation_pixel;
+	CachedPixelShaderSetting<float, 3> m_sun_position_pixel;
+	CachedPixelShaderSetting<float> m_sun_brightness_pixel;
+	CachedPixelShaderSetting<float, 3> m_moon_position_pixel;
+	CachedPixelShaderSetting<float> 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<PostProcessingStep>(shader_id, std::vector<u8> { TEXTURE_COLOR, TEXTURE_EXPOSURE_1 });
+			RenderStep *extract_bloom = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { TEXTURE_COLOR, TEXTURE_EXPOSURE_1, TEXTURE_DEPTH });
 			extract_bloom->setRenderSource(buffer);
 			extract_bloom->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, TEXTURE_BLOOM));
 			source = TEXTURE_BLOOM;