diff --git a/README.md b/README.md
index d968370..a505c18 100644
--- a/README.md
+++ b/README.md
@@ -20,11 +20,11 @@ La [vision complète](docs/vision.md) conserve les intentions ; le
L’économie, les claims et les dimensions décrits dans la vision restent
à implémenter.
-La version **beta.137** ajoute des **rayons solaires volumétriques** qui traversent
-les ouvertures du décor et le feuillage. Activés à 35 %, réglables indépendamment
-du bloom dans **Options → Shaders**. Carte d'ombres partagée, portée de 64 blocs
-maximum. Le [ticket](docs/shader-beta137.md) détaille les vérifications.
-Release publiée et même instance Prism synchronisée ; sauvegardes et réglages conservés.
+La version **beta.138** adapte l’épaisseur des **rayons solaires à la météo** :
+plus denses et diffus dans le brouillard, renforcés sous les précipitations,
+avec moins de lumière pendant l’orage. Le curseur reste inchangé.
+Le [ticket](docs/shader-beta138.md) détaille le rendu et ses vérifications.
+Livraison en cours de vérification.
La [livraison précédente](docs/clay-workshop-integration-beta110.md) réunit déjà
l’atelier, les œufs et la neige saisonnière dans l’instance **Sanctuary Beta**.
Les [synchronisations antérieures](docs/git-sync-beta092.md) restent documentées.
@@ -35,6 +35,13 @@ La [carte native beta.005](docs/atlas-beta005.md) prend le relais après
l’étude de Xaero et des alternatives libres.
+## beta.138 — Rayons et brouillard
+
+Densité automatique des rayons selon le brouillard et les précipitations.
+[Contrat et vérifications](docs/shader-beta138.md) ·
+[Pack normal](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.138/Sanctuary-beta.138.mrpack) ·
+[Pack de test](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.138/Sanctuary-Test-beta.138.mrpack).
+
## beta.137 — Rayons solaires
Faisceaux volumétriques légers, intensité réglable et feuillage perméable.
diff --git a/docs/shader-beta138.md b/docs/shader-beta138.md
new file mode 100644
index 0000000..41d5161
--- /dev/null
+++ b/docs/shader-beta138.md
@@ -0,0 +1,86 @@
+# SHADER-138 — Épaisseur des rayons et météo
+
+Demande : épaissir les sunshafts avec la météo, surtout le brouillard.
+Socle beta.137, branche `codex/sunshaft-weather-beta138`, Minecraft 26.3 / Java 25.
+Aucune modification des règles météo, des sauvegardes ou du gameplay.
+
+## Contrat
+
+Densité et diffusion liées au brouillard environnemental réellement rendu,
+indépendamment de la limite de distance de rendu. Pluie/neige natives :
+renforcement plus modéré ; orage : réduction supplémentaire du soleil.
+Transitions continues, mêmes deux passes et seize segments. Le réglage
+manuel conserve sa valeur, aucune nouvelle option.
+
+## Rendu
+
+Le brouillard utilise `FogData.environmentalEnd`, séparé de la distance de
+rendu native. Sa contribution croît en douceur entre 192 et 80 blocs de
+visibilité. La densité passe de ×1 à ×3,5 pour le brouillard seul ; la diffusion
+angulaire s'élargit et la lumière lointaine s'éteint plus vite. Les coefficients
+ne dépendent d'aucun bruit animé ni historique d'image.
+
+Pluie et neige utilisent le niveau natif de précipitation : densité jusqu'à
+×1,8, diffusion plus légère. La lumière disponible tombe progressivement à
+25 % sous une pluie maximale et à 8,75 % sous un orage maximal. La carte solaire
+reste disponible pour le volume même lorsque Minecraft cache entièrement
+son disque solaire ; les ombres sur les surfaces conservent leur comportement.
+Le brouillard et la météo continuent leurs transitions natives.
+
+Même cible au quart de chaque dimension, deux passes, seize segments et
+quatre lectures de la carte par segment. L'empreinte de l'ombre ne change pas :
+la météo ne crée pas d'ouverture artificielle à travers un mur.
+
+## Vérification
+
+Scénario client natif réussi en **3 min 29 s**, macOS / Apple M1 / OpenGL.
+Les contrôles des volumes beta.137 passent à nouveau : ouvertures, feuilles,
+pièce fermée, mouvement/retour, proximité, soleil hors champ, intensité,
+OFF/zéro/nuit, redimensionnement, rechargement, préférences et libellés FR/EN.
+
+La suite utilise ensuite les vraies commandes serveur de météo : clair,
+`weather fog`, pluie, orage et retour au clair, dans le même décor avec le
+curseur maintenu à 35. Transition progressive du brouillard, rechargement
+sous brouillard, absence d'éclairage d'une pièce fermée et restitution de
+l'image claire initiale vérifiés. La contribution normalisée du volume dans
+cette scène passe d'environ 0,0034 au clair à 0,0236 sous brouillard ; ce n'est
+pas un multiplicateur universel de luminosité. L'orage reste sous la pluie.
+
+Log : `build/shader138-client-final.log`, marqueurs `SUNSHAFT137_PASS` et
+`SUNSHAFT138_WEATHER_PASS`. Comparatif natif autonome :
+`build/Shader-beta.138-comparaison.html`.
+
+## Diagnostic de la pluie
+
+La première exécution native a validé le brouillard, puis s'est arrêtée en
+attendant des rayons sous une pluie maximale. Minecraft 26.3 fournit alors
+`rainBrightness = 1 - rainLevel`, soit zéro : beta.137 ne construisait plus la
+carte solaire. Le volume reçoit désormais son atténuation météo propre et
+conserve la carte nécessaire, sans réactiver les ombres de surface sous la
+pluie maximale. Log de diagnostic : `build/shader138-client-first.log`.
+
+## Limites
+
+Coefficients artistiques bornés, sans simulation physique de gouttes ni de
+nuages. Le brouillard environnemental est distinct de la simple distance
+d'affichage ; la pluie et la neige partagent le niveau natif de précipitation.
+Les limites des volumes beta.137 restent applicables : 64 blocs maximum,
+formes de la carte d'ombres et feuillage approximé. Pas de mesure de FPS ni
+de validation Windows/Vulkan revendiquée.
+
+
+## Construction et archives
+
+`./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest`
+réussit en **2 min 20 s**, 126 tâches. Le serveur GameTest dédié reste exclu
+conformément au refus EULA antérieur. Log : `build/shader138-build.log`.
+Comparaison à beta.137 : **6 entrées de production** modifiées, uniquement
+le rendu et les aides FR/EN. Gameplay et textures des gemmes strictement
+conservés. JAR normal/Test identique, sources et template vérifiés.
+Reçu local : `build/shader138-artifact.json`.
+
+- `Sanctuary-beta.138.mrpack` : 10432860 octets ; SHA-256 `67093d3a53e3d90c31e8fb940d709ccf41ad730595e358346fa27ad744b44460`.
+
+- `Sanctuary-Test-beta.138.mrpack` : 10451787 octets ; SHA-256 `1ff39158b47f44a831b6506c8c2636441b2bff02644d20b1698330097e5ff059`.
+
+JAR : `44b6dd14f0e962fcb6f1efd61e0e7bb2c8dbb033cef81598582ec16c46a46518`.
diff --git a/gradle.properties b/gradle.properties
index 52b0c7d..7503cac 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -9,8 +9,8 @@ loom_version=1.17.20
fabric_api_version=0.160.5+26.3
# Release counter: beta.001, .002, .003, ... (see docs/versioning.md).
-mod_version=beta.137
-pack_version=beta.137
+mod_version=beta.138
+pack_version=beta.138
resource_pack_version=beta.121
maven_group=fr.koka.sanctuary
jei_version=30.32.0-sanctuary.3
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/PixelShadows122ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/PixelShadows122ClientChecks.java
index efa0af5..77e6e82 100644
--- a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/PixelShadows122ClientChecks.java
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/PixelShadows122ClientChecks.java
@@ -268,7 +268,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
});
});
c.waitTicks(3);
- c.takeScreenshot("sanctuary-beta137-shader-options-" + locale);
+ c.takeScreenshot("sanctuary-beta138-shader-options-" + locale);
}
c.runOnClient(m -> m.gui.setScreen(null));
}
@@ -552,7 +552,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
}
static Frame capture(ClientGameTestContext c, String name) {
- if (name != null) c.takeScreenshot("sanctuary-beta137-shader-" + name);
+ if (name != null) c.takeScreenshot("sanctuary-beta138-shader-" + name);
var result = new CompletableFuture();
c.runOnClient(m -> Screenshot.takeScreenshot(m.gameRenderer.mainRenderTarget(), image -> {
try (image) {
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/SunshaftWeather138ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/SunshaftWeather138ClientChecks.java
new file mode 100644
index 0000000..fd9ccb9
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/SunshaftWeather138ClientChecks.java
@@ -0,0 +1,39 @@
+package fr.koka.sanctuary.client.shader;
+
+import static fr.koka.sanctuary.client.shader.PixelShadows122ClientChecks.*;
+import static fr.koka.sanctuary.client.shader.Sunshafts137ClientChecks.*;
+import fr.koka.sanctuary.client.WeatherClient;
+import fr.koka.sanctuary.weather.DailyWeather;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.fabricmc.fabric.api.client.gametest.v1.context.TestServerContext;
+
+/** Actual server weather -> native atmospheric fog -> shader scattering, same scene and manual strength. */
+final class SunshaftWeather138ClientChecks {
+ static void run(ClientGameTestContext c,TestServerContext server){
+ check(Sunshafts.fogDensity(1024)==0&&Sunshafts.fogDensity(192)==0&&Sunshafts.fogDensity(80)==1,"Clear and full fog endpoints");
+ float previous=0;for(int end=192;end>=80;end--){float d=Sunshafts.fogDensity(end);check(d>=previous&&d-previous<.014,"Fog density is bounded, smooth and monotonic");previous=d;}
+ check(Sunshafts.fogDensity(Float.POSITIVE_INFINITY)==0&&Sunshafts.fogDensity(Float.NaN)==0,"Invalid fog distance cannot create NaN shafts");
+ c.runOnClient(m->{VanillaLight.setSunshafts(false);VanillaLight.setSunshaftIntensity(35);VanillaLight.setPixelShadows(false);VanillaLight.setBloom(false);VanillaLight.setLensFlare(false);VanillaLight.setOreEmission(false);VanillaLight.setPbr(false);VanillaLight.setSsgi(false);VanillaLight.setEdgeHighlights(false);});
+ roof(server,1);camera(c,server,100.5,101,110.5,70);aim(c,server);
+ profile(c,server,DailyWeather.Kind.CLEAR);enable(c,false);var clearOff=capture(c,"weather-shafts-clear-off");enable(c,true);var clear=capture(c,"weather-shafts-clear-35");double clearEnergy=energy(clearOff,clear);
+ server.runOnServer(s->command(s,"weather fog"));c.waitFor(m->WeatherClient.state().kind()==DailyWeather.Kind.FOG,200);
+ float[] transition=new float[12];for(int i=0;iSunshafts.fogDensity(m.gameRenderer.gameRenderState().levelRenderState.cameraRenderState.fogData.environmentalEnd));if(i>0)check(transition[i]>=transition[i-1]-.002&&transition[i]-transition[i-1]<.4,"Live fog transition has no abrupt density jump");}
+ c.runOnClient(m->{check(m.gameRenderer.gameRenderState().levelRenderState.cameraRenderState.fogData.environmentalEnd<=80.01,"Fog command reaches actual native FogData");check(Sunshafts.applied(),"Atmospheric fog does not disable rays");});
+ enable(c,false);var fogOff=capture(c,"weather-shafts-fog-off");enable(c,true);var fog=capture(c,"weather-shafts-fog-35");double fogEnergy=energy(fogOff,fog);
+ check(fogEnergy>clearEnergy*1.7,"Fog visibly thickens scattering at the same intensity: "+clearEnergy+" -> "+fogEnergy);unchanged(fog,capture(c,null),"Stable fog adds no animated noise");
+ var reload=c.computeOnClient(net.minecraft.client.Minecraft::reloadResourcePacks);c.waitFor(m->reload.isDone()&&m.gui.overlay()==null,1200);reload.join();settle(c);unchanged(fog,capture(c,"weather-shafts-fog-reload"),"Weather coupling survives shader reload");
+ roof(server,0);settle(c);enable(c,false);var sealedOff=capture(c,null);enable(c,true);unchanged(sealedOff,capture(c,"weather-shafts-fog-sealed"),"Dense fog cannot illuminate a sealed room through walls");roof(server,1);settle(c);
+ profile(c,server,DailyWeather.Kind.RAIN);enable(c,false);var rainOff=capture(c,"weather-shafts-rain-off");enable(c,true);double rainEnergy=energy(rainOff,capture(c,"weather-shafts-rain-35"));check(rainEnergy>0&&rainEnergycheck(VanillaLight.sunshaftIntensity()==35,"Weather never changes the player's intensity setting"));
+ c.runOnClient(m->VanillaLight.setSunshaftIntensity(0));c.waitTicks(10);c.runOnClient(m->check(!Sunshafts.allocated(),"Zero still frees atmospheric scattering"));
+ System.out.println("SUNSHAFT138_WEATHER_PASS clear="+clearEnergy+" fog="+fogEnergy+" rain="+rainEnergy+" storm="+stormEnergy+" smooth transitions, native fog, closed room, reload, restored clear and manual setting");
+ }
+ private static void profile(ClientGameTestContext c,TestServerContext server,DailyWeather.Kind kind){
+ server.runOnServer(s->command(s,"sanctuary weather "+kind.key()));
+ c.waitFor(m->WeatherClient.state().active()&&WeatherClient.state().kind()==kind,200);
+ c.waitFor(m->(kind.raining()?m.level.getRainLevel(1)>.999:m.level.getRainLevel(1)<.001)&&(kind.thundering()?m.level.getThunderLevel(1)>.999:m.level.getThunderLevel(1)<.001),600);
+ c.waitTicks(240);
+ }
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/Sunshafts137ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/Sunshafts137ClientChecks.java
index d3549c0..99429e3 100644
--- a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/Sunshafts137ClientChecks.java
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/Sunshafts137ClientChecks.java
@@ -50,14 +50,15 @@ public final class Sunshafts137ClientChecks implements FabricClientGameTest {
c.runOnClient(m->{VanillaLight.setSunshafts(false);});c.waitTicks(10);c.runOnClient(m->check(!Sunshafts.allocated()&&PixelShadows.applied(),"OFF preserves independently enabled surface shadows"));
for(String locale:new String[]{"fr_fr","en_us"})c.runOnClient(m->{m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());for(String suffix:new String[]{"sunshafts","sunshafts_help","sunshaft_intensity","sunshaft_intensity_help"}){String key="sanctuary.shaders."+suffix;check(!Component.translatable(key).getString().equals(key),"Localized volume controls: "+locale);}});
System.out.println("SUNSHAFT137_PASS slotGain="+low+" leafGain="+leaf+" openGain="+open+" closed room, offscreen sun, camera return, proximity, reload/resize, shared map, zero/night, preferences, FR/EN");
+ SunshaftWeather138ClientChecks.run(c,server);
}
- private static void roof(TestServerContext server,int mode){server.runOnServer(s->{for(int x=76;x<=124;x++)for(int z=76;z<=124;z++)s.overworld().setBlockAndUpdate(new BlockPos(x,113,z),(mode==3||mode==1&&Math.floorMod(z,8)<3?Blocks.AIR:mode==2?Blocks.OAK_LEAVES:Blocks.OAK_PLANKS).defaultBlockState().trySetValue(net.minecraft.world.level.block.state.properties.BlockStateProperties.PERSISTENT,true));});}
- private static void aim(ClientGameTestContext c,TestServerContext server){var d=c.computeOnClient(m->PixelShadows.sunDirection(m.gameRenderer.gameRenderState().levelRenderState.skyRenderState.sunAngle));look(c,server,Mth.wrapDegrees((float)Math.toDegrees(Math.atan2(-d.x,d.z))+25),-12);}
- private static void settle(ClientGameTestContext c){int before=c.computeOnClient(m->Sunshafts.renderedFrames());c.waitFor(m->Sunshafts.applied()&&PixelShadows.ready()&&Sunshafts.renderedFrames()>before+10,1200);c.waitTicks(30);}
- private static void enable(ClientGameTestContext c,boolean enabled){c.runOnClient(m->VanillaLight.setSunshafts(enabled));if(enabled)settle(c);else c.waitTicks(10);}
+ static void roof(TestServerContext server,int mode){server.runOnServer(s->{for(int x=76;x<=124;x++)for(int z=76;z<=124;z++)s.overworld().setBlockAndUpdate(new BlockPos(x,113,z),(mode==3||mode==1&&Math.floorMod(z,8)<3?Blocks.AIR:mode==2?Blocks.OAK_LEAVES:Blocks.OAK_PLANKS).defaultBlockState().trySetValue(net.minecraft.world.level.block.state.properties.BlockStateProperties.PERSISTENT,true));});}
+ static void aim(ClientGameTestContext c,TestServerContext server){var d=c.computeOnClient(m->PixelShadows.sunDirection(m.gameRenderer.gameRenderState().levelRenderState.skyRenderState.sunAngle));look(c,server,Mth.wrapDegrees((float)Math.toDegrees(Math.atan2(-d.x,d.z))+25),-12);}
+ static void settle(ClientGameTestContext c){int before=c.computeOnClient(m->Sunshafts.renderedFrames());c.waitFor(m->Sunshafts.applied()&&PixelShadows.ready()&&Sunshafts.renderedFrames()>before+10,1200);c.waitTicks(30);}
+ static void enable(ClientGameTestContext c,boolean enabled){c.runOnClient(m->VanillaLight.setSunshafts(enabled));if(enabled)settle(c);else c.waitTicks(10);}
private static long gain(Frame off,Frame on){long sum=0;for(int i=0;i>shift&255)-(off.pixels()[i]>>shift&255));return sum;}
// Screen blending has less headroom on a brighter vanilla wall. Remove that factor
// before comparing roofs, and exclude the roof itself where geometry changes.
- private static double energy(Frame off,Frame on){double sum=0;int n=0;for(int y=off.height()/3;y>shift&255,b=on.pixels()[i]>>shift&255;if(a<240){sum+=Math.max(0,b-a)/(255.0-a);n++;}}}return sum/Math.max(1,n);}
+ static double energy(Frame off,Frame on){double sum=0;int n=0;for(int y=off.height()/3;y>shift&255,b=on.pixels()[i]>>shift&255;if(a<240){sum+=Math.max(0,b-a)/(255.0-a);n++;}}}return sum/Math.max(1,n);}
private static void preferences(){try{var p=FabricLoader.getInstance().getConfigDir().resolve("sanctuary-shaders.json");Files.writeString(p,"{\"bloom\":false,\"pixelShadows\":false}");forgetPreferences();check(VanillaLight.sunshafts()&&VanillaLight.sunshaftIntensity()==35&&!VanillaLight.bloom()&&!VanillaLight.pixelShadows(),"Missing shaft settings preserve existing choices");VanillaLight.setSunshafts(false);VanillaLight.setSunshaftIntensity(73);forgetPreferences();check(!VanillaLight.sunshafts()&&VanillaLight.sunshaftIntensity()==73,"Custom shafts persist");for(int n:new int[]{-10,0,35,100,150}){VanillaLight.setSunshaftIntensity(n);forgetPreferences();check(VanillaLight.sunshaftIntensity()==Math.clamp(n,0,100),"Intensity clamps and persists");}VanillaLight.setSunshaftIntensity(35);}catch(java.io.IOException e){throw new AssertionError(e);}}
}
diff --git a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/shader/PixelShadows.java b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/shader/PixelShadows.java
index be5cf28..32056f2 100644
--- a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/shader/PixelShadows.java
+++ b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/shader/PixelShadows.java
@@ -91,7 +91,10 @@ public final class PixelShadows {
float moon = sky.moonPhase == null ? 1 : (float)(.5 + .5 * Math.cos(sky.moonPhase.index() * Math.PI / 4));
float strength = (lunar ? .10f * moon : .38f)
* Math.clamp((sun.y - .08f) / .16f, 0, 1) * sky.rainBrightness;
- if (strength <= .001f) return;
+ // Rain hides the native solar sprite completely. The atmospheric volume still
+ // needs occluders for its weak scattered daylight; surface shadows stay off.
+ boolean solarVolume=!lunar&&VanillaLight.sunshafts()&&VanillaLight.sunshaftIntensity()>0;
+ if (strength <= .001f && !solarVolume) return;
int distance = VanillaLight.shadowDistance(), pixels = VanillaLight.shadowPixels();
TERRAIN.update(client.level, camera.pos, distance, sun);
int size = mapSize(distance, pixels);
@@ -142,7 +145,7 @@ public final class PixelShadows {
}
}
depthReady = !lunar;
- if (!VanillaLight.pixelShadows()) { renderedFrames++; return; }
+ if (!VanillaLight.pixelShadows() || strength <= .001f) { renderedFrames++; return; }
var main = client.gameRenderer.mainRenderTarget();
var nearest = RenderSystem.getSamplerCache().getClampToEdge(FilterMode.NEAREST);
try (var pass = encoder.createRenderPass(() -> "Sanctuary pixel shadows", main.getColorTextureView(), Optional.empty())) {
diff --git a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/shader/Sunshafts.java b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/shader/Sunshafts.java
index 382b99a..7f48c2d 100644
--- a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/shader/Sunshafts.java
+++ b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/shader/Sunshafts.java
@@ -45,11 +45,21 @@ public final class Sunshafts {
if(!PixelShadows.depthReady()){release();return;}
var main=mc.gameRenderer.mainRenderTarget();int width=Math.max(1,(main.width+3)/4),height=Math.max(1,(main.height+3)/4);
if(light==null||light.width!=width||light.height!=height){release();light=new TextureTarget("Sanctuary sunshafts",width,height,GpuFormat.RGBA16_FLOAT,null);}
- if(settings==null)settings=new MappableRingBuffer(()->"Sanctuary sunshafts",GpuBuffer.USAGE_UNIFORM|GpuBuffer.USAGE_MAP_WRITE,32);
- var fog=mc.gameRenderer.gameRenderState().levelRenderState.cameraRenderState.fogData;
+ if(settings==null)settings=new MappableRingBuffer(()->"Sanctuary sunshafts",GpuBuffer.USAGE_UNIFORM|GpuBuffer.USAGE_MAP_WRITE,48);
+ var camera=mc.gameRenderer.gameRenderState().levelRenderState.cameraRenderState;
+ var fog=camera.fogData;
+ float mist=fogDensity(fog.environmentalEnd);
+ float rain=mc.level.getRainLevel(camera.cameraEntityPartialTicks);
+ float thunder=mc.level.getThunderLevel(camera.cameraEntityPartialTicks);
+ var sun=PixelShadows.sunDirection(mc.gameRenderer.gameRenderState().levelRenderState.skyRenderState.sunAngle);
+ float daylight=Math.clamp((sun.y-.08f)/.16f,0,1)*(1-.75f*rain)*(1-.65f*thunder);
+ float density=1+2.5f*mist+.8f*rain;
+ float spread=Math.max(mist,.35f*rain);
+ float extinction=1+.7f*mist+.25f*rain;
settings.rotate();try(var mapped=settings.currentBuffer().map(false,true)){
Std140Builder.intoBuffer(mapped.data()).putVec4(.85f+.15f*fog.color.x,.78f+.15f*fog.color.y,.66f+.15f*fog.color.z,0)
- .putVec4(VanillaLight.sunshaftIntensity()/100f,Math.min(64,VanillaLight.shadowDistance()*.5f),0,0);
+ .putVec4(VanillaLight.sunshaftIntensity()/100f,Math.min(64,VanillaLight.shadowDistance()*.5f),0,0)
+ .putVec4(density,spread,extinction,daylight);
}
var encoder=RenderSystem.getDevice().createCommandEncoder();var nearest=RenderSystem.getSamplerCache().getClampToEdge(FilterMode.NEAREST);
try(var pass=encoder.createRenderPass(()->"Sanctuary solar volume",light.getColorTextureView(),Optional.empty())){
@@ -62,6 +72,12 @@ public final class Sunshafts {
}
applied=true;frames++;
}
+ /** Environmental fog has its own end distance; changing render distance is not weather. */
+ static float fogDensity(float environmentalEnd){
+ if(!Float.isFinite(environmentalEnd))return 0;
+ float t=Math.clamp((192-environmentalEnd)/112,0,1);
+ return t*t*(3-2*t);
+ }
public static void release(){if(light!=null){light.destroyBuffers();light=null;}if(settings!=null){settings.close();settings=null;}applied=false;}
static boolean applied(){return applied;}
static boolean allocated(){return light!=null;}
diff --git a/mods/sanctuary/src/main/resources/assets/sanctuary/lang/en_us.json b/mods/sanctuary/src/main/resources/assets/sanctuary/lang/en_us.json
index 367b55d..6ad000e 100644
--- a/mods/sanctuary/src/main/resources/assets/sanctuary/lang/en_us.json
+++ b/mods/sanctuary/src/main/resources/assets/sanctuary/lang/en_us.json
@@ -2307,6 +2307,6 @@
"sanctuary.shaders.sunshafts": "Sunshafts",
"sanctuary.shaders.sunshafts_help": "Light beams through openings in the scenery. Independent of bloom; range follows shadows (up to 64 blocks).",
"sanctuary.shaders.sunshaft_intensity": "Sunshaft intensity",
- "sanctuary.shaders.sunshaft_intensity_help": "Strength of light scattered in the air. Zero disables the effect.",
+ "sanctuary.shaders.sunshaft_intensity_help": "Strength of light scattered in the air. Beams automatically thicken with fog and precipitation. Zero disables the effect.",
"sanctuary.shaders.sunshaft_intensity_value": "Sunshafts: %s %%"
}
diff --git a/mods/sanctuary/src/main/resources/assets/sanctuary/lang/fr_fr.json b/mods/sanctuary/src/main/resources/assets/sanctuary/lang/fr_fr.json
index bae2ac3..1433666 100644
--- a/mods/sanctuary/src/main/resources/assets/sanctuary/lang/fr_fr.json
+++ b/mods/sanctuary/src/main/resources/assets/sanctuary/lang/fr_fr.json
@@ -2307,6 +2307,6 @@
"sanctuary.shaders.sunshafts": "Rayons solaires",
"sanctuary.shaders.sunshafts_help": "Faisceaux de lumière dans les ouvertures du décor. Indépendants du bloom, portée liée aux ombres (64 blocs maximum).",
"sanctuary.shaders.sunshaft_intensity": "Intensité des rayons",
- "sanctuary.shaders.sunshaft_intensity_help": "Force de la lumière dans l’air. Zéro désactive le calcul.",
+ "sanctuary.shaders.sunshaft_intensity_help": "Force de la lumière dans l’air. Les rayons s’épaississent automatiquement avec le brouillard et les précipitations. Zéro désactive le calcul.",
"sanctuary.shaders.sunshaft_intensity_value": "Rayons : %s %%"
}
diff --git a/mods/sanctuary/src/main/resources/assets/sanctuary/shaders/core/sunshaft_gather.fsh b/mods/sanctuary/src/main/resources/assets/sanctuary/shaders/core/sunshaft_gather.fsh
index 4fdb452..9bcf506 100644
--- a/mods/sanctuary/src/main/resources/assets/sanctuary/shaders/core/sunshaft_gather.fsh
+++ b/mods/sanctuary/src/main/resources/assets/sanctuary/shaders/core/sunshaft_gather.fsh
@@ -33,12 +33,15 @@ void main(){
float span=max(0.0,stop-start);if(span<=0.0)break;
float t=(start+stop)*.5;
float distanceFade=1.0-smoothstep(range*.7,range,t);
- total+=sunlight(ray*t)*span*exp(-t/24.0)*distanceFade;
+ total+=sunlight(ray*t)*span*exp(-t/24.0*ShaftAtmosphere.z)*distanceFade;
}
- float phase=.12+.88*pow(max(0.0,dot(ray,SunStrength.xyz)),4.0);
- float weather=clamp(SunStrength.w/.38,0.0,1.0);
+ // Thick air scatters into wider viewing angles, while extinguishing distant light.
+ // Keep the shadow footprint unchanged: weather cannot open a hole in a solid wall.
+ float base=mix(.12,.30,ShaftAtmosphere.y);
+ float phase=base+(1.0-base)*pow(max(0.0,dot(ray,SunStrength.xyz)),mix(4.0,2.0,ShaftAtmosphere.y));
+ float weather=ShaftAtmosphere.w;
float fogDistance=max(abs(ray.x),max(abs(ray.y),abs(ray.z)))*end;
float nativeFog=1.0-smoothstep(DistanceClipFog.z,max(DistanceClipFog.z+.01,DistanceClipFog.w),fogDistance);
- float amount=total*.018*phase*weather*ShaftSettings.x*nativeFog;
+ float amount=total*.018*phase*weather*ShaftSettings.x*nativeFog*ShaftAtmosphere.x;
fragColor=vec4(ShaftColor.rgb*amount,end);
}
diff --git a/mods/sanctuary/src/main/resources/assets/sanctuary/shaders/include/sunshafts.glsl b/mods/sanctuary/src/main/resources/assets/sanctuary/shaders/include/sunshafts.glsl
index b37d4d0..1722c6b 100644
--- a/mods/sanctuary/src/main/resources/assets/sanctuary/shaders/include/sunshafts.glsl
+++ b/mods/sanctuary/src/main/resources/assets/sanctuary/shaders/include/sunshafts.glsl
@@ -6,7 +6,10 @@ layout(std140) uniform PixelShadows {
vec4 DistanceClipFog;
vec4 EnvironmentFog;
};
-layout(std140) uniform ShaftParams { vec4 ShaftColor; vec4 ShaftSettings; };
+layout(std140) uniform ShaftParams {
+ vec4 ShaftColor; vec4 ShaftSettings;
+ vec4 ShaftAtmosphere; // density, angular diffusion, extinction, available daylight
+};
uniform sampler2D WorldDepth;
vec2 shaftTexel(vec2 uv){
vec2 size=vec2(textureSize(WorldDepth,0));
diff --git a/packwiz/pack.toml b/packwiz/pack.toml
index 4a47d50..8a1f763 100644
--- a/packwiz/pack.toml
+++ b/packwiz/pack.toml
@@ -1,6 +1,6 @@
name = "Sanctuary"
author = "KOKA99CAB"
-version = "beta.137"
+version = "beta.138"
pack-format = "packwiz:1.1.0"
[index]