Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf9a670f44 |
@@ -1,5 +1,13 @@
|
||||
# Sanctuary
|
||||
|
||||
## beta.146 — Reflets et transitions de l’eau (pack local)
|
||||
|
||||
Correction du masquage des personnages en F5 et sélection stricte des surfaces PBR.
|
||||
PBR à 80 % et SSR à 20 % par défaut, distance commune PBR/SSR
|
||||
(16/32/64/128 blocs), reflet solaire large et blanc avec léger bloom des matières.
|
||||
Eau naturelle selon le type de monde et mélange 1/3/5/9/17 blocs.
|
||||
[Contrat et vérifications](docs/reflections-water-beta146.md).
|
||||
|
||||
## beta.145 — PBR de l’eau et reflets pixélisés (livraison locale)
|
||||
|
||||
L’eau et le verre rejoignent le PBR natif. L’option expérimentale de reflets
|
||||
@@ -2328,7 +2336,7 @@ Depuis beta.090, la cible est **Minecraft 26.3 finale**, publiée le 15 septembr
|
||||
| Fabric API | 0.160.5+26.3 |
|
||||
| Fabric Loom | 1.17.20 |
|
||||
| Gradle Wrapper | 9.5.1, distribution vérifiée par SHA-256 |
|
||||
| Sanctuary / pack | beta.122 |
|
||||
| Sanctuary / pack | beta.146 |
|
||||
|
||||
Java 25 et Python 3.11 ou plus récent sont nécessaires. Le script pack utilise
|
||||
uniquement la bibliothèque standard et repère aussi une installation Python
|
||||
@@ -2357,7 +2365,7 @@ décrits dans [Validation](docs/testing.md).
|
||||
|
||||
Résultats :
|
||||
|
||||
- `mods/sanctuary/build/libs/sanctuary-beta.122.jar` : mod à installer avec
|
||||
- `mods/sanctuary/build/libs/sanctuary-beta.146.jar` : mod à installer avec
|
||||
Fabric API sur la version Minecraft indiquée.
|
||||
- `build/packwiz/` : pack de développement complet, avec le mod construit et
|
||||
l'index vérifié. Voir [Installation packwiz](packwiz/README.md).
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
# RENDER-146 — Reflets, portée commune et transitions de l’eau
|
||||
|
||||
Branche `codex/reflections-water-beta146`, socle beta.145 `ed2d088`.
|
||||
Minecraft 26.3, Java 25 et dépendances inchangés. Préparation locale uniquement.
|
||||
|
||||
## Contrat
|
||||
|
||||
- Les passes PBR et SSR vérifient la profondeur finale du rendu natif pour
|
||||
masquer les personnages, notamment en F5. La profondeur du paysage capturée
|
||||
avant les transparences reste celle utilisée pour rechercher le reflet.
|
||||
- Le PBR utilise le test matériel EQUAL contre la profondeur du récepteur
|
||||
sélectionné. L’ancienne tolérance de 3,5 cm pouvait accepter des faces
|
||||
concurrentes près des coins ; elle est supprimée. La comparaison avec la
|
||||
profondeur native conserve seulement une marge de 5 mm pour les différences
|
||||
d’ordre de calcul des matrices, sans relâcher la sélection stricte du récepteur.
|
||||
La position du vertex est déclarée invariante entre les passes.
|
||||
- PBR à 80 % et SSR à 20 % par défaut, pour toutes les matières, eau comprise.
|
||||
Deux intensités d’effets distinctes, sans réglage séparé solide/liquide.
|
||||
Les préférences explicites déjà enregistrées restent conservées.
|
||||
- Distance PBR/SSR commune : 16, 32 (défaut), 64, 128 blocs. Maillages,
|
||||
sélection des récepteurs et fondu final emploient ce réglage. Le rayon SSR
|
||||
conserve sa recherche maximale de 128 blocs dans le paysage visible.
|
||||
Budgets de maillages inchangés : solides 24 Mio, transparences 8 Mio.
|
||||
Une forte densité à grande portée peut donc limiter les surfaces disponibles.
|
||||
- Le reflet du soleil adopte un lobe plus large sur l’eau, avec une irradiance
|
||||
renforcée : grande empreinte blanche vue du dessus et traînée étendue aux
|
||||
angles rasants. Les lampes conservent leur lobe local.
|
||||
Les éclats spéculaires ne sont plus plafonnés au gris moyen. Leur compression
|
||||
conserve une valeur finie pouvant approcher le blanc. Une contribution douce
|
||||
des seules passes PBR/SSR alimente le bloom existant, sans faire briller tous
|
||||
les blocs blancs. Le réglage Bloom et son intensité gardent autorité.
|
||||
- Eau naturelle OFF par défaut dans une dimension ordinaire, ON pour une
|
||||
dimension dont le générateur est Sanctuary / ExpansionChunkGenerator.
|
||||
Le serveur transmet ce contexte à la connexion, au changement de dimension
|
||||
et à la réapparition, y compris en multijoueur. Le défaut automatique nécessite
|
||||
ce contexte envoyé par un serveur beta.146 ; sans lui, le client utilise le
|
||||
défaut ordinaire OFF, toujours réglable. Aucune donnée de monde modifiée.
|
||||
Les choix ON/OFF sont mémorisés séparément pour ces deux catégories.
|
||||
Un ancien OFF explicite est conservé dans les deux ; un ancien ON global
|
||||
adopte les nouveaux défauts par contexte.
|
||||
- Mélange des profils sur 1×1, 3×3, 5×5, 9×9 (défaut) ou 17×17 blocs, puis
|
||||
interpolation aux sommets. Océans toujours clairs. La texture demeure native.
|
||||
Surface et immersion utilisent la même étendue. Les changements du fond
|
||||
invalident toute la largeur dépendante ; changer le réglage reconstruit les
|
||||
sections chargées contenant de l’eau.
|
||||
|
||||
Le filtre de surface est calculé dans la copie de région native, avec sommes
|
||||
préfixées par hauteur pour borner le coût des sommets. Aucun accès au monde
|
||||
vivant depuis le worker, chargement de chunk ou modification de sauvegarde.
|
||||
Les textes des nouvelles options sont fournis en français et en anglais.
|
||||
|
||||
## Vérifications
|
||||
|
||||
Machine : Apple M1, macOS, Java 25, Minecraft 26.3. Mondes plats jetables,
|
||||
graine 122 ; aucune sauvegarde personnelle utilisée.
|
||||
|
||||
- Suite SSR/PBR Vulkan avec transparence améliorée : réussie
|
||||
(`build/beta146-final-settings-vulkan.log`). F5 avec personnage réel en créatif,
|
||||
portée 16/32/64/128, couleur du paysage, eau/verre/métal, OFF/zéro,
|
||||
immersion, rechargement de ressources et redimensionnement.
|
||||
- Suite SSR/PBR OpenGL finale réussie également
|
||||
(`build/beta146-final-settings-opengl.log`), avec 22 450 pixels quasi blancs
|
||||
dans la scène solaire et 99 / 518 400 pixels modifiés dans le contrôle
|
||||
des intersections. Captures dans `build/evidence/final-settings-opengl/`.
|
||||
- F5 : les 1 900 pixels du torse contrôlés sont strictement identiques entre
|
||||
SSR OFF et ON, tandis que le reflet reste présent autour du personnage.
|
||||
- Soleil : 22 111 pixels quasi blancs sur 854 × 480 à PBR 80 % / SSR 20 %,
|
||||
sans bloom, sur la scène de contrôle Vulkan. Les captures vues du dessus et
|
||||
à soleil bas montrent une empreinte large et une traînée allongée.
|
||||
Bloom OFF libère sa cible.
|
||||
- Mur, sol et escaliers en contact : 86 pixels sur 518 400 changent de plus
|
||||
de 12 niveaux cumulés lors d’une rotation de 0,00002 degré ; seuil du test
|
||||
0,1 %. Le cadre exclut l’eau animée, qui ne constitue pas une référence
|
||||
de stabilité pour les blocs statiques.
|
||||
|
||||
- Eau : suites fonctionnelles Vulkan et OpenGL réussies
|
||||
(`build/beta146-water-vulkan.log`, `build/beta146-water-opengl.log`).
|
||||
Profils, mélange 5/9/17, changement de contexte transmis par le serveur,
|
||||
préférences séparées, immersion, fond modifié, océans, eau courante,
|
||||
waterlogging et rechargement. OpenGL contrôle aussi l’égalité des couleurs
|
||||
aux frontières des copies de région et avec l’échantillonnage de brume.
|
||||
- `./gradlew check build` exécuté dans un instantané isolé des sources :
|
||||
252 tests serveur, 229 réussis et 23 échecs. Les identifiants des échecs
|
||||
correspondent exactement au reçu archivé beta.145 ; aucun nouvel identifiant.
|
||||
Il ne s’agit pas d’une nouvelle exécution comparative de beta.145.
|
||||
Journal : `build/beta146-check-build-isolated.log` ; comparaison :
|
||||
`build/beta146-server-failures.json`. Les derniers changements après cet
|
||||
instantané concernent le rendu client, ses réglages et ses tests.
|
||||
|
||||
Les preuves Vulkan finales sont dans `build/evidence/final-settings-vulkan/`.
|
||||
La suite de régression PBR et de préférences finale réussit
|
||||
(`build/beta146-final-pbr-preferences.log`) : PBR 80 %, SSR 20 %, persistance,
|
||||
migration des anciennes préférences, FR/EN, OFF/zéro et rechargement.
|
||||
La commande finale `./gradlew check build assemblePack -x :sanctuary:runGameTest`
|
||||
réussit (125 tâches), sans répéter la suite serveur isolée déjà exécutée.
|
||||
Journal : `build/beta146-final-build-pack.log`. Cette exclusion ne transforme
|
||||
pas les 23 échecs serveur en validation globale réussie.
|
||||
|
||||
## Artefact local
|
||||
|
||||
Le pack [Sanctuary-beta.146.mrpack](../build/Sanctuary-beta.146.mrpack) est exporté
|
||||
et copié dans le dossier `sanctuary-beta/build/`, à côté des autres versions.
|
||||
Intégrité ZIP, version beta.146, Minecraft 26.3 et identité du JAR embarqué
|
||||
avec le JAR construit contrôlées ; shaders finaux présents et identiques aux sources.
|
||||
Taille : 10 519 634 octets. SHA-256 :
|
||||
`9f3f7a840652d401741cbe3c85bbb5626326542179754cb599479b01a6b4c955`.
|
||||
Reçu : `build/beta146-artifacts.json`.
|
||||
|
||||
Aucun benchmark isolé de performance de beta.146 n’est revendiqué : des
|
||||
contrôles serveur peuvent tourner simultanément. Les limites SSR hors écran,
|
||||
de maillage à grande portée et de compatibilité avec d’autres GPU subsistent.
|
||||
Aucune publication ou synchronisation Prism.
|
||||
+2
-2
@@ -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.145
|
||||
pack_version=beta.145
|
||||
mod_version=beta.146
|
||||
pack_version=beta.146
|
||||
resource_pack_version=beta.121
|
||||
maven_group=fr.koka.sanctuary
|
||||
jei_version=30.32.0-sanctuary.3
|
||||
|
||||
+14
-5
@@ -30,9 +30,9 @@ public final class Pbr134ClientChecks implements FabricClientGameTest {
|
||||
c.runOnClient(m->VanillaLight.setPbrIntensity(20));frames(c);var low=difference(off,capture(c,"pbr-20"));
|
||||
check(delta.total>low.total*1.3,"PBR strength affects actual GPU output");
|
||||
c.runOnClient(m->VanillaLight.setPbrIntensity(100));frames(c);var high=difference(off,capture(c,"pbr-100"));check(high.total>delta.total*1.2,"Strong PBR visibly increases relief");
|
||||
c.runOnClient(m->VanillaLight.setPbrIntensity(0));c.waitTicks(8);unchanged(off,capture(c,null),"PBR zero restores native image");
|
||||
c.runOnClient(m->{VanillaLight.setPbrIntensity(0);});c.waitTicks(8);unchanged(off,capture(c,null),"PBR zero restores native image");
|
||||
c.runOnClient(m->check(!ProceduralPbr.allocated()&&ProceduralPbr.materialCount()==0,"Zero releases targets and material cache"));
|
||||
c.runOnClient(m->VanillaLight.setPbrIntensity(50));frames(c);
|
||||
c.runOnClient(m->{VanillaLight.setPbrIntensity(50);});frames(c);
|
||||
server.runOnServer(s->command(s,"time set 11000"));settle(c);enable(c,false);var eveningOff=capture(c,null);enable(c,true);var eveningOn=capture(c,"pbr-evening");
|
||||
int directions=0;for(int i=0;i<on.pixels().length;i++){int am=(on.pixels()[i]>>16&255)-(off.pixels()[i]>>16&255),pm=(eveningOn.pixels()[i]>>16&255)-(eveningOff.pixels()[i]>>16&255);if(Math.abs(am-pm)>5)directions++;}
|
||||
check(directions>500,"Relief follows the moving sun: "+directions);
|
||||
@@ -67,17 +67,26 @@ public final class Pbr134ClientChecks implements FabricClientGameTest {
|
||||
var p=FabricLoader.getInstance().getConfigDir().resolve("sanctuary-shaders.json");Files.writeString(p,"{\"ssgi\":true,\"ssgiIntensity\":35,\"bloom\":false}");forgetPreferences();
|
||||
check(VanillaLight.pbr()&&VanillaLight.pbrIntensity()==80&&VanillaLight.ssgi()&&!VanillaLight.bloom(),"Missing PBR setting defaults ON while preserving explicit old choices");
|
||||
for(int n:new int[]{-1,0,50,100,101}){VanillaLight.setPbr(true);VanillaLight.setPbrIntensity(n);forgetPreferences();check(VanillaLight.pbr()&&VanillaLight.pbrIntensity()==Math.clamp(n,0,100),"PBR preference persistence/clamp");}
|
||||
check(!VanillaLight.ssr()&&VanillaLight.ssrIntensity()==50,"Missing SSR defaults OFF at 50 percent");
|
||||
check(VanillaLight.pbrDistance()==32,"Shared distance default");
|
||||
check(!VanillaLight.naturalWater(false)&&VanillaLight.naturalWater(true),"World-specific water defaults");
|
||||
for(int n:new int[]{16,32,64,128}){VanillaLight.setPbrDistance(n);forgetPreferences();check(VanillaLight.pbrDistance()==n,"Shared distance persistence");}
|
||||
for(int n:new int[]{0,1,2,4,8}){VanillaLight.setWaterBlendRadius(n);forgetPreferences();check(VanillaLight.waterBlendRadius()==n,"Water blend persistence");}
|
||||
VanillaLight.setPbrDistance(32);VanillaLight.setWaterBlendRadius(4);
|
||||
check(!VanillaLight.ssr()&&VanillaLight.ssrIntensity()==20,"Missing SSR defaults OFF at 20 percent");
|
||||
for(int n:new int[]{-1,0,50,100,101}){VanillaLight.setSsr(true);VanillaLight.setSsrIntensity(n);forgetPreferences();check(VanillaLight.ssr()&&VanillaLight.ssrIntensity()==Math.clamp(n,0,100),"SSR preference persistence/clamp");}
|
||||
VanillaLight.setSsr(false);VanillaLight.setSsrIntensity(50);forgetPreferences();check(!VanillaLight.ssr(),"SSR OFF persists");
|
||||
VanillaLight.setSsr(false);VanillaLight.setSsrIntensity(20);forgetPreferences();check(!VanillaLight.ssr(),"SSR OFF persists");
|
||||
var manager=net.minecraft.client.Minecraft.getInstance().getLanguageManager();var oldLanguage=manager.getSelected();
|
||||
for(String locale:new String[]{"fr_fr","en_us"}){
|
||||
manager.setSelected(locale);manager.onResourceManagerReload(net.minecraft.client.Minecraft.getInstance().getResourceManager());
|
||||
for(String key:new String[]{"ssr","ssr_help","ssr_intensity","ssr_intensity_help","ssr_intensity_value"}){
|
||||
for(String key:new String[]{"pbr_distance","pbr_distance_help","pbr_distance_value","water_blend","water_blend_help","water_blend_value","ssr","ssr_help","ssr_intensity","ssr_intensity_help","ssr_intensity_value"}){
|
||||
String full="sanctuary.shaders."+key;check(!net.minecraft.network.chat.Component.translatable(full,50).getString().equals(full),"SSR FR/EN "+full);
|
||||
}
|
||||
}
|
||||
manager.setSelected(oldLanguage);manager.onResourceManagerReload(net.minecraft.client.Minecraft.getInstance().getResourceManager());
|
||||
Files.writeString(p,"{\"naturalWater\":false,\"pbrIntensity\":37}");forgetPreferences();
|
||||
check(!VanillaLight.naturalWater(false)&&!VanillaLight.naturalWater(true)&&VanillaLight.pbrIntensity()==37,"Legacy water OFF and personal PBR intensity are preserved");
|
||||
Files.writeString(p,"{\"naturalWater\":true}");forgetPreferences();
|
||||
check(!VanillaLight.naturalWater(false)&&VanillaLight.naturalWater(true),"Legacy global ON adopts contextual defaults");
|
||||
VanillaLight.setPbrIntensity(50);
|
||||
}catch(Exception e){throw new AssertionError(e);}}
|
||||
private record Delta(int changed,int brighter,int darker,long total){}
|
||||
|
||||
+53
-1
@@ -10,7 +10,7 @@ import net.minecraft.world.level.block.Blocks;
|
||||
final class PixelReflectionsClientChecks {
|
||||
static void run(ClientGameTestContext c,TestServerContext server){
|
||||
c.runOnClient(m->{VanillaLight.setEnabled(true);VanillaLight.setSaturation(100);VanillaLight.setPbr(true);VanillaLight.setPbrIntensity(80);
|
||||
VanillaLight.setNaturalWater(true);VanillaLight.setWaterIntensity(100);VanillaLight.setSsr(false);VanillaLight.setSsrIntensity(100);VanillaLight.setColoredLights(false);VanillaLight.setSsgi(false);
|
||||
VanillaLight.setNaturalWater(true);VanillaLight.setWaterIntensity(100);VanillaLight.setWaterBlendRadius(0);VanillaLight.setSsr(false);VanillaLight.setSsrIntensity(100);VanillaLight.setColoredLights(false);VanillaLight.setSsgi(false);
|
||||
VanillaLight.setSunshafts(false);VanillaLight.setLensFlare(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);
|
||||
VanillaLight.setEdgeHighlights(false);VanillaLight.setPixelShadows(false);});
|
||||
server.runOnServer(s->{
|
||||
@@ -32,6 +32,41 @@ final class PixelReflectionsClientChecks {
|
||||
long changed=changed(off,on);System.out.println("SSR_WATER_DIFF "+changed);
|
||||
check(changed>100,"SSR changes water receiver pixels: "+changed);
|
||||
double redSignal=colourSignal(off,on);
|
||||
server.runOnServer(s->{command(s,"gamemode creative @a");var player=s.getPlayerList().getPlayers().getFirst();player.getAbilities().flying=true;player.onUpdateAbilities();});
|
||||
c.waitTicks(10);
|
||||
c.runOnClient(m->m.options.setCameraType(net.minecraft.client.CameraType.THIRD_PERSON_BACK));c.waitTicks(20);
|
||||
c.runOnClient(m->VanillaLight.setSsr(false));c.waitTicks(8);var f5Off=capture(c,"ssr146-f5-off");
|
||||
c.runOnClient(m->VanillaLight.setSsr(true));frames(c);var f5On=capture(c,"ssr146-f5-on");
|
||||
check(changed(f5Off,f5On)>100,"SSR remains visible around third-person player");
|
||||
int torsoChange=0,torsoPixels=0;
|
||||
for(int y=f5Off.height()*55/100;y<f5Off.height()*71/100;y++)for(int x=f5Off.width()*485/1000;x<f5Off.width()*515/1000;x++){
|
||||
int index=y*f5Off.width()+x;torsoPixels++;if(f5Off.pixels()[index]!=f5On.pixels()[index])torsoChange++;
|
||||
}
|
||||
check(torsoChange<torsoPixels*.01,"SSR does not draw through the visible F5 torso: "+torsoChange+" / "+torsoPixels);
|
||||
c.runOnClient(m->m.options.setCameraType(net.minecraft.client.CameraType.FIRST_PERSON));c.waitTicks(20);
|
||||
server.runOnServer(s->command(s,"gamemode spectator @a"));c.waitTicks(10);
|
||||
for(int distance:new int[]{16,64,128,32}){
|
||||
c.runOnClient(m->VanillaLight.setPbrDistance(distance));settle(c);frames(c);capture(c,"ssr146-distance-"+distance);
|
||||
c.runOnClient(m->check(ProceduralPbr.applied()&&PixelReflections.applied(),"Both material passes share distance "+distance));
|
||||
}
|
||||
|
||||
c.runOnClient(m->{VanillaLight.setPbrIntensity(80);VanillaLight.setSsrIntensity(20);});
|
||||
camera(c,server,.5,68,10,70);settle(c);frames(c);
|
||||
var solar=capture(c,"ssr146-solar-water");
|
||||
int white=0;for(int pixel:solar.pixels())if((pixel>>16&255)>235&&(pixel>>8&255)>235&&(pixel&255)>235)white++;
|
||||
check(white>300,"Solar reflection has a broad near-white footprint at PBR 80 / SSR 20 percent: "+white);
|
||||
System.out.println("SSR146_SOLAR_WHITE pixels="+white+" width="+solar.width()+" height="+solar.height());
|
||||
c.runOnClient(m->VanillaLight.setBloom(true));frames(c);capture(c,"ssr146-solar-water-bloom");
|
||||
c.runOnClient(m->check(MaterialBloom.contribution()!=null,"Material light reaches bloom"));
|
||||
c.runOnClient(m->VanillaLight.setBloom(false));frames(c);
|
||||
c.runOnClient(m->check(MaterialBloom.contribution()==null,"Bloom OFF releases material halo"));
|
||||
server.runOnServer(s->command(s,"time set 1000"));c.waitTicks(10);
|
||||
var sunLook=c.computeOnClient(m->{var sun=PixelShadows.sunDirection(m.gameRenderer.gameRenderState().levelRenderState.skyRenderState.sunAngle);return new float[]{(float)Math.toDegrees(Math.atan2(-sun.x,sun.z)),(float)Math.toDegrees(Math.asin(sun.y))};});
|
||||
look(c,server,sunLook[0],sunLook[1]);frames(c);capture(c,"ssr146-solar-path-low-sun");
|
||||
c.runOnClient(m->VanillaLight.setBloom(true));frames(c);capture(c,"ssr146-solar-path-low-sun-bloom");
|
||||
c.runOnClient(m->{VanillaLight.setBloom(false);VanillaLight.setSsrIntensity(100);});
|
||||
server.runOnServer(s->command(s,"time set 6000"));c.waitTicks(10);
|
||||
camera(c,server,.5,68,0,70);look(c,server,0,12);settle(c);
|
||||
server.runOnServer(s->{for(int x=-12;x<=12;x++)for(int y=65;y<=77-Math.abs(x)/2;y++)s.overworld().setBlockAndUpdate(new BlockPos(x,y,19),Blocks.CONCRETE.blue().defaultBlockState());});
|
||||
settle(c);c.runOnClient(m->VanillaLight.setSsr(false));c.waitTicks(8);var blueOff=capture(c,"ssr-blue-off");
|
||||
c.runOnClient(m->VanillaLight.setSsr(true));frames(c);var blueOn=capture(c,"ssr-blue-on");double blueSignal=colourSignal(blueOff,blueOn);
|
||||
@@ -82,6 +117,13 @@ final class PixelReflectionsClientChecks {
|
||||
check(changed(metalOff,metalOn)>100,"Solid PBR reflects landscape");
|
||||
look(c,server,.00002f,12.00002f);frames(c);var tiny=capture(c,"ssr-metal-tiny-motion");
|
||||
check(stableMetal(metalOn,tiny),"Tiny camera movement keeps material texels stable");look(c,server,0,12);frames(c);
|
||||
server.runOnServer(s->{
|
||||
command(s,"fill -7 67 9 7 70 9 iron_block");
|
||||
for(int x=-6;x<=6;x+=2)command(s,"setblock "+x+" 67 8 polished_andesite_stairs[facing=south]");
|
||||
});settle(c);frames(c);var intersections=capture(c,"ssr146-intersections");
|
||||
look(c,server,.00002f,12.00002f);frames(c);var shifted=capture(c,"ssr146-intersections-tiny-motion");
|
||||
check(stableIntersections(intersections,shifted),"Block intersections remain stable during subpixel motion");
|
||||
look(c,server,0,12);frames(c);
|
||||
c.runOnClient(m->{VanillaLight.setPixelShadows(true);VanillaLight.setSsgi(true);VanillaLight.setColoredLights(true);VanillaLight.setBloom(true);VanillaLight.setSunshafts(true);});
|
||||
frames(c);capture(c,"ssr-combined");
|
||||
server.runOnServer(s->command(s,"time set 18000"));c.waitTicks(15);frames(c);capture(c,"ssr-moon");
|
||||
@@ -112,6 +154,16 @@ final class PixelReflectionsClientChecks {
|
||||
c.runOnClient(m->VanillaLight.setPbr(false));c.waitTicks(8);c.runOnClient(m->check(!PixelReflections.allocated(),"PBR OFF releases reflection resources"));
|
||||
System.out.println("SSR_PASS water; transparent glass; solids; OFF/zero; motion; reload; resize");
|
||||
}
|
||||
private static boolean stableIntersections(Frame a,Frame b){
|
||||
// The surrounding animated water is not a stability oracle for static blocks.
|
||||
int changed=0,count=0;
|
||||
for(int y=a.height()*30/100;y<a.height()*80/100;y++)for(int x=a.width()/4;x<a.width()*3/4;x++){
|
||||
int i=y*a.width()+x,aa=a.pixels()[i],bb=b.pixels()[i];count++;
|
||||
if(Math.abs((aa>>16&255)-(bb>>16&255))+Math.abs((aa>>8&255)-(bb>>8&255))+Math.abs((aa&255)-(bb&255))>12)changed++;
|
||||
}
|
||||
System.out.println("SSR146_INTERSECTIONS changed="+changed+" samples="+count);
|
||||
return changed<count*.001;
|
||||
}
|
||||
private static boolean stableMetal(Frame a,Frame b){int changes=0,count=0;
|
||||
for(int y=a.height()*58/100;y<a.height()*68/100;y++)for(int x=a.width()*3/10;x<a.width()*7/10;x++){
|
||||
int i=y*a.width()+x,aa=a.pixels()[i],bb=b.pixels()[i];count++;
|
||||
|
||||
+1
-1
@@ -454,7 +454,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
|
||||
var path=FabricLoader.getInstance().getConfigDir().resolve("sanctuary-shaders.json");
|
||||
Files.deleteIfExists(path);forgetPreferences();
|
||||
check(VanillaLight.bloom()&&VanillaLight.bloomIntensity()==20&&VanillaLight.pbr()&&VanillaLight.ssgi(),"Fresh defaults: bloom20, PBR and SSGI ON");
|
||||
check(VanillaLight.pbrIntensity()==80&&VanillaLight.ssgiIntensity()==35,"PBR default 80, SSGI strength retained");
|
||||
check(VanillaLight.pbrIntensity()==80&&VanillaLight.ssrIntensity()==20&&VanillaLight.ssgiIntensity()==35,"PBR 80 and SSR 20 defaults, SSGI strength retained");
|
||||
Files.writeString(path,"{\"pbr\":false,\"ssgi\":false,\"bloomIntensity\":73}");forgetPreferences();
|
||||
check(!VanillaLight.pbr()&&!VanillaLight.ssgi()&&VanillaLight.bloomIntensity()==73,"Explicit personal toggles and intensity survive changed defaults");
|
||||
Files.writeString(path,"{\"bloom\":false}");forgetPreferences();
|
||||
|
||||
+25
-1
@@ -11,7 +11,7 @@ import java.util.*;
|
||||
final class Water144ClientChecks {
|
||||
static void run(ClientGameTestContext c,TestServerContext server){
|
||||
c.runOnClient(m->{
|
||||
VanillaLight.setEnabled(true);VanillaLight.setNaturalWater(true);VanillaLight.setWaterIntensity(100);
|
||||
VanillaLight.setEnabled(true);VanillaLight.setNaturalWater(true);VanillaLight.setWaterIntensity(100);VanillaLight.setWaterBlendRadius(0);
|
||||
VanillaLight.setPbr(false);VanillaLight.setSsgi(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);
|
||||
VanillaLight.setPixelShadows(false);VanillaLight.setColoredLights(false);VanillaLight.setSunshafts(false);VanillaLight.setLensFlare(false);VanillaLight.setEdgeHighlights(false);
|
||||
});
|
||||
@@ -41,6 +41,30 @@ final class Water144ClientChecks {
|
||||
check(NaturalWater.sample(m.level,88.5,110.1,8.5).equals(NaturalWater.CLEAR),"No bed in bounded search uses clear");
|
||||
var boundary=NaturalWater.sample(m.level,80,99.1,8.5);check(boundary.tint()>.1&&boundary.tint()<.85,"Section boundary mixes profiles");
|
||||
});
|
||||
c.runOnClient(m->check(!NaturalWater.sanctuaryWorld(),"Server identifies ordinary test dimension"));
|
||||
server.runOnServer(s->net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking.send(s.getPlayerList().getPlayers().getFirst(),new fr.koka.sanctuary.water.WaterWorldSettings.Context(true)));
|
||||
c.waitTicks(5);c.runOnClient(m->{check(NaturalWater.sanctuaryWorld()&&VanillaLight.naturalWater(),"Sanctuary context defaults ON");VanillaLight.setNaturalWater(false);});
|
||||
server.runOnServer(s->net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking.send(s.getPlayerList().getPlayers().getFirst(),new fr.koka.sanctuary.water.WaterWorldSettings.Context(false)));
|
||||
c.waitTicks(5);c.runOnClient(m->check(VanillaLight.naturalWater(),"Ordinary explicit ON is independent of Sanctuary OFF"));
|
||||
server.runOnServer(s->net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking.send(s.getPlayerList().getPlayers().getFirst(),new fr.koka.sanctuary.water.WaterWorldSettings.Context(true)));
|
||||
c.waitTicks(5);c.runOnClient(m->{check(!VanillaLight.naturalWater(),"Sanctuary explicit OFF survives context round trip");VanillaLight.setNaturalWater(true);});
|
||||
server.runOnServer(s->net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking.send(s.getPlayerList().getPlayers().getFirst(),new fr.koka.sanctuary.water.WaterWorldSettings.Context(false)));
|
||||
c.waitTicks(5);
|
||||
for(int radius:new int[]{2,4,8}){
|
||||
c.runOnClient(m->VanillaLight.setWaterBlendRadius(radius));settle(c);
|
||||
c.runOnClient(m->{
|
||||
var left=NaturalWater.sample(m.level,79.5,99.1,8.5);
|
||||
var right=NaturalWater.sample(m.level,80.5,99.1,8.5);
|
||||
check(left.tint()>.1&&right.tint()<.85&&right.tint()>left.tint(),"Wider transition smooths both sides of section seam");
|
||||
var west=new NaturalWater.Region(m.level,3,5,-1).corner(m.level,new BlockPos(79,99,8),1,0);
|
||||
var east=new NaturalWater.Region(m.level,4,5,-1).corner(m.level,new BlockPos(80,99,8),0,0);
|
||||
var expected=NaturalWater.sample(m.level,80,99,8);
|
||||
check(Math.abs(west.tint()-east.tint())<.0002&&Math.abs(west.alpha()-east.alpha())<.0002,"Adjacent compiled sections share continuous vertex tint and alpha");
|
||||
check(Math.abs(west.tint()-expected.tint())<.0002&&Math.abs(west.alpha()-expected.alpha())<.0002,"Compiled surface blend matches immersion sampling");
|
||||
});
|
||||
capture(c,"blend146-"+(radius*2+1));
|
||||
}
|
||||
c.runOnClient(m->VanillaLight.setWaterBlendRadius(0));settle(c);
|
||||
enable(c,false);var off=capture(c,"surface-off");enable(c,true);var on=capture(c,"surface-on");
|
||||
check(difference(off,on)>100000,"Visible surface colour and opacity change");
|
||||
c.runOnClient(m->VanillaLight.setWaterIntensity(0));settle(c);var zero=capture(c,"surface-zero");
|
||||
|
||||
@@ -71,6 +71,7 @@ public final class SanctuaryMod implements ModInitializer {
|
||||
fr.koka.sanctuary.notifications.DiscoveryNotifications.register();
|
||||
fr.koka.sanctuary.realtime.RealtimeService.register();
|
||||
fr.koka.sanctuary.weather.WeatherService.register();
|
||||
fr.koka.sanctuary.water.WaterWorldSettings.register();
|
||||
fr.koka.sanctuary.plans.PlanService.register();
|
||||
fr.koka.sanctuary.plans.Statuary.register();
|
||||
fr.koka.sanctuary.food.FoodSync.register();
|
||||
|
||||
@@ -42,8 +42,9 @@ 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")||name.equals("lens_flare_source")?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_material_add")||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 MATERIAL=screen("bloom_material_add",new BlendFunction(BlendFactor.ONE,BlendFactor.ONE,BlendFactor.ZERO,BlendFactor.ONE),false,false);
|
||||
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);
|
||||
@@ -121,6 +122,7 @@ public final class Bloom {
|
||||
}
|
||||
if(halo){
|
||||
screen(DOWNSAMPLE,small.getColorTextureView(),emission.getColorTextureView(),false,null);
|
||||
if(MaterialBloom.contribution()!=null)screen(MATERIAL,small.getColorTextureView(),MaterialBloom.contribution(),false,null);
|
||||
screen(BLUR,ping.getColorTextureView(),small.getColorTextureView(),false,horizontal);
|
||||
screen(BLUR,small.getColorTextureView(),ping.getColorTextureView(),false,vertical);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package fr.koka.sanctuary.client.shader;
|
||||
|
||||
import com.mojang.blaze3d.pipeline.TextureTarget;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.renderpearl.api.GpuFormat;
|
||||
import com.mojang.renderpearl.api.pipeline.*;
|
||||
import com.mojang.renderpearl.api.textures.*;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import org.joml.Vector4f;
|
||||
import java.util.Optional;
|
||||
|
||||
/** Extract only the positive contribution of each material pass, before other effects. */
|
||||
final class MaterialBloom {
|
||||
private static final RenderPipeline EXTRACT=RenderPipelines.register(RenderPipeline.builder()
|
||||
.withLocation(Identifier.parse("sanctuary:pipeline/material_bloom"))
|
||||
.withVertexShader("core/screenquad").withFragmentShader(Identifier.parse("sanctuary:core/material_bloom"))
|
||||
.withBindGroupLayout(BindGroupLayout.builder().withUniform("Before",UniformType.COMBINED_IMAGE_SAMPLER)
|
||||
.withUniform("After",UniformType.COMBINED_IMAGE_SAMPLER).build())
|
||||
.withPrimitiveTopology(PrimitiveTopology.TRIANGLES).withCull(false)
|
||||
.withColorTargetState(new ColorTargetState(Optional.of(new BlendFunction(BlendFactor.ONE,BlendFactor.ONE,BlendFactor.ZERO,BlendFactor.ONE)),GpuFormat.RGBA16_FLOAT,ColorTargetState.WRITE_COLOR))
|
||||
.withDepthStencilState(Optional.empty()).build());
|
||||
private static TextureTarget contribution;
|
||||
private static boolean ready;
|
||||
private MaterialBloom(){}
|
||||
static void beginFrame(){ready=false;if(!VanillaLight.bloom()||VanillaLight.bloomIntensity()==0)release();}
|
||||
static void accumulate(GpuTextureView before){
|
||||
if(!VanillaLight.bloom()||VanillaLight.bloomIntensity()==0)return;
|
||||
var main=Minecraft.getInstance().gameRenderer.mainRenderTarget();
|
||||
int w=Math.max(1,main.width/8),h=Math.max(1,main.height/8);
|
||||
if(contribution==null||contribution.width!=w||contribution.height!=h){release();contribution=new TextureTarget("Sanctuary material glow",w,h,GpuFormat.RGBA16_FLOAT,null);}
|
||||
try(var pass=RenderSystem.getDevice().createCommandEncoder().createRenderPass(()->"Sanctuary material glow",contribution.getColorTextureView(),ready?Optional.empty():Optional.of(new Vector4f()))){
|
||||
pass.setPipeline(RenderSystem.getCompiledPipeline(EXTRACT));
|
||||
var nearest=RenderSystem.getSamplerCache().getClampToEdge(FilterMode.NEAREST);
|
||||
pass.setUniform("Before",before,nearest);pass.setUniform("After",main.getColorTextureView(),nearest);pass.draw(3,1,0,0);
|
||||
}
|
||||
ready=true;
|
||||
}
|
||||
static GpuTextureView contribution(){return ready?contribution.getColorTextureView():null;}
|
||||
static void release(){if(contribution!=null){contribution.destroyBuffers();contribution=null;}ready=false;}
|
||||
}
|
||||
@@ -29,11 +29,18 @@ public final class NaturalWater {
|
||||
private static volatile Map<Block,Integer> materials=Map.of();
|
||||
private static volatile float strength;
|
||||
private static ClientLevel previousLevel;
|
||||
private static boolean sanctuaryWorld;
|
||||
private static volatile int blendRadius=4;
|
||||
public static boolean sanctuaryWorld(){return sanctuaryWorld;}
|
||||
private static Profile fogFrom=CLEAR,fogTarget=CLEAR;
|
||||
private static long fogChanged;
|
||||
private static boolean wasUnderwater;
|
||||
private NaturalWater(){}
|
||||
public static void register(){
|
||||
net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking.registerGlobalReceiver(
|
||||
fr.koka.sanctuary.water.WaterWorldSettings.Context.TYPE,(payload,context)->context.client().execute(()->sanctuaryWorld=payload.sanctuary()));
|
||||
net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents.DISCONNECT.register((listener,client)->sanctuaryWorld=false);
|
||||
net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationConnectionEvents.INIT.register((listener,client)->sanctuaryWorld=false);
|
||||
ResourceLoader.get(PackType.CLIENT_RESOURCES).registerReloadListener(Identifier.parse("sanctuary:water_materials"),
|
||||
(ResourceManagerReloadListener) manager->{
|
||||
try(var reader=manager.getResourceOrThrow(Identifier.parse("sanctuary:water_materials.json")).openAsReader()){
|
||||
@@ -48,7 +55,7 @@ public final class NaturalWater {
|
||||
});
|
||||
ClientTickEvents.END_CLIENT_TICK.register(client->{
|
||||
float next=VanillaLight.active()&&VanillaLight.naturalWater()?VanillaLight.waterIntensity()/100f:0;
|
||||
if(previousLevel!=client.level||strength!=next){previousLevel=client.level;strength=next;wasUnderwater=false;
|
||||
if(previousLevel!=client.level||strength!=next||blendRadius!=VanillaLight.waterBlendRadius()){previousLevel=client.level;strength=next;blendRadius=VanillaLight.waterBlendRadius();wasUnderwater=false;
|
||||
if(client.level!=null)invalidateWaterSections(client);}
|
||||
if(client.level==null||next==0){wasUnderwater=false;return;}
|
||||
var camera=client.gameRenderer.mainCamera();
|
||||
@@ -103,10 +110,19 @@ public final class NaturalWater {
|
||||
}
|
||||
private static Profile live(ClientLevel l,int x,int y,int z){
|
||||
var p=new BlockPos(x,y,z);if(!l.hasChunk(x>>4,z>>4)||ocean(l,p))return CLEAR;
|
||||
return PROFILES[classify(l::getBlockState,p,materials)];
|
||||
float tint=0,alpha=0,fog=0;int count=0;
|
||||
for(int dz=-blendRadius;dz<=blendRadius;dz++)for(int dx=-blendRadius;dx<=blendRadius;dx++){
|
||||
var q=p.offset(dx,0,dz);
|
||||
Profile v=!l.hasChunk(q.getX()>>4,q.getZ()>>4)||ocean(l,q)?CLEAR:PROFILES[classify(l::getBlockState,q,materials)];
|
||||
tint+=v.tint;alpha+=v.alpha;fog+=v.fog;count++;
|
||||
}
|
||||
return new Profile(tint/count,alpha/count,fog/count);
|
||||
}
|
||||
/** Owned by a single native section compilation, with ocean data captured before worker submission. */
|
||||
public static final class Region {
|
||||
private static final int PAD=9,SIDE=34;
|
||||
private final int radius=blendRadius;
|
||||
private final float[][][] sums=new float[16][][];
|
||||
private final int ox,oy,oz;
|
||||
private final net.minecraft.world.level.biome.BiomeManager biomes;
|
||||
private byte[] oceanKinds;
|
||||
@@ -117,30 +133,47 @@ public final class NaturalWater {
|
||||
ox=(minX+1)*16;oy=(minY+1)*16;oz=(minZ+1)*16;
|
||||
// Copy quart holders, then keep the native seeded biome zoom on the immutable copy.
|
||||
// Sampling only quart centres would misclassify the jagged edges of ocean biomes.
|
||||
var copied=new java.util.ArrayList<net.minecraft.core.Holder<net.minecraft.world.level.biome.Biome>>(216);
|
||||
int qx=(ox>>2)-1,qy=(oy>>2)-1,qz=(oz>>2)-1;
|
||||
for(int y=0;y<6;y++)for(int z=0;z<6;z++)for(int x=0;x<6;x++)
|
||||
var copied=new java.util.ArrayList<net.minecraft.core.Holder<net.minecraft.world.level.biome.Biome>>(600);
|
||||
int qx=(ox>>2)-3,qy=(oy>>2)-1,qz=(oz>>2)-3;
|
||||
for(int y=0;y<6;y++)for(int z=0;z<10;z++)for(int x=0;x<10;x++)
|
||||
copied.add(level.hasChunk((qx+x)>>2,(qz+z)>>2)?level.getBiomeManager().getNoiseBiomeAtQuart(qx+x,qy+y,qz+z):null);
|
||||
biomes=level.getBiomeManager().withDifferentSource((x,y,z)->copied.get(((y-qy)*6+z-qz)*6+x-qx));
|
||||
biomes=level.getBiomeManager().withDifferentSource((x,y,z)->copied.get(((y-qy)*10+z-qz)*10+x-qx));
|
||||
}
|
||||
public boolean ocean(int x,int y,int z){
|
||||
if(oceanKinds==null)oceanKinds=new byte[18*16*18];
|
||||
int key=((y-oy)*18+z-oz+1)*18+x-ox+1;
|
||||
if(oceanKinds==null)oceanKinds=new byte[SIDE*16*SIDE];
|
||||
int key=((y-oy)*SIDE+z-oz+PAD)*SIDE+x-ox+PAD;
|
||||
int code=oceanKinds[key];if(code==0){var biome=biomes.getBiome(x,y,z);code=biome!=null&&biome.is(BiomeTags.IS_OCEAN)?2:1;oceanKinds[key]=(byte)code;}
|
||||
return code==2;
|
||||
}
|
||||
private Profile profile(BlockAndTintGetter region,int x,int y,int z){
|
||||
if(x<ox-1||x>ox+16||y<oy||y>oy+15||z<oz-1||z>oz+16)return CLEAR;
|
||||
if(x<ox-PAD||x>=ox-PAD+SIDE||y<oy||y>oy+15||z<oz-PAD||z>=oz-PAD+SIDE)return CLEAR;
|
||||
if(ocean(x,y,z))return CLEAR;
|
||||
int key=((y-oy)*18+z-oz+1)*18+x-ox+1;
|
||||
if(profiles==null)profiles=new byte[18*16*18];
|
||||
int key=((y-oy)*SIDE+z-oz+PAD)*SIDE+x-ox+PAD;
|
||||
if(profiles==null)profiles=new byte[SIDE*16*SIDE];
|
||||
int code=profiles[key];if(code==0){code=1+classify(region::getBlockState,new BlockPos(x,y,z),table);profiles[key]=(byte)code;}
|
||||
return PROFILES[code-1];
|
||||
}
|
||||
private Profile blended(BlockAndTintGetter region,int x,int y,int z){
|
||||
if(radius==0)return profile(region,x,y,z);
|
||||
int layer=y-oy,stride=SIDE+1;
|
||||
if(sums[layer]==null){
|
||||
var sum=new float[3][stride*stride];
|
||||
for(int iz=0;iz<SIDE;iz++)for(int ix=0;ix<SIDE;ix++){
|
||||
var p=profile(region,ox-PAD+ix,y,oz-PAD+iz);int at=(iz+1)*stride+ix+1;
|
||||
float[] values={p.tint,p.alpha,p.fog};
|
||||
for(int c=0;c<3;c++)sum[c][at]=values[c]+sum[c][at-1]+sum[c][at-stride]-sum[c][at-stride-1];
|
||||
}
|
||||
sums[layer]=sum;
|
||||
}
|
||||
int left=x-ox+PAD-radius,top=z-oz+PAD-radius,right=left+radius*2+1,bottom=top+radius*2+1;
|
||||
float[] values=new float[3];float area=(radius*2+1)*(radius*2+1);
|
||||
for(int c=0;c<3;c++){var a=sums[layer][c];values[c]=(a[bottom*stride+right]-a[top*stride+right]-a[bottom*stride+left]+a[top*stride+left])/area;}
|
||||
return new Profile(values[0],values[1],values[2]);
|
||||
}
|
||||
public Profile corner(BlockAndTintGetter region,BlockPos p,int dx,int dz){
|
||||
if(ocean(p.getX(),p.getY(),p.getZ()))return CLEAR;
|
||||
int x=p.getX()+dx-1,y=p.getY(),z=p.getZ()+dz-1;
|
||||
return profile(region,x,y,z).mix(profile(region,x+1,y,z),.5f).mix(profile(region,x,y,z+1).mix(profile(region,x+1,y,z+1),.5f),.5f);
|
||||
return blended(region,x,y,z).mix(blended(region,x+1,y,z),.5f).mix(blended(region,x,y,z+1).mix(blended(region,x+1,y,z+1),.5f),.5f);
|
||||
}
|
||||
}
|
||||
public interface RegionAccess { Region sanctuary$waterRegion(); }
|
||||
@@ -158,7 +191,8 @@ public final class NaturalWater {
|
||||
public static void dirty(BlockPos pos){
|
||||
if(strength==0)return;
|
||||
var renderer=Minecraft.getInstance().levelExtractor;
|
||||
renderer.setSectionRangeDirty((pos.getX()-1)>>4,pos.getY()>>4,(pos.getZ()-1)>>4,
|
||||
(pos.getX()+1)>>4,(pos.getY()+DEPTH)>>4,(pos.getZ()+1)>>4);
|
||||
int margin=blendRadius+1;
|
||||
renderer.setSectionRangeDirty((pos.getX()-margin)>>4,pos.getY()>>4,(pos.getZ()-margin)>>4,
|
||||
(pos.getX()+margin)>>4,(pos.getY()+DEPTH)>>4,(pos.getZ()+margin)>>4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ final class PbrTerrain implements AutoCloseable {
|
||||
long frame=mc.level.getGameTime();var active=new ArrayList<Section>();
|
||||
for(var render:mc.levelRenderer.visibleSections()){
|
||||
long key=render.getSectionNode();var pos=render.getRenderOrigin();
|
||||
if(pos.offset(8,8,8).distToCenterSqr(camera)>48*48)continue;
|
||||
if(pos.offset(8,8,8).distToCenterSqr(camera)>Math.pow(VanillaLight.pbrDistance()+16,2))continue;
|
||||
var chunk=mc.level.getChunkSource().getChunk(pos.getX()>>4,pos.getZ()>>4,ChunkStatus.FULL,false);
|
||||
if(chunk==null)continue;
|
||||
var storage=chunk.getSection(mc.level.getSectionIndex(pos.getY()));
|
||||
|
||||
@@ -60,7 +60,7 @@ public final class PixelReflections {
|
||||
&&mc.gameRenderer.gameRenderState().levelRenderState.cameraRenderState.fogType==FogType.NONE
|
||||
&&mc.gameRenderer.gameRenderState().levelRenderState.skyRenderState.skybox==net.minecraft.world.level.dimension.DimensionType.Skybox.OVERWORLD;
|
||||
}
|
||||
public static void beginFrame(){captured=ready=applied=false;if(!eligible())release();}
|
||||
public static void beginFrame(){MaterialBloom.beginFrame();captured=ready=applied=false;if(!eligible())release();}
|
||||
/** Called only with the opaque render pass closed, on both native transparency paths. */
|
||||
public static void captureOpaque(){
|
||||
if(!eligible())return;
|
||||
@@ -90,7 +90,7 @@ public final class PixelReflections {
|
||||
if(uniform==null)uniform=new MappableRingBuffer(()->"Sanctuary reflection parameters",GpuBuffer.USAGE_UNIFORM|GpuBuffer.USAGE_MAP_WRITE,160);
|
||||
uniform.rotate();try(var map=uniform.currentBuffer().map(false,true)){
|
||||
Std140Builder.intoBuffer(map.data()).putMat4f(vp).putMat4f(new Matrix4f(vp).invert())
|
||||
.putVec4(RenderSystem.getDevice().getDeviceInfo().isZZeroToOne()?1:0,VanillaLight.ssrIntensity()/100f,VanillaLight.pbrIntensity()/100f,32)
|
||||
.putVec4(RenderSystem.getDevice().getDeviceInfo().isZZeroToOne()?1:0,VanillaLight.ssrIntensity()/100f,VanillaLight.pbrIntensity()/100f,VanillaLight.pbrDistance())
|
||||
.putVec4(128,32,0,0);
|
||||
}
|
||||
int max=java.util.stream.Stream.concat(solid.stream(),transparent.stream()).mapToInt(PbrTerrain.Entry::indices).max().orElse(0);
|
||||
@@ -111,7 +111,7 @@ public final class PixelReflections {
|
||||
pass.setUniform("OpaqueDepth",opaqueDepthView,nearest);
|
||||
pass.setUniform("OpaqueColor",opaqueColorView!=null?opaqueColorView:scene,nearest);
|
||||
// Never bind the receiver as a sampled texture while it is attached for depth writes.
|
||||
pass.setUniform("ReceiverDepth",opaqueDepthView,nearest);
|
||||
pass.setUniform("ReceiverDepth",reflection?Minecraft.getInstance().gameRenderer.mainRenderTarget().getDepthTextureView():opaqueDepthView,nearest);
|
||||
pass.setUniform("SceneColor",scene,nearest);
|
||||
pass.setUniform("BlockAtlas",Minecraft.getInstance().getTextureManager().getTexture(TextureAtlas.LOCATION_BLOCKS).getTextureView(),nearest);
|
||||
}
|
||||
@@ -135,6 +135,7 @@ public final class PixelReflections {
|
||||
pass.setPipeline(RenderSystem.getCompiledPipeline(REFLECT));bind(pass,scene,true);
|
||||
draw(pass,solid,solidAtlas);draw(pass,transparent,transparentAtlas);
|
||||
}
|
||||
MaterialBloom.accumulate(scene);
|
||||
applied=true;frames++;
|
||||
}
|
||||
public static void release(){
|
||||
|
||||
@@ -33,9 +33,9 @@ public final class ProceduralPbr {
|
||||
private static final RenderPipeline PIPELINE=RenderPipelines.register(RenderPipeline.builder()
|
||||
.withLocation(Identifier.parse("sanctuary:pipeline/pbr")).withVertexShader(Identifier.parse("sanctuary:core/pbr"))
|
||||
.withFragmentShader(Identifier.parse("sanctuary:core/pbr")).withBindGroupLayout(BindGroupLayouts.DYNAMIC_TRANSFORMS).withBindGroupLayout(SCENE)
|
||||
.withVertexBinding(0,PbrTerrain.FORMAT).withPrimitiveTopology(PrimitiveTopology.QUADS).withCull(false)
|
||||
.withVertexBinding(0,PbrTerrain.FORMAT).withPrimitiveTopology(PrimitiveTopology.QUADS).withCull(true)
|
||||
.withColorTargetState(new ColorTargetState(Optional.empty(),GpuFormat.RGBA8_UNORM,ColorTargetState.WRITE_COLOR))
|
||||
.withDepthStencilState(Optional.empty()).build());
|
||||
.withDepthStencilState(new DepthStencilState(CompareOp.EQUAL,false)).build());
|
||||
private static final PbrTerrain TERRAIN=new PbrTerrain();
|
||||
private static final PbrTerrain TRANSPARENT=new PbrTerrain(true);
|
||||
private static final Matrix4f projection=new Matrix4f();
|
||||
@@ -61,6 +61,7 @@ public final class ProceduralPbr {
|
||||
var entries=new ArrayList<PbrTerrain.Entry>(solidEntries);entries.addAll(transparentEntries);
|
||||
var vp=new Matrix4f(projection).mul(camera.viewRotationMatrix);var fog=camera.fogData;
|
||||
PixelReflections.prepare(vp,solidEntries,atlas,transparentEntries,transparentAtlas);
|
||||
if(PixelReflections.depth()==null)return;
|
||||
var sun=PixelShadows.sunDirection(sky.sunAngle);boolean lunar=sun.y<=.08f;
|
||||
if(lunar)sun=PixelShadows.sunDirection(sky.moonAngle);
|
||||
float moon=sky.moonPhase==null?1:(float)(.5+.5*Math.cos(sky.moonPhase.index()*Math.PI/4));
|
||||
@@ -69,7 +70,7 @@ public final class ProceduralPbr {
|
||||
scene.rotate();try(var mapped=scene.currentBuffer().map(false,true)){
|
||||
Std140Builder.intoBuffer(mapped.data()).putMat4f(new Matrix4f(vp).invert())
|
||||
.putVec4(sun.x,sun.y,sun.z,strength)
|
||||
.putVec4(VanillaLight.pbrIntensity()/100f,RenderSystem.getDevice().getDeviceInfo().isZZeroToOne()?1:0,shadow?1:0,32)
|
||||
.putVec4(VanillaLight.pbrIntensity()/100f,RenderSystem.getDevice().getDeviceInfo().isZZeroToOne()?1:0,shadow?1:0,VanillaLight.pbrDistance())
|
||||
.putVec4(fog.renderDistanceStart,fog.renderDistanceEnd,fog.environmentalStart,fog.environmentalEnd).putVec4(fog.color.w,ColoredLights.directionalReady()?1:0,0,0)
|
||||
.putVec4(0,0,0,0).putVec4(0,0,0,0).putVec4(0,0,0,0).putVec4(0,0,0,0);
|
||||
}
|
||||
@@ -80,11 +81,11 @@ public final class ProceduralPbr {
|
||||
var encoder=RenderSystem.getDevice().createCommandEncoder();
|
||||
encoder.copyTextureToTexture(main.getColorTexture(),source.getColorTexture(),0,0,0,0,0,main.width,main.height);
|
||||
var nearest=RenderSystem.getSamplerCache().getClampToEdge(FilterMode.NEAREST);
|
||||
if((atlas!=null||transparentAtlas!=null)&&!entries.isEmpty())try(var pass=encoder.createRenderPass(()->"Sanctuary PBR materials",main.getColorTextureView(),Optional.empty())){
|
||||
if((atlas!=null||transparentAtlas!=null)&&!entries.isEmpty())try(var pass=encoder.createRenderPass(()->"Sanctuary PBR materials",main.getColorTextureView(),Optional.empty(),PixelReflections.depth(),OptionalDouble.empty())){
|
||||
pass.setPipeline(RenderSystem.getCompiledPipeline(PIPELINE));pass.setUniform("PbrScene",scene.currentBuffer());
|
||||
pass.setUniform("PixelShadows",shadow?PixelShadows.materialUniforms():scene.currentBuffer());
|
||||
pass.setUniform("SunDepth",shadow?PixelShadows.materialDepth():main.getDepthTextureView(),nearest);
|
||||
pass.setUniform("WorldDepth",PixelReflections.depth()!=null?PixelReflections.depth():main.getDepthTextureView(),nearest);pass.setUniform("SceneColor",source.getColorTextureView(),nearest);
|
||||
pass.setUniform("WorldDepth",main.getDepthTextureView(),nearest);pass.setUniform("SceneColor",source.getColorTextureView(),nearest);
|
||||
pass.setUniform("BlockAtlas",mc.getTextureManager().getTexture(TextureAtlas.LOCATION_BLOCKS).getTextureView(),nearest);
|
||||
pass.setUniform("MaterialAtlas",(atlas!=null?atlas:transparentAtlas).getTextureView(),nearest);
|
||||
if(ColoredLights.directionalReady())ColoredLights.bindDirections(pass);
|
||||
@@ -93,6 +94,7 @@ public final class ProceduralPbr {
|
||||
for(int i=0;i<entries.size();i++){var entry=entries.get(i);
|
||||
pass.setUniform("MaterialAtlas",(i<solidEntries.size()?atlas:transparentAtlas).getTextureView(),nearest);pass.setUniform("DynamicTransforms",transforms.get(i));pass.setVertexBuffer(0,entry.vertices().slice());pass.drawIndexed(entry.indices(),1,0,0,0);}
|
||||
}
|
||||
MaterialBloom.accumulate(source.getColorTextureView());
|
||||
applied=true;frames++;
|
||||
}
|
||||
static com.mojang.renderpearl.api.textures.GpuTextureView copyScene(){
|
||||
@@ -100,7 +102,7 @@ public final class ProceduralPbr {
|
||||
RenderSystem.getDevice().createCommandEncoder().copyTextureToTexture(main.getColorTexture(),source.getColorTexture(),0,0,0,0,0,main.width,main.height);
|
||||
return source.getColorTextureView();
|
||||
}
|
||||
public static void release(){TERRAIN.close();TRANSPARENT.close();PixelReflections.release();if(source!=null){source.destroyBuffers();source=null;}if(scene!=null){scene.close();scene=null;}applied=false;}
|
||||
public static void release(){MaterialBloom.release();TERRAIN.close();TRANSPARENT.close();PixelReflections.release();if(source!=null){source.destroyBuffers();source=null;}if(scene!=null){scene.close();scene=null;}applied=false;}
|
||||
static long solidBytes(){return TERRAIN.allocatedBytes();}
|
||||
static long transparentBytes(){return TRANSPARENT.allocatedBytes();}
|
||||
static int waterUpdates(){return TRANSPARENT.materials.animationUpdates();}
|
||||
|
||||
@@ -17,6 +17,9 @@ public final class ShaderOptionsScreen extends OptionsSubScreen {
|
||||
list.addBig(OptionInstance.createBoolean("sanctuary.shaders.natural_water",OptionInstance.cachedConstantTooltip(
|
||||
Component.translatable("sanctuary.shaders.natural_water_help")),VanillaLight.naturalWater(),VanillaLight::setNaturalWater));
|
||||
slider("water_intensity",VanillaLight.waterIntensity(),VanillaLight::setWaterIntensity);
|
||||
list.addBig(new OptionInstance<Integer>("sanctuary.shaders.water_blend",OptionInstance.cachedConstantTooltip(
|
||||
Component.translatable("sanctuary.shaders.water_blend_help")),(caption,value)->Component.translatable("sanctuary.shaders.water_blend_value",value*2+1,value*2+1),
|
||||
new OptionInstance.Enum<>(List.of(0,1,2,4,8),Codec.INT),VanillaLight.waterBlendRadius(),VanillaLight::setWaterBlendRadius));
|
||||
list.addBig(new OptionInstance<Integer>("sanctuary.shaders.saturation",OptionInstance.cachedConstantTooltip(
|
||||
Component.translatable("sanctuary.shaders.saturation_help")),(caption,value)->Component.translatable("sanctuary.shaders.saturation_value",value),
|
||||
new OptionInstance.IntRange(0,200),VanillaLight.saturation(),VanillaLight::setSaturation));
|
||||
@@ -29,6 +32,9 @@ public final class ShaderOptionsScreen extends OptionsSubScreen {
|
||||
list.addBig(OptionInstance.createBoolean("sanctuary.shaders.ssr",OptionInstance.cachedConstantTooltip(
|
||||
Component.translatable("sanctuary.shaders.ssr_help")),VanillaLight.ssr(),VanillaLight::setSsr));
|
||||
slider("ssr_intensity",VanillaLight.ssrIntensity(),VanillaLight::setSsrIntensity);
|
||||
list.addBig(new OptionInstance<Integer>("sanctuary.shaders.pbr_distance",OptionInstance.cachedConstantTooltip(
|
||||
Component.translatable("sanctuary.shaders.pbr_distance_help")),(caption,value)->Component.translatable("sanctuary.shaders.pbr_distance_value",value),
|
||||
new OptionInstance.Enum<>(List.of(16,32,64,128),Codec.INT),VanillaLight.pbrDistance(),VanillaLight::setPbrDistance));
|
||||
list.addBig(OptionInstance.createBoolean("sanctuary.shaders.colored_lights",OptionInstance.cachedConstantTooltip(
|
||||
Component.translatable("sanctuary.shaders.colored_lights_help")),VanillaLight.coloredLights(),VanillaLight::setColoredLights));
|
||||
list.addBig(OptionInstance.createBoolean("sanctuary.shaders.warm_torches",OptionInstance.cachedConstantTooltip(
|
||||
|
||||
@@ -16,7 +16,10 @@ public final class VanillaLight {
|
||||
private VanillaLight() { }
|
||||
private static final class Preferences {
|
||||
boolean enabled=true;
|
||||
boolean naturalWater=true;
|
||||
Boolean naturalWater; // Legacy OFF is an explicit opt-out in both world types.
|
||||
Boolean sanctuaryNaturalWater;
|
||||
Boolean ordinaryNaturalWater;
|
||||
int waterBlendRadius=4;
|
||||
int waterIntensity=100;
|
||||
int saturation=142;
|
||||
boolean pixelShadows=true;
|
||||
@@ -27,8 +30,9 @@ public final class VanillaLight {
|
||||
int lensFlareIntensity=30;
|
||||
boolean pbr=true;
|
||||
int pbrIntensity=80;
|
||||
int pbrDistance=32;
|
||||
boolean ssr=false;
|
||||
int ssrIntensity=50;
|
||||
int ssrIntensity=20;
|
||||
boolean ssgi=true;
|
||||
boolean coloredLights=true;
|
||||
boolean warmTorches=false;
|
||||
@@ -51,9 +55,15 @@ 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();
|
||||
if(Boolean.FALSE.equals(preferences.naturalWater)){
|
||||
if(preferences.sanctuaryNaturalWater==null)preferences.sanctuaryNaturalWater=false;
|
||||
if(preferences.ordinaryNaturalWater==null)preferences.ordinaryNaturalWater=false;
|
||||
}
|
||||
preferences.naturalWater=null;
|
||||
preferences.sunshaftIntensity=Math.clamp(preferences.sunshaftIntensity,0,100);
|
||||
preferences.lensFlareIntensity=Math.clamp(preferences.lensFlareIntensity,0,100);
|
||||
preferences.ssrIntensity=Math.clamp(preferences.ssrIntensity,0,100);
|
||||
preferences.pbrDistance=normalizePbrDistance(preferences.pbrDistance);
|
||||
preferences.pbrIntensity=Math.clamp(preferences.pbrIntensity,0,100);
|
||||
preferences.coloredLightIntensity=Math.clamp(preferences.coloredLightIntensity,0,100);
|
||||
preferences.coloredLightDistance=normalizeColoredLightDistance(preferences.coloredLightDistance);
|
||||
@@ -63,6 +73,7 @@ public final class VanillaLight {
|
||||
preferences.oreIntensity=Math.clamp(preferences.oreIntensity,0,100);
|
||||
preferences.edgeIntensity=Math.clamp(preferences.edgeIntensity,0,100);
|
||||
preferences.edgeDistance=normalizeShadowDistance(preferences.edgeDistance);
|
||||
preferences.waterBlendRadius=normalizeWaterBlendRadius(preferences.waterBlendRadius);
|
||||
preferences.waterIntensity=Math.clamp(preferences.waterIntensity,0,100);
|
||||
preferences.saturation=Math.clamp(preferences.saturation,0,200);
|
||||
preferences.shadowPixels=normalizePrecision(preferences.shadowPixels);
|
||||
@@ -72,9 +83,13 @@ public final class VanillaLight {
|
||||
}
|
||||
public static boolean externalEngine(){return EXTERNAL_ENGINE;}
|
||||
public static boolean active(){return ProgressionClient.active()&&!EXTERNAL_ENGINE&&enabled();}
|
||||
public static boolean naturalWater(){enabled();return preferences.naturalWater;}
|
||||
public static boolean naturalWater(){return naturalWater(NaturalWater.sanctuaryWorld());}
|
||||
static boolean naturalWater(boolean sanctuary){enabled();Boolean value=sanctuary?preferences.sanctuaryNaturalWater:preferences.ordinaryNaturalWater;return value!=null?value:sanctuary;}
|
||||
public static int waterBlendRadius(){enabled();return preferences.waterBlendRadius;}
|
||||
public static void setWaterBlendRadius(int value){enabled();preferences.waterBlendRadius=normalizeWaterBlendRadius(value);save();}
|
||||
static int normalizeWaterBlendRadius(int value){return value<1?0:value<2?1:value<3?2:value<6?4:8;}
|
||||
public static int waterIntensity(){enabled();return preferences.waterIntensity;}
|
||||
public static void setNaturalWater(boolean value){enabled();preferences.naturalWater=value;save();}
|
||||
public static void setNaturalWater(boolean value){enabled();if(NaturalWater.sanctuaryWorld())preferences.sanctuaryNaturalWater=value;else preferences.ordinaryNaturalWater=value;save();}
|
||||
public static void setWaterIntensity(int value){enabled();preferences.waterIntensity=Math.clamp(value,0,100);save();}
|
||||
public static int saturation(){enabled();return preferences.saturation;}
|
||||
public static boolean edgeHighlights(){enabled();return preferences.edgeHighlights;}
|
||||
@@ -95,6 +110,9 @@ public final class VanillaLight {
|
||||
public static int ssrIntensity(){enabled();return preferences.ssrIntensity;}
|
||||
public static void setSsr(boolean value){enabled();preferences.ssr=value;save();}
|
||||
public static void setSsrIntensity(int value){enabled();preferences.ssrIntensity=Math.clamp(value,0,100);save();}
|
||||
public static int pbrDistance(){enabled();return preferences.pbrDistance;}
|
||||
public static void setPbrDistance(int value){enabled();preferences.pbrDistance=normalizePbrDistance(value);save();}
|
||||
static int normalizePbrDistance(int value){return value<24?16:value<48?32:value<96?64:128;}
|
||||
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();}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package fr.koka.sanctuary.water;
|
||||
|
||||
import fr.koka.sanctuary.SanctuaryMod;
|
||||
import fr.koka.sanctuary.expansion.ExpansionChunkGenerator;
|
||||
import net.fabricmc.fabric.api.networking.v1.*;
|
||||
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityLevelChangeEvents;
|
||||
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
|
||||
/** Read-only dimension context; no persistent world or generation changes. */
|
||||
public final class WaterWorldSettings {
|
||||
private WaterWorldSettings() {}
|
||||
public record Context(boolean sanctuary) implements CustomPacketPayload {
|
||||
public static final Type<Context> TYPE=new Type<>(SanctuaryMod.id("water_world_v1"));
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf,Context> CODEC=CustomPacketPayload.codec(
|
||||
(p,b)->b.writeBoolean(p.sanctuary),b->new Context(b.readBoolean()));
|
||||
public Type<Context> type(){return TYPE;}
|
||||
}
|
||||
public static void register(){
|
||||
PayloadTypeRegistry.clientboundPlay().register(Context.TYPE,Context.CODEC);
|
||||
ServerPlayConnectionEvents.JOIN.register((handler,sender,server)->send(handler.player));
|
||||
ServerEntityLevelChangeEvents.AFTER_PLAYER_CHANGE_LEVEL.register((player,origin,destination)->send(player));
|
||||
ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer,player,alive)->send(player));
|
||||
}
|
||||
private static void send(ServerPlayer player){
|
||||
if(ServerPlayNetworking.canSend(player,Context.TYPE))
|
||||
ServerPlayNetworking.send(player,new Context(player.level().getChunkSource().getGenerator() instanceof ExpansionChunkGenerator));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"sanctuary.shaders.ssr_intensity_value": "Reflection strength: %s%%",
|
||||
"sanctuary.shaders.ssr_intensity_help": "Amount of landscape reflected by materials. Zero disables SSR.",
|
||||
"sanctuary.shaders.ssr_intensity_help": "Strength of visible landscape reflections on solids and water. Default: 20%.",
|
||||
"sanctuary.shaders.ssr": "Landscape reflections — experimental",
|
||||
"sanctuary.shaders.ssr_help": "Colours material texels with the visible landscape. Requires PBR; off-screen objects cannot be reflected.",
|
||||
"sanctuary.shaders.ssr_intensity": "Reflection strength",
|
||||
@@ -2302,9 +2302,9 @@
|
||||
"sanctuary.shaders.ssgi_intensity_value": "SSGI: %s%%",
|
||||
"sanctuary.shaders.pbr": "PBR · material relief",
|
||||
"sanctuary.shaders.pbr_help": "Experimental: procedural block normals and roughness, sun, moon and local light highlights. Enable Colored lights for local direction. Range 32 blocks. ON at 80% 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": "PBR intensity",
|
||||
"sanctuary.shaders.pbr_intensity_help": "Material relief and highlights, including water. Default: 80%.",
|
||||
"sanctuary.shaders.pbr_intensity_value": "PBR intensity: %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",
|
||||
@@ -2326,8 +2326,14 @@
|
||||
"sanctuary.shaders.warm_torches": "Warm torches",
|
||||
"sanctuary.shaders.warm_torches_help": "Off: ordinary torches keep the native color temperature. On: orange tint. Other sources retain their own colors.",
|
||||
"sanctuary.shaders.natural_water": "Natural water",
|
||||
"sanctuary.shaders.natural_water_help": "Biome colour, clarity from the bed. Clear oceans. Also changes underwater visibility.",
|
||||
"sanctuary.shaders.natural_water_help": "Bed-dependent tint, transparency and fog. Defaults OFF in ordinary worlds and ON on Sanctuary islands. Preferences are stored separately for each world type.",
|
||||
"sanctuary.shaders.water_intensity": "Water intensity",
|
||||
"sanctuary.shaders.water_intensity_help": "At 0%, keeps native rendering. At 100%, applies all three profiles.",
|
||||
"sanctuary.shaders.water_intensity_value": "Water intensity: %s%%"
|
||||
"sanctuary.shaders.water_intensity_value": "Water intensity: %s%%",
|
||||
"sanctuary.shaders.pbr_distance": "PBR and SSR distance",
|
||||
"sanctuary.shaders.pbr_distance_value": "PBR and SSR distance: %s blocks",
|
||||
"sanctuary.shaders.pbr_distance_help": "Shared range for relief and reflecting surfaces. 16, 32, 64 or 128 blocks. Longer distances cost more; mesh memory is bounded.",
|
||||
"sanctuary.shaders.water_blend": "Water blending",
|
||||
"sanctuary.shaders.water_blend_value": "Water blending: %s × %s blocks",
|
||||
"sanctuary.shaders.water_blend_help": "Transition width between bed-dependent water colours: 1, 3, 5, 9 or 17 blocks. Default: 9 × 9."
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"sanctuary.shaders.ssr_intensity_value": "Intensité des reflets : %s %%",
|
||||
"sanctuary.shaders.ssr_intensity_help": "Dose de paysage réfléchie par les matériaux. Zéro désactive le SSR.",
|
||||
"sanctuary.shaders.ssr_intensity_help": "Intensité des reflets du paysage visible, sur les solides et l’eau. Défaut : 20 %.",
|
||||
"sanctuary.shaders.ssr": "Reflets du paysage — expérimental",
|
||||
"sanctuary.shaders.ssr_help": "Colore les pixels de matière avec le paysage visible. PBR requis ; les objets hors écran ne sont pas réfléchis.",
|
||||
"sanctuary.shaders.ssr_intensity": "Intensité des reflets",
|
||||
@@ -2302,9 +2302,9 @@
|
||||
"sanctuary.shaders.ssgi_intensity_value": "SSGI : %s %%",
|
||||
"sanctuary.shaders.pbr": "PBR · relief des matériaux",
|
||||
"sanctuary.shaders.pbr_help": "Expérimental : normales procédurales et rugosité des blocs, reflets du soleil, de la lune et des lumières locales. Activer Lumières colorées pour la direction locale. Portée 32 blocs. Activé par défaut à 80 %.",
|
||||
"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": "Intensité PBR",
|
||||
"sanctuary.shaders.pbr_intensity_help": "Relief et éclats des matières, eau comprise. Défaut : 80 %.",
|
||||
"sanctuary.shaders.pbr_intensity_value": "Intensité 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",
|
||||
@@ -2326,8 +2326,14 @@
|
||||
"sanctuary.shaders.warm_torches": "Torches chaudes",
|
||||
"sanctuary.shaders.warm_torches_help": "Désactivé : les torches ordinaires conservent la température de couleur native. Activé : teinte orangée. Les autres sources gardent leurs propres couleurs.",
|
||||
"sanctuary.shaders.natural_water": "Eau naturelle",
|
||||
"sanctuary.shaders.natural_water_help": "Couleur du biome, clarté selon le fond. Océans clairs. Modifie aussi la visibilité sous l’eau.",
|
||||
"sanctuary.shaders.natural_water_help": "Teintes, transparence et brume selon le fond. OFF par défaut dans les mondes ordinaires, ON sur les îles Sanctuary. Choix mémorisé séparément pour chaque type de monde.",
|
||||
"sanctuary.shaders.water_intensity": "Intensité de l’eau",
|
||||
"sanctuary.shaders.water_intensity_help": "À 0 %, conserve le rendu natif. À 100 %, applique les trois profils.",
|
||||
"sanctuary.shaders.water_intensity_value": "Intensité de l’eau: %s%%"
|
||||
"sanctuary.shaders.water_intensity_value": "Intensité de l’eau: %s%%",
|
||||
"sanctuary.shaders.pbr_distance": "Distance PBR et SSR",
|
||||
"sanctuary.shaders.pbr_distance_value": "Distance PBR et SSR : %s blocs",
|
||||
"sanctuary.shaders.pbr_distance_help": "Portée commune du relief et des surfaces réfléchissantes. 16, 32, 64 ou 128 blocs. Les distances élevées coûtent davantage ; budget de maillage limité.",
|
||||
"sanctuary.shaders.water_blend": "Mélange des eaux",
|
||||
"sanctuary.shaders.water_blend_value": "Mélange des eaux : %s × %s blocs",
|
||||
"sanctuary.shaders.water_blend_help": "Étendue de la transition des teintes selon le fond : 1, 3, 5, 9 ou 17 blocs. Défaut : 9 × 9."
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#version 330
|
||||
#extension GL_ARB_separate_shader_objects : require
|
||||
uniform sampler2D InputTexture;
|
||||
layout(location=0) in vec2 texCoord;
|
||||
layout(location=0) out vec4 fragColor;
|
||||
void main(){fragColor=vec4(texture(InputTexture,texCoord).rgb,0);}
|
||||
@@ -0,0 +1,16 @@
|
||||
#version 330
|
||||
#extension GL_ARB_separate_shader_objects : require
|
||||
uniform sampler2D Before;
|
||||
uniform sampler2D After;
|
||||
layout(location=0) in vec2 texCoord;
|
||||
layout(location=0) out vec4 fragColor;
|
||||
void main(){
|
||||
vec2 pixel=1.0/vec2(textureSize(After,0));vec3 glow=vec3(0);
|
||||
for(int y=0;y<8;y++)for(int x=0;x<8;x++){
|
||||
vec2 uv=texCoord+(vec2(x,y)-3.5)*pixel;
|
||||
vec3 a=texture(After,uv).rgb,b=texture(Before,uv).rgb;
|
||||
// A restrained halo for material light, stronger for near-white solar glints.
|
||||
glow+=max(a*a-b*b,vec3(0))*(.12+.28*smoothstep(.75,1.0,max(a.r,max(a.g,a.b))));
|
||||
}
|
||||
fragColor=vec4(glow/64.0,0);
|
||||
}
|
||||
@@ -89,8 +89,8 @@ vec3 localShine(vec3 n,vec3 v,vec3 l,vec4 material,vec3 albedo){
|
||||
vec3 halfVector=l+v;float size=dot(halfVector,halfVector);if(size<.00001)return vec3(0);
|
||||
vec3 h=halfVector*inversesqrt(size);
|
||||
float nl=clamp(dot(n,l),0.0,1.0),nv=clamp(dot(n,v),.001,1.0),nh=clamp(dot(n,h),0.0,1.0),vh=clamp(dot(v,h),0.0,1.0);
|
||||
float roughness=max(material.b,.25),alpha=roughness*roughness,a2=alpha*alpha;
|
||||
float denom=nh*nh*(a2-1.0)+1.0,distribution=a2/max(3.14159265*denom*denom,.00001);
|
||||
float roughness=max(material.b,.16),alpha=roughness*roughness,a2=alpha*alpha;
|
||||
float denom=nh*nh*(a2-1.0)+1.0,distribution=a2/max(3.14159265*denom*denom,.00000001);
|
||||
float k=(roughness+1.0)*(roughness+1.0)/8.0;
|
||||
float geometry=nv/(nv*(1.0-k)+k)*nl/max(nl*(1.0-k)+k,.00001);
|
||||
// Explicit integer power stays defined even at round-off limits on OpenGL drivers.
|
||||
@@ -104,24 +104,33 @@ void main(){
|
||||
vec2 tx=dFdx(blockUv),ty=dFdy(blockUv);
|
||||
float det=tx.x*ty.y-tx.y*ty.x;
|
||||
vec3 baseNormal=normalize(cross(dx,dy));if(dot(baseNormal,-relativePosition)<0.0)baseNormal=-baseNormal;
|
||||
vec2 uv=gl_FragCoord.xy/vec2(textureSize(WorldDepth,0));float depth=texelFetch(WorldDepth,ivec2(gl_FragCoord.xy),0).r;
|
||||
vec4 projected=PbrInverseVP*vec4(uv*2.0-1.0,PbrParams.y>0.5?depth:depth*2.0-1.0,1.0);
|
||||
vec3 visible=projected.xyz/projected.w;
|
||||
if(depth<=0.0||abs(length(visible)-length(relativePosition))>0.035||length(relativePosition)>=PbrParams.w||abs(det)<1e-12)discard;
|
||||
// Exact receiver depth is tested by the GPU; final scene depth also masks entities.
|
||||
float visibleDepth=texelFetch(WorldDepth,ivec2(gl_FragCoord.xy),0).r;
|
||||
vec2 screenUv=gl_FragCoord.xy/vec2(textureSize(WorldDepth,0));
|
||||
vec4 visiblePoint=PbrInverseVP*vec4(screenUv*2.0-1.0,PbrParams.y>.5?visibleDepth:visibleDepth*2.0-1.0,1);
|
||||
// Native terrain and material matrices have different evaluation order. Use a
|
||||
// bounded 5 mm tolerance only for native occlusion; receiver selection stays exact.
|
||||
if((visibleDepth>0.0&&length(visiblePoint.xyz/visiblePoint.w)+.005<length(relativePosition))||length(relativePosition)>=PbrParams.w||abs(det)<1e-12)discard;
|
||||
// Keep atlas derivatives valid after per-fragment depth/alpha rejection.
|
||||
float family=floor(maskUv.y*.5);
|
||||
vec4 texel=textureGrad(BlockAtlas,blockUv,tx,ty);if(family<.5&&texel.a<0.99)discard;
|
||||
vec3 tangent=normalize((dx*ty.y-dy*tx.y)/det),bitangent=normalize((dy*tx.x-dx*ty.x)/det);
|
||||
vec4 material=textureLod(MaterialAtlas,vec2(maskUv.x,maskUv.y-family*2.0),0.0);
|
||||
// RG stores signed height derivatives; B roughness, A metalness, not transparency.
|
||||
vec2 slope=(material.rg*255.0-128.0)/127.0*PbrParams.x*(family>1.5?.24:2.0);
|
||||
float intensity=PbrParams.x;
|
||||
vec2 slope=(material.rg*255.0-128.0)/127.0*intensity*(family>1.5?.24:2.0);
|
||||
vec3 n=normalize(baseNormal+tangent*slope.x+bitangent*slope.y);
|
||||
// Reject degenerate/interpolated geometry without replacing the native scene with NaNs.
|
||||
if(any(isnan(n))||any(isinf(n))||dot(relativePosition,relativePosition)<.000001)discard;
|
||||
vec3 l=normalize(PbrSun.xyz),v=normalize(-relativePosition);
|
||||
float nl=clamp(dot(n,l),0.0,1.0);
|
||||
vec3 albedo=texel.rgb*tint.rgb;albedo*=albedo;
|
||||
vec3 specular=localShine(n,v,l,material,albedo);
|
||||
// A broad solar lobe gives water an extended glitter path at grazing angles.
|
||||
// Local lamps retain the material's original narrow lobe.
|
||||
vec4 solarMaterial=material;
|
||||
solarMaterial.b=max(material.b,family>1.5?.34:.24);
|
||||
vec3 specular=localShine(n,v,l,solarMaterial,albedo);
|
||||
float solarRadiance=family>1.5?20.0:4.0;
|
||||
float fog=max(clamp((max(length(relativePosition.xz),abs(relativePosition.y))-PbrFog.x)/max(PbrFog.y-PbrFog.x,.001),0.0,1.0),
|
||||
clamp((length(relativePosition)-PbrFog.z)/max(PbrFog.w-PbrFog.z,.001),0.0,1.0));
|
||||
float amount=PbrSun.w*pow(tint.a,8.0)*visibility(relativePosition,baseNormal)*(1.0-fog*PbrEnvironment.x)
|
||||
@@ -137,7 +146,8 @@ void main(){
|
||||
float localRelief=family<.5?(max(dot(n,arrival.xyz),0.0)-max(dot(baseNormal,arrival.xyz),0.0))*.7:0.0;
|
||||
lit=clamp(lit*(1.0+localRelief*localAmount),0.0,1.0);
|
||||
}
|
||||
vec3 shine=min((specular*amount+localShine(n,v,arrival.xyz,material,albedo)*localAmount)*PbrParams.x*3.0,vec3(0.5));
|
||||
// Preserve bright solar highlights instead of capping them at a medium grey.
|
||||
vec3 shine=min((specular*amount*solarRadiance+localShine(n,v,arrival.xyz,material,albedo)*localAmount)*intensity*5.0,vec3(8.0));
|
||||
lit+=(1.0-lit)*(1.0-exp(-shine));
|
||||
if(any(isnan(lit))||any(isinf(lit)))discard;
|
||||
fragColor=vec4(sqrt(clamp(lit,0.0,1.0)),1.0);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#version 330
|
||||
#extension GL_ARB_separate_shader_objects : require
|
||||
#include <minecraft:dynamictransforms.glsl>
|
||||
// Receiver and colour passes must produce bit-identical depth on every backend.
|
||||
invariant gl_Position;
|
||||
layout(location=0) in vec3 Position;
|
||||
layout(location=1) in vec4 Color;
|
||||
layout(location=2) in vec2 UV0;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#version 330
|
||||
#extension GL_ARB_separate_shader_objects : require
|
||||
uniform sampler2D OpaqueDepth;
|
||||
layout(std140) uniform ReflectionScene { mat4 ReflectionVP; mat4 ReflectionInverseVP; vec4 ReflectionParams; vec4 ReflectionLimits; };
|
||||
uniform sampler2D BlockAtlas;
|
||||
layout(location=0) in vec3 relativePosition;
|
||||
layout(location=1) in vec2 blockUv;
|
||||
@@ -8,7 +9,7 @@ layout(location=2) in vec2 maskUv;
|
||||
layout(location=3) in vec4 tint;
|
||||
void main(){
|
||||
vec2 dx=dFdx(blockUv),dy=dFdy(blockUv);
|
||||
if(length(relativePosition)>=32.0)discard;
|
||||
if(length(relativePosition)>=ReflectionParams.w)discard;
|
||||
// Minecraft's world projection uses reverse depth: zero is the far plane.
|
||||
float opaque=texelFetch(OpaqueDepth,ivec2(gl_FragCoord.xy),0).r;
|
||||
if(gl_FragCoord.z+0.0000001<opaque)discard;
|
||||
|
||||
+8
-3
@@ -87,13 +87,18 @@ void main(){
|
||||
vec2 materialUv=vec2(maskUv.x,maskUv.y-family*2.0);
|
||||
vec3 base=normalize(cross(dx,dy));if(dot(base,-relativePosition)<0.0)base=-base;
|
||||
if(abs(det)<1e-14||length(relativePosition)>=ReflectionParams.w)discard;
|
||||
// The receiver depth attachment rejects hidden and secondary surfaces before ray tracing.
|
||||
// The final native depth includes entities rendered after the landscape capture.
|
||||
float finalDepth=texelFetch(ReceiverDepth,ivec2(gl_FragCoord.xy),0).r;
|
||||
vec2 screenUv=gl_FragCoord.xy/vec2(textureSize(ReceiverDepth,0));
|
||||
if(finalDepth>0.0&&length(reconstruct(screenUv,finalDepth))+.005<length(relativePosition))discard;
|
||||
float intensity=ReflectionParams.z;
|
||||
if(intensity<=0.0)discard;
|
||||
vec4 texel=textureGrad(BlockAtlas,blockUv,tx,ty);if(family<.5&&texel.a<.99)discard;
|
||||
vec2 center=(floor(materialUv*1280.0)+.5)/1280.0;
|
||||
vec3 u=(dx*my.y-dy*mx.y)/det,v=(dy*mx.x-dx*my.x)/det;
|
||||
vec3 point=relativePosition+u*(center.x-materialUv.x)+v*(center.y-materialUv.y);
|
||||
vec4 material=textureLod(MaterialAtlas,center,0);
|
||||
vec2 slope=(material.rg*255.0-128.0)/127.0*ReflectionParams.z*(family>1.5?.24:2.0);
|
||||
vec2 slope=(material.rg*255.0-128.0)/127.0*intensity*(family>1.5?.24:2.0);
|
||||
vec3 normal=normalize(base+normalize(u)*slope.x+normalize(v)*slope.y);
|
||||
if(any(isnan(normal))||any(isinf(normal)))discard;
|
||||
vec4 hit=trace(point,normal,base);if(hit.w<=0.0)discard;
|
||||
@@ -117,7 +122,7 @@ void main(){
|
||||
vec3 albedo=textureLod(BlockAtlas,centerBlock,0).rgb*tint.rgb;
|
||||
vec3 f0=mix(vec3(family>1.5?.02:.04),albedo*albedo,material.a);
|
||||
vec3 fresnel=f0+(1.0-f0)*(g2*g2*grazing);
|
||||
float fade=(1.0-smoothstep(24.0,32.0,length(point)))*hit.w;
|
||||
float fade=(1.0-smoothstep(ReflectionParams.w*.75,ReflectionParams.w,length(point)))*hit.w;
|
||||
vec3 weight=clamp(fresnel*ReflectionParams.y*(1.0-material.b*material.b)*fade,0.0,.9);
|
||||
vec3 scene=texelFetch(SceneColor,ivec2(gl_FragCoord.xy),0).rgb;
|
||||
vec3 result=mix(scene*scene,reflected*reflected,weight);
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
name = "Sanctuary"
|
||||
author = "KOKA99CAB"
|
||||
version = "beta.145"
|
||||
version = "beta.146"
|
||||
pack-format = "packwiz:1.1.0"
|
||||
|
||||
[index]
|
||||
|
||||
Reference in New Issue
Block a user