feat: make SSGI color bounce visible in beta.133
Build Sanctuary / build (push) Canceled after 0s

This commit is contained in:
koka
2026-09-17 22:18:12 +02:00
parent e48fc656c7
commit 216d3f75b5
11 changed files with 137 additions and 24 deletions
+11 -7
View File
@@ -20,13 +20,10 @@ 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.132** ajoute le **SSGI · couleurs diffusées** expérimental :
les surfaces visibles proches se transmettent une légère teinte. Effet
optionnel, intensité réglable, rayon local de 2,5 blocs.
**Options → Shaders** ; les réglages existants restent conservés.
Le [ticket de livraison](docs/shader-beta132.md) décrit le rendu, les limites
et la validation. Release publiée et même instance **Sanctuary Beta**
synchronisée, sauvegardes et réglages conservés.
La version **beta.133** renforce le **SSGI · couleurs diffusées** : atténuation
spatiale revue et composition lumineuse plus visible dès 35 %.
**Options → Shaders** ; intensité réglable, préférences existantes conservées.
Le [ticket de livraison](docs/shader-beta133.md) décrit le rendu et ses limites.
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 +34,13 @@ La [carte native beta.005](docs/atlas-beta005.md) prend le relais après
l’étude de Xaero et des alternatives libres.
## beta.133 — SSGI renforcé
Couleurs voisines plus visibles, même budget de rayons et de cibles GPU.
[Contrat et vérifications](docs/shader-beta133.md) ·
[Pack normal](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.133/Sanctuary-beta.133.mrpack) ·
[Pack de test](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.133/Sanctuary-Test-beta.133.mrpack).
## beta.132 — SSGI : diffusion locale des couleurs
Rebond diffus des couleurs visibles, intensité réglable et reconstruction
+89
View File
@@ -0,0 +1,89 @@
# SHADER-133 — SSGI visible au réglage courant
Demande : le rebond diffus de beta.132 est trop discret pour être distingué.
Branche `codex/ssgi-strength-beta133`, socle beta.132, Minecraft 26.3 / Java 25.
## Changement
La collecte conserve douze directions, dix étapes, une demi-résolution et
un rayon de 2,5 blocs. L'atténuation spatiale devient linéaire plutôt que
quadratique. Le mélange est reconstruit en lumière approximativement linéaire,
puis réencodé une seule fois. Un gain artistique plus fort rend la couleur
lisible et une courbe progressive évite de couper brutalement les canaux
à blanc. Les surfaces sans rebond restent intactes. La force demeure réglable
de 0 à 100, avec le même réglage initial de 35 ; aucune préférence enregistrée
n'est écrasée. SSGI reste optionnel, initialement désactivé.
Aucun rayon, cible GPU ou texture de matériau supplémentaire. La composition
ajoute une exponentielle et une racine carrée par canal ; le coût exact en FPS
reste à mesurer sur la machine du joueur. Les normales de géométrie orientent
déjà le rebond. Pas de PBR ni de normales procédurales de textures ajoutés.
## Piste matériaux
Pour une étape distincte, des normales de faible amplitude calculées au
chargement des textures, mises en cache, et une rugosité par famille de matériaux
permettraient d'étudier du relief et des reflets orientés. Une simple conversion
luminosité → hauteur ne connaît pas le relief réel : un motif peint peut devenir
une bosse artificielle. Commencer sur pierre, briques et bois avec une amplitude
réglable serait préférable à une génération aveugle pour tous les blocs.
La passe actuelle ne possède ni atlas de normales, ni identifiants de matériaux,
ni albédo non éclairé ; des images seules ne suffiraient pas à les exploiter.
## Limites conservées
Approximation diffuse à partir de la couleur déjà éclairée, pas un transport
physique complet. Seules les surfaces visibles contribuent, avec variations
possibles aux entrées/sorties d'écran. Premier obstacle bloquant, pas d'historique
ni de bruit temporel ; aucune modification d'eau, monde ou lumière serveur.
## Vérifications
Suite client native complète réussie en **4 min 29 s**, sur macOS / Apple M1 /
OpenGL, dans un monde de test plat (graine 122). Les comparaisons ON/OFF à 35 %
mesurent, sur les pixels neutres du sol :
| Mur | Pixels avec gain ≥ 8/255 | Pixels avec gain ≥ 16/255 | Gain cumulé du canal dominant |
| --- | ---: | ---: | ---: |
| Rouge | 16 487 | 7 311 | 305 221 |
| Bleu | 14 954 | 7 328 | 309 991 |
À 90 %, les gains dominants atteignent 654 683 et 655 867, contre 8 968 et
9 168 au même réglage dans la fixture beta.132. Ce rapport concerne uniquement
cette scène et ces pixels, pas une promesse de gain uniforme dans tous les mondes.
Les captures confirment une bande colorée au pied du mur. Un premier réglage
intermédiaire a été rejeté car aucun pixel du sol ne dépassait 8/255 à 35 % ;
un second a passé les contrôles ciblés, puis la force a encore été augmentée
après examen visuel. Les valeurs finales sont celles de la suite complète.
Le test impose maintenant plus de 5 000 pixels à 8/255 et plus de 500 à 16/255
au réglage 35, pour les deux couleurs. Intensité, OFF/zéro, absence de rebond
coplanaire, occultation d'un mur changeant de couleur, caméra fixe, déplacement
infinitésimal, retour de caméra, redimensionnement et rechargement passent.
Ombres, lune, feuillage, CTM, cinq précisions, bloom, émissifs indépendants et
interfaces FR/EN restent validés, y compris avec le SSGI actif.
Commande : `./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true
-PsanctuaryPixelShadows122ClientTests=true -PsanctuaryClientNoVsync=true
-PsanctuaryQuickTests=true`. Log : `build/shader133-client-full.log`.
Le nom de la propriété ciblée `sanctuarySsgi132ClientTests` est conservé.
Comparateur local autonome : `build/SSGI-beta.133-comparaison.html`, captures
natives OFF/35/90 des murs rouge/bleu, sans retouche. Pas de validation
Windows/Vulkan ni de mesure de coût FPS revendiquée.
`./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest`
réussit en **2 min 21 s**, 126 tâches. Le serveur GameTest dédié reste exclu
conformément au refus EULA antérieur. Log : `build/shader133-build.log`.
Les archives normal/Test contiennent le même JAR ; sources archivées et template
de ressources correspondent aux sources. Comparaison avec beta.132 : **5 entrées
de production** changées (classe du compositeur, deux shaders, libellés FR/EN).
Gameplay, textures, sauvegardes et dépendances inchangés.
## Artefacts
- `Sanctuary-beta.133.mrpack` : 10394282 octets, SHA-256 `0c90a070da7f969706abeeec1bdfb0830bb52244c20fccbca4fd876f3fbe0564`.
- `Sanctuary-Test-beta.133.mrpack` : 10413204 octets, SHA-256 `e01f0355bf2cce18156fe94a069e142bedc498624ba0b6e2729ae0f23ea73e80`.
JAR : `a01cae4a98606007b533ca09ec9f072f19a1db2e9b8de466f4ad075d5c1fc185`.
+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.132
pack_version=beta.132
mod_version=beta.133
pack_version=beta.133
resource_pack_version=beta.121
maven_group=fr.koka.sanctuary
jei_version=30.32.0-sanctuary.3
@@ -261,7 +261,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
});
});
c.waitTicks(3);
c.takeScreenshot("sanctuary-beta132-shader-options-" + locale);
c.takeScreenshot("sanctuary-beta133-shader-options-" + locale);
}
c.runOnClient(m -> m.gui.setScreen(null));
}
@@ -528,7 +528,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
}
static Frame capture(ClientGameTestContext c, String name) {
if (name != null) c.takeScreenshot("sanctuary-beta132-shader-" + name);
if (name != null) c.takeScreenshot("sanctuary-beta133-shader-" + name);
var result = new CompletableFuture<Frame>();
c.runOnClient(m -> Screenshot.takeScreenshot(m.gameRenderer.mainRenderTarget(), image -> {
try (image) {
@@ -21,7 +21,12 @@ public final class Ssgi132ClientChecks implements FabricClientGameTest {
camera(c,server,10.5,69,5.5,65);look(c,server,0,40);settle(c);
var flatOff=capture(c,"ssgi-flat-off");enable(c,true);unchanged(flatOff,capture(c,"ssgi-flat-on"),"Coplanar ground does not bounce light onto itself");
wall(server,12,Blocks.CONCRETE.red().defaultBlockState());settle(c);enable(c,false);var redOff=capture(c,"ssgi-red-off");
enable(c,true);var redOn=capture(c,"ssgi-red-on");var red=measure(redOff,redOn);System.out.println("SSGI132_RED "+red);
c.runOnClient(m->VanillaLight.setSsgiIntensity(35));
enable(c,true);var defaultRed=measure(redOff,capture(c,"ssgi-red-default-35"));
System.out.println("SSGI133_DEFAULT "+defaultRed);
check(defaultRed.visible>5000&&defaultRed.strong>500,"Default intensity produces 8/255 gain over 5000 pixels and 16/255 over 500 pixels: "+defaultRed);
c.runOnClient(m->VanillaLight.setSsgiIntensity(90));frames(c);
var redOn=capture(c,"ssgi-red-on");var red=measure(redOff,redOn);System.out.println("SSGI133_RED "+red);
check(red.changed>30&&red.red>red.blue*1.5+20,"Visible red wall tints neutral ground: "+red);
unchanged(redOn,capture(c,null),"Stationary SSGI has no animated noise or accumulating feedback");
look(c,server,0.00002f,40.00002f);frames(c);unchanged(redOn,capture(c,null),"Subpixel motion keeps actual red bounce stable");
@@ -40,7 +45,11 @@ public final class Ssgi132ClientChecks implements FabricClientGameTest {
c.runOnClient(m->m.getWindow().setWindowed(oldWidth,oldHeight));
c.waitFor(m->m.gameRenderer.mainRenderTarget().width==redOn.width(),200);frames(c);
wall(server,12,Blocks.CONCRETE.blue().defaultBlockState());settle(c);enable(c,false);var blueOff=capture(c,"ssgi-blue-off");enable(c,true);var blue=measure(blueOff,capture(c,"ssgi-blue-on"));
System.out.println("SSGI132_BLUE "+blue);check(blue.changed>30&&blue.blue>blue.red*1.5+20,"Live blue wall changes bounced hue: "+blue);
c.runOnClient(m->VanillaLight.setSsgiIntensity(35));frames(c);
var defaultBlue=measure(blueOff,capture(c,"ssgi-blue-default-35"));
check(defaultBlue.visible>5000&&defaultBlue.strong>500&&defaultBlue.blue>defaultBlue.red*1.5+20,"Default blue bounce is visible too: "+defaultBlue);
c.runOnClient(m->VanillaLight.setSsgiIntensity(90));frames(c);
System.out.println("SSGI133_BLUE "+blue+" default="+defaultBlue);check(blue.changed>30&&blue.blue>blue.red*1.5+20,"Live blue wall changes bounced hue: "+blue);
wall(server,11,Blocks.CONCRETE.white().defaultBlockState());settle(c);enable(c,false);var coveredOff=capture(c,null);enable(c,true);var covered=capture(c,"ssgi-covered-blue");
wall(server,12,Blocks.CONCRETE.red().defaultBlockState());settle(c);unchanged(covered,capture(c,"ssgi-covered-red"),"Hidden wall color cannot pass through foreground wall");
var reload=c.computeOnClient(net.minecraft.client.Minecraft::reloadResourcePacks);c.waitFor(m->reload.isDone()&&m.gui.overlay()==null,1200);reload.join();frames(c);unchanged(covered,capture(c,"ssgi-reloaded"),"Resource reload restores SSGI");
@@ -53,7 +62,7 @@ public final class Ssgi132ClientChecks implements FabricClientGameTest {
c.runOnClient(m->VanillaLight.setEnabled(false));c.waitTicks(8);
c.runOnClient(m->check(!ScreenSpaceGI.allocated(),"Main shader OFF releases SSGI too"));
c.runOnClient(m->VanillaLight.setEnabled(true));frames(c);enable(c,false);
System.out.println("SSGI132_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);
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 wall(TestServerContext server,int z,BlockState state){server.runOnServer(s->{for(int x=6;x<=15;x++)for(int y=65;y<=(z==11?72:69);y++)s.overworld().setBlockAndUpdate(new BlockPos(x,y,z),state);});}
private static void settle(ClientGameTestContext c){c.waitTicks(30);c.waitFor(m->m.levelRenderer.hasRenderedAllSections(),1200);c.waitTicks(8);}
@@ -65,11 +74,13 @@ public final class Ssgi132ClientChecks implements FabricClientGameTest {
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);}}
private record Gain(int changed,double red,double green,double blue){}
private static Gain measure(Frame off,Frame on){int count=0;double r=0,g=0,b=0;for(int i=0;i<off.pixels().length;i++){
private record Gain(int changed,int visible,int strong,double red,double green,double blue){}
private static Gain measure(Frame off,Frame on){int count=0,visible=0,strong=0;double r=0,g=0,b=0;for(int i=0;i<off.pixels().length;i++){
int a=off.pixels()[i],ar=a>>16&255,ag=a>>8&255,ab=a&255;
if(Math.max(ar,Math.max(ag,ab))-Math.min(ar,Math.min(ag,ab))>25||ar<60||ar>245)continue;
int z=on.pixels()[i];int dr=(z>>16&255)-ar,dg=(z>>8&255)-ag,db=(z&255)-ab;
if(Math.max(dr,Math.max(dg,db))>=16)strong++;
if(Math.max(dr,Math.max(dg,db))>=8)visible++;
if(Math.max(dr,Math.max(dg,db))>1)count++;r+=Math.max(0,dr);g+=Math.max(0,dg);b+=Math.max(0,db);
}return new Gain(count,r,g,b);}
}return new Gain(count,visible,strong,r,g,b);}
}
@@ -27,7 +27,7 @@ public final class ScreenSpaceGI {
return RenderPipelines.register(RenderPipeline.builder().withLocation(Identifier.parse("sanctuary:pipeline/"+name))
.withVertexShader("core/screenquad").withFragmentShader(Identifier.parse("sanctuary:core/"+name))
.withBindGroupLayout(LAYOUT).withPrimitiveTopology(PrimitiveTopology.TRIANGLES).withCull(false)
.withColorTargetState(new ColorTargetState(composite?Optional.of(new BlendFunction(BlendFactor.ONE,BlendFactor.ONE_MINUS_SRC_COLOR,BlendFactor.ZERO,BlendFactor.ONE)):Optional.empty(),
.withColorTargetState(new ColorTargetState(composite?Optional.of(new BlendFunction(BlendFactor.ONE,BlendFactor.ZERO,BlendFactor.ZERO,BlendFactor.ONE)):Optional.empty(),
composite?GpuFormat.RGBA8_UNORM:GpuFormat.RGBA16_FLOAT,composite?ColorTargetState.WRITE_COLOR:ColorTargetState.WRITE_ALL))
.withDepthStencilState(Optional.empty()).build());
}
@@ -2290,7 +2290,7 @@
"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: subtle 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. OFF 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%%"
@@ -2290,7 +2290,7 @@
"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 : légère diffusion 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. Désactivé 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 %%"
@@ -21,6 +21,14 @@ void main(){
bounce+=sampleLight.rgb*weight;total+=weight;
}
if(total<=0.0001)discard;
vec3 albedo=texture(SceneColor,texCoord).rgb;
fragColor=vec4(clamp(albedo*bounce/total*GISettings.x*2.0,0.0,0.25),0.0);
if(max(bounce.r,max(bounce.g,bounce.b))<=0.0)discard;
vec3 scene=texture(SceneColor,texCoord).rgb;
vec3 linearScene=scene*scene;
// 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 reflected=linearScene*irradiance;
vec3 lit=linearScene+(1.0-linearScene)*(1.0-exp(-reflected));
fragColor=vec4(sqrt(clamp(lit,0.0,1.0)),0.0);
}
@@ -38,7 +38,8 @@ void main(){
float edge=smoothstep(0.0,0.06,min(min(hitUV.x,hitUV.y),min(1.0-hitUV.x,1.0-hitUV.y)));
vec3 color=texture(SceneColor,hitUV).rgb;
float falloff=1.0-separation/GISettings.z;
sum+=color*color*facing*falloff*falloff*edge*giFog(hit);
// Decode the scene approximately to linear light; one spatial falloff is enough.
sum+=color*color*facing*falloff*edge*giFog(hit);
}
break;
}
+1 -1
View File
@@ -1,6 +1,6 @@
name = "Sanctuary"
author = "KOKA99CAB"
version = "beta.132"
version = "beta.133"
pack-format = "packwiz:1.1.0"
[index]