diff --git a/README.md b/README.md index 833c4e5..f5b7153 100644 --- a/README.md +++ b/README.md @@ -20,13 +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.135** corrige le SSGI en contre-plongée et près des surfaces : -normales indépendantes de la taille des pixels et atténuation progressive au -contact. Bloom initial à **20 %**, PBR et SSGI **activés par défaut**. -**Options → Shaders** ; réglages existants conservés. -Le [ticket de livraison](docs/shader-beta135.md) décrit le rendu et ses limites. -Release publiée et même instance **Sanctuary Beta** synchronisée, sauvegardes -et réglages conservés. +La version **beta.136** ajoute un **lens flare rectangulaire doux** pour le +soleil et la lune, activé à 30 % et réglable indépendamment du bloom. +**Options → Shaders** ; les blocs masquent les astres et leurs reflets. +Le [ticket de livraison](docs/shader-beta136.md) décrit 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. @@ -37,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.136 — Lens flare rectangulaire + +Soleil et lune, reflets doux et intensité indépendante du bloom. +[Contrat et vérifications](docs/shader-beta136.md) · +[Pack normal](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.136/Sanctuary-beta.136.mrpack) · +[Pack de test](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.136/Sanctuary-Test-beta.136.mrpack). + ## beta.135 — SSGI près des surfaces Correction des coupures à courte distance, sans flouter les textures. diff --git a/docs/shader-beta136.md b/docs/shader-beta136.md new file mode 100644 index 0000000..759a042 --- /dev/null +++ b/docs/shader-beta136.md @@ -0,0 +1,70 @@ +# SHADER-136 — Lens flare rectangulaire doux + +Demande : ajouter un lens flare avec des rectangles dégradés, exclusivement +pour le soleil et la lune. Socle beta.135, branche `codex/lens-flare-beta136`. +Minecraft 26.3 / Java 25. Aucun changement de gameplay ou de monde. + +## Contrat + +Effet activé initialement à **30 %**, intensité réglable 0–100, indépendant du halo de bloom. +Rectangles translucides aux bords doux, suivant la position de l'astre à l'écran. +La visibilité provient de l'image émissive céleste native, avant les blocs : +les murs masquent l'effet, les sources de terrain ne le déclenchent pas. +L'astre sortant du champ doit s'atténuer sans sauter sur le bord opposé. +Pas d'historique, ni lecture GPU vers le CPU, ni texture générée sur disque. + +## Rendu et budget + +Trois rectangles à dégradé doux par astre : un halo horizontal près de sa +position et deux reflets plus petits le long de l'axe passant par le centre de +l'écran. Teinte solaire chaude, lunaire légèrement bleue. Intégration de la +lumière réellement dessinée par le ciel natif ; phases de lune conservées. +Atténuation aux bords de l'écran et à l'horizon, sans projection derrière la caméra. + +Le masque céleste est mesuré avant l'ajout des émissifs de terrain. Réduction +vers une texture 2 × 1 RGBA16F (512 échantillons au maximum au total), puis une +passe de composition avec deux lectures de cette texture et des dégradés analytiques. +La cible d'émission plein écran existante est réutilisée. Quand seul le flare +fonctionne, aucun maillage d'émissif de terrain ni tampon de flou n'est construit. +OFF et zéro libèrent la cible du flare ; les autres effets gardent leur autonomie. + +## Vérification + +Le scénario client natif réussit en **1 min 52 s** sur macOS / Apple M1 / OpenGL. +Soleil OFF/30/80, lune et nouvelle lune, mur occultant, source hors champ, +glowstone exclue, image statique, redimensionnement et rechargement des ressources. +Le flare reste actif sans bloom ; OFF/zéro libèrent ses ressources. Préférences +vierges, partielles, personnalisées, bornage et libellés FR/EN vérifiés. +Les contrôles existants du bloom et des minerais émissifs passent également. +Log : `build/shader136-client-final.log`, marqueurs `EMISSIVE131_PASS` et +`LENS_FLARE136_PASS`. Les captures natives restent dans le dossier de tests ; comparatif autonome +`build/Shader-beta.136-comparaison.html` (soleil OFF/30/80 et lune OFF/30). + +Une première tentative s'est arrêtée sur l'attente d'un angle de caméra : +Minecraft normalise 180° en −180°. Le scénario utilise maintenant un angle +canonique ; ce changement concerne uniquement le test. + +## Limites + +Effet optique en espace écran : il s'atténue quand l'astre quitte le champ et +à l'horizon. Le masque natif du ciel contrôle l'occultation ; la lune reste +volontairement beaucoup plus discrète. Le brouillard de submersion suspend +l'effet comme le bloom existant. Aucun essai Windows/Vulkan ni mesure de FPS +revendiqués. + + +## Construction et archives + +`./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest` +réussit en **2 min 29 s**, 126 tâches. Le serveur GameTest dédié reste exclu +conformément au refus EULA antérieur. Log : `build/shader136-build.log`. +Comparaison à beta.135 : **9 entrées de production** modifiées, uniquement +le rendu, ses préférences et ses libellés. Gameplay et textures des gemmes +strictement conservés. JAR normal/Test identique, sources et template vérifiés. +Reçu local : `build/shader136-artifact.json`. + +- `Sanctuary-beta.136.mrpack` : 10424821 octets ; SHA-256 `808006bf2b0cb3fe63d3e16ae3a0003d27ad0b7e793a3a05f25b5d73ae88bc8d`. + +- `Sanctuary-Test-beta.136.mrpack` : 10443744 octets ; SHA-256 `70f593ad16d1f4fd4305b276ce2727a63f87bcb84df45e55840c1427a314a1eb`. + +JAR : `086afede22903f8988889f6085631451f65d6d55c2e4c81f249d2234698e68e5`. diff --git a/gradle.properties b/gradle.properties index 65d94de..e618afe 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.135 -pack_version=beta.135 +mod_version=beta.136 +pack_version=beta.136 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/Bloom130ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/Bloom130ClientChecks.java index a81ff1c..dd4041d 100644 --- a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/Bloom130ClientChecks.java +++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/Bloom130ClientChecks.java @@ -23,7 +23,7 @@ public final class Bloom130ClientChecks implements FabricClientGameTest { String ns=name.equals("ruby")||name.equals("sapphire")?"sanctuary:":"minecraft:";result.add(ns+name+"_ore");result.add(ns+"deepslate_"+name+"_ore");} result.add("minecraft:nether_quartz_ore");result.add("minecraft:nether_gold_ore");return List.copyOf(result);} static void run(ClientGameTestContext c,TestServerContext server){ - c.runOnClient(m->{preferences();VanillaLight.setPbr(false);VanillaLight.setSsgi(false);analyseTextures();VanillaLight.setSaturation(100);VanillaLight.setPixelShadows(false);VanillaLight.setEdgeHighlights(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);}); + c.runOnClient(m->{preferences();VanillaLight.setLensFlare(false);VanillaLight.setPbr(false);VanillaLight.setSsgi(false);analyseTextures();VanillaLight.setSaturation(100);VanillaLight.setPixelShadows(false);VanillaLight.setEdgeHighlights(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);}); server.runOnServer(s->{command(s,"time set 18000"); for(int x=4;x<=20;x++)for(int z=4;z<=20;z++){ s.overworld().setBlockAndUpdate(new BlockPos(x,64,z),Blocks.CONCRETE.black().defaultBlockState()); @@ -97,6 +97,7 @@ public final class Bloom130ClientChecks implements FabricClientGameTest { unchanged(coveredOff,capture(c,null),"Zero bloom leaves no residual glow"); c.runOnClient(m->{VanillaLight.setBloomIntensity(35);VanillaLight.setBloom(false);VanillaLight.setOreEmission(true);VanillaLight.setOreIntensity(50);}); System.out.println("EMISSIVE131_PASS independent cores with halo OFF/zero; blur resources released; defaults; masks for 20 ore textures; selective night glow; halo intensity/spread; no through-wall glow; live edits; torch/glowstone/lamp/lava/sun; resource reload; defaults/migration; orePixels="+orePixels+" oreGain="+oreGain+" haloLow="+low+" haloHigh="+high+" sunHalo="+sunlight); + LensFlare136ClientChecks.run(c,server); } private static BlockPos orePos(int i){return new BlockPos(8+i%5,65,8+i/5);} private static void preferences(){try{ diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/EdgeHighlights129ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/EdgeHighlights129ClientChecks.java index a7aaa8a..751b7d6 100644 --- a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/EdgeHighlights129ClientChecks.java +++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/EdgeHighlights129ClientChecks.java @@ -160,6 +160,7 @@ final class EdgeHighlights129ClientChecks { check(VanillaLight.edgeHighlights(),"Toggle persists independently"); VanillaLight.setEdgeHighlights(false); VanillaLight.setBloom(false);VanillaLight.setOreEmission(false); + VanillaLight.setLensFlare(false); VanillaLight.setPbr(false);VanillaLight.setSsgi(false); } catch(java.io.IOException e) { throw new AssertionError(e); } } diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/LensFlare136ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/LensFlare136ClientChecks.java new file mode 100644 index 0000000..3e33df0 --- /dev/null +++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/LensFlare136ClientChecks.java @@ -0,0 +1,70 @@ +package fr.koka.sanctuary.client.shader; + +import static fr.koka.sanctuary.client.shader.PixelShadows122ClientChecks.*; +import java.nio.file.Files; +import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext; +import net.fabricmc.fabric.api.client.gametest.v1.context.TestServerContext; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.network.chat.Component; + +/** Native sky image, not a generated mockup; explicit independent effect controls. */ +final class LensFlare136ClientChecks { + static void run(ClientGameTestContext c,TestServerContext server){ + c.runOnClient(m->{preferences();VanillaLight.setEnabled(true);VanillaLight.setSaturation(100);VanillaLight.setPbr(false);VanillaLight.setSsgi(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);VanillaLight.setPixelShadows(false);VanillaLight.setEdgeHighlights(false);VanillaLight.setLensFlare(false);}); + server.runOnServer(s->{command(s,"time add 1");command(s,"time set 3000");}); + camera(c,server,100.5,130,100.5,70);aim(c,server,false); + var off=capture(c,"flare-sun-off");enable(c,true);var on=capture(c,"flare-sun-30"); + long low=gain(off,on);check(low>1000,"Default sun flare is visible: "+low); + c.runOnClient(m->check(!Bloom.haloAllocated()&&Bloom.flareAllocated(),"Flare runs without bloom blur targets")); + unchanged(on,capture(c,null),"Static rectangular flare has no animated noise"); + c.runOnClient(m->VanillaLight.setLensFlareIntensity(80));frames(c);var high=capture(c,"flare-sun-80"); + check(gain(off,high)>low*1.5,"Flare intensity changes native pixels"); + var reload=c.computeOnClient(net.minecraft.client.Minecraft::reloadResourcePacks);c.waitFor(m->reload.isDone()&&m.gui.overlay()==null,1200);reload.join();frames(c); + unchanged(high,capture(c,"flare-reloaded"),"Flare recovers after resource reload"); + int width=c.computeOnClient(m->m.getWindow().getScreenWidth()),height=c.computeOnClient(m->m.getWindow().getScreenHeight()); + c.runOnClient(m->m.getWindow().setWindowed(961,541));c.waitFor(m->m.gameRenderer.mainRenderTarget().width!=off.width(),200);frames(c); + enable(c,false);var smallOff=capture(c,null);enable(c,true);check(gain(smallOff,capture(c,"flare-resized"))>1000,"Flare survives odd-sized targets"); + c.runOnClient(m->m.getWindow().setWindowed(width,height));c.waitFor(m->m.gameRenderer.mainRenderTarget().width==off.width(),200);frames(c); + var direction=c.computeOnClient(m->PixelShadows.sunDirection(m.gameRenderer.gameRenderState().levelRenderState.skyRenderState.sunAngle)); + var eye=c.computeOnClient(m->m.player.getEyePosition());var center=BlockPos.containing(eye.x+direction.x*8,eye.y+direction.y*8,eye.z+direction.z*8); + cover(server,center,true);c.waitTicks(35);frames(c);var hiddenOn=capture(c,"flare-sun-covered");enable(c,false); + unchanged(hiddenOn,capture(c,null),"Occluded sun produces no rectangular flare through a wall"); + cover(server,center,false);c.waitTicks(35);aim(c,server,false); + c.runOnClient(m->{VanillaLight.setBloom(true);VanillaLight.setBloomIntensity(20);VanillaLight.setLensFlareIntensity(30);});enable(c,true);capture(c,"flare-bloom-combined"); + c.runOnClient(m->VanillaLight.setBloom(false));look(c,server,0,80);enable(c,false);var away=capture(c,null);enable(c,true);unchanged(away,capture(c,"flare-away"),"Behind-camera celestial sources produce no reflected screen edge"); + server.runOnServer(s->s.overworld().setBlockAndUpdate(new BlockPos(100,129,104),Blocks.GLOWSTONE.defaultBlockState())); + look(c,server,0,35);c.waitTicks(25);enable(c,false);var lampOff=capture(c,null);enable(c,true);unchanged(lampOff,capture(c,"flare-glowstone-excluded"),"Visible glowstone does not create a celestial flare"); + server.runOnServer(s->command(s,"time set 15000"));c.waitTicks(20);aim(c,server,true); + enable(c,false);var moonOff=capture(c,"flare-moon-off");enable(c,true);long moon=gain(moonOff,capture(c,"flare-moon-30")); + check(moon>200,"Native visible moon creates its own subtle flare: "+moon); + server.runOnServer(s->command(s,"time set 111000"));c.waitFor(m->m.gameRenderer.gameRenderState().levelRenderState.skyRenderState.moonPhase.index()==4,200);aim(c,server,true); + enable(c,false);var newOff=capture(c,null);enable(c,true);check(gain(newOff,capture(c,"flare-new-moon"))VanillaLight.setLensFlareIntensity(0));c.waitTicks(8);c.runOnClient(m->check(!Bloom.flareAllocated()&&!Bloom.allocated(),"Zero frees all resources with other effects OFF")); + unchanged(newOff,capture(c,null),"Zero flare restores source image"); + c.runOnClient(m->{VanillaLight.setLensFlareIntensity(30);VanillaLight.setLensFlare(false);}); + 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[]{"lens_flare","lens_flare_help","lens_flare_intensity","lens_flare_intensity_help"}){ + String key="sanctuary.shaders."+suffix;check(!Component.translatable(key).getString().equals(key),"Localized flare control: "+locale+" "+key); + } + }); + System.out.println("LENS_FLARE136_PASS sunGain="+low+" moonGain="+moon+" visibility, resize, reload, strength, independent bloom, zero, preferences, FR/EN"); + } + private static void cover(TestServerContext server,BlockPos center,boolean on){server.runOnServer(s->{for(int x=-4;x<=4;x++)for(int y=-4;y<=4;y++)for(int z=-4;z<=4;z++)s.overworld().setBlockAndUpdate(center.offset(x,y,z),on?Blocks.STONE.defaultBlockState():Blocks.AIR.defaultBlockState());});} + private static void aim(ClientGameTestContext c,TestServerContext server,boolean moon){ + var direction=c.computeOnClient(m->PixelShadows.sunDirection(moon?m.gameRenderer.gameRenderState().levelRenderState.skyRenderState.moonAngle:m.gameRenderer.gameRenderState().levelRenderState.skyRenderState.sunAngle)); + look(c,server,net.minecraft.util.Mth.wrapDegrees((float)Math.toDegrees(Math.atan2(-direction.x,direction.z))+12),-(float)Math.toDegrees(Math.asin(direction.y))+8); + } + private static void frames(ClientGameTestContext c){int before=c.computeOnClient(m->Bloom.renderedFrames());c.waitFor(m->Bloom.applied()&&Bloom.ready()&&Bloom.renderedFrames()>before+10,1200);c.waitTicks(8);} + private static void enable(ClientGameTestContext c,boolean value){c.runOnClient(m->VanillaLight.setLensFlare(value));if(value)frames(c);else c.waitTicks(8);} + 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;} + private static void preferences(){try{ + var path=FabricLoader.getInstance().getConfigDir().resolve("sanctuary-shaders.json");Files.writeString(path,"{\"bloom\":false,\"bloomIntensity\":20}");forgetPreferences(); + check(VanillaLight.lensFlare()&&VanillaLight.lensFlareIntensity()==30&&!VanillaLight.bloom()&&VanillaLight.bloomIntensity()==20,"Flare defaults preserve bloom choices"); + VanillaLight.setLensFlare(false);VanillaLight.setLensFlareIntensity(73);forgetPreferences();check(!VanillaLight.lensFlare()&&VanillaLight.lensFlareIntensity()==73,"Saved custom flare retained"); + for(int n:new int[]{-10,0,30,100,150}){VanillaLight.setLensFlareIntensity(n);forgetPreferences();check(VanillaLight.lensFlareIntensity()==Math.clamp(n,0,100),"Flare intensity persists and clamps");} + VanillaLight.setLensFlareIntensity(30); + }catch(java.io.IOException e){throw new AssertionError(e);}} +} diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/Pbr134ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/Pbr134ClientChecks.java index d46a66d..132eda2 100644 --- a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/Pbr134ClientChecks.java +++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/Pbr134ClientChecks.java @@ -13,7 +13,7 @@ import java.util.Arrays; public final class Pbr134ClientChecks implements FabricClientGameTest { public void runTest(ClientGameTestContext c){new PixelShadows122ClientChecks(false,false,true).runTest(c);} static void run(ClientGameTestContext c,TestServerContext server){ - c.runOnClient(m->{preferences();materials();VanillaLight.setEnabled(true);VanillaLight.setSaturation(100);VanillaLight.setPbr(false);VanillaLight.setSsgi(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);VanillaLight.setEdgeHighlights(false);VanillaLight.setPixelShadows(false);}); + c.runOnClient(m->{preferences();VanillaLight.setLensFlare(false);materials();VanillaLight.setEnabled(true);VanillaLight.setSaturation(100);VanillaLight.setPbr(false);VanillaLight.setSsgi(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);VanillaLight.setEdgeHighlights(false);VanillaLight.setPixelShadows(false);}); server.runOnServer(s->{command(s,"time set 1000");for(int x=-5;x<=24;x++)for(int z=-5;z<=24;z++){ s.overworld().setBlockAndUpdate(new BlockPos(x,64,z),(x<10?Blocks.BRICKS:x<17?Blocks.OAK_PLANKS:Blocks.IRON_BLOCK).defaultBlockState()); for(int y=65;y<=102;y++)s.overworld().setBlockAndUpdate(new BlockPos(x,y,z),Blocks.AIR.defaultBlockState()); 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 91689ef..8da355f 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 @@ -266,7 +266,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest { }); }); c.waitTicks(3); - c.takeScreenshot("sanctuary-beta135-shader-options-" + locale); + c.takeScreenshot("sanctuary-beta136-shader-options-" + locale); } c.runOnClient(m -> m.gui.setScreen(null)); } @@ -446,6 +446,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest { private static void checkPreferences() { checkDefaults135(); + VanillaLight.setLensFlare(false); VanillaLight.setPbr(false); VanillaLight.setSsgi(false); VanillaLight.setBloom(false); @@ -549,7 +550,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest { } static Frame capture(ClientGameTestContext c, String name) { - if (name != null) c.takeScreenshot("sanctuary-beta135-shader-" + name); + if (name != null) c.takeScreenshot("sanctuary-beta136-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/Ssgi132ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/Ssgi132ClientChecks.java index bddd748..41cd4e5 100644 --- a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/Ssgi132ClientChecks.java +++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/shader/Ssgi132ClientChecks.java @@ -13,7 +13,7 @@ import java.nio.file.Files; public final class Ssgi132ClientChecks implements FabricClientGameTest { public void runTest(ClientGameTestContext c){new PixelShadows122ClientChecks(false,true).runTest(c);} static void run(ClientGameTestContext c,TestServerContext server){ - c.runOnClient(m->{preferences();VanillaLight.setPbr(false);VanillaLight.setEnabled(true);VanillaLight.setSaturation(100);VanillaLight.setSsgi(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);VanillaLight.setPixelShadows(false);VanillaLight.setEdgeHighlights(false);}); + c.runOnClient(m->{preferences();VanillaLight.setLensFlare(false);VanillaLight.setPbr(false);VanillaLight.setEnabled(true);VanillaLight.setSaturation(100);VanillaLight.setSsgi(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);VanillaLight.setPixelShadows(false);VanillaLight.setEdgeHighlights(false);}); server.runOnServer(s->{command(s,"time set 6000");for(int x=-5;x<=24;x++)for(int z=-5;z<=24;z++){ s.overworld().setBlockAndUpdate(new BlockPos(x,64,z),Blocks.CONCRETE.white().defaultBlockState()); for(int y=65;y<=102;y++)s.overworld().setBlockAndUpdate(new BlockPos(x,y,z),Blocks.AIR.defaultBlockState()); diff --git a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/shader/Bloom.java b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/shader/Bloom.java index c36f964..1c33926 100644 --- a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/shader/Bloom.java +++ b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/shader/Bloom.java @@ -42,16 +42,18 @@ public final class Bloom { var builder=RenderPipeline.builder().withLocation(id("pipeline/"+name)).withVertexShader("core/screenquad").withFragmentShader(id("core/"+name)).withBindGroupLayout(layout.build()); if(scene)builder.withBindGroupLayout(SCENE); return RenderPipelines.register(builder.withPrimitiveTopology(PrimitiveTopology.TRIANGLES).withCull(false) - .withColorTargetState(new ColorTargetState(Optional.ofNullable(blend),name.equals("bloom_blur")||name.equals("bloom_downsample")?GpuFormat.RGBA16_FLOAT:GpuFormat.RGBA8_UNORM,blend==null?ColorTargetState.WRITE_ALL:ColorTargetState.WRITE_COLOR)).withDepthStencilState(Optional.empty()).build()); + .withColorTargetState(new ColorTargetState(Optional.ofNullable(blend),name.equals("bloom_blur")||name.equals("bloom_downsample")||name.equals("lens_flare_source")?GpuFormat.RGBA16_FLOAT:GpuFormat.RGBA8_UNORM,blend==null?ColorTargetState.WRITE_ALL:ColorTargetState.WRITE_COLOR)).withDepthStencilState(Optional.empty()).build()); } private static final RenderPipeline SKY=screen("bloom_sky",null,true,false); private static final RenderPipeline DOWNSAMPLE=screen("bloom_downsample",null,false,false); private static final RenderPipeline BLUR=screen("bloom_blur",null,false,true); private static final RenderPipeline CORE=screen("bloom_core",new BlendFunction(BlendFactor.ONE,BlendFactor.ONE_MINUS_SRC_ALPHA,BlendFactor.ZERO,BlendFactor.ONE),false,false); private static final RenderPipeline APPLY=screen("bloom_apply",new BlendFunction(BlendFactor.ONE,BlendFactor.ONE_MINUS_SRC_COLOR,BlendFactor.ZERO,BlendFactor.ONE),true,false); + private static final RenderPipeline FLARE_SOURCE=screen("lens_flare_source",null,true,false); + private static final RenderPipeline FLARE=screen("lens_flare",new BlendFunction(BlendFactor.ONE,BlendFactor.ONE_MINUS_SRC_COLOR,BlendFactor.ZERO,BlendFactor.ONE),true,false); private static final BloomTerrain TERRAIN=new BloomTerrain(); private static final Matrix4f projection=new Matrix4f(); - private static TextureTarget emission,small,ping; + private static TextureTarget emission,small,ping,flareSource; private static MappableRingBuffer scene,horizontal,vertical; private static int frames; private static boolean applied; @@ -62,8 +64,9 @@ public final class Bloom { public static void render(){ applied=false;var mc=Minecraft.getInstance(); boolean halo=VanillaLight.bloom()&&VanillaLight.bloomIntensity()>0; + boolean flare=VanillaLight.lensFlare()&&VanillaLight.lensFlareIntensity()>0; boolean cores=VanillaLight.oreEmission()&&VanillaLight.oreIntensity()>0; - if(!VanillaLight.active()||(!halo&&!cores)||mc.level==null){release();return;} + if(!VanillaLight.active()||(!halo&&!cores&&!flare)||mc.level==null){release();return;} if(PanoramaCapture.rendering())return; var state=mc.gameRenderer.gameRenderState().levelRenderState;var camera=state.cameraRenderState; if(camera.fogType!=FogType.NONE)return; @@ -71,21 +74,28 @@ public final class Bloom { if(emission==null||emission.width!=main.width||emission.height!=main.height){ targetsClose();emission=target("emission",main.width,main.height); } - if(scene==null)scene=uniforms("bloom scene",144); + if(scene==null)scene=uniforms("bloom scene",192); if(halo){ if(small==null){small=target("bloom low",Math.max(1,main.width/8),Math.max(1,main.height/8));ping=target("bloom ping",small.width,small.height);} if(horizontal==null){horizontal=uniforms("bloom horizontal",16);vertical=uniforms("bloom vertical",16);} }else haloClose(); - var entries=TERRAIN.update();var atlas=TERRAIN.materials.atlas(); + if(flare){if(flareSource==null)flareSource=target("lens flare sources",2,1);}else flareClose(); + // A celestial-only flare never needs to scan or mesh terrain emitters. + var entries=(halo||cores)?TERRAIN.update():List.of(); + if(!halo&&!cores)TERRAIN.close(); + var atlas=TERRAIN.materials.atlas(); var vp=new Matrix4f(projection).mul(camera.viewRotationMatrix);var inverse=new Matrix4f(vp).invert(); var sun=PixelShadows.sunDirection(state.skyRenderState.sunAngle);var moon=PixelShadows.sunDirection(state.skyRenderState.moonAngle); float celestial=state.skyRenderState.skybox==DimensionType.Skybox.OVERWORLD?state.skyRenderState.rainBrightness:0; + var flareSun=projectFlare(vp,sun,celestial); + var flareMoon=projectFlare(vp,moon,celestial*.35f); var fog=camera.fogData;scene.rotate(); try(var mapped=scene.currentBuffer().map(false,true)){ Std140Builder.intoBuffer(mapped.data()).putMat4f(inverse) .putVec4(VanillaLight.oreEmission()?VanillaLight.oreIntensity()/100f:0,RenderSystem.getDevice().getDeviceInfo().isZZeroToOne()?1:0,VanillaLight.bloomIntensity()/50f,0) .putVec4(sun.x,sun.y,sun.z,celestial).putVec4(moon.x,moon.y,moon.z,celestial*.35f) - .putVec4(fog.renderDistanceStart,fog.renderDistanceEnd,fog.environmentalStart,fog.environmentalEnd).putVec4(fog.color.w,0,0,0); + .putVec4(fog.renderDistanceStart,fog.renderDistanceEnd,fog.environmentalStart,fog.environmentalEnd).putVec4(fog.color.w,0,0,0) + .putVec4(flareSun).putVec4(flareMoon).putVec4(flare?VanillaLight.lensFlareIntensity()/100f:0,(float)main.width/main.height,0,0); } if(halo){ float radius=.25f+VanillaLight.bloomRadius()*.0075f; @@ -98,9 +108,11 @@ public final class Bloom { transforms.add(RenderSystem.getDynamicUniforms().writeTransform(new Matrix4f(vp).translate(offset),new Vector4f(1,1,1,1),offset,new Matrix4f()));max=Math.max(max,entry.indices());} var indices=RenderSystem.getSequentialBuffer(PrimitiveTopology.QUADS);indices.requestIndexCount(max); var nearest=RenderSystem.getSamplerCache().getClampToEdge(FilterMode.NEAREST); - if(halo)screen(SKY,emission.getColorTextureView(),main.getColorTextureView(),true,null); + if(halo||flare)screen(SKY,emission.getColorTextureView(),main.getColorTextureView(),true,null); else try(var clear=RenderSystem.getDevice().createCommandEncoder().createRenderPass(()->"Sanctuary clear emission",emission.getColorTextureView(),Optional.of(new Vector4f(0,0,0,0)))){ } + // Reduce the celestial-only mask before drawing any torch, ore or other terrain emitter. + if(flare)screen(FLARE_SOURCE,flareSource.getColorTextureView(),emission.getColorTextureView(),true,null); if(atlas!=null&&!entries.isEmpty())try(var pass=RenderSystem.getDevice().createCommandEncoder().createRenderPass(()->"Sanctuary visible emitters",emission.getColorTextureView(),Optional.empty())){ pass.setPipeline(RenderSystem.getCompiledPipeline(EMITTERS));pass.setUniform("BloomScene",scene.currentBuffer());pass.setUniform("WorldDepth",main.getDepthTextureView(),nearest); pass.setUniform("BlockAtlas",mc.getTextureManager().getTexture(TextureAtlas.LOCATION_BLOCKS).getTextureView(),nearest);pass.setUniform("EmissionMasks",atlas.getTextureView(),nearest); @@ -114,8 +126,19 @@ public final class Bloom { } if(cores)screen(CORE,main.getColorTextureView(),emission.getColorTextureView(),false,null); if(halo)screen(APPLY,main.getColorTextureView(),small.getColorTextureView(),true,null); + if(flare)screen(FLARE,main.getColorTextureView(),flareSource.getColorTextureView(),true,null); applied=true;frames++; } + private static Vector4f projectFlare(Matrix4f vp,Vector3f direction,float strength){ + var clip=vp.transform(new Vector4f(direction,0)); + if(clip.w<=.05f||direction.y<=0||strength<=0)return new Vector4f(0,0,0,0); + float x=clip.x/clip.w*.5f+.5f,y=clip.y/clip.w*.5f+.5f; + // Actual sprite light is measured on GPU. This is only its projected sampling region. + float radius=Math.min(.3f,Math.abs(projection.m11())*.08f/clip.w); + float horizon=Math.clamp(direction.y/.08f,0,1); + return new Vector4f(x,y,radius,strength*horizon*horizon*(3-2*horizon)); + } + private static void flareClose(){if(flareSource!=null){flareSource.destroyBuffers();flareSource=null;}} private static void screen(RenderPipeline pipeline,GpuTextureView output,GpuTextureView input,boolean useScene,MappableRingBuffer blur){ try(var pass=RenderSystem.getDevice().createCommandEncoder().createRenderPass(()->"Sanctuary bloom",output,Optional.empty())){ pass.setPipeline(RenderSystem.getCompiledPipeline(pipeline));pass.setUniform("InputTexture",input,RenderSystem.getSamplerCache().getClampToEdge(pipeline==CORE||pipeline==SKY?FilterMode.NEAREST:FilterMode.LINEAR)); @@ -129,8 +152,10 @@ public final class Bloom { if(small!=null){small.destroyBuffers();ping.destroyBuffers();small=ping=null;} if(horizontal!=null){horizontal.close();vertical.close();horizontal=vertical=null;} } - private static void targetsClose(){haloClose();if(emission!=null){emission.destroyBuffers();emission=null;}} + private static void targetsClose(){haloClose();flareClose();if(emission!=null){emission.destroyBuffers();emission=null;}} public static void release(){TERRAIN.close();targetsClose();if(scene!=null){scene.close();scene=null;}applied=false;} + static TextureTarget flareTarget(){return flareSource;} + static boolean flareAllocated(){return flareSource!=null;} static boolean haloAllocated(){return small!=null||horizontal!=null;} static boolean applied(){return applied;} static boolean allocated(){return emission!=null;} diff --git a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/shader/ShaderOptionsScreen.java b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/shader/ShaderOptionsScreen.java index b86646b..7b09c46 100644 --- a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/shader/ShaderOptionsScreen.java +++ b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/shader/ShaderOptionsScreen.java @@ -30,6 +30,9 @@ public final class ShaderOptionsScreen extends OptionsSubScreen { Component.translatable("sanctuary.shaders.bloom_help")),VanillaLight.bloom(),VanillaLight::setBloom)); slider("bloom_intensity",VanillaLight.bloomIntensity(),VanillaLight::setBloomIntensity); slider("bloom_radius",VanillaLight.bloomRadius(),VanillaLight::setBloomRadius); + list.addBig(OptionInstance.createBoolean("sanctuary.shaders.lens_flare",OptionInstance.cachedConstantTooltip( + Component.translatable("sanctuary.shaders.lens_flare_help")),VanillaLight.lensFlare(),VanillaLight::setLensFlare)); + slider("lens_flare_intensity",VanillaLight.lensFlareIntensity(),VanillaLight::setLensFlareIntensity); list.addBig(OptionInstance.createBoolean("sanctuary.shaders.ore_emission",OptionInstance.cachedConstantTooltip( Component.translatable("sanctuary.shaders.ore_emission_help")),VanillaLight.oreEmission(),VanillaLight::setOreEmission)); slider("ore_intensity",VanillaLight.oreIntensity(),VanillaLight::setOreIntensity); diff --git a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/shader/VanillaLight.java b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/shader/VanillaLight.java index 011937b..631c357 100644 --- a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/shader/VanillaLight.java +++ b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/shader/VanillaLight.java @@ -19,6 +19,8 @@ public final class VanillaLight { int saturation=142; boolean pixelShadows=true; boolean bloom=true; + boolean lensFlare=true; + int lensFlareIntensity=30; boolean pbr=true; int pbrIntensity=50; boolean ssgi=true; @@ -39,6 +41,7 @@ public final class VanillaLight { try{if(Files.isRegularFile(path()))preferences=new GsonBuilder().create().fromJson(Files.readString(path()),Preferences.class);} catch(Exception e){SanctuaryMod.LOGGER.warn("Cannot read Sanctuary shader preference",e);} if(preferences==null)preferences=new Preferences(); + preferences.lensFlareIntensity=Math.clamp(preferences.lensFlareIntensity,0,100); preferences.pbrIntensity=Math.clamp(preferences.pbrIntensity,0,100); preferences.ssgiIntensity=Math.clamp(preferences.ssgiIntensity,0,100); preferences.bloomIntensity=Math.clamp(preferences.bloomIntensity,0,100); @@ -61,6 +64,10 @@ public final class VanillaLight { public static int edgeIntensity(){enabled();return preferences.edgeIntensity;} public static void setEdgeHighlights(boolean value){enabled();preferences.edgeHighlights=value;save();} public static void setEdgeIntensity(int value){enabled();preferences.edgeIntensity=Math.clamp(value,0,100);save();} + public static boolean lensFlare(){enabled();return preferences.lensFlare;} + public static int lensFlareIntensity(){enabled();return preferences.lensFlareIntensity;} + public static void setLensFlare(boolean value){enabled();preferences.lensFlare=value;save();} + public static void setLensFlareIntensity(int value){enabled();preferences.lensFlareIntensity=Math.clamp(value,0,100);save();} public static boolean pbr(){enabled();return preferences.pbr;} public static int pbrIntensity(){enabled();return preferences.pbrIntensity;} public static void setPbr(boolean value){enabled();preferences.pbr=value;save();} 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 2e880c1..8a9cb59 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 @@ -2298,5 +2298,10 @@ "sanctuary.shaders.pbr_help": "Experimental: procedural block normals and roughness, sun and moon highlights. Range 32 blocks. ON by default.", "sanctuary.shaders.pbr_intensity": "PBR strength", "sanctuary.shaders.pbr_intensity_help": "Material relief and highlights. Zero stops rendering and frees the cache. Original textures remain unchanged.", - "sanctuary.shaders.pbr_intensity_value": "PBR: %s%%" + "sanctuary.shaders.pbr_intensity_value": "PBR: %s%%", + "sanctuary.shaders.lens_flare": "Lens flare · soft rectangles", + "sanctuary.shaders.lens_flare_help": "Soft rectangular sun and moon reflections. Obstacles hide the sources. Independent of the bloom halo.", + "sanctuary.shaders.lens_flare_intensity": "Lens flare intensity", + "sanctuary.shaders.lens_flare_intensity_help": "Celestial reflection strength. Zero disables the effect.", + "sanctuary.shaders.lens_flare_intensity_value": "Lens flare: %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 5806654..6ed7689 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 @@ -2298,5 +2298,10 @@ "sanctuary.shaders.pbr_help": "Expérimental : normales procédurales et rugosité des blocs, reflets du soleil et de la lune. Portée 32 blocs. Activé par défaut.", "sanctuary.shaders.pbr_intensity": "Intensité du PBR", "sanctuary.shaders.pbr_intensity_help": "Relief et reflets des matériaux. Zéro coupe le calcul et libère le cache. Ne modifie pas les textures originales.", - "sanctuary.shaders.pbr_intensity_value": "PBR : %s %%" + "sanctuary.shaders.pbr_intensity_value": "PBR : %s %%", + "sanctuary.shaders.lens_flare": "Lens flare · rectangles doux", + "sanctuary.shaders.lens_flare_help": "Reflets rectangulaires du soleil et de la lune, aux bords dégradés. Les obstacles masquent les sources. Indépendant du halo de bloom.", + "sanctuary.shaders.lens_flare_intensity": "Intensité du lens flare", + "sanctuary.shaders.lens_flare_intensity_help": "Force des reflets célestes. Zéro désactive le calcul.", + "sanctuary.shaders.lens_flare_intensity_value": "Lens flare : %s %%" } diff --git a/mods/sanctuary/src/main/resources/assets/sanctuary/shaders/core/lens_flare.fsh b/mods/sanctuary/src/main/resources/assets/sanctuary/shaders/core/lens_flare.fsh new file mode 100644 index 0000000..f948b82 --- /dev/null +++ b/mods/sanctuary/src/main/resources/assets/sanctuary/shaders/core/lens_flare.fsh @@ -0,0 +1,24 @@ +#version 330 +#extension GL_ARB_separate_shader_objects : require +#include +uniform sampler2D InputTexture; +layout(location=0) in vec2 texCoord; +layout(location=0) out vec4 fragColor; +float softBox(vec2 center,vec2 halfSize){ + vec2 q=abs((texCoord-center)*vec2(FlareParams.y,1))/halfSize; + float fade=1.0-smoothstep(.08,1.0,max(q.x,q.y)); + return fade*fade; +} +vec3 flare(vec4 source,vec3 light,vec3 tint){ + vec2 axis=source.xy-vec2(.5); + float shape=softBox(source.xy,vec2(.26,.075))*.55; + shape+=softBox(vec2(.5)-axis*.4,vec2(.115,.085))*.35; + shape+=softBox(vec2(.5)-axis*.9,vec2(.055,.045))*.22; + return light*tint*shape; +} +void main(){ + vec3 sun=texture(InputTexture,vec2(.25,.5)).rgb; + vec3 moon=texture(InputTexture,vec2(.75,.5)).rgb; + vec3 light=flare(FlareSun,sun,vec3(1,.92,.78))+flare(FlareMoon,moon,vec3(.75,.87,1)); + fragColor=vec4(1.0-exp(-light*FlareParams.x*6.0),0.0); +} diff --git a/mods/sanctuary/src/main/resources/assets/sanctuary/shaders/core/lens_flare_source.fsh b/mods/sanctuary/src/main/resources/assets/sanctuary/shaders/core/lens_flare_source.fsh new file mode 100644 index 0000000..3c4d610 --- /dev/null +++ b/mods/sanctuary/src/main/resources/assets/sanctuary/shaders/core/lens_flare_source.fsh @@ -0,0 +1,23 @@ +#version 330 +#extension GL_ARB_separate_shader_objects : require +#include +uniform sampler2D InputTexture; +layout(location=0) in vec2 texCoord; +layout(location=0) out vec4 fragColor; +void main(){ + vec4 source=gl_FragCoord.x<1.0?FlareSun:FlareMoon; + fragColor=vec4(0.0); + if(source.w<=0.0)return; + float border=min(min(source.x,source.y),min(1.0-source.x,1.0-source.y)); + float edge=smoothstep(0.0,.08,border); + if(edge<=0.0)return; + vec3 color=vec3(0); + // Two output pixels only: stable area integration, not a full-screen sampling loop. + for(int y=0;y<16;y++)for(int x=0;x<16;x++){ + vec2 offset=(vec2(x,y)+.5)/16.0*2.0-1.0; + vec2 uv=source.xy+offset*source.z/vec2(FlareParams.y,1); + if(any(lessThan(uv,vec2(0)))||any(greaterThan(uv,vec2(1))))continue; + color+=texture(InputTexture,uv).rgb; + } + fragColor=vec4(color/256.0*source.w*edge,1.0); +} diff --git a/mods/sanctuary/src/main/resources/assets/sanctuary/shaders/include/bloom_scene.glsl b/mods/sanctuary/src/main/resources/assets/sanctuary/shaders/include/bloom_scene.glsl index f7587d8..578fb5f 100644 --- a/mods/sanctuary/src/main/resources/assets/sanctuary/shaders/include/bloom_scene.glsl +++ b/mods/sanctuary/src/main/resources/assets/sanctuary/shaders/include/bloom_scene.glsl @@ -6,6 +6,9 @@ layout(std140) uniform BloomScene { vec4 MoonDirection; vec4 FogDistance; vec4 FogInfo; + vec4 FlareSun; // projected UV, sampling radius in screen-height units, visibility + vec4 FlareMoon; + vec4 FlareParams; // strength, aspect ratio, reserved, reserved }; vec3 bloomPosition(vec2 uv,float depth) { vec4 p=InverseViewProjection*vec4(uv*2.0-1.0,BloomParams.y>0.5?depth:depth*2.0-1.0,1.0); diff --git a/packwiz/pack.toml b/packwiz/pack.toml index e7f7ef4..5baf3f1 100644 --- a/packwiz/pack.toml +++ b/packwiz/pack.toml @@ -1,6 +1,6 @@ name = "Sanctuary" author = "KOKA99CAB" -version = "beta.135" +version = "beta.136" pack-format = "packwiz:1.1.0" [index]