Skip to content

Commit 3cedbbc

Browse files
sofarclaude
andcommitted
Reflect sun and moon using actual textures or generated moon phase.
This patch replaces the "placeholder" sun reflection in water. For this to work, you must have waving water enabled. Previously this only worked when shadows were enabled, but, with this change, it no longer depends on shadows being enabled or not. The sun/moon reflection uses the actual texture that the game uses. I've used Mineclonia to test it and tweak sizes (they're a tiny tiny bit larger in the reflection but I think it's reasonable, because smaller looks weird really quick) and test that the actual reflections are correct (this part took quite a while). Mineclonia relies on the generated sun texture (a bunch of squares stacked on top of each other) so this has been tested with both variants - texture and generated sun/moon. A bunch of new shader stuff is needed to make this work, this patch pulls all of those changes in. The noise functions previously only in the shadow ifdefs are now also needed outside of it. The specular dot that was previously used is now gone. The only times when it now could be used is when the sun or moon is actively hidden or there is a loading issue, or at the wicked time boundary. We avoid any weird visual in all those conditions by just not having it anymore. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d5fc9a1 commit 3cedbbc

File tree

8 files changed

+201
-11
lines changed

8 files changed

+201
-11
lines changed

builtin/settingtypes.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -828,7 +828,7 @@ enable_translucent_foliage (Translucent foliage) bool false
828828

829829
# When enabled, liquid reflections are simulated.
830830
#
831-
# Requires: enable_waving_water, enable_dynamic_shadows
831+
# Requires: enable_waving_water
832832
enable_water_reflections (Liquid reflections) bool false
833833

834834
[*Audio]

client/shaders/nodes_shader/opengl_fragment.glsl

Lines changed: 85 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ uniform float crackAnimationLength;
1919
uniform float crackLevel;
2020
uniform float crackTextureScale;
2121

22+
uniform vec3 sunLightDirection;
23+
uniform vec3 skyColor;
24+
uniform sampler2D sunReflectionSampler;
25+
uniform float sunReflectionScale;
26+
2227
#ifdef ENABLE_DYNAMIC_SHADOWS
2328
// shadow texture
2429
uniform sampler2D ShadowMapSampler;
@@ -57,7 +62,6 @@ flat VARYING_ uint varTexLayer;
5762
CENTROID_ VARYING_ float nightRatio;
5863
VARYING_ highp vec3 eyeVec;
5964

60-
#ifdef ENABLE_DYNAMIC_SHADOWS
6165
#if (defined(ENABLE_WATER_REFLECTIONS) && MATERIAL_WATER_REFLECTIONS && ENABLE_WAVING_WATER)
6266
vec4 perm(vec4 x)
6367
{
@@ -106,6 +110,8 @@ vec2 wave_noise(vec3 p, float off) {
106110
}
107111
#endif
108112

113+
#ifdef ENABLE_DYNAMIC_SHADOWS
114+
109115
// assuming near is always 1.0
110116
float getLinearDepth()
111117
{
@@ -557,14 +563,31 @@ void main(void)
557563
fresnel_factor = clamp(pow(1.0 - fresnel_factor * fresnel_factor, 8.0), 0.0, 1.0) * 0.8 + 0.2;
558564
col.rgb *= 0.5;
559565
vec3 reflection_color = mix(vec3(max(fogColor.r, max(fogColor.g, fogColor.b))), fogColor.rgb, f_shadow_strength);
560-
561-
// Sky reflection
562566
col.rgb += reflection_color * pow(fresnel_factor, 2.0) * 0.5 * brightness_factor;
563-
vec3 water_reflect_color = 12.0 * dayLight * fresnel_factor * mtsmoothstep(0.85, 0.9, pow(clamp(dot(reflect_ray, viewVec), 0.0, 1.0), 32.0)) * max(1.0 - shadow_uncorrected, 0.0);
564567

565-
// This line exists to prevent ridiculously bright reflection colors.
566-
water_reflect_color /= clamp(max(water_reflect_color.r, max(water_reflect_color.g, water_reflect_color.b)) * 0.375, 1.0, 400.0);
567-
col.rgb += water_reflect_color * f_adj_shadow_strength * brightness_factor;
568+
if (sunReflectionScale > 0.0) {
569+
vec3 reflectedView = reflect(viewVec, fNormal);
570+
vec3 sunDir = -v_LightDirection;
571+
float cosAngle = dot(reflectedView, sunDir);
572+
573+
if (cosAngle > 0.0) {
574+
vec3 toSun = reflectedView - sunDir * cosAngle;
575+
vec3 uBasis = normalize(cross(sunDir, vec3(0.0, 1.0, 0.0)));
576+
vec3 vBasis = normalize(cross(cross(sunDir, vec3(0.0, 1.0, 0.0)), sunDir));
577+
float sideSign = sign(sunDir.x);
578+
vec2 sunUV = vec2(
579+
sideSign * dot(toSun, uBasis),
580+
sideSign * dot(toSun, vBasis))
581+
/ sunReflectionScale + 0.5;
582+
583+
if (sunUV.x > 0.0 && sunUV.x < 1.0 && sunUV.y > 0.0 && sunUV.y < 1.0) {
584+
vec4 sunSample = texture2D(sunReflectionSampler, sunUV);
585+
vec3 bodyLight = mix(dayLight, vec3(0.5, 0.57, 0.65), adjusted_night_ratio);
586+
col.rgb += sunSample.rgb * sunSample.a * 4.0 * fresnel_factor
587+
* bodyLight * max(1.0 - shadow_uncorrected, 0.0);
588+
}
589+
}
590+
}
568591
#endif
569592

570593
#if (defined(ENABLE_NODE_SPECULAR) && !MATERIAL_WATER_REFLECTIONS)
@@ -587,6 +610,61 @@ void main(void)
587610
}
588611
#endif
589612

613+
#if !defined(ENABLE_DYNAMIC_SHADOWS) && defined(ENABLE_WATER_REFLECTIONS) && MATERIAL_WATER_REFLECTIONS
614+
{
615+
vec3 fNormal = vNormal;
616+
vec3 viewVec = normalize(worldPosition + cameraOffset - cameraPosition);
617+
618+
#if ENABLE_WAVING_WATER
619+
vec3 wavePos = worldPosition * vec3(2.0, 0.0, 2.0);
620+
float off = animationTimer * WATER_WAVE_SPEED * 10.0;
621+
wavePos.x /= WATER_WAVE_LENGTH * 3.0;
622+
wavePos.z /= WATER_WAVE_LENGTH * 2.0;
623+
624+
vec2 gradient = wave_noise(wavePos, off);
625+
fNormal = normalize(normalize(fNormal) + vec3(gradient.x, 0., gradient.y) * WATER_WAVE_HEIGHT * abs(fNormal.y) * 0.25);
626+
#endif
627+
628+
float fresnel_factor = dot(fNormal, viewVec);
629+
fresnel_factor = clamp(pow(1.0 - fresnel_factor * fresnel_factor, 8.0), 0.0, 1.0) * 0.8 + 0.2;
630+
631+
// Derive nighttime from dayLight brightness
632+
float dayBrightness = dot(dayLight, vec3(0.2125, 0.7154, 0.0721));
633+
float isNight = 1.0 - smoothstep(0.0, 0.3, dayBrightness);
634+
635+
// Suppress reflections in dark caves; allow at night via isNight floor
636+
float localLight = dot(varColor.rgb, vec3(0.2125, 0.7154, 0.0721));
637+
float skyVisibility = max(smoothstep(0.1, 0.4, localLight), isNight);
638+
639+
col.rgb *= mix(0.8, 0.5, skyVisibility);
640+
col.rgb += skyColor * pow(fresnel_factor, 2.0) * 0.5 * skyVisibility;
641+
642+
if (sunReflectionScale > 0.0) {
643+
vec3 reflectedView = reflect(viewVec, fNormal);
644+
vec3 sunDir = sunLightDirection;
645+
float cosAngle = dot(reflectedView, sunDir);
646+
647+
if (cosAngle > 0.0) {
648+
vec3 toSun = reflectedView - sunDir * cosAngle;
649+
vec3 uBasis = normalize(cross(sunDir, vec3(0.0, 1.0, 0.0)));
650+
vec3 vBasis = normalize(cross(cross(sunDir, vec3(0.0, 1.0, 0.0)), sunDir));
651+
float sideSign = sign(sunDir.x);
652+
vec2 sunUV = vec2(
653+
sideSign * dot(toSun, uBasis),
654+
sideSign * dot(toSun, vBasis))
655+
/ sunReflectionScale + 0.5;
656+
657+
if (sunUV.x > 0.0 && sunUV.x < 1.0 && sunUV.y > 0.0 && sunUV.y < 1.0) {
658+
vec4 sunSample = texture2D(sunReflectionSampler, sunUV);
659+
vec3 bodyLight = mix(dayLight, vec3(0.5, 0.57, 0.65), isNight);
660+
col.rgb += sunSample.rgb * sunSample.a * 4.0
661+
* fresnel_factor * skyVisibility * bodyLight;
662+
}
663+
}
664+
}
665+
}
666+
#endif
667+
590668
// Due to a bug in some (older ?) graphics stacks (possibly in the glsl compiler ?),
591669
// the fog will only be rendered correctly if the last operation before the
592670
// clamp() is an addition. Else, the clamp() seems to be ignored.

src/client/clientmap.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,13 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
11091109
});
11101110
material.Wireframe = m_control.show_wireframe;
11111111

1112+
if (m_sun_reflection_texture) {
1113+
auto &sun_layer = material.TextureLayers[TEXTURE_LAYER_SUN_REFLECTION];
1114+
sun_layer.Texture = m_sun_reflection_texture;
1115+
sun_layer.MinFilter = video::ETMINF_LINEAR_MIPMAP_NEAREST;
1116+
sun_layer.MagFilter = video::ETMAGF_LINEAR;
1117+
}
1118+
11121119
// pass the shadow map texture to the buffer texture
11131120
ShadowRenderer *shadow = m_rendering_engine->get_shadow_renderer();
11141121
if (shadow && shadow->is_active()) {
@@ -1130,6 +1137,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
11301137
++array_texture_use;
11311138

11321139
material.TextureLayers[ShadowRenderer::TEXTURE_LAYER_SHADOW].Texture = nullptr;
1140+
material.TextureLayers[TEXTURE_LAYER_SUN_REFLECTION].Texture = nullptr;
11331141
}
11341142

11351143
m.setTranslation(descriptor.m_pos);

src/client/clientmap.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ class ClientMap : public Map, public scene::ISceneNode
7171

7272
void updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset, video::SColor light_color);
7373

74+
static constexpr int TEXTURE_LAYER_SUN_REFLECTION = 2;
75+
void setSunReflectionTexture(video::ITexture *tex) { m_sun_reflection_texture = tex; }
76+
7477
/*
7578
Forcefully get a sector from somewhere
7679
*/
@@ -167,6 +170,7 @@ class ClientMap : public Map, public scene::ISceneNode
167170
f32 m_camera_fov = M_PI;
168171
v3s16 m_camera_offset;
169172
video::SColor m_camera_light_color = video::SColor(0xFFFFFFFF);
173+
video::ITexture *m_sun_reflection_texture = nullptr;
170174
bool m_needs_update_transparent_meshes = true;
171175

172176
std::map<v3s16, MapBlock*, MapBlockComparer> m_drawlist;

src/client/game.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ class GameGlobalShaderUniformSetter : public IShaderUniformSetter
109109
CachedPixelShaderSetting<float> m_moon_brightness_pixel{"moonBrightness"};
110110
CachedPixelShaderSetting<float>
111111
m_volumetric_light_strength_pixel{"volumetricLightStrength"};
112+
CachedPixelShaderSetting<float, 3> m_sky_color_pixel{"skyColor"};
113+
CachedPixelShaderSetting<float, 3> m_light_direction_pixel{"sunLightDirection"};
114+
CachedPixelShaderSetting<s32> m_sun_texture_pixel{"sunReflectionSampler"};
115+
CachedPixelShaderSetting<float> m_sun_texture_scale_pixel{"sunReflectionScale"};
112116

113117
static constexpr std::array<const char*, 1> SETTING_CALLBACKS = {
114118
"exposure_compensation",
@@ -256,6 +260,35 @@ class GameGlobalShaderUniformSetter : public IShaderUniformSetter
256260
float volumetric_light_strength = lighting.volumetric_light_strength;
257261
m_volumetric_light_strength_pixel.set(&volumetric_light_strength, services);
258262
}
263+
264+
{
265+
video::SColor sky = m_sky->getSkyColor();
266+
v3f sky_color(sky.getRed() / 255.0f, sky.getGreen() / 255.0f, sky.getBlue() / 255.0f);
267+
m_sky_color_pixel.set(sky_color, services);
268+
}
269+
270+
{
271+
float wicked_tod = getWickedTimeOfDay(m_client->getEnv().getTimeOfDay() / 24000.f);
272+
bool is_day = wicked_tod > 0.25f && wicked_tod < 0.75f;
273+
v3f light_dir = (is_day && m_sky->getSunVisible())
274+
? m_sky->getSunDirection()
275+
: (m_sky->getMoonVisible() ? m_sky->getMoonDirection() : v3f(0, -1, 0));
276+
m_light_direction_pixel.set(light_dir, services);
277+
}
278+
279+
{
280+
float wicked_tod = getWickedTimeOfDay(m_client->getEnv().getTimeOfDay() / 24000.f);
281+
bool is_day = wicked_tod > 0.25f && wicked_tod < 0.75f;
282+
float body_scale = 0.0f;
283+
if (is_day && m_sky->getSunVisible() && m_sky->getSunTexture())
284+
body_scale = 0.25f;
285+
else if (m_sky->getMoonVisible() && m_sky->getMoonTexture())
286+
body_scale = 0.63f;
287+
m_sun_texture_scale_pixel.set(&body_scale, services);
288+
289+
s32 layer = 2;
290+
m_sun_texture_pixel.set(&layer, services);
291+
}
259292
}
260293

261294
void onSetMaterial(const video::SMaterial &material) override
@@ -358,6 +391,7 @@ class NodeShaderConstantSetter : public IShaderConstantSetter
358391
case TILE_MATERIAL_WAVING_LIQUID_OPAQUE:
359392
case TILE_MATERIAL_WAVING_LIQUID_BASIC:
360393
case TILE_MATERIAL_LIQUID_TRANSPARENT:
394+
case TILE_MATERIAL_LIQUID_OPAQUE:
361395
constants["MATERIAL_WATER_REFLECTIONS"] = 1;
362396
break;
363397
default:
@@ -3446,6 +3480,18 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
34463480
sunlight_seen, camera->getCameraMode(), player->getYaw(),
34473481
player->getPitch());
34483482

3483+
// Set sun/moon texture for water surface reflections.
3484+
{
3485+
float wicked_tod = getWickedTimeOfDay(time_of_day_smooth);
3486+
bool is_day = wicked_tod > 0.25f && wicked_tod < 0.75f;
3487+
video::ITexture *body_tex = nullptr;
3488+
if (is_day && sky->getSunVisible())
3489+
body_tex = sky->getSunTexture();
3490+
else if (sky->getMoonVisible())
3491+
body_tex = sky->getMoonTexture();
3492+
client->getEnv().getClientMap().setSunReflectionTexture(body_tex);
3493+
}
3494+
34493495
/*
34503496
Update clouds
34513497
*/

src/client/shader.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,9 @@ class MainShaderConstantSetter : public IShaderConstantSetter
212212
{
213213
constants["ENABLE_TONE_MAPPING"] = g_settings->getBool("tone_mapping") ? 1 : 0;
214214

215+
if (g_settings->getBool("enable_water_reflections"))
216+
constants["ENABLE_WATER_REFLECTIONS"] = 1;
217+
215218
if (g_settings->getBool("enable_dynamic_shadows")) {
216219
constants["ENABLE_DYNAMIC_SHADOWS"] = 1;
217220
if (g_settings->getBool("shadow_map_color"))
@@ -220,9 +223,6 @@ class MainShaderConstantSetter : public IShaderConstantSetter
220223
if (g_settings->getBool("shadow_poisson_filter"))
221224
constants["POISSON_FILTER"] = 1;
222225

223-
if (g_settings->getBool("enable_water_reflections"))
224-
constants["ENABLE_WATER_REFLECTIONS"] = 1;
225-
226226
if (g_settings->getBool("enable_translucent_foliage"))
227227
constants["ENABLE_TRANSLUCENT_FOLIAGE"] = 1;
228228

src/client/sky.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ Sky::Sky(s32 id, RenderingEngine *rendering_engine, ITextureSource *tsrc, IShade
8484
m_sky_params.body_orbit_tilt = g_settings->getFloat("shadow_sky_body_orbit_tilt", -60., 60.);
8585
m_sky_params.fog_start = rangelim(g_settings->getFloat("fog_start"), 0.0f, 0.99f);
8686

87+
generateReflectionTextures(rendering_engine->get_video_driver());
88+
8789
setStarCount(1000);
8890
}
8991

@@ -724,6 +726,53 @@ static void getTextureAsImage(video::IImage *&dst, const std::string &name, ITex
724726
}
725727
}
726728

729+
void Sky::generateReflectionTextures(video::IVideoDriver *driver)
730+
{
731+
const u32 size = 64;
732+
const float center = (size - 1) * 0.5f;
733+
734+
auto generate = [&](const float sizes[], int nlayers, const float alphas[],
735+
const char *name) -> video::ITexture * {
736+
video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
737+
core::dimension2du(size, size));
738+
img->fill(video::SColor(0, 255, 255, 255));
739+
740+
for (u32 y = 0; y < size; y++) {
741+
for (u32 x = 0; x < size; x++) {
742+
float fx = (x - center) / center;
743+
float fy = (y - center) / center;
744+
float dist = std::max(std::fabs(fx), std::fabs(fy));
745+
746+
float a = 0.0f;
747+
for (int i = 0; i < nlayers; i++) {
748+
if (dist <= sizes[i]) {
749+
a = alphas[i] + a * (1.0f - alphas[i]);
750+
}
751+
}
752+
a = std::min(a, 1.0f);
753+
img->setPixel(x, y, video::SColor(
754+
(u32)(a * 255), 255, 255, 255));
755+
}
756+
}
757+
758+
video::ITexture *tex = driver->addTexture(name, img);
759+
img->drop();
760+
return tex;
761+
};
762+
763+
{
764+
const float sizes[] = {1.0f, 1.0f/1.7f*1.2f, 1.0f/1.7f, 1.0f/1.7f*0.7f};
765+
const float alphas[] = {0.05f, 0.15f, 1.0f, 1.0f};
766+
m_sun_reflection_texture = generate(sizes, 4, alphas, "__sun_reflection");
767+
}
768+
769+
{
770+
const float sizes[] = {1.0f, 1.0f/1.9f*1.3f, 1.0f/1.9f, 1.0f/1.9f*0.6f};
771+
const float alphas[] = {0.05f, 0.15f, 1.0f, 1.0f};
772+
m_moon_reflection_texture = generate(sizes, 4, alphas, "__moon_reflection");
773+
}
774+
}
775+
727776
void Sky::setSunTexture(const std::string &sun_texture,
728777
const std::string &sun_tonemap, ITextureSource *tsrc)
729778
{

src/client/sky.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,15 @@ class Sky : public scene::ISceneNode
6464
void setSunriseVisible(bool glow_visible) { m_sun_params.sunrise_visible = glow_visible; }
6565
void setSunriseTexture(const std::string &sunglow_texture, ITextureSource* tsrc);
6666
v3f getSunDirection();
67+
video::ITexture *getSunTexture() const { return m_sun_texture ? m_sun_texture : m_sun_reflection_texture; }
6768

6869
void setMoonVisible(bool moon_visible) { m_moon_params.visible = moon_visible; }
6970
bool getMoonVisible() const { return m_moon_params.visible; }
7071
void setMoonTexture(const std::string &moon_texture,
7172
const std::string &moon_tonemap, ITextureSource *tsrc);
7273
void setMoonScale(f32 moon_scale) { m_moon_params.scale = moon_scale; }
7374
v3f getMoonDirection();
75+
video::ITexture *getMoonTexture() const { return m_moon_texture ? m_moon_texture : m_moon_reflection_texture; }
7476

7577
void setStarsVisible(bool stars_visible) { m_star_params.visible = stars_visible; }
7678
void setStarCount(u16 star_count);
@@ -213,9 +215,12 @@ class Sky : public scene::ISceneNode
213215

214216
video::ITexture *m_sun_texture = nullptr;
215217
video::ITexture *m_moon_texture = nullptr;
218+
video::ITexture *m_sun_reflection_texture = nullptr;
219+
video::ITexture *m_moon_reflection_texture = nullptr;
216220
video::IImage *m_sun_tonemap = nullptr;
217221
video::IImage *m_moon_tonemap = nullptr;
218222

223+
void generateReflectionTextures(video::IVideoDriver *driver);
219224
void updateStars();
220225

221226
void draw_sun(video::IVideoDriver *driver, const video::SColor &suncolor,

0 commit comments

Comments
 (0)