Compare commits

...
Author SHA1 Message Date
koka 7fada570ef fix: stabilize close-range SSGI and update shader defaults for beta.135
Build Sanctuary / build (push) Canceled after 0s
2026-09-17 23:46:29 +02:00
17 changed files with 272 additions and 42 deletions
+13 -7
View File
@@ -20,13 +20,12 @@ 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.134** ajoute le **PBR · relief des matériaux** optionnel :
normales procédurales mises en cache, rugosité par matériau et reflets orientés.
Le SSGI reçoit un filtre spatial guidé par la profondeur et des normales de faces
stabilisées. **Options → Shaders** ; réglages existants conservés.
Le [ticket de livraison](docs/shader-beta134.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.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.
Contrôles ciblés et archives vérifiés ; livraison prête.
La [livraison précédente](docs/clay-workshop-integration-beta110.md) réunit déjà
latelier, les œufs et la neige saisonnière dans linstance **Sanctuary Beta**.
Les [synchronisations antérieures](docs/git-sync-beta092.md) restent documentées.
@@ -37,6 +36,13 @@ La [carte native beta.005](docs/atlas-beta005.md) prend le relais après
l’étude de Xaero et des alternatives libres.
## beta.135 — SSGI près des surfaces
Correction des coupures à courte distance, sans flouter les textures.
[Contrat et vérifications](docs/shader-beta135.md) ·
[Pack normal](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.135/Sanctuary-beta.135.mrpack) ·
[Pack de test](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.135/Sanctuary-Test-beta.135.mrpack).
## beta.134 — PBR optionnel et SSGI filtré
Relief et reflets des matériaux ; lissage de la lumière indirecte.
+101
View File
@@ -0,0 +1,101 @@
# SHADER-135 — SSGI en contre-plongée et près des surfaces
Demande : supprimer les déchirures horizontales sous une surface, notamment
près de la lave, et atténuer proprement le rebond quand la caméra s'approche
d'une source ou d'un mur. Branche `codex/shader-closeup-beta135`, socle beta.134.
Minecraft 26.3 / Java 25 ; aucune modification de monde ou de gameplay.
## Diagnostic et correction
Le calcul des normales comparait l'aire du pixel en unités du monde à un seuil
fixe. À courte distance ou à haute résolution, cette aire devient naturellement
très petite : le rejet pouvait couper une surface continue. Les deux directions
sont maintenant normalisées avant leur produit vectoriel. Les texels de bord
sont bornés et les voisins sans profondeur ne créent plus de fausse surface.
La composition utilise le centre exact du texel de profondeur. Le rebond
s'atténue entre 0,4 et 0,06 bloc de distance au **plan** receveur, et lorsque la
vue devient presque tangente. Cette distance évite une découpe circulaire sur
un mur plat. L'image native reste nette ; aucune texture du décor n'est floutée.
Les sondes sortant du volume de projection sont rejetées. Leur contribution
s'atténue près du plan de caméra et de la limite d'épaisseur d'un impact, au lieu
d'une contribution pleine jusqu'à cette limite. Trois ressources GLSL modifiées ;
aucune passe, cible GPU, lecture CPU, histoire temporelle ou option ajoutée.
Les 12 rayons × 10 pas et les deux passes de filtrage spatial restent inchangés.
## Réglages initiaux
Demande ajoutée pendant la livraison : **bloom 20 %**, **PBR ON** et **SSGI ON**.
Les intensités PBR 50 et SSGI 35 restent inchangées. Les champs absents du fichier
de préférences prennent ces nouvelles valeurs ; les choix explicites du joueur
restent conservés. Aides françaises et anglaises mises à jour. Vérification
native supplémentaire des préférences vierges, partielles et personnalisées.
## Reproduction native
Deux scènes isolées : stone au-dessus de la lave, puis coin à 0,5 bloc d'une
paroi rouge. Caméra en contre-plongée, sept distances au plafond de 2 à 0,04 bloc,
comparaisons OFF/35/100 avant/après. Dans la première scène, à 0,1 bloc et
intensité 100, le saut maximal de gain rouge moyen entre lignes de pixels neutres
passe de **14,88 à 0,052** niveaux sur 255. Dans le coin, à 0,4 bloc et intensité
35, le rebond auparavant rejeté revient (gain moyen de **6,01** niveaux), puis
s'atténue à l'approche du contact. Ces chiffres concernent ces scènes uniquement.
Les deux essais ciblés corrigés réussissent en 1 min 18 s et 1 min 17 s sur
Apple M1 / OpenGL. Images avant/après conservées dans `build/closeup-*` ;
comparateur autonome `build/Shader-beta.135-comparaison.html`.
## Régression et livraison
Les contrôles natifs de la suite générale réussissent jusqu'au parcours SSGI :
ombres, lune, feuillage, temps réel, CTM, émissifs/bloom, couleurs de rebond,
occlusion, réglages, rechargement et déplacement latéral de 12 blocs à 35/100.
Log : `build/shader135-client-main.log`. La suite n'est pas présentée comme
entièrement réussie : elle s'est arrêtée sur une attente incorrecte du nouveau
test, qui demandait du rebond alors que sa source était sortie du champ.
Le scénario corrigé distingue la haute résolution avec source visible de la
vue très relevée sans source visible. Le bassin possède un fond pour isoler la
lave des essais suivants ; les particules du scénario précédent sont effacées
avant la comparaison PBR statique. Une première tentative avait aussi rencontré
le refus natif de remettre l'horloge à sa valeur actuelle ; la préparation de
l'heure est désormais répétable. Ces changements concernent uniquement les tests.
Le contrôle final ciblé **réussit en 2 min 52 s** : les deux scènes, 35/100,
sept distances, 1280 × 720 avec source visible, source hors champ, mouvement
minuscule, rendu combiné, puis contrôles PBR complets (cache, intensités,
matériaux, soleil, toit, OFF/zéro, redimensionnement et rechargement).
Log : `build/shader135-client-final.log` ; marqueurs `SSGI135_CLOSEUP_PASS` et
`PBR134_PASS`. Les seuils de proximité et PBR sont conservés.
Le contrôle supplémentaire des nouveaux défauts et du PBR **réussit en
1 min 16 s** : préférences vierges, champs absents, valeurs personnelles conservées,
puis rendu PBR et combinaison des effets. Log : `build/shader135-defaults-client.log`.
`./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest`
réussit en **2 min 18 s**, 126 tâches. Le serveur GameTest dédié demeure exclu
conformément au refus EULA antérieur. Les classes des scénarios clients compilent
aussi après leur isolation explicite des effets activés par défaut.
Log : `build/shader135-build.log` ; compilation : `build/shader135-test-compile.log`.
## Archives vérifiées
Comparaison à beta.134 : **6 entrées de production** modifiées, limitées aux
trois ressources SSGI, aux valeurs initiales et aux aides FR/EN. Gameplay et
textures des gemmes inchangés. Packs normal/Test : JAR Sanctuary identique ;
archives de sources et template conformes aux sources. Reçu local ignoré :
`build/shader135-artifact.json`.
## Limites
Le SSGI utilise seulement les surfaces visibles : les sources hors champ restent
absentes et un grand changement de vue peut modifier le rebond. L'atténuation
proche est volontaire ; ce n'est pas une simulation volumétrique. Le brouillard
natif de submersion continue de suspendre le SSGI, comme en beta.134. Aucun
résultat Windows/Vulkan ni mesure de FPS revendiqués.
- `Sanctuary-beta.135.mrpack` : 10422391 octets ; SHA-256 `36ac278f9f5ecd2cefd69da16ea7d153ff44c5fc8e6bf0b3a3d6b71f5d767af6`.
- `Sanctuary-Test-beta.135.mrpack` : 10441314 octets ; SHA-256 `e096374f7ea0856b20392e5d277e94f6c3d411723d7ffc10ceccdd9f0bf5c4d4`.
JAR : `a68ccd04a92a962c46a76b26a80cb44a4faf7a13a562094dde51010ade594c10`.
+2 -2
View File
@@ -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.134
pack_version=beta.134
mod_version=beta.135
pack_version=beta.135
resource_pack_version=beta.121
maven_group=fr.koka.sanctuary
jei_version=30.32.0-sanctuary.3
+3
View File
@@ -99,6 +99,9 @@ tasks.named('processGametestResources') {
def ssgi132Client = providers.gradleProperty('sanctuarySsgi132ClientTests').map { it.toBoolean() }.getOrElse(false)
inputs.property('sanctuarySsgi132ClientTests', ssgi132Client)
if (ssgi132Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.shader.Ssgi132ClientChecks') }
def closeup135Client = providers.gradleProperty('sanctuarySsgiCloseup135ClientTests').map { it.toBoolean() }.getOrElse(false)
inputs.property('sanctuarySsgiCloseup135ClientTests', closeup135Client)
if (closeup135Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.shader.SsgiCloseup135ClientChecks') }
def bloom130Client = providers.gradleProperty('sanctuaryBloom130ClientTests').map { it.toBoolean() }.getOrElse(false)
inputs.property('sanctuaryBloom130ClientTests', bloom130Client)
@@ -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();analyseTextures();VanillaLight.setSaturation(100);VanillaLight.setPixelShadows(false);VanillaLight.setEdgeHighlights(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);});
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);});
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());
@@ -102,7 +102,7 @@ public final class Bloom130ClientChecks implements FabricClientGameTest {
private static void preferences(){try{
var path=FabricLoader.getInstance().getConfigDir().resolve("sanctuary-shaders.json");Files.deleteIfExists(path);forgetPreferences();
check(VanillaLight.pixelShadows()&&VanillaLight.shadowDistance()==128&&VanillaLight.shadowPixels()==16&&VanillaLight.edgeDistance()==32,"Fresh requested defaults: shadows ON, 128; highlights 32; shared precision 16");
check(VanillaLight.bloom()&&VanillaLight.oreEmission()&&VanillaLight.edgeHighlights()&&VanillaLight.bloomIntensity()==35&&VanillaLight.bloomRadius()==50,"Bloom, ore emission and edge highlights default to ON");
check(VanillaLight.bloom()&&VanillaLight.oreEmission()&&VanillaLight.edgeHighlights()&&VanillaLight.bloomIntensity()==20&&VanillaLight.bloomRadius()==50,"Bloom, ore emission and edge highlights default to ON");
Files.writeString(path,"{\"bloom\":false,\"oreEmission\":false,\"edgeHighlights\":false,\"pixelShadows\":false,\"shadowDistance\":64,\"shadowPixels\":8,\"edgeDistance\":16}");forgetPreferences();
check(!VanillaLight.bloom()&&!VanillaLight.oreEmission()&&!VanillaLight.edgeHighlights()&&!VanillaLight.pixelShadows()&&VanillaLight.shadowDistance()==64&&VanillaLight.shadowPixels()==8&&VanillaLight.edgeDistance()==16,"Existing user choices are retained");
for(int n:new int[]{-10,0,35,100,400}){VanillaLight.setBloomIntensity(n);VanillaLight.setBloomRadius(n);VanillaLight.setOreIntensity(n);forgetPreferences();check(VanillaLight.bloomIntensity()==Math.clamp(n,0,100)&&VanillaLight.bloomRadius()==Math.clamp(n,0,100)&&VanillaLight.oreIntensity()==Math.clamp(n,0,100),"Persisted bloom controls are bounded");}
@@ -160,6 +160,7 @@ final class EdgeHighlights129ClientChecks {
check(VanillaLight.edgeHighlights(),"Toggle persists independently");
VanillaLight.setEdgeHighlights(false);
VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);
VanillaLight.setPbr(false);VanillaLight.setSsgi(false);
} catch(java.io.IOException e) { throw new AssertionError(e); }
}
@@ -19,6 +19,8 @@ public final class Pbr134ClientChecks implements FabricClientGameTest {
for(int y=65;y<=102;y++)s.overworld().setBlockAndUpdate(new BlockPos(x,y,z),Blocks.AIR.defaultBlockState());
}});
camera(c,server,10.5,69,5.5,65);look(c,server,0,40);settle(c);
// The preceding lava/mob fixture may leave animated particles after its blocks are removed.
c.runOnClient(m->m.particleEngine.clearParticles());c.waitTicks(2);
var off=capture(c,"pbr-off");enable(c,true);var on=capture(c,"pbr-50");
var delta=difference(off,on);System.out.println("PBR134_DIFF "+delta);check(delta.changed>500&&delta.brighter>100&&delta.darker>100,"Texture relief has lit and shaded pixels: "+delta);
int analyses=c.computeOnClient(m->ProceduralPbr.analyses());c.waitTicks(20);
@@ -59,7 +61,7 @@ public final class Pbr134ClientChecks implements FabricClientGameTest {
}
private static void preferences(){try{
var p=FabricLoader.getInstance().getConfigDir().resolve("sanctuary-shaders.json");Files.writeString(p,"{\"ssgi\":true,\"ssgiIntensity\":35,\"bloom\":false}");forgetPreferences();
check(!VanillaLight.pbr()&&VanillaLight.pbrIntensity()==50&&VanillaLight.ssgi()&&!VanillaLight.bloom(),"PBR optional default preserves old choices");
check(VanillaLight.pbr()&&VanillaLight.pbrIntensity()==50&&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");}
VanillaLight.setPbrIntensity(50);
}catch(Exception e){throw new AssertionError(e);}}
@@ -36,11 +36,12 @@ import net.minecraft.world.phys.Vec3;
/** Real terrain/depth-map checks on a new flat seed 122 world; GPU readback is test-only. */
public final class PixelShadows122ClientChecks implements FabricClientGameTest {
private final boolean bloomOnly,ssgiOnly,pbrOnly;
private final boolean bloomOnly,ssgiOnly,pbrOnly,closeupOnly;
public PixelShadows122ClientChecks(){this(false);}
PixelShadows122ClientChecks(boolean bloomOnly){this(bloomOnly,false);}
PixelShadows122ClientChecks(boolean bloomOnly,boolean ssgiOnly){this(bloomOnly,ssgiOnly,false);}
PixelShadows122ClientChecks(boolean bloomOnly,boolean ssgiOnly,boolean pbrOnly){this.bloomOnly=bloomOnly;this.ssgiOnly=ssgiOnly;this.pbrOnly=pbrOnly;}
PixelShadows122ClientChecks(boolean bloomOnly,boolean ssgiOnly,boolean pbrOnly){this(bloomOnly,ssgiOnly,pbrOnly,false);}
PixelShadows122ClientChecks(boolean bloomOnly,boolean ssgiOnly,boolean pbrOnly,boolean closeupOnly){this.bloomOnly=bloomOnly;this.ssgiOnly=ssgiOnly;this.pbrOnly=pbrOnly;this.closeupOnly=closeupOnly;}
private static final int FLOOR = 64;
private static final AABB PILLAR = new AABB(0, FLOOR + 1, 0, 2, FLOOR + 9, 2);
private static final BlockPos SLAB = new BlockPos(5, FLOOR + 3, -5);
@@ -135,6 +136,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
c.runOnClient(m -> check(VanillaLight.active(), "Native Sanctuary shader is active without an external engine"));
float morning = c.computeOnClient(m -> m.gameRenderer.gameRenderState().levelRenderState.skyRenderState.sunAngle);
if(closeupOnly){SsgiCloseup135ClientChecks.run(c,server);Pbr134ClientChecks.run(c,server);return;}
if(pbrOnly){Pbr134ClientChecks.run(c,server);return;}
if(ssgiOnly){Ssgi132ClientChecks.run(c,server);return;}
if(bloomOnly){Bloom130ClientChecks.run(c,server);return;}
@@ -264,7 +266,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
});
});
c.waitTicks(3);
c.takeScreenshot("sanctuary-beta134-shader-options-" + locale);
c.takeScreenshot("sanctuary-beta135-shader-options-" + locale);
}
c.runOnClient(m -> m.gui.setScreen(null));
}
@@ -428,7 +430,23 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
? java.util.stream.Stream.concat(java.util.stream.Stream.of(widget), widgets(child)) : java.util.stream.Stream.of(widget));
}
private static void checkDefaults135() {
try {
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()==50&&VanillaLight.ssgiIntensity()==35,"Existing effect strengths 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();
check(!VanillaLight.bloom()&&VanillaLight.bloomIntensity()==20&&VanillaLight.pbr()&&VanillaLight.ssgi(),"Missing fields get new defaults without replacing explicit values");
System.out.println("SHADER135_DEFAULTS_PASS fresh, partial and personal preferences");
} catch(IOException e){throw new AssertionError(e);}
}
private static void checkPreferences() {
checkDefaults135();
VanillaLight.setPbr(false);
VanillaLight.setSsgi(false);
VanillaLight.setBloom(false);
VanillaLight.setOreEmission(false);
@@ -531,7 +549,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
}
static Frame capture(ClientGameTestContext c, String name) {
if (name != null) c.takeScreenshot("sanctuary-beta134-shader-" + name);
if (name != null) c.takeScreenshot("sanctuary-beta135-shader-" + name);
var result = new CompletableFuture<Frame>();
c.runOnClient(m -> Screenshot.takeScreenshot(m.gameRenderer.mainRenderTarget(), image -> {
try (image) {
@@ -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.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.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());
@@ -63,6 +63,7 @@ public final class Ssgi132ClientChecks implements FabricClientGameTest {
c.runOnClient(m->check(!ScreenSpaceGI.allocated(),"Main shader OFF releases SSGI too"));
c.runOnClient(m->VanillaLight.setEnabled(true));frames(c);enable(c,false);
motion(c,server);
SsgiCloseup135ClientChecks.run(c,server);
System.out.println("SSGI133_PASS red/blue diffuse bounce; intensity; flat rejection; foreground occlusion; static stability; tiny camera motion; resize; reload; combined shader; OFF/zero; preferences; red="+red+" blue="+blue);
}
private static void motion(ClientGameTestContext c,TestServerContext server){
@@ -109,7 +110,7 @@ public final class Ssgi132ClientChecks implements FabricClientGameTest {
private static void frames(ClientGameTestContext c){int start=c.computeOnClient(m->ScreenSpaceGI.renderedFrames());c.waitFor(m->ScreenSpaceGI.applied()&&ScreenSpaceGI.renderedFrames()>start+10,1200);c.waitTicks(6);}
private static void preferences(){try{
var p=FabricLoader.getInstance().getConfigDir().resolve("sanctuary-shaders.json");Files.writeString(p,"{\"bloom\":false,\"shadowPixels\":128}");forgetPreferences();
check(!VanillaLight.ssgi()&&VanillaLight.ssgiIntensity()==35&&!VanillaLight.bloom()&&VanillaLight.shadowPixels()==128,"Legacy choices preserved; experimental SSGI defaults OFF at 35");
check(VanillaLight.ssgi()&&VanillaLight.ssgiIntensity()==35&&!VanillaLight.bloom()&&VanillaLight.shadowPixels()==128,"Legacy choices preserved; missing SSGI setting defaults ON at 35");
for(int i:new int[]{-10,0,35,90,140}){VanillaLight.setSsgiIntensity(i);VanillaLight.setSsgi(true);forgetPreferences();check(VanillaLight.ssgi()&&VanillaLight.ssgiIntensity()==Math.clamp(i,0,100),"SSGI persists and clamps");}
VanillaLight.setSsgiIntensity(90);
}catch(Exception e){throw new AssertionError(e);}}
@@ -0,0 +1,78 @@
package fr.koka.sanctuary.client.shader;
import static fr.koka.sanctuary.client.shader.PixelShadows122ClientChecks.*;
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
import net.fabricmc.fabric.api.client.gametest.v1.context.TestServerContext;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Blocks;
/** Native low-angle and near-plane regression fixture; never opens a personal world. */
public final class SsgiCloseup135ClientChecks implements net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest {
public void runTest(ClientGameTestContext c){new PixelShadows122ClientChecks(false,false,false,true).runTest(c);}
static void run(ClientGameTestContext c,TestServerContext server){
c.runOnClient(m->{VanillaLight.setEnabled(true);VanillaLight.setSaturation(100);VanillaLight.setSsgi(false);VanillaLight.setPbr(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);VanillaLight.setPixelShadows(false);VanillaLight.setEdgeHighlights(false);});
server.runOnServer(s->{
// Minecraft 26.3 rejects setting a clock to its existing value.
command(s,"time add 1");command(s,"time set 6000");
for(int x=-5;x<=24;x++)for(int z=-5;z<=24;z++){
// Contain lava: no flow below the platform into the following PBR fixture.
s.overworld().setBlockAndUpdate(new BlockPos(x,63,z),Blocks.STONE.defaultBlockState());
for(int y=64;y<=102;y++)s.overworld().setBlockAndUpdate(new BlockPos(x,y,z),y==64?Blocks.STONE.defaultBlockState():Blocks.AIR.defaultBlockState());
if(x>=5&&x<=16&&z>=4&&z<=14)s.overworld().setBlockAndUpdate(new BlockPos(x,64,z),Blocks.LAVA.defaultBlockState());
if(x>=5&&x<=16&&z>=8&&z<=14)s.overworld().setBlockAndUpdate(new BlockPos(x,68,z),Blocks.STONE.defaultBlockState());
if(x>=5&&x<=16&&z==12)for(int y=65;y<=67;y++)s.overworld().setBlockAndUpdate(new BlockPos(x,y,z),Blocks.CONCRETE.red().defaultBlockState());
}
});c.waitTicks(40);c.waitFor(m->m.levelRenderer.hasRenderedAllSections(),1200);
for(int scene=0;scene<2;scene++)for(int intensity:new int[]{35,100}){
c.runOnClient(m->VanillaLight.setSsgiIntensity(intensity));
for(int step=0;step<7;step++){
// Eye approaches the underside, crossing the old fixed normal-area threshold.
double gap=new double[]{2.0,1.0,.65,.4,.2,.1,.04}[step];
camera(c,server,10.5,68-gap-1.62,scene==0?9.5:11.5,65);look(c,server,0,-25);
c.runOnClient(m->VanillaLight.setSsgi(false));c.waitTicks(8);
var off=capture(c,"closeup-"+scene+"-"+intensity+"-"+step+"-off");
c.runOnClient(m->VanillaLight.setSsgi(true));c.waitFor(m->ScreenSpaceGI.applied(),1200);c.waitTicks(12);
var on=capture(c,"closeup-"+scene+"-"+intensity+"-"+step+"-on");
var stats=rowJump(off,on);
System.out.println("SSGI135_CLOSEUP scene="+scene+" intensity="+intensity+" gap="+gap+" "+stats);
if(scene==0&&gap<=.1)check(stats.jump<2&&stats.gain<.1,"Near-plane bounce fades without a horizontal bright strip: "+stats);
if(scene==1&&gap==.4)check(stats.rows>30&&stats.gain>1,"Close corner retains actual bounce instead of rejecting small world pixels: "+stats);
if(gap==.04)check(stats.gain<.05,"At contact, indirect light fades to the source image: "+stats);
}
}
int width=c.computeOnClient(m->m.getWindow().getScreenWidth()),height=c.computeOnClient(m->m.getWindow().getScreenHeight());
camera(c,server,10.5,65.98,11.5,65);look(c,server,0,-25);
c.runOnClient(m->{VanillaLight.setSsgiIntensity(100);m.getWindow().setWindowed(1280,720);});
c.waitFor(m->m.gameRenderer.mainRenderTarget().width!=width,200);c.waitTicks(15);
c.runOnClient(m->VanillaLight.setSsgi(false));c.waitTicks(8);var largeOff=capture(c,"closeup-large-off");
c.runOnClient(m->VanillaLight.setSsgi(true));c.waitTicks(15);var large=capture(c,"closeup-large-on");
var largeStats=rowJump(largeOff,large);check(largeStats.rows>30&&largeStats.gain>1,"High resolution with a visible source retains bounce: "+largeStats);
look(c,server,.00002f,-25.00002f);c.waitTicks(10);unchanged(large,capture(c,null),"Closeup has no subpixel normal threshold flicker");
look(c,server,0,-65);c.runOnClient(m->VanillaLight.setSsgi(false));c.waitTicks(8);var steepOff=capture(c,"closeup-offscreen-source-off");
c.runOnClient(m->VanillaLight.setSsgi(true));c.waitTicks(15);unchanged(steepOff,capture(c,"closeup-offscreen-source-on"),"Looking away from the source does not invent hidden light");
c.runOnClient(m->m.getWindow().setWindowed(width,height));c.waitFor(m->m.gameRenderer.mainRenderTarget().width==width,200);c.waitTicks(15);
c.runOnClient(m->{VanillaLight.setPbr(true);VanillaLight.setBloom(true);VanillaLight.setOreEmission(true);VanillaLight.setPixelShadows(true);VanillaLight.setEdgeHighlights(true);});
c.waitFor(m->ProceduralPbr.applied()&&ProceduralPbr.ready()&&Bloom.applied()&&PixelShadows.applied()&&PixelShadows.ready(),1200);
c.waitTicks(15);capture(c,"closeup-combined");
c.runOnClient(m->{VanillaLight.setSsgi(false);VanillaLight.setSsgiIntensity(35);VanillaLight.setPbr(false);});
System.out.println("SSGI135_CLOSEUP_PASS low-angle lava/stone and seven camera distances at 35/100");
}
private record Rows(double jump,double gain,int rows){}
private static Rows rowJump(Frame a,Frame b){
double previous=0,max=0,gain=0;int rows=0;
for(int y=a.height()/8;y<a.height()*7/8;y++){
double sum=0;int count=0;
for(int x=a.width()/4;x<a.width()*3/4;x++){
int i=y*a.width()+x,pa=a.pixels()[i],pb=b.pixels()[i];
int r=pa>>16&255,g=pa>>8&255,blue=pa&255;
if(Math.max(r,Math.max(g,blue))-Math.min(r,Math.min(g,blue))<12&&g>15){
sum+=Math.max(0,(pb>>16&255)-r);count++;
}
}
if(count<a.width()/8)continue;
double mean=sum/count;if(rows>0)max=Math.max(max,Math.abs(mean-previous));
previous=mean;gain+=mean;rows++;
}
return new Rows(max,gain/Math.max(rows,1),rows);
}
}
@@ -19,11 +19,11 @@ public final class VanillaLight {
int saturation=142;
boolean pixelShadows=true;
boolean bloom=true;
boolean pbr=false;
boolean pbr=true;
int pbrIntensity=50;
boolean ssgi=false;
boolean ssgi=true;
int ssgiIntensity=35;
int bloomIntensity=35;
int bloomIntensity=20;
int bloomRadius=50;
boolean oreEmission=true;
int oreIntensity=50;
@@ -2279,7 +2279,7 @@
"sanctuary.shaders.bloom": "Bloom",
"sanctuary.shaders.bloom_help": "Glow around the sun and luminous surfaces. ON by default. Turning it off removes halos while keeping emissive ores. Requires Vanilla Light.",
"sanctuary.shaders.bloom_intensity": "Bloom strength",
"sanctuary.shaders.bloom_intensity_help": "Halo strength. Zero removes halos and their blur passes while keeping emissive ores.",
"sanctuary.shaders.bloom_intensity_help": "Halo strength, 20% by default. Zero removes halos and their blur passes while keeping emissive ores.",
"sanctuary.shaders.bloom_intensity_value": "Bloom: %s%%",
"sanctuary.shaders.bloom_radius": "Bloom spread",
"sanctuary.shaders.bloom_radius_help": "Halo size around luminous pixels. Texture details stay sharp.",
@@ -2290,12 +2290,12 @@
"sanctuary.shaders.ore_intensity_help": "Inclusion brightness. Does not change world light levels or mob spawning.",
"sanctuary.shaders.ore_intensity_value": "Ores: %s%%",
"sanctuary.shaders.ssgi": "SSGI · color bounce",
"sanctuary.shaders.ssgi_help": "Experimental: enhanced color bounce between nearby visible surfaces. Radius 2.5 blocks. Off-screen surfaces do not contribute. OFF by default.",
"sanctuary.shaders.ssgi_help": "Experimental: enhanced color bounce between nearby visible surfaces. Radius 2.5 blocks. Off-screen surfaces do not contribute. ON by default.",
"sanctuary.shaders.ssgi_intensity": "SSGI strength",
"sanctuary.shaders.ssgi_intensity_help": "Strength of diffuse color bounce. Zero stops rendering and frees its resources. Does not change world light.",
"sanctuary.shaders.ssgi_intensity_value": "SSGI: %s%%",
"sanctuary.shaders.pbr": "PBR · material relief",
"sanctuary.shaders.pbr_help": "Experimental: procedural block normals and roughness, sun and moon highlights. Range 32 blocks. OFF by default.",
"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%%"
@@ -2279,7 +2279,7 @@
"sanctuary.shaders.bloom": "Bloom",
"sanctuary.shaders.bloom_help": "Halo du soleil et des surfaces lumineuses. Activé par défaut. Larrêt coupe le halo et conserve les minerais émissifs. Nécessite Vanilla Light.",
"sanctuary.shaders.bloom_intensity": "Intensité du bloom",
"sanctuary.shaders.bloom_intensity_help": "Force du halo. Zéro supprime le halo et ses passes de diffusion, tout en conservant les minerais émissifs.",
"sanctuary.shaders.bloom_intensity_help": "Force du halo, 20 % par défaut. Zéro supprime le halo et ses passes de diffusion, tout en conservant les minerais émissifs.",
"sanctuary.shaders.bloom_intensity_value": "Bloom : %s %%",
"sanctuary.shaders.bloom_radius": "Diffusion du bloom",
"sanctuary.shaders.bloom_radius_help": "Taille du halo autour des pixels lumineux. Les détails des textures restent nets.",
@@ -2290,12 +2290,12 @@
"sanctuary.shaders.ore_intensity_help": "Luminosité des inclusions. Aucun changement de la lumière du monde ou des apparitions de mobs.",
"sanctuary.shaders.ore_intensity_value": "Minerais : %s %%",
"sanctuary.shaders.ssgi": "SSGI · couleurs diffusées",
"sanctuary.shaders.ssgi_help": "Expérimental : diffusion renforcée des couleurs entre surfaces visibles proches. Rayon 2,5 blocs. Les surfaces hors écran ne contribuent pas. Désactivé par défaut.",
"sanctuary.shaders.ssgi_help": "Expérimental : diffusion renforcée des couleurs entre surfaces visibles proches. Rayon 2,5 blocs. Les surfaces hors écran ne contribuent pas. Activé par défaut.",
"sanctuary.shaders.ssgi_intensity": "Intensité du SSGI",
"sanctuary.shaders.ssgi_intensity_help": "Force de la teinte diffusée. Zéro coupe le calcul et libère ses ressources. Aucun changement de lumière du monde.",
"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 et de la lune. Portée 32 blocs. Désactivé par défaut.",
"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 %%"
@@ -4,9 +4,10 @@
layout(location=0) in vec2 texCoord;
layout(location=0) out vec4 fragColor;
void main(){
float depth=texture(WorldDepth,texCoord).r;
vec2 surface=giTexel(texCoord);float depth=texture(WorldDepth,surface).r;
if(depth<=0.0)discard;
vec3 p=giPosition(texCoord,depth),normal=giNormal(texCoord,p);
vec3 p=giPosition(surface,depth),normal=giNormal(surface,p);
float confidence=giConfidence(p,normal);if(confidence<=0.0)discard;
vec2 size=vec2(textureSize(IndirectLight,0)),coord=texCoord*size-0.5,base=floor(coord),f=fract(coord);
vec3 bounce=vec3(0.0);float total=0.0;
for(int y=0;y<2;y++)for(int x=0;x<2;x++){
@@ -27,7 +28,7 @@ void main(){
// Artistic single-bounce gain: the old gamma-space screen blend hid almost all
// of the transport on bright blocks. Compose in approximate linear light and
// encode once; a soft shoulder keeps strong bounce below channel clipping.
vec3 irradiance=bounce/total*(GISettings.x*96.0);
vec3 irradiance=bounce/total*(GISettings.x*96.0*confidence);
vec3 reflected=linearScene*irradiance;
vec3 lit=linearScene+(1.0-linearScene)*(1.0-exp(-reflected));
fragColor=vec4(sqrt(clamp(lit,0.0,1.0)),0.0);
@@ -23,7 +23,10 @@ void main(){
float travel=GISettings.z*float(step)/10.0;
vec3 probe=start+direction*travel;
vec4 clip=ViewProjection*vec4(probe,1.0);
if(clip.w<=0.0)break;
if(clip.w<=0.05)break;
float probeDepth=clip.z/clip.w;
if(GISettings.y<0.5)probeDepth=probeDepth*.5+.5;
if(probeDepth<=0.0||probeDepth>=1.0)break;
vec2 hitUV=clip.xy/clip.w*0.5+0.5;
if(any(lessThan(hitUV,vec2(0.002)))||any(greaterThan(hitUV,vec2(0.998))))break;
hitUV=giTexel(hitUV);float hitDepth=texture(WorldDepth,hitUV).r;
@@ -42,7 +45,8 @@ void main(){
// remainder instead of re-injecting bright grey ground into every nearby face.
vec3 linear=color*color;
vec3 chroma=linear-vec3(min(linear.r,min(linear.g,linear.b)));
sum+=chroma*facing*falloff*edge*giFog(hit);
float confidence=smoothstep(.05,.3,clip.w)*(1.0-smoothstep(.12,.35,behind));
sum+=chroma*facing*falloff*edge*giFog(hit)*confidence;
}
break;
}
@@ -12,23 +12,38 @@ vec3 giPosition(vec2 uv,float depth){
vec4 p=InverseViewProjection*vec4(uv*2.0-1.0,GISettings.y>0.5?depth:depth*2.0-1.0,1.0);
return p.xyz/p.w;
}
vec2 giTexel(vec2 uv){vec2 size=vec2(textureSize(WorldDepth,0));return (floor(uv*size)+0.5)/size;}
vec2 giTexel(vec2 uv){
vec2 size=vec2(textureSize(WorldDepth,0));
return (clamp(floor(uv*size),vec2(0),size-1.0)+0.5)/size;
}
vec3 giNeighbor(vec2 uv,vec3 center){
uv=giTexel(uv);float depth=texture(WorldDepth,uv).r;
return depth>0.0?giPosition(uv,depth):center;
}
vec3 giNormal(vec2 uv,vec3 p){
vec2 pixel=1.0/vec2(textureSize(WorldDepth,0));
vec3 l=giPosition(uv-vec2(pixel.x,0),texture(WorldDepth,uv-vec2(pixel.x,0)).r);
vec3 r=giPosition(uv+vec2(pixel.x,0),texture(WorldDepth,uv+vec2(pixel.x,0)).r);
vec3 d=giPosition(uv-vec2(0,pixel.y),texture(WorldDepth,uv-vec2(0,pixel.y)).r);
vec3 u=giPosition(uv+vec2(0,pixel.y),texture(WorldDepth,uv+vec2(0,pixel.y)).r);
vec3 dx=length(r-p)<length(p-l)?r-p:p-l;
vec3 dy=length(u-p)<length(p-d)?u-p:p-d;
vec3 n=cross(dx,dy);n/=max(length(n),0.00001);
// Keep voxel planes exact: tiny depth-reconstruction errors must not rotate an entire ray fan.
vec3 l=p-giNeighbor(uv-vec2(pixel.x,0),p),r=giNeighbor(uv+vec2(pixel.x,0),p)-p;
vec3 d=p-giNeighbor(uv-vec2(0,pixel.y),p),u=giNeighbor(uv+vec2(0,pixel.y),p)-p;
vec3 dx=dot(l,l)>0.0&&(dot(l,l)<dot(r,r)||dot(r,r)==0.0)?l:r;
vec3 dy=dot(d,d)>0.0&&(dot(d,d)<dot(u,u)||dot(u,u)==0.0)?d:u;
// Normalize directions BEFORE the cross product. Pixel area approaches zero
// near a surface (or at high resolution); it is not a validity criterion.
float xx=dot(dx,dx),yy=dot(dy,dy);
if(xx<1e-20||yy<1e-20)return vec3(0);
vec3 n=cross(dx*inversesqrt(xx),dy*inversesqrt(yy));
float nn=dot(n,n);if(nn<1e-12)return vec3(0);n*=inversesqrt(nn);
vec3 a=abs(n);
if(max(a.x,max(a.y,a.z))>.995){
n=a.x>a.y&&a.x>a.z?vec3(sign(n.x),0,0):a.y>a.z?vec3(0,sign(n.y),0):vec3(0,0,sign(n.z));
}
return dot(n,-p)<0.0?-n:n;
}
float giConfidence(vec3 p,vec3 n){
// Distance to the receiving plane, not radial distance: no circular cut in
// a flat wall. Fade before entering it; also soften nearly tangent views.
float planeDistance=abs(dot(p,n));
return smoothstep(.06,.4,planeDistance)*smoothstep(.025,.12,planeDistance/max(length(p),.0001));
}
float giFog(vec3 p){
float a=clamp((max(length(p.xz),abs(p.y))-GIFog.x)/max(GIFog.y-GIFog.x,0.001),0.0,1.0);
float b=clamp((length(p)-GIFog.z)/max(GIFog.w-GIFog.z,0.001),0.0,1.0);
+1 -1
View File
@@ -1,6 +1,6 @@
name = "Sanctuary"
author = "KOKA99CAB"
version = "beta.134"
version = "beta.135"
pack-format = "packwiz:1.1.0"
[index]