Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d7072b8a8 | ||
|
|
0781cbd964 |
@@ -20,10 +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.136** ajoute un **lens flare rectangulaire doux** pour le
|
||||
soleil et la lune, activé à 30 % et réglable indépendamment du bloom.
|
||||
**Options → Shaders** ; les blocs masquent les astres et leurs reflets.
|
||||
Le [ticket de livraison](docs/shader-beta136.md) décrit le rendu et ses vérifications.
|
||||
La version **beta.137** ajoute des **rayons solaires volumétriques** qui traversent
|
||||
les ouvertures du décor et le feuillage. Activés à 35 %, réglables indépendamment
|
||||
du bloom dans **Options → Shaders**. Carte d'ombres partagée, portée de 64 blocs
|
||||
maximum. Le [ticket](docs/shader-beta137.md) détaille les vérifications.
|
||||
Livraison en cours de vérification.
|
||||
La [livraison précédente](docs/clay-workshop-integration-beta110.md) réunit déjà
|
||||
l’atelier, les œufs et la neige saisonnière dans l’instance **Sanctuary Beta**.
|
||||
@@ -35,6 +35,13 @@ La [carte native beta.005](docs/atlas-beta005.md) prend le relais après
|
||||
l’étude de Xaero et des alternatives libres.
|
||||
|
||||
|
||||
## beta.137 — Rayons solaires
|
||||
|
||||
Faisceaux volumétriques légers, intensité réglable et feuillage perméable.
|
||||
[Contrat et vérifications](docs/shader-beta137.md) ·
|
||||
[Pack normal](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.137/Sanctuary-beta.137.mrpack) ·
|
||||
[Pack de test](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.137/Sanctuary-Test-beta.137.mrpack).
|
||||
|
||||
## beta.136 — Lens flare rectangulaire
|
||||
|
||||
Soleil et lune, reflets doux et intensité indépendante du bloom.
|
||||
|
||||
@@ -14,6 +14,18 @@ JAR Sanctuary sont des pièces jointes de releases Gitea. Aucun JAR de mod ou
|
||||
dépendance téléchargée n'entre dans l'historique Git. Le canal Beta est distinct
|
||||
de l'ancien pack 26.2.
|
||||
|
||||
## beta.136 — Lens flare rectangulaire
|
||||
|
||||
Rectangles à dégradé doux pour le soleil et la lune, activés à 30 % et
|
||||
réglables indépendamment du bloom. Occultation par les blocs et phases lunaires.
|
||||
[Contrat et vérifications](shader-beta136.md).
|
||||
[Release beta.136](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.136)
|
||||
publiée ; canal `dd5b545a7cfbb957f2c70e9dc17034b44caef98f`. Deux synchronisations isolées,
|
||||
puis deux dans la même instance Sanctuary Beta réussies. Les 923 fichiers
|
||||
personnels suivis gardent leurs hashes. Aucun monde personnel ouvert ;
|
||||
sauvegarde préalable `sanctuary-backups/before-beta.136/`.
|
||||
Packs normal/Test/template vérifiés dans `sanctuary-beta/build/`.
|
||||
|
||||
## beta.135 — SSGI en proximité et nouveaux défauts
|
||||
|
||||
Normales stables près des surfaces, atténuation progressive du rebond au contact.
|
||||
|
||||
@@ -68,3 +68,18 @@ Reçu local : `build/shader136-artifact.json`.
|
||||
- `Sanctuary-Test-beta.136.mrpack` : 10443744 octets ; SHA-256 `70f593ad16d1f4fd4305b276ce2727a63f87bcb84df45e55840c1427a314a1eb`.
|
||||
|
||||
JAR : `086afede22903f8988889f6085631451f65d6d55c2e4c81f249d2234698e68e5`.
|
||||
|
||||
## Livraison
|
||||
|
||||
[Release beta.136](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.136)
|
||||
publiée depuis `71296a9cfb0f6b1a6e6f0c48145f3cc432c8afd0`. Canal packwiz
|
||||
`dd5b545a7cfbb957f2c70e9dc17034b44caef98f` ; téléchargements publics vérifiés par SHA-256.
|
||||
Deux synchronisations isolées puis deux dans la même instance **Sanctuary Beta**
|
||||
réussissent. Un seul JAR beta.136 actif ; les **923 fichiers personnels suivis**,
|
||||
dont sauvegardes et préférences, gardent leurs hashes. Copie préalable dans
|
||||
`sanctuary-backups/before-beta.136/`, aucun monde personnel ouvert.
|
||||
|
||||
Packs normal/Test/template et comparatif natif également vérifiés dans
|
||||
`sanctuary-beta/build/`. Le checkout original garde ses changements préexistants.
|
||||
Reçus locaux ignorés : `build/shader136-artifact.json`, `build/shader136-isolated.json`,
|
||||
`build/shader136-prism.json`, `build/shader136-publication.log`.
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
# SHADER-137 — Rayons solaires volumétriques
|
||||
|
||||
Demande validée : faisceaux légers découpés par les blocs, carte d'ombres partagée,
|
||||
feuillage perméable, rendu natif préservé, option et intensité indépendantes du bloom.
|
||||
Socle beta.136, branche `codex/sunshafts-beta137`, Minecraft 26.3 / Java 25.
|
||||
Aucun changement de monde ni de gameplay.
|
||||
|
||||
## Contrat
|
||||
|
||||
Soleil uniquement ; activé initialement à 35 %, intensité 0–100.
|
||||
Intégration de 16 segments par pixel à un quart de chaque dimension.
|
||||
Reconstruction guidée par la profondeur, sans historique ni bruit animé.
|
||||
Réutilisation du masque de feuillage déjà stable dans les ombres.
|
||||
La carte solaire peut fonctionner sans afficher les ombres sur les surfaces.
|
||||
Portée bornée à 64 blocs et à la moitié de la portée des ombres.
|
||||
Atténuation aux limites de la carte, à l'horizon et avec la météo.
|
||||
|
||||
## Rendu et coût borné
|
||||
|
||||
Deux passes : intégration dans une cible RGBA16F de largeur et hauteur divisées
|
||||
par quatre, puis reconstruction guidée par la profondeur. Seize segments
|
||||
quadratiques privilégient les premiers mètres ; quatre lectures fixes de la
|
||||
carte d'ombres par segment adoucissent seulement la lumière dans l'air.
|
||||
La dernière portion du rayon rétrécit continûment à l'approche d'une surface.
|
||||
La lumière utilise une teinte solaire chaude mélangée au brouillard natif.
|
||||
|
||||
La carte d'ombres et ses maillages sont partagés, y compris les trous de
|
||||
transmission déterministes des feuilles. Aucune copie d'image plein écran,
|
||||
aucune duplication du terrain, aucune lecture GPU côté CPU ni accumulation
|
||||
entre images dans le rendu de production. OFF/zéro libèrent le volume ; la
|
||||
carte d'ombres reste seulement si les ombres de surface sont activées.
|
||||
Le soleil sous l'horizon et la submersion suspendent les rayons.
|
||||
|
||||
## Vérifications
|
||||
|
||||
Scénario natif réussi en **1 min 41 s** sur macOS / Apple M1 / OpenGL : pièce
|
||||
entièrement fermée, toit ajouré OFF/35/100, feuilles puis ciel ouvert, déplacement
|
||||
de quatre blocs et retour, caméra proche d'un mur à 0,5 puis 0,125 bloc,
|
||||
soleil hors champ, nuit, OFF/zéro, carte d'ombres partagée, effet combiné,
|
||||
redimensionnement à 961 × 541 et rechargement des ressources. Préférences
|
||||
vierges/partielles/personnelles, bornage et libellés FR/EN également vérifiés.
|
||||
Log : `build/shader137-client-final.log`, marqueur `SUNSHAFT137_PASS`.
|
||||
Comparatif autonome : `build/Shader-beta.137-comparaison.html`.
|
||||
|
||||
La première tentative s'est arrêtée sur une comparaison de gain brut entre
|
||||
feuillage et ciel ouvert. La composition dispose de moins de marge sur un mur
|
||||
déjà clair : le test compare maintenant la contribution normalisée sur les mêmes
|
||||
surfaces, et les feuilles du scénario sont persistantes. Aucun seuil du rendu
|
||||
n'a été assoupli pour faire passer ce contrôle.
|
||||
|
||||
Les contrôles natifs existants du bloom, des vingt textures de minerais émissifs
|
||||
et du lens flare réussissent également en **1 min 32 s**, avec leurs seuils
|
||||
inchangés. Log : `build/shader137-regression.log`, marqueurs `EMISSIVE131_PASS`
|
||||
et `LENS_FLARE136_PASS`. Les autres effets sont explicitement isolés dans les
|
||||
scénarios qui nécessitent une image témoin sans rayons.
|
||||
|
||||
## Limites
|
||||
|
||||
Le volume utilise les formes présentes dans la carte d'ombres existante :
|
||||
il n'ajoute pas de projection d'ombre des entités ou des nuages. Le feuillage
|
||||
reste une approximation par transmission déterministe. Portée maximale de
|
||||
64 blocs, atténuée à son extrémité. Le faible nombre de segments et la résolution
|
||||
réduite peuvent lisser les ouvertures très fines. Aucun résultat Windows/Vulkan
|
||||
ni mesure de FPS revendiqués.
|
||||
|
||||
|
||||
## Construction et archives
|
||||
|
||||
`./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest`
|
||||
réussit en **2 min 27 s**, 126 tâches. Le serveur GameTest dédié reste exclu
|
||||
conformément au refus EULA antérieur. Log : `build/shader137-build.log`.
|
||||
Comparaison à beta.136 : **11 entrées de production** modifiées, uniquement
|
||||
le rendu, ses préférences et ses libellés. Gameplay et textures des gemmes
|
||||
strictement conservés. JAR normal/Test identique, sources et template vérifiés.
|
||||
Reçu local : `build/shader137-artifact.json`.
|
||||
|
||||
- `Sanctuary-beta.137.mrpack` : 10431723 octets ; SHA-256 `6684d1dbe48cd3031b373a03d4380519fdbf5d841e1c7cb5e290dec63b5ba9f6`.
|
||||
|
||||
- `Sanctuary-Test-beta.137.mrpack` : 10450650 octets ; SHA-256 `32b0cee60152b6a5c8f31b248391c79a5fdf47b649de74902b066457ad4785ef`.
|
||||
|
||||
JAR : `f1fbc106b4b048753f9a669af37e9c12f92168af486d4072f514e86920c81faa`.
|
||||
+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.136
|
||||
pack_version=beta.136
|
||||
mod_version=beta.137
|
||||
pack_version=beta.137
|
||||
resource_pack_version=beta.121
|
||||
maven_group=fr.koka.sanctuary
|
||||
jei_version=30.32.0-sanctuary.3
|
||||
|
||||
@@ -103,6 +103,9 @@ tasks.named('processGametestResources') {
|
||||
inputs.property('sanctuarySsgiCloseup135ClientTests', closeup135Client)
|
||||
if (closeup135Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.shader.SsgiCloseup135ClientChecks') }
|
||||
|
||||
def sunshaft137Client = providers.gradleProperty('sanctuarySunshaft137ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('sanctuarySunshaft137ClientTests', sunshaft137Client)
|
||||
if (sunshaft137Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.shader.Sunshafts137ClientChecks') }
|
||||
def bloom130Client = providers.gradleProperty('sanctuaryBloom130ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('sanctuaryBloom130ClientTests', bloom130Client)
|
||||
if (bloom130Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.shader.Bloom130ClientChecks') }
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ public final class Bloom130ClientChecks implements FabricClientGameTest {
|
||||
String ns=name.equals("ruby")||name.equals("sapphire")?"sanctuary:":"minecraft:";result.add(ns+name+"_ore");result.add(ns+"deepslate_"+name+"_ore");}
|
||||
result.add("minecraft:nether_quartz_ore");result.add("minecraft:nether_gold_ore");return List.copyOf(result);}
|
||||
static void run(ClientGameTestContext c,TestServerContext server){
|
||||
c.runOnClient(m->{preferences();VanillaLight.setLensFlare(false);VanillaLight.setPbr(false);VanillaLight.setSsgi(false);analyseTextures();VanillaLight.setSaturation(100);VanillaLight.setPixelShadows(false);VanillaLight.setEdgeHighlights(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);});
|
||||
c.runOnClient(m->{preferences();VanillaLight.setSunshafts(false);VanillaLight.setLensFlare(false);VanillaLight.setPbr(false);VanillaLight.setSsgi(false);analyseTextures();VanillaLight.setSaturation(100);VanillaLight.setPixelShadows(false);VanillaLight.setEdgeHighlights(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);});
|
||||
server.runOnServer(s->{command(s,"time set 18000");
|
||||
for(int x=4;x<=20;x++)for(int z=4;z<=20;z++){
|
||||
s.overworld().setBlockAndUpdate(new BlockPos(x,64,z),Blocks.CONCRETE.black().defaultBlockState());
|
||||
|
||||
+1
-1
@@ -160,7 +160,7 @@ final class EdgeHighlights129ClientChecks {
|
||||
check(VanillaLight.edgeHighlights(),"Toggle persists independently");
|
||||
VanillaLight.setEdgeHighlights(false);
|
||||
VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);
|
||||
VanillaLight.setLensFlare(false);
|
||||
VanillaLight.setSunshafts(false);VanillaLight.setLensFlare(false);
|
||||
VanillaLight.setPbr(false);VanillaLight.setSsgi(false);
|
||||
} catch(java.io.IOException e) { throw new AssertionError(e); }
|
||||
}
|
||||
|
||||
+3
-3
@@ -12,7 +12,7 @@ import net.minecraft.network.chat.Component;
|
||||
/** Native sky image, not a generated mockup; explicit independent effect controls. */
|
||||
final class LensFlare136ClientChecks {
|
||||
static void run(ClientGameTestContext c,TestServerContext server){
|
||||
c.runOnClient(m->{preferences();VanillaLight.setEnabled(true);VanillaLight.setSaturation(100);VanillaLight.setPbr(false);VanillaLight.setSsgi(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);VanillaLight.setPixelShadows(false);VanillaLight.setEdgeHighlights(false);VanillaLight.setLensFlare(false);});
|
||||
c.runOnClient(m->{preferences();VanillaLight.setEnabled(true);VanillaLight.setSaturation(100);VanillaLight.setPbr(false);VanillaLight.setSsgi(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);VanillaLight.setPixelShadows(false);VanillaLight.setEdgeHighlights(false);VanillaLight.setSunshafts(false);VanillaLight.setLensFlare(false);});
|
||||
server.runOnServer(s->{command(s,"time add 1");command(s,"time set 3000");});
|
||||
camera(c,server,100.5,130,100.5,70);aim(c,server,false);
|
||||
var off=capture(c,"flare-sun-off");enable(c,true);var on=capture(c,"flare-sun-30");
|
||||
@@ -43,7 +43,7 @@ final class LensFlare136ClientChecks {
|
||||
enable(c,false);var newOff=capture(c,null);enable(c,true);check(gain(newOff,capture(c,"flare-new-moon"))<moon*.1,"Dark new moon does not retain full moon flare");
|
||||
c.runOnClient(m->VanillaLight.setLensFlareIntensity(0));c.waitTicks(8);c.runOnClient(m->check(!Bloom.flareAllocated()&&!Bloom.allocated(),"Zero frees all resources with other effects OFF"));
|
||||
unchanged(newOff,capture(c,null),"Zero flare restores source image");
|
||||
c.runOnClient(m->{VanillaLight.setLensFlareIntensity(30);VanillaLight.setLensFlare(false);});
|
||||
c.runOnClient(m->{VanillaLight.setLensFlareIntensity(30);VanillaLight.setSunshafts(false);VanillaLight.setLensFlare(false);});
|
||||
for(String locale:new String[]{"fr_fr","en_us"})c.runOnClient(m->{
|
||||
m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());
|
||||
for(String suffix:new String[]{"lens_flare","lens_flare_help","lens_flare_intensity","lens_flare_intensity_help"}){
|
||||
@@ -63,7 +63,7 @@ final class LensFlare136ClientChecks {
|
||||
private static void preferences(){try{
|
||||
var path=FabricLoader.getInstance().getConfigDir().resolve("sanctuary-shaders.json");Files.writeString(path,"{\"bloom\":false,\"bloomIntensity\":20}");forgetPreferences();
|
||||
check(VanillaLight.lensFlare()&&VanillaLight.lensFlareIntensity()==30&&!VanillaLight.bloom()&&VanillaLight.bloomIntensity()==20,"Flare defaults preserve bloom choices");
|
||||
VanillaLight.setLensFlare(false);VanillaLight.setLensFlareIntensity(73);forgetPreferences();check(!VanillaLight.lensFlare()&&VanillaLight.lensFlareIntensity()==73,"Saved custom flare retained");
|
||||
VanillaLight.setSunshafts(false);VanillaLight.setLensFlare(false);VanillaLight.setLensFlareIntensity(73);forgetPreferences();check(!VanillaLight.lensFlare()&&VanillaLight.lensFlareIntensity()==73,"Saved custom flare retained");
|
||||
for(int n:new int[]{-10,0,30,100,150}){VanillaLight.setLensFlareIntensity(n);forgetPreferences();check(VanillaLight.lensFlareIntensity()==Math.clamp(n,0,100),"Flare intensity persists and clamps");}
|
||||
VanillaLight.setLensFlareIntensity(30);
|
||||
}catch(java.io.IOException e){throw new AssertionError(e);}}
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import java.util.Arrays;
|
||||
public final class Pbr134ClientChecks implements FabricClientGameTest {
|
||||
public void runTest(ClientGameTestContext c){new PixelShadows122ClientChecks(false,false,true).runTest(c);}
|
||||
static void run(ClientGameTestContext c,TestServerContext server){
|
||||
c.runOnClient(m->{preferences();VanillaLight.setLensFlare(false);materials();VanillaLight.setEnabled(true);VanillaLight.setSaturation(100);VanillaLight.setPbr(false);VanillaLight.setSsgi(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);VanillaLight.setEdgeHighlights(false);VanillaLight.setPixelShadows(false);});
|
||||
c.runOnClient(m->{preferences();VanillaLight.setSunshafts(false);VanillaLight.setLensFlare(false);materials();VanillaLight.setEnabled(true);VanillaLight.setSaturation(100);VanillaLight.setPbr(false);VanillaLight.setSsgi(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);VanillaLight.setEdgeHighlights(false);VanillaLight.setPixelShadows(false);});
|
||||
server.runOnServer(s->{command(s,"time set 1000");for(int x=-5;x<=24;x++)for(int z=-5;z<=24;z++){
|
||||
s.overworld().setBlockAndUpdate(new BlockPos(x,64,z),(x<10?Blocks.BRICKS:x<17?Blocks.OAK_PLANKS:Blocks.IRON_BLOCK).defaultBlockState());
|
||||
for(int y=65;y<=102;y++)s.overworld().setBlockAndUpdate(new BlockPos(x,y,z),Blocks.AIR.defaultBlockState());
|
||||
|
||||
+7
-5
@@ -36,12 +36,13 @@ 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,closeupOnly;
|
||||
private final boolean bloomOnly,ssgiOnly,pbrOnly,closeupOnly,sunshaftOnly;
|
||||
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,ssgiOnly,pbrOnly,false);}
|
||||
PixelShadows122ClientChecks(boolean bloomOnly,boolean ssgiOnly,boolean pbrOnly,boolean closeupOnly){this.bloomOnly=bloomOnly;this.ssgiOnly=ssgiOnly;this.pbrOnly=pbrOnly;this.closeupOnly=closeupOnly;}
|
||||
PixelShadows122ClientChecks(boolean bloomOnly,boolean ssgiOnly,boolean pbrOnly,boolean closeupOnly){this(bloomOnly,ssgiOnly,pbrOnly,closeupOnly,false);}
|
||||
PixelShadows122ClientChecks(boolean bloomOnly,boolean ssgiOnly,boolean pbrOnly,boolean closeupOnly,boolean sunshaftOnly){this.sunshaftOnly=sunshaftOnly;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);
|
||||
@@ -136,6 +137,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(sunshaftOnly){Sunshafts137ClientChecks.run(c,server);return;}
|
||||
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;}
|
||||
@@ -266,7 +268,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
|
||||
});
|
||||
});
|
||||
c.waitTicks(3);
|
||||
c.takeScreenshot("sanctuary-beta136-shader-options-" + locale);
|
||||
c.takeScreenshot("sanctuary-beta137-shader-options-" + locale);
|
||||
}
|
||||
c.runOnClient(m -> m.gui.setScreen(null));
|
||||
}
|
||||
@@ -446,7 +448,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
|
||||
|
||||
private static void checkPreferences() {
|
||||
checkDefaults135();
|
||||
VanillaLight.setLensFlare(false);
|
||||
VanillaLight.setSunshafts(false);VanillaLight.setLensFlare(false);
|
||||
VanillaLight.setPbr(false);
|
||||
VanillaLight.setSsgi(false);
|
||||
VanillaLight.setBloom(false);
|
||||
@@ -550,7 +552,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
|
||||
}
|
||||
|
||||
static Frame capture(ClientGameTestContext c, String name) {
|
||||
if (name != null) c.takeScreenshot("sanctuary-beta136-shader-" + name);
|
||||
if (name != null) c.takeScreenshot("sanctuary-beta137-shader-" + name);
|
||||
var result = new CompletableFuture<Frame>();
|
||||
c.runOnClient(m -> Screenshot.takeScreenshot(m.gameRenderer.mainRenderTarget(), image -> {
|
||||
try (image) {
|
||||
|
||||
+1
-1
@@ -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.setLensFlare(false);VanillaLight.setPbr(false);VanillaLight.setEnabled(true);VanillaLight.setSaturation(100);VanillaLight.setSsgi(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);VanillaLight.setPixelShadows(false);VanillaLight.setEdgeHighlights(false);});
|
||||
c.runOnClient(m->{preferences();VanillaLight.setSunshafts(false);VanillaLight.setLensFlare(false);VanillaLight.setPbr(false);VanillaLight.setEnabled(true);VanillaLight.setSaturation(100);VanillaLight.setSsgi(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);VanillaLight.setPixelShadows(false);VanillaLight.setEdgeHighlights(false);});
|
||||
server.runOnServer(s->{command(s,"time set 6000");for(int x=-5;x<=24;x++)for(int z=-5;z<=24;z++){
|
||||
s.overworld().setBlockAndUpdate(new BlockPos(x,64,z),Blocks.CONCRETE.white().defaultBlockState());
|
||||
for(int y=65;y<=102;y++)s.overworld().setBlockAndUpdate(new BlockPos(x,y,z),Blocks.AIR.defaultBlockState());
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package fr.koka.sanctuary.client.shader;
|
||||
|
||||
import static fr.koka.sanctuary.client.shader.PixelShadows122ClientChecks.*;
|
||||
import java.nio.file.Files;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.context.TestServerContext;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.util.Mth;
|
||||
|
||||
/** Native solar volume in a sealed room, a slotted roof and a leaf canopy. */
|
||||
public final class Sunshafts137ClientChecks implements FabricClientGameTest {
|
||||
public void runTest(ClientGameTestContext c){new PixelShadows122ClientChecks(false,false,false,false,true).runTest(c);}
|
||||
static void run(ClientGameTestContext c,TestServerContext server){
|
||||
c.runOnClient(m->{preferences();VanillaLight.setEnabled(true);VanillaLight.setSaturation(100);VanillaLight.setPbr(false);VanillaLight.setSsgi(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);VanillaLight.setLensFlare(false);VanillaLight.setEdgeHighlights(false);VanillaLight.setPixelShadows(false);VanillaLight.setSunshafts(false);VanillaLight.setShadowDistance(64);});
|
||||
server.runOnServer(s->{command(s,"time add 1");command(s,"time set 3000");
|
||||
for(int x=76;x<=124;x++)for(int z=76;z<=124;z++){
|
||||
s.overworld().setBlockAndUpdate(new BlockPos(x,96,z),Blocks.STONE_BRICKS.defaultBlockState());
|
||||
if(x==76||x==124||z==76||z==124)for(int y=97;y<=113;y++)s.overworld().setBlockAndUpdate(new BlockPos(x,y,z),Blocks.STONE_BRICKS.defaultBlockState());
|
||||
}
|
||||
});
|
||||
roof(server,0);camera(c,server,100.5,101,110.5,70);aim(c,server);c.waitTicks(50);
|
||||
var closedOff=capture(c,"shafts-closed-off");enable(c,true);var closed=capture(c,"shafts-closed-on");
|
||||
check(gain(closedOff,closed)<300,"Sealed room receives no sunlight through opaque blocks: "+gain(closedOff,closed));
|
||||
c.runOnClient(m->check(!PixelShadows.applied()&&PixelShadows.depthReady()&&!Bloom.allocated(),"Volume alone shares depth without surface shadows or bloom"));
|
||||
roof(server,1);settle(c);enable(c,false);var off=capture(c,"shafts-slots-off");enable(c,true);var on=capture(c,"shafts-slots-35");long low=gain(off,on);
|
||||
check(low>1000,"Roof openings form visible shafts: "+low);unchanged(on,capture(c,null),"Static shafts have no animated sampling noise");
|
||||
c.runOnClient(m->VanillaLight.setSunshaftIntensity(100));settle(c);var strong=capture(c,"shafts-slots-100");check(gain(off,strong)>low*1.5,"Intensity increases volume energy");
|
||||
camera(c,server,104.5,101,110.5,70);aim(c,server);settle(c);capture(c,"shafts-moved-four-blocks");
|
||||
camera(c,server,100.5,101,110.5,70);aim(c,server);settle(c);unchanged(strong,capture(c,"shafts-returned"),"Moving and returning leaves no history or trailing light");
|
||||
var reload=c.computeOnClient(net.minecraft.client.Minecraft::reloadResourcePacks);c.waitFor(m->reload.isDone()&&m.gui.overlay()==null,1200);reload.join();settle(c);unchanged(strong,capture(c,"shafts-reloaded"),"Volume recovers after resource reload");
|
||||
int w=c.computeOnClient(m->m.getWindow().getScreenWidth()),h=c.computeOnClient(m->m.getWindow().getScreenHeight());
|
||||
c.runOnClient(m->m.getWindow().setWindowed(961,541));c.waitFor(m->m.gameRenderer.mainRenderTarget().width!=off.width(),200);settle(c);
|
||||
enable(c,false);var small=capture(c,null);enable(c,true);check(gain(small,capture(c,"shafts-resized"))>1000,"Bilateral volume survives odd dimensions");
|
||||
c.runOnClient(m->m.getWindow().setWindowed(w,h));c.waitFor(m->m.gameRenderer.mainRenderTarget().width==off.width(),200);settle(c);
|
||||
roof(server,2);settle(c);enable(c,false);var leafOff=capture(c,null);enable(c,true);var leafOn=capture(c,"shafts-leaves");long leaf=gain(leafOff,leafOn);double leafEnergy=energy(leafOff,leafOn);check(leaf>1000,"Leaf canopy transmits light: "+leaf);
|
||||
roof(server,3);settle(c);enable(c,false);var openOff=capture(c,null);enable(c,true);var openOn=capture(c,"shafts-open");long open=gain(openOff,openOn);double openEnergy=energy(openOff,openOn);check(openEnergy>leafEnergy,"Leaf canopy attenuates normalized scattering on identical walls: "+leafEnergy+" / "+openEnergy);
|
||||
look(c,server,0,0);settle(c);enable(c,false);var sideOff=capture(c,null);enable(c,true);check(gain(sideOff,capture(c,"shafts-sun-offscreen"))>1000,"Volume persists with sun outside the viewport");
|
||||
long[] close=new long[2];int index=0;
|
||||
for(double z:new double[]{123.5,123.875}){camera(c,server,100.5,101,z,70);look(c,server,0,0);settle(c);enable(c,false);var nearOff=capture(c,null);enable(c,true);close[index++]=gain(nearOff,capture(c,"shafts-wall-"+z));}
|
||||
check(close[1]<=close[0]+100,"Approaching wall attenuates scattering instead of increasing it: "+close[0]+" / "+close[1]);
|
||||
c.runOnClient(m->VanillaLight.setSunshaftIntensity(0));c.waitTicks(10);c.runOnClient(m->check(!Sunshafts.allocated()&&PixelShadows.shadowMap()==null,"Zero frees volume and unneeded shared map"));
|
||||
c.runOnClient(m->VanillaLight.setSunshaftIntensity(35));enable(c,true);
|
||||
server.runOnServer(s->command(s,"time set 18000"));c.waitTicks(30);c.runOnClient(m->check(!Sunshafts.allocated()&&!Sunshafts.applied(),"Solar volume stops at night"));
|
||||
server.runOnServer(s->command(s,"time set 3000"));camera(c,server,100.5,101,110.5,70);aim(c,server);settle(c);
|
||||
c.runOnClient(m->{VanillaLight.setPixelShadows(true);VanillaLight.setPbr(true);VanillaLight.setSsgi(true);VanillaLight.setBloom(true);VanillaLight.setLensFlare(true);VanillaLight.setEdgeHighlights(true);});activeFrames(c);capture(c,"shafts-combined");
|
||||
c.runOnClient(m->{VanillaLight.setSunshafts(false);});c.waitTicks(10);c.runOnClient(m->check(!Sunshafts.allocated()&&PixelShadows.applied(),"OFF preserves independently enabled surface shadows"));
|
||||
for(String locale:new String[]{"fr_fr","en_us"})c.runOnClient(m->{m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());for(String suffix:new String[]{"sunshafts","sunshafts_help","sunshaft_intensity","sunshaft_intensity_help"}){String key="sanctuary.shaders."+suffix;check(!Component.translatable(key).getString().equals(key),"Localized volume controls: "+locale);}});
|
||||
System.out.println("SUNSHAFT137_PASS slotGain="+low+" leafGain="+leaf+" openGain="+open+" closed room, offscreen sun, camera return, proximity, reload/resize, shared map, zero/night, preferences, FR/EN");
|
||||
}
|
||||
private static void roof(TestServerContext server,int mode){server.runOnServer(s->{for(int x=76;x<=124;x++)for(int z=76;z<=124;z++)s.overworld().setBlockAndUpdate(new BlockPos(x,113,z),(mode==3||mode==1&&Math.floorMod(z,8)<3?Blocks.AIR:mode==2?Blocks.OAK_LEAVES:Blocks.OAK_PLANKS).defaultBlockState().trySetValue(net.minecraft.world.level.block.state.properties.BlockStateProperties.PERSISTENT,true));});}
|
||||
private static void aim(ClientGameTestContext c,TestServerContext server){var d=c.computeOnClient(m->PixelShadows.sunDirection(m.gameRenderer.gameRenderState().levelRenderState.skyRenderState.sunAngle));look(c,server,Mth.wrapDegrees((float)Math.toDegrees(Math.atan2(-d.x,d.z))+25),-12);}
|
||||
private static void settle(ClientGameTestContext c){int before=c.computeOnClient(m->Sunshafts.renderedFrames());c.waitFor(m->Sunshafts.applied()&&PixelShadows.ready()&&Sunshafts.renderedFrames()>before+10,1200);c.waitTicks(30);}
|
||||
private static void enable(ClientGameTestContext c,boolean enabled){c.runOnClient(m->VanillaLight.setSunshafts(enabled));if(enabled)settle(c);else c.waitTicks(10);}
|
||||
private static long gain(Frame off,Frame on){long sum=0;for(int i=0;i<off.pixels().length;i++)for(int shift:new int[]{0,8,16})sum+=Math.max(0,(on.pixels()[i]>>shift&255)-(off.pixels()[i]>>shift&255));return sum;}
|
||||
// Screen blending has less headroom on a brighter vanilla wall. Remove that factor
|
||||
// before comparing roofs, and exclude the roof itself where geometry changes.
|
||||
private static double energy(Frame off,Frame on){double sum=0;int n=0;for(int y=off.height()/3;y<off.height();y++)for(int x=0;x<off.width();x++){int i=y*off.width()+x;for(int shift:new int[]{0,8,16}){int a=off.pixels()[i]>>shift&255,b=on.pixels()[i]>>shift&255;if(a<240){sum+=Math.max(0,b-a)/(255.0-a);n++;}}}return sum/Math.max(1,n);}
|
||||
private static void preferences(){try{var p=FabricLoader.getInstance().getConfigDir().resolve("sanctuary-shaders.json");Files.writeString(p,"{\"bloom\":false,\"pixelShadows\":false}");forgetPreferences();check(VanillaLight.sunshafts()&&VanillaLight.sunshaftIntensity()==35&&!VanillaLight.bloom()&&!VanillaLight.pixelShadows(),"Missing shaft settings preserve existing choices");VanillaLight.setSunshafts(false);VanillaLight.setSunshaftIntensity(73);forgetPreferences();check(!VanillaLight.sunshafts()&&VanillaLight.sunshaftIntensity()==73,"Custom shafts persist");for(int n:new int[]{-10,0,35,100,150}){VanillaLight.setSunshaftIntensity(n);forgetPreferences();check(VanillaLight.sunshaftIntensity()==Math.clamp(n,0,100),"Intensity clamps and persists");}VanillaLight.setSunshaftIntensity(35);}catch(java.io.IOException e){throw new AssertionError(e);}}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ public final class PixelShadows {
|
||||
private static TextureTarget shadowMap;
|
||||
private static MappableRingBuffer uniforms;
|
||||
private static int renderedFrames;
|
||||
private static boolean applied;
|
||||
private static boolean applied, depthReady;
|
||||
|
||||
private PixelShadows() { }
|
||||
public static void register() {
|
||||
@@ -73,8 +73,9 @@ public final class PixelShadows {
|
||||
/** Called after the world's depth is resolved and before the first-person hand and GUI. */
|
||||
public static void render() {
|
||||
applied = false;
|
||||
depthReady = false;
|
||||
var client = Minecraft.getInstance();
|
||||
if (!VanillaLight.active() || !VanillaLight.pixelShadows() || client.level == null) {
|
||||
if (!VanillaLight.active() || (!VanillaLight.pixelShadows() && !(VanillaLight.sunshafts() && VanillaLight.sunshaftIntensity()>0)) || client.level == null) {
|
||||
release();
|
||||
return;
|
||||
}
|
||||
@@ -84,6 +85,7 @@ public final class PixelShadows {
|
||||
CameraRenderState camera = state.cameraRenderState;
|
||||
var sun = sunDirection(sky.sunAngle);
|
||||
boolean lunar = sun.y <= .08f;
|
||||
if (lunar && !VanillaLight.pixelShadows()) { release(); return; }
|
||||
if (lunar) sun = sunDirection(sky.moonAngle);
|
||||
if (sky.skybox != DimensionType.Skybox.OVERWORLD || camera.fogType != FogType.NONE || sun.y <= .08f) return;
|
||||
float moon = sky.moonPhase == null ? 1 : (float)(.5 + .5 * Math.cos(sky.moonPhase.index() * Math.PI / 4));
|
||||
@@ -139,6 +141,8 @@ public final class PixelShadows {
|
||||
pass.drawIndexed(entry.indexCount(), 1, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
depthReady = !lunar;
|
||||
if (!VanillaLight.pixelShadows()) { renderedFrames++; return; }
|
||||
var main = client.gameRenderer.mainRenderTarget();
|
||||
var nearest = RenderSystem.getSamplerCache().getClampToEdge(FilterMode.NEAREST);
|
||||
try (var pass = encoder.createRenderPass(() -> "Sanctuary pixel shadows", main.getColorTextureView(), Optional.empty())) {
|
||||
@@ -168,7 +172,9 @@ public final class PixelShadows {
|
||||
if (shadowMap != null) { shadowMap.destroyBuffers(); shadowMap = null; }
|
||||
if (uniforms != null) { uniforms.close(); uniforms = null; }
|
||||
applied = false;
|
||||
depthReady = false;
|
||||
}
|
||||
static boolean depthReady() { return depthReady; }
|
||||
static int renderedFrames() { return renderedFrames; }
|
||||
static boolean applied() { return applied; }
|
||||
static boolean ready() { return TERRAIN.settled(); }
|
||||
|
||||
@@ -30,6 +30,9 @@ public final class ShaderOptionsScreen extends OptionsSubScreen {
|
||||
Component.translatable("sanctuary.shaders.bloom_help")),VanillaLight.bloom(),VanillaLight::setBloom));
|
||||
slider("bloom_intensity",VanillaLight.bloomIntensity(),VanillaLight::setBloomIntensity);
|
||||
slider("bloom_radius",VanillaLight.bloomRadius(),VanillaLight::setBloomRadius);
|
||||
list.addBig(OptionInstance.createBoolean("sanctuary.shaders.sunshafts",OptionInstance.cachedConstantTooltip(
|
||||
Component.translatable("sanctuary.shaders.sunshafts_help")),VanillaLight.sunshafts(),VanillaLight::setSunshafts));
|
||||
slider("sunshaft_intensity",VanillaLight.sunshaftIntensity(),VanillaLight::setSunshaftIntensity);
|
||||
list.addBig(OptionInstance.createBoolean("sanctuary.shaders.lens_flare",OptionInstance.cachedConstantTooltip(
|
||||
Component.translatable("sanctuary.shaders.lens_flare_help")),VanillaLight.lensFlare(),VanillaLight::setLensFlare));
|
||||
slider("lens_flare_intensity",VanillaLight.lensFlareIntensity(),VanillaLight::setLensFlareIntensity);
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package fr.koka.sanctuary.client.shader;
|
||||
|
||||
import com.mojang.blaze3d.buffers.Std140Builder;
|
||||
import com.mojang.blaze3d.pipeline.TextureTarget;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.renderpearl.api.GpuFormat;
|
||||
import com.mojang.renderpearl.api.buffers.GpuBuffer;
|
||||
import com.mojang.renderpearl.api.pipeline.*;
|
||||
import com.mojang.renderpearl.api.textures.FilterMode;
|
||||
import fr.koka.sanctuary.client.panorama.PanoramaCapture;
|
||||
import java.util.Optional;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.MappableRingBuffer;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
/** Solar scattering in the existing shadow volume. No scene copy, readback, temporal history or terrain duplication. */
|
||||
public final class Sunshafts {
|
||||
private static final BindGroupLayout SCENE=BindGroupLayout.builder()
|
||||
.withUniform("PixelShadows",UniformType.UNIFORM_BUFFER)
|
||||
.withUniform("ShaftParams",UniformType.UNIFORM_BUFFER)
|
||||
.withUniform("WorldDepth",UniformType.COMBINED_IMAGE_SAMPLER).build();
|
||||
private static RenderPipeline pipeline(String name,boolean composite){
|
||||
var inputs=BindGroupLayout.builder();
|
||||
if(composite)inputs.withUniform("ShaftLight",UniformType.COMBINED_IMAGE_SAMPLER);
|
||||
else inputs.withUniform("SunDepth",UniformType.COMBINED_IMAGE_SAMPLER);
|
||||
return RenderPipelines.register(RenderPipeline.builder().withLocation(Identifier.parse("sanctuary:pipeline/"+name))
|
||||
.withVertexShader("core/screenquad").withFragmentShader(Identifier.parse("sanctuary:core/"+name))
|
||||
.withBindGroupLayout(SCENE).withBindGroupLayout(inputs.build())
|
||||
.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(),
|
||||
composite?GpuFormat.RGBA8_UNORM:GpuFormat.RGBA16_FLOAT,composite?ColorTargetState.WRITE_COLOR:ColorTargetState.WRITE_ALL))
|
||||
.withDepthStencilState(Optional.empty()).build());
|
||||
}
|
||||
private static final RenderPipeline GATHER=pipeline("sunshaft_gather",false),APPLY=pipeline("sunshaft_apply",true);
|
||||
private static TextureTarget light;
|
||||
private static MappableRingBuffer settings;
|
||||
private static boolean applied;
|
||||
private static int frames;
|
||||
private Sunshafts(){}
|
||||
public static void render(){
|
||||
applied=false;var mc=Minecraft.getInstance();
|
||||
if(!VanillaLight.active()||!VanillaLight.sunshafts()||VanillaLight.sunshaftIntensity()==0||mc.level==null){release();return;}
|
||||
if(PanoramaCapture.rendering())return;
|
||||
if(!PixelShadows.depthReady()){release();return;}
|
||||
var main=mc.gameRenderer.mainRenderTarget();int width=Math.max(1,(main.width+3)/4),height=Math.max(1,(main.height+3)/4);
|
||||
if(light==null||light.width!=width||light.height!=height){release();light=new TextureTarget("Sanctuary sunshafts",width,height,GpuFormat.RGBA16_FLOAT,null);}
|
||||
if(settings==null)settings=new MappableRingBuffer(()->"Sanctuary sunshafts",GpuBuffer.USAGE_UNIFORM|GpuBuffer.USAGE_MAP_WRITE,32);
|
||||
var fog=mc.gameRenderer.gameRenderState().levelRenderState.cameraRenderState.fogData;
|
||||
settings.rotate();try(var mapped=settings.currentBuffer().map(false,true)){
|
||||
Std140Builder.intoBuffer(mapped.data()).putVec4(.85f+.15f*fog.color.x,.78f+.15f*fog.color.y,.66f+.15f*fog.color.z,0)
|
||||
.putVec4(VanillaLight.sunshaftIntensity()/100f,Math.min(64,VanillaLight.shadowDistance()*.5f),0,0);
|
||||
}
|
||||
var encoder=RenderSystem.getDevice().createCommandEncoder();var nearest=RenderSystem.getSamplerCache().getClampToEdge(FilterMode.NEAREST);
|
||||
try(var pass=encoder.createRenderPass(()->"Sanctuary solar volume",light.getColorTextureView(),Optional.empty())){
|
||||
pass.setPipeline(RenderSystem.getCompiledPipeline(GATHER));pass.setUniform("PixelShadows",PixelShadows.materialUniforms());pass.setUniform("ShaftParams",settings.currentBuffer());
|
||||
pass.setUniform("WorldDepth",main.getDepthTextureView(),nearest);pass.setUniform("SunDepth",PixelShadows.materialDepth(),nearest);pass.draw(3,1,0,0);
|
||||
}
|
||||
try(var pass=encoder.createRenderPass(()->"Sanctuary solar volume composite",main.getColorTextureView(),Optional.empty())){
|
||||
pass.setPipeline(RenderSystem.getCompiledPipeline(APPLY));pass.setUniform("PixelShadows",PixelShadows.materialUniforms());pass.setUniform("ShaftParams",settings.currentBuffer());
|
||||
pass.setUniform("WorldDepth",main.getDepthTextureView(),nearest);pass.setUniform("ShaftLight",light.getColorTextureView(),nearest);pass.draw(3,1,0,0);
|
||||
}
|
||||
applied=true;frames++;
|
||||
}
|
||||
public static void release(){if(light!=null){light.destroyBuffers();light=null;}if(settings!=null){settings.close();settings=null;}applied=false;}
|
||||
static boolean applied(){return applied;}
|
||||
static boolean allocated(){return light!=null;}
|
||||
static int renderedFrames(){return frames;}
|
||||
static TextureTarget target(){return light;}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ public final class VanillaLight {
|
||||
int saturation=142;
|
||||
boolean pixelShadows=true;
|
||||
boolean bloom=true;
|
||||
boolean sunshafts=true;
|
||||
int sunshaftIntensity=35;
|
||||
boolean lensFlare=true;
|
||||
int lensFlareIntensity=30;
|
||||
boolean pbr=true;
|
||||
@@ -41,6 +43,7 @@ public final class VanillaLight {
|
||||
try{if(Files.isRegularFile(path()))preferences=new GsonBuilder().create().fromJson(Files.readString(path()),Preferences.class);}
|
||||
catch(Exception e){SanctuaryMod.LOGGER.warn("Cannot read Sanctuary shader preference",e);}
|
||||
if(preferences==null)preferences=new Preferences();
|
||||
preferences.sunshaftIntensity=Math.clamp(preferences.sunshaftIntensity,0,100);
|
||||
preferences.lensFlareIntensity=Math.clamp(preferences.lensFlareIntensity,0,100);
|
||||
preferences.pbrIntensity=Math.clamp(preferences.pbrIntensity,0,100);
|
||||
preferences.ssgiIntensity=Math.clamp(preferences.ssgiIntensity,0,100);
|
||||
@@ -64,6 +67,10 @@ public final class VanillaLight {
|
||||
public static int edgeIntensity(){enabled();return preferences.edgeIntensity;}
|
||||
public static void setEdgeHighlights(boolean value){enabled();preferences.edgeHighlights=value;save();}
|
||||
public static void setEdgeIntensity(int value){enabled();preferences.edgeIntensity=Math.clamp(value,0,100);save();}
|
||||
public static boolean sunshafts(){enabled();return preferences.sunshafts;}
|
||||
public static int sunshaftIntensity(){enabled();return preferences.sunshaftIntensity;}
|
||||
public static void setSunshafts(boolean value){enabled();preferences.sunshafts=value;save();}
|
||||
public static void setSunshaftIntensity(int value){enabled();preferences.sunshaftIntensity=Math.clamp(value,0,100);save();}
|
||||
public static boolean lensFlare(){enabled();return preferences.lensFlare;}
|
||||
public static int lensFlareIntensity(){enabled();return preferences.lensFlareIntensity;}
|
||||
public static void setLensFlare(boolean value){enabled();preferences.lensFlare=value;save();}
|
||||
|
||||
+3
-2
@@ -7,6 +7,7 @@ import fr.koka.sanctuary.client.shader.PixelShadows;
|
||||
import fr.koka.sanctuary.client.shader.ProceduralPbr;
|
||||
import fr.koka.sanctuary.client.shader.EdgeHighlights;
|
||||
import fr.koka.sanctuary.client.shader.Bloom;
|
||||
import fr.koka.sanctuary.client.shader.Sunshafts;
|
||||
import fr.koka.sanctuary.client.shader.ScreenSpaceGI;
|
||||
import net.minecraft.client.renderer.GameRenderer;
|
||||
import net.minecraft.client.renderer.ProjectionMatrixBuffer;
|
||||
@@ -28,7 +29,7 @@ abstract class PixelShadowRendererMixin {
|
||||
return original.call(buffer, matrix);
|
||||
}
|
||||
@Inject(method="renderLevel", at=@At(value="INVOKE", target="Lnet/minecraft/client/renderer/GameRenderer;render3dHud(Lnet/minecraft/client/renderer/state/level/CameraRenderState;Lnet/minecraft/client/renderer/state/level/PlayerRenderState;Lnet/minecraft/client/renderer/state/OptionsRenderState;Z)V"))
|
||||
private void sanctuary$shadows(CallbackInfo ci) { PixelShadows.render(); ProceduralPbr.render(); ScreenSpaceGI.render(); EdgeHighlights.render(); Bloom.render(); }
|
||||
private void sanctuary$shadows(CallbackInfo ci) { PixelShadows.render(); ProceduralPbr.render(); ScreenSpaceGI.render(); EdgeHighlights.render(); Bloom.render(); Sunshafts.render(); }
|
||||
@Inject(method={"close", "setLevel"}, at=@At("HEAD"))
|
||||
private void sanctuary$close(CallbackInfo ci) { PixelShadows.release(); ProceduralPbr.release(); EdgeHighlights.release(); Bloom.release(); ScreenSpaceGI.release(); }
|
||||
private void sanctuary$close(CallbackInfo ci) { PixelShadows.release(); ProceduralPbr.release(); EdgeHighlights.release(); Bloom.release(); ScreenSpaceGI.release(); Sunshafts.release(); }
|
||||
}
|
||||
|
||||
@@ -2303,5 +2303,10 @@
|
||||
"sanctuary.shaders.lens_flare_help": "Soft rectangular sun and moon reflections. Obstacles hide the sources. Independent of the bloom halo.",
|
||||
"sanctuary.shaders.lens_flare_intensity": "Lens flare intensity",
|
||||
"sanctuary.shaders.lens_flare_intensity_help": "Celestial reflection strength. Zero disables the effect.",
|
||||
"sanctuary.shaders.lens_flare_intensity_value": "Lens flare: %s %%"
|
||||
"sanctuary.shaders.lens_flare_intensity_value": "Lens flare: %s %%",
|
||||
"sanctuary.shaders.sunshafts": "Sunshafts",
|
||||
"sanctuary.shaders.sunshafts_help": "Light beams through openings in the scenery. Independent of bloom; range follows shadows (up to 64 blocks).",
|
||||
"sanctuary.shaders.sunshaft_intensity": "Sunshaft intensity",
|
||||
"sanctuary.shaders.sunshaft_intensity_help": "Strength of light scattered in the air. Zero disables the effect.",
|
||||
"sanctuary.shaders.sunshaft_intensity_value": "Sunshafts: %s %%"
|
||||
}
|
||||
|
||||
@@ -2303,5 +2303,10 @@
|
||||
"sanctuary.shaders.lens_flare_help": "Reflets rectangulaires du soleil et de la lune, aux bords dégradés. Les obstacles masquent les sources. Indépendant du halo de bloom.",
|
||||
"sanctuary.shaders.lens_flare_intensity": "Intensité du lens flare",
|
||||
"sanctuary.shaders.lens_flare_intensity_help": "Force des reflets célestes. Zéro désactive le calcul.",
|
||||
"sanctuary.shaders.lens_flare_intensity_value": "Lens flare : %s %%"
|
||||
"sanctuary.shaders.lens_flare_intensity_value": "Lens flare : %s %%",
|
||||
"sanctuary.shaders.sunshafts": "Rayons solaires",
|
||||
"sanctuary.shaders.sunshafts_help": "Faisceaux de lumière dans les ouvertures du décor. Indépendants du bloom, portée liée aux ombres (64 blocs maximum).",
|
||||
"sanctuary.shaders.sunshaft_intensity": "Intensité des rayons",
|
||||
"sanctuary.shaders.sunshaft_intensity_help": "Force de la lumière dans l’air. Zéro désactive le calcul.",
|
||||
"sanctuary.shaders.sunshaft_intensity_value": "Rayons : %s %%"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#version 330
|
||||
#extension GL_ARB_separate_shader_objects : require
|
||||
#include <sanctuary:sunshafts.glsl>
|
||||
uniform sampler2D ShaftLight;
|
||||
layout(location=0) in vec2 texCoord;
|
||||
layout(location=0) out vec4 fragColor;
|
||||
void main(){
|
||||
float end=shaftEnd(shaftTexel(texCoord));
|
||||
vec2 size=vec2(textureSize(ShaftLight,0)),at=texCoord*size-.5,base=floor(at),f=fract(at);
|
||||
vec3 sum=vec3(0);float weight=0.0;
|
||||
for(int y=0;y<2;y++)for(int x=0;x<2;x++){
|
||||
vec4 light=texture(ShaftLight,(base+vec2(x,y)+.5)/size);
|
||||
float tolerance=.15+end*.035;
|
||||
float compatible=1.0-smoothstep(tolerance*.2,tolerance,abs(light.a-end));
|
||||
float w=(x==0?1.0-f.x:f.x)*(y==0?1.0-f.y:f.y)*compatible;
|
||||
sum+=light.rgb*w;weight+=w;
|
||||
}
|
||||
if(weight<.0001)discard;
|
||||
// Smooth energy shoulder and near-contact fade; preserve framebuffer alpha.
|
||||
fragColor=vec4(1.0-exp(-sum/weight*smoothstep(.03,.2,end)),0);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#version 330
|
||||
#extension GL_ARB_separate_shader_objects : require
|
||||
#include <sanctuary:sunshafts.glsl>
|
||||
uniform sampler2D SunDepth;
|
||||
layout(location=0) in vec2 texCoord;
|
||||
layout(location=0) out vec4 fragColor;
|
||||
float sunlight(vec3 point){
|
||||
vec4 light=LightMatrix*vec4(point+CameraFractionPixels.xyz,1);
|
||||
vec3 q=light.xyz/light.w;q.xy=q.xy*.5+.5;
|
||||
if(DistanceClipFog.y<.5)q.z=q.z*.5+.5;
|
||||
// No clamped shadow-map edge may become a fictitious column of light.
|
||||
float border=min(min(q.x,q.y),min(1.0-q.x,1.0-q.y));
|
||||
if(border<=0.0||q.z<=0.0||q.z>=1.0)return 0.0;
|
||||
vec2 size=vec2(textureSize(SunDepth,0));
|
||||
// Fixed world-scale footprint softens the volume, not the block textures.
|
||||
vec2 footprint=max(vec2(1.0)/size,vec2(.12/(DistanceClipFog.x+16.0)));
|
||||
float lit=0.0;
|
||||
for(int y=0;y<2;y++)for(int x=0;x<2;x++){
|
||||
float depth=texture(SunDepth,q.xy+(vec2(x,y)-.5)*footprint).r;
|
||||
lit+=smoothstep(-.00035,.00035,depth-q.z);
|
||||
}
|
||||
return lit*.25*smoothstep(0.0,.06,border)*smoothstep(0.0,.03,min(q.z,1.0-q.z));
|
||||
}
|
||||
void main(){
|
||||
vec2 uv=shaftTexel(texCoord);float end=shaftEnd(uv);
|
||||
vec3 ray=normalize(shaftPosition(uv,.0001));
|
||||
float total=0.0,range=ShaftSettings.y;
|
||||
// Quadratic, deterministic segments resolve nearby occluders without frame noise.
|
||||
// Partial last segments fade continuously as a wall approaches the camera.
|
||||
for(int i=0;i<16;i++){
|
||||
float a=float(i)/16.0,b=float(i+1)/16.0;
|
||||
float start=range*a*a,stop=min(end,range*b*b);
|
||||
float span=max(0.0,stop-start);if(span<=0.0)break;
|
||||
float t=(start+stop)*.5;
|
||||
float distanceFade=1.0-smoothstep(range*.7,range,t);
|
||||
total+=sunlight(ray*t)*span*exp(-t/24.0)*distanceFade;
|
||||
}
|
||||
float phase=.12+.88*pow(max(0.0,dot(ray,SunStrength.xyz)),4.0);
|
||||
float weather=clamp(SunStrength.w/.38,0.0,1.0);
|
||||
float fogDistance=max(abs(ray.x),max(abs(ray.y),abs(ray.z)))*end;
|
||||
float nativeFog=1.0-smoothstep(DistanceClipFog.z,max(DistanceClipFog.z+.01,DistanceClipFog.w),fogDistance);
|
||||
float amount=total*.018*phase*weather*ShaftSettings.x*nativeFog;
|
||||
fragColor=vec4(ShaftColor.rgb*amount,end);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
layout(std140) uniform PixelShadows {
|
||||
mat4 InverseViewProjection;
|
||||
mat4 LightMatrix;
|
||||
vec4 CameraFractionPixels;
|
||||
vec4 SunStrength;
|
||||
vec4 DistanceClipFog;
|
||||
vec4 EnvironmentFog;
|
||||
};
|
||||
layout(std140) uniform ShaftParams { vec4 ShaftColor; vec4 ShaftSettings; };
|
||||
uniform sampler2D WorldDepth;
|
||||
vec2 shaftTexel(vec2 uv){
|
||||
vec2 size=vec2(textureSize(WorldDepth,0));
|
||||
return (clamp(floor(uv*size),vec2(0),size-1.0)+.5)/size;
|
||||
}
|
||||
vec3 shaftPosition(vec2 uv,float depth){
|
||||
float z=DistanceClipFog.y>.5?depth:depth*2.0-1.0;
|
||||
vec4 p=InverseViewProjection*vec4(uv*2.0-1.0,z,1);
|
||||
return p.xyz/p.w;
|
||||
}
|
||||
float shaftEnd(vec2 uv){
|
||||
float depth=texture(WorldDepth,uv).r;
|
||||
return depth<=0.0?ShaftSettings.y:min(ShaftSettings.y,length(shaftPosition(uv,depth)));
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
name = "Sanctuary"
|
||||
author = "KOKA99CAB"
|
||||
version = "beta.136"
|
||||
version = "beta.137"
|
||||
pack-format = "packwiz:1.1.0"
|
||||
|
||||
[index]
|
||||
|
||||
Reference in New Issue
Block a user