Drive PBR normals with cached local light directions in beta.141
Build Sanctuary / build (push) Canceled after 0s
Build Sanctuary / build (push) Canceled after 0s
This commit is contained in:
@@ -20,14 +20,11 @@ La [vision complète](docs/vision.md) conserve les intentions ; le
|
|||||||
L’économie, les claims et les dimensions décrits dans la vision restent
|
L’économie, les claims et les dimensions décrits dans la vision restent
|
||||||
à implémenter.
|
à implémenter.
|
||||||
|
|
||||||
La version **beta.140** relie les lumières colorées aux objets en main et sur
|
La version **beta.141** donne une direction approximative aux lumières locales
|
||||||
la tête, renforce leur teinte et ajoute une distance réglable de 8 à 64 blocs
|
pour orienter les normales et les reflets PBR. Les blocs posés utilisent une
|
||||||
avec apparition progressive. Activation par défaut à 30 % ; torches natives,
|
estimation en cache ; les objets portés réutilisent leur position. Les sources
|
||||||
option « Torches chaudes » désactivée, cuivre vert et redstone rouge saturé.
|
opposées adoucissent le relief, avec les fondus existants et sans nouveau bouton.
|
||||||
Les frontières de saturation des lumières bleues s’atténuent progressivement.
|
Le [ticket](docs/directional-lights-beta141.md) décrit le rendu et les vérifications.
|
||||||
Le [ticket](docs/colored-dynamic-beta140.md) décrit le rendu et les vérifications.
|
|
||||||
La beta.140 est publiée et synchronisée dans l’instance Sanctuary Beta, avec
|
|
||||||
les 923 fichiers personnels suivis conservés.
|
|
||||||
La [livraison précédente](docs/clay-workshop-integration-beta110.md) réunit déjà
|
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**.
|
l’atelier, les œufs et la neige saisonnière dans l’instance **Sanctuary Beta**.
|
||||||
Les [synchronisations antérieures](docs/git-sync-beta092.md) restent documentées.
|
Les [synchronisations antérieures](docs/git-sync-beta092.md) restent documentées.
|
||||||
@@ -38,6 +35,15 @@ La [carte native beta.005](docs/atlas-beta005.md) prend le relais après
|
|||||||
l’étude de Xaero et des alternatives libres.
|
l’étude de Xaero et des alternatives libres.
|
||||||
|
|
||||||
|
|
||||||
|
## beta.141 — Direction locale et normales PBR
|
||||||
|
|
||||||
|
Relief et reflets orientés par les lumières locales. Cache compact, confiance
|
||||||
|
réduite pour les sources opposées, objets portés et fondus. Activer PBR et
|
||||||
|
Lumières colorées ; portée PBR 32 blocs.
|
||||||
|
[Contrat et vérifications](docs/directional-lights-beta141.md) ·
|
||||||
|
[Pack normal](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.141/Sanctuary-beta.141.mrpack) ·
|
||||||
|
[Pack de test](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.141/Sanctuary-Test-beta.141.mrpack).
|
||||||
|
|
||||||
## beta.140 — Lumières portées et fondus
|
## beta.140 — Lumières portées et fondus
|
||||||
|
|
||||||
Mains et tête, distances 8/16/32/64, fondus, cuivre vert, redstone rouge et
|
Mains et tête, distances 8/16/32/64, fondus, cuivre vert, redstone rouge et
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
# LIGHT-141 — Direction locale pour les normales PBR
|
||||||
|
|
||||||
|
Socle beta.140 `c9f5c56`, branche `codex/directional-lights-beta141`.
|
||||||
|
Minecraft 26.3 / Java 25. Livraison beta.141.
|
||||||
|
|
||||||
|
## Contrat
|
||||||
|
|
||||||
|
Estimer une direction d'arrivée depuis les six voisins du champ lumineux,
|
||||||
|
avec intensité et confiance. Conserver cette estimation dans une texture
|
||||||
|
compacte reconstruite avec le champ, uniquement quand le PBR est actif.
|
||||||
|
Les sources portées utilisent leurs positions déjà disponibles. Brancher ces
|
||||||
|
directions sur les normales et reflets PBR existants, sans nouveau bouton.
|
||||||
|
Les sources opposées doivent réduire la directionnalité, sans bascule brutale.
|
||||||
|
Les champs précédent/courant gardent leur fondu et les obstacles restent pris
|
||||||
|
en compte pour les blocs posés. Aucun changement de lumière de gameplay,
|
||||||
|
de sauvegarde ou de génération ; aucune promesse de lancer de rayons exact.
|
||||||
|
|
||||||
|
## Programme de vérification
|
||||||
|
|
||||||
|
Contrôles natifs OpenGL/Vulkan : direction, sources opposées, murs, rendu de
|
||||||
|
relief en intérieur, déplacement des sources, transitions et PBR OFF.
|
||||||
|
Régression des lumières beta.139/140 puis check/build et assemblage. GameTest
|
||||||
|
serveur dédié exclu selon le refus EULA antérieur. Aucun monde personnel ouvert.
|
||||||
|
|
||||||
|
## Réalisation
|
||||||
|
|
||||||
|
Le champ RGB conserve une texture directionnelle RGBA8 supplémentaire :
|
||||||
|
les trois composantes codent la direction pondérée, l'alpha sa réponse lumineuse.
|
||||||
|
Les voisins plus lumineux et accessibles contribuent à l'estimation ; les
|
||||||
|
arrivées opposées s'annulent dans le vecteur, sans supprimer l'éclairage natif.
|
||||||
|
Le shader interpole les moments pondérés avant normalisation, y compris entre
|
||||||
|
les champs précédent et courant. La confiance réduit le relief quand aucune
|
||||||
|
direction ne domine.
|
||||||
|
|
||||||
|
Cette texture est préparée dans le budget de reconstruction existant, avec
|
||||||
|
un maximum de six voisins par cellule éclairée et une table d'énergie réutilisée.
|
||||||
|
Aucune recherche de sources ni rayons par pixel pour les blocs posés. La
|
||||||
|
lecture GPU interpole huit cellules par champ ; les deux champs sont utilisés
|
||||||
|
pendant le fondu. Les sources mobiles réutilisent les positions, intensités et
|
||||||
|
transitions déjà collectées, dans la limite existante de 128 sources.
|
||||||
|
|
||||||
|
Une texture directionnelle occupe environ 1,5 Mo à 16 blocs de distance et
|
||||||
|
19 Mo à 64 blocs, par copie CPU/GPU ; deux champs coexistent lors des transitions.
|
||||||
|
Le champ directionnel n'est construit que si PBR est actif avec une intensité
|
||||||
|
non nulle. Sa désactivation libère les directions après la transition.
|
||||||
|
Le coût est borné par ces réglages, sans garantie de FPS.
|
||||||
|
|
||||||
|
La passe PBR applique la différence entre normale de base et normale perturbée,
|
||||||
|
puis un reflet GGX local. Elle garde sa portée de 32 blocs et les réglages PBR
|
||||||
|
existants ; Lumières colorées doit être actif. Le soleil et la lune continuent
|
||||||
|
à fonctionner lorsque les lumières colorées sont coupées. Les aides FR/EN
|
||||||
|
expliquent cette dépendance.
|
||||||
|
|
||||||
|
C'est une direction dominante approximative : deux lumières colorées opposées
|
||||||
|
ne produisent pas deux reflets indépendants. Les sources mobiles gardent la
|
||||||
|
propagation radiale et les limites d'occultation existantes. Les normales PBR
|
||||||
|
concernent les surfaces de blocs prises en charge, pas tous les modèles de mobs.
|
||||||
|
Les exclusions existantes des panoramas et de l'immersion restent inchangées.
|
||||||
|
|
||||||
|
## Premier essai ciblé
|
||||||
|
|
||||||
|
L'essai natif OpenGL ciblé réussit en **1 min 12 s** : direction gauche/droite,
|
||||||
|
annulation des sources opposées avec énergie conservée, occultation par un mur,
|
||||||
|
relief sur la pierre en intérieur et avec une torche en main, stabilité au repos,
|
||||||
|
libération des directions avec PBR OFF et maintien du chemin solaire avec
|
||||||
|
Lumières colorées OFF. La somme d'écarts PBR OFF/ON sur la pierre vaut 878 114
|
||||||
|
au réglage PBR 100 % ; c'est une comparaison de pixels, pas une mesure de FPS.
|
||||||
|
Log : `build/directional141-focused.log`.
|
||||||
|
|
||||||
|
Les essais de préparation ont révélé un conflit de nom de matrice GLSL, corrigé,
|
||||||
|
puis des erreurs de fixture (angle 180° normalisé par Minecraft, suppression
|
||||||
|
d'entités alors que la sélection était vide, commande d'heure déjà à minuit),
|
||||||
|
corrigées sans changer le rendu.
|
||||||
|
|
||||||
|
## Validation complète OpenGL
|
||||||
|
|
||||||
|
La chaîne complète réussit en **3 min 1 s** sur OpenGL / Apple M1 :
|
||||||
|
`COLORED139_PASS`, `COLORED140_PASS` et `DIRECTIONAL141_PASS`.
|
||||||
|
Elle réunit les contrôles de couleurs, objets portés, caméra, occultation,
|
||||||
|
rechargement des ressources, préférences, distances/fondus et directions PBR.
|
||||||
|
Le test ciblé est reproductible avec `-PsanctuaryDirectional141Only=true` ;
|
||||||
|
la chaîne complète utilise `-PsanctuaryDirectional141ClientTests=true` avec
|
||||||
|
les deux suites de lumières existantes.
|
||||||
|
Log : `build/directional141-opengl.log`. Captures natives conservées dans
|
||||||
|
`build/directional141-opengl-screenshots/`. Comparaison autonome sans retraitement :
|
||||||
|
`build/Shader-beta.141-comparaison.html`.
|
||||||
|
|
||||||
|
## Validation complète Vulkan
|
||||||
|
|
||||||
|
La même chaîne réussit en **2 min 56 s** sur Vulkan / Apple M1, MoltenVK 1.4.2,
|
||||||
|
profondeur 0–1 et transparence améliorée. Les trois marqueurs de réussite sont
|
||||||
|
présents dans `build/directional141-vulkan.log`. L'écart PBR local OFF/ON vaut
|
||||||
|
878 107, contre 878 114 sous OpenGL dans cette scène. Les captures natives sont
|
||||||
|
conservées dans `build/directional141-vulkan-screenshots/`.
|
||||||
|
Les deux moteurs vérifient la compilation, les chemins actifs et désactivés,
|
||||||
|
le cache, les murs, les sources opposées et portées, ainsi que les régressions
|
||||||
|
beta.139/140. Pas de validation des pilotes Windows de l'utilisateur revendiquée.
|
||||||
|
|
||||||
|
## Construction et archives
|
||||||
|
|
||||||
|
`./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest`
|
||||||
|
réussit en **2 min 35 s**, 126 tâches. Le GameTest serveur dédié reste exclu
|
||||||
|
selon le refus EULA antérieur. Les suites clientes ci-dessus ont été exécutées
|
||||||
|
séparément. Log : `build/shader141-build.log`.
|
||||||
|
|
||||||
|
Comparaison à beta.140 : **8 entrées de production modifiées**, limitées au
|
||||||
|
champ lumineux, à sa préparation avant le PBR, au shader PBR et aux aides FR/EN.
|
||||||
|
Les archives normale/Test contiennent le même JAR Sanctuary, sans classes
|
||||||
|
de test. Version, dépendances exactes, sources des JAR, textures de gemmes et
|
||||||
|
de la clé de Steve, et template complet sont vérifiés.
|
||||||
|
Reçu : `build/shader141-artifact.json`.
|
||||||
|
|
||||||
|
SHA-256 du JAR : `660f29c3fa77fee4de6a8db5673c61619985804494f70e961998da36f396d0f1`.
|
||||||
|
|
||||||
|
- Sanctuary-beta.141.mrpack : `08df23dac9e98fbfa2935f747345c38244bddc3a3d9371c6416f251e85a7eb19`.
|
||||||
|
- Sanctuary-Test-beta.141.mrpack : `a300ef5bfff7b6990c9f5c2760e9c3a23d7a27aeb10b8f04d29db9a7409fa0e1`.
|
||||||
+2
-2
@@ -9,8 +9,8 @@ loom_version=1.17.20
|
|||||||
fabric_api_version=0.160.5+26.3
|
fabric_api_version=0.160.5+26.3
|
||||||
|
|
||||||
# Release counter: beta.001, .002, .003, ... (see docs/versioning.md).
|
# Release counter: beta.001, .002, .003, ... (see docs/versioning.md).
|
||||||
mod_version=beta.140
|
mod_version=beta.141
|
||||||
pack_version=beta.140
|
pack_version=beta.141
|
||||||
resource_pack_version=beta.121
|
resource_pack_version=beta.121
|
||||||
maven_group=fr.koka.sanctuary
|
maven_group=fr.koka.sanctuary
|
||||||
jei_version=30.32.0-sanctuary.3
|
jei_version=30.32.0-sanctuary.3
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ if (clientRenderTests) {
|
|||||||
if (improvedTransparency.isPresent()) {
|
if (improvedTransparency.isPresent()) {
|
||||||
systemProperty('sanctuary.test.improvedTransparency', improvedTransparency.get())
|
systemProperty('sanctuary.test.improvedTransparency', improvedTransparency.get())
|
||||||
}
|
}
|
||||||
|
systemProperty('sanctuary.test.directional141Only', providers.gradleProperty('sanctuaryDirectional141Only').getOrElse('false'))
|
||||||
|
systemProperty('sanctuary.test.directional141', providers.gradleProperty('sanctuaryDirectional141ClientTests').getOrElse('false'))
|
||||||
systemProperty('sanctuary.test.colored140', providers.gradleProperty('sanctuaryColored140ClientTests').getOrElse('false'))
|
systemProperty('sanctuary.test.colored140', providers.gradleProperty('sanctuaryColored140ClientTests').getOrElse('false'))
|
||||||
systemProperty('sanctuary.test.noVsync', providers.gradleProperty('sanctuaryClientNoVsync').getOrElse('false'))
|
systemProperty('sanctuary.test.noVsync', providers.gradleProperty('sanctuaryClientNoVsync').getOrElse('false'))
|
||||||
systemProperty('sanctuary.test.quick', providers.gradleProperty('sanctuaryQuickTests').getOrElse('false'))
|
systemProperty('sanctuary.test.quick', providers.gradleProperty('sanctuaryQuickTests').getOrElse('false'))
|
||||||
|
|||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
package fr.koka.sanctuary.client.shader;
|
||||||
|
|
||||||
|
import static fr.koka.sanctuary.client.shader.PixelShadows122ClientChecks.*;
|
||||||
|
import net.fabricmc.fabric.api.client.gametest.v1.context.*;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
|
||||||
|
/** Real enclosed stone surfaces: local normal response without sunlight. */
|
||||||
|
final class DirectionalLights141ClientChecks {
|
||||||
|
private static final BlockPos LEFT=new BlockPos(96,101,102),RIGHT=new BlockPos(104,101,102),PROBE=new BlockPos(100,101,102);
|
||||||
|
static void run(ClientGameTestContext c,TestServerContext server){
|
||||||
|
c.runOnClient(m->{VanillaLight.setEnabled(true);VanillaLight.setSaturation(100);VanillaLight.setSsgi(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);VanillaLight.setLensFlare(false);VanillaLight.setEdgeHighlights(false);VanillaLight.setPixelShadows(false);VanillaLight.setSunshafts(false);m.options.particles().set(net.minecraft.server.level.ParticleStatus.MINIMAL);VanillaLight.setColoredLights(true);VanillaLight.setColoredLightIntensity(30);VanillaLight.setColoredLightDistance(16);VanillaLight.setWarmTorches(false);VanillaLight.setPbr(false);VanillaLight.setPbrIntensity(100);});
|
||||||
|
camera(c,server,100.5,101,98.5,70);look(c,server,0,35);c.waitTicks(30);
|
||||||
|
server.runOnServer(s->{if(s.clockManager().getInstance(s.overworld().dimensionType().defaultClock().orElseThrow()).totalTicks()!=18000)command(s,"time set 18000");
|
||||||
|
for(int x=88;x<=112;x++)for(int z=92;z<=116;z++)for(int y=100;y<=109;y++)
|
||||||
|
s.overworld().setBlockAndUpdate(new BlockPos(x,y,z),(y==100)?Blocks.STONE.defaultBlockState():(y==109||x==88||x==112||z==92||z==116)?Blocks.CONCRETE.white().defaultBlockState():Blocks.AIR.defaultBlockState());
|
||||||
|
s.overworld().setBlockAndUpdate(LEFT,Blocks.TORCH.defaultBlockState());
|
||||||
|
});c.waitTicks(40);settle(c);var flat=capture(c,"direction-left-pbr-off");
|
||||||
|
c.runOnClient(m->VanillaLight.setPbr(true));c.waitFor(m->ColoredLights.directionSample(PROBE)!=0&&ProceduralPbr.ready(),2000);settle(c);
|
||||||
|
int left=c.computeOnClient(m->ColoredLights.directionSample(PROBE));check(dx(left)<-.9&&Math.abs(dy(left))<.02&&Math.abs(dz(left))<.02,"Cached arrival points toward the left source");
|
||||||
|
var relief=capture(c,"direction-left-pbr-on");long delta=difference(flat,relief);check(delta>1000,"Local light visibly affects stone normals without skylight: "+delta);
|
||||||
|
int builds=c.computeOnClient(m->ColoredLights.builds());c.waitTicks(25);check(c.computeOnClient(m->ColoredLights.builds())==builds,"Static directions reuse the cache");unchanged(relief,capture(c,null),"Static local PBR is stable");
|
||||||
|
server.runOnServer(s->{s.overworld().setBlockAndUpdate(LEFT,Blocks.AIR.defaultBlockState());s.overworld().setBlockAndUpdate(RIGHT,Blocks.TORCH.defaultBlockState());});c.waitTicks(40);settle(c);
|
||||||
|
int right=c.computeOnClient(m->ColoredLights.directionSample(PROBE));check(dx(right)>.9,"Moving the source reverses arrival direction");capture(c,"direction-right-pbr-on");
|
||||||
|
server.runOnServer(s->s.overworld().setBlockAndUpdate(LEFT,Blocks.TORCH.defaultBlockState()));c.waitTicks(40);settle(c);
|
||||||
|
int opposed=c.computeOnClient(m->ColoredLights.directionSample(PROBE));check(Math.abs(dx(opposed))<.02&&Math.abs(dy(opposed))<.02&&Math.abs(dz(opposed))<.02&&(opposed>>>24)>0,"Equal opposing lights cancel direction, retain illumination");capture(c,"direction-opposed");
|
||||||
|
// A sealed barrier must prevent both intensity and direction reaching the probe.
|
||||||
|
server.runOnServer(s->{s.overworld().setBlockAndUpdate(RIGHT,Blocks.AIR.defaultBlockState());for(int y=101;y<109;y++)for(int z=93;z<116;z++)s.overworld().setBlockAndUpdate(new BlockPos(99,y,z),Blocks.STONE.defaultBlockState());});c.waitTicks(40);settle(c);
|
||||||
|
check(c.computeOnClient(m->ColoredLights.directionSample(PROBE))==0,"Closed wall blocks local direction and energy");capture(c,"direction-blocked");
|
||||||
|
server.runOnServer(s->{s.overworld().setBlockAndUpdate(LEFT,Blocks.AIR.defaultBlockState());
|
||||||
|
for(int y=101;y<109;y++)for(int z=93;z<116;z++)s.overworld().setBlockAndUpdate(new BlockPos(99,y,z),Blocks.AIR.defaultBlockState());
|
||||||
|
command(s,"gamemode creative @a");s.getPlayerList().getPlayers().getFirst().setItemSlot(net.minecraft.world.entity.EquipmentSlot.MAINHAND,new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.TORCH));});
|
||||||
|
c.waitTicks(45);settle(c);capture(c,"direction-held-pbr-on");
|
||||||
|
c.runOnClient(m->VanillaLight.setPbr(false));c.waitTicks(35);settle(c);var heldFlat=capture(c,"direction-held-pbr-off");
|
||||||
|
c.runOnClient(m->VanillaLight.setPbr(true));c.waitTicks(35);settle(c);var heldRelief=capture(c,"direction-held-pbr-on-final");
|
||||||
|
long heldDelta=difference(heldFlat,heldRelief);check(heldDelta>1000&&c.computeOnClient(m->ColoredLights.dynamicCount())==1,"Carried light drives local PBR: "+heldDelta);
|
||||||
|
server.runOnServer(s->s.getPlayerList().getPlayers().getFirst().setItemSlot(net.minecraft.world.entity.EquipmentSlot.MAINHAND,net.minecraft.world.item.ItemStack.EMPTY));c.waitTicks(35);
|
||||||
|
c.runOnClient(m->VanillaLight.setPbr(false));c.waitTicks(35);settle(c);check(c.computeOnClient(m->ColoredLights.directionSample(LEFT))==0,"PBR OFF releases directional field");
|
||||||
|
c.runOnClient(m->{VanillaLight.setColoredLights(false);VanillaLight.setPbr(true);});c.waitTicks(25);capture(c,"direction-colored-off");
|
||||||
|
check(!c.computeOnClient(m->ColoredLights.allocated()),"Color OFF releases local field while PBR keeps its solar path");
|
||||||
|
System.out.println("DIRECTIONAL141_PASS localPbrDelta="+delta+" native normals, left/right, opposing sources, wall, held light, stable cache, OFF");
|
||||||
|
}
|
||||||
|
private static double dx(int c){return ((c>>16&255)-128)/127.;}
|
||||||
|
private static double dy(int c){return ((c>>8&255)-128)/127.;}
|
||||||
|
private static double dz(int c){return ((c&255)-128)/127.;}
|
||||||
|
private static void settle(ClientGameTestContext c){c.waitFor(m->ColoredLights.applied()&&ColoredLights.settled(),2400);c.waitTicks(14);}
|
||||||
|
private static long difference(Frame a,Frame b){long n=0;for(int i=0;i<a.pixels().length;i++)n+=channelDifference(a.pixels()[i],b.pixels()[i]);return n;}
|
||||||
|
}
|
||||||
+3
-3
@@ -149,7 +149,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
|
|||||||
c.runOnClient(m -> check(VanillaLight.active(), "Native Sanctuary shader is active without an external engine"));
|
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);
|
float morning = c.computeOnClient(m -> m.gameRenderer.gameRenderState().levelRenderState.skyRenderState.sunAngle);
|
||||||
|
|
||||||
if(coloredOnly){ColoredLights139ClientChecks.run(c,server);if(Boolean.getBoolean("sanctuary.test.colored140"))ColoredLights140ClientChecks.run(c,server);return;}
|
if(coloredOnly){if(Boolean.getBoolean("sanctuary.test.directional141Only")){DirectionalLights141ClientChecks.run(c,server);return;}ColoredLights139ClientChecks.run(c,server);if(Boolean.getBoolean("sanctuary.test.colored140"))ColoredLights140ClientChecks.run(c,server);if(Boolean.getBoolean("sanctuary.test.directional141"))DirectionalLights141ClientChecks.run(c,server);return;}
|
||||||
if(sunshaftOnly){Sunshafts137ClientChecks.run(c,server);return;}
|
if(sunshaftOnly){Sunshafts137ClientChecks.run(c,server);return;}
|
||||||
if(closeupOnly){SsgiCloseup135ClientChecks.run(c,server);Pbr134ClientChecks.run(c,server);return;}
|
if(closeupOnly){SsgiCloseup135ClientChecks.run(c,server);Pbr134ClientChecks.run(c,server);return;}
|
||||||
if(pbrOnly){Pbr134ClientChecks.run(c,server);return;}
|
if(pbrOnly){Pbr134ClientChecks.run(c,server);return;}
|
||||||
@@ -281,7 +281,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
c.waitTicks(3);
|
c.waitTicks(3);
|
||||||
c.takeScreenshot("sanctuary-beta140-shader-options-" + locale);
|
c.takeScreenshot("sanctuary-beta141-shader-options-" + locale);
|
||||||
}
|
}
|
||||||
c.runOnClient(m -> m.gui.setScreen(null));
|
c.runOnClient(m -> m.gui.setScreen(null));
|
||||||
}
|
}
|
||||||
@@ -565,7 +565,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Frame capture(ClientGameTestContext c, String name) {
|
static Frame capture(ClientGameTestContext c, String name) {
|
||||||
if (name != null) c.takeScreenshot("sanctuary-beta140-shader-" + name);
|
if (name != null) c.takeScreenshot("sanctuary-beta141-shader-" + name);
|
||||||
var result = new CompletableFuture<Frame>();
|
var result = new CompletableFuture<Frame>();
|
||||||
c.runOnClient(m -> Screenshot.takeScreenshot(m.gameRenderer.mainRenderTarget(), image -> {
|
c.runOnClient(m -> Screenshot.takeScreenshot(m.gameRenderer.mainRenderTarget(), image -> {
|
||||||
try (image) {
|
try (image) {
|
||||||
|
|||||||
+38
-11
@@ -22,7 +22,10 @@ final class ColoredLightVolume implements AutoCloseable {
|
|||||||
private ClientLevel level;
|
private ClientLevel level;
|
||||||
private Object models;
|
private Object models;
|
||||||
private Build build;
|
private Build build;
|
||||||
private DynamicTexture texture,previous;
|
private DynamicTexture texture,previous,direction,previousDirection;
|
||||||
|
private boolean directional;
|
||||||
|
DynamicTexture direction(){return direction;}
|
||||||
|
DynamicTexture previousDirection(){return previousDirection;}
|
||||||
private BlockPos previousOrigin;
|
private BlockPos previousOrigin;
|
||||||
private int side,previousSide;
|
private int side,previousSide;
|
||||||
private long transitionStarted;
|
private long transitionStarted;
|
||||||
@@ -40,18 +43,20 @@ final class ColoredLightVolume implements AutoCloseable {
|
|||||||
if(level!=mc.level||models!=modelSet){close();level=mc.level;models=modelSet;}
|
if(level!=mc.level||models!=modelSet){close();level=mc.level;models=modelSet;}
|
||||||
if(warm!=VanillaLight.warmTorches()){warm=VanillaLight.warmTorches();materials.clear();revision++;if(build!=null){build.close();build=null;}}
|
if(warm!=VanillaLight.warmTorches()){warm=VanillaLight.warmTorches();materials.clear();revision++;if(build!=null){build.close();build=null;}}
|
||||||
var p=mc.gameRenderer.gameRenderState().levelRenderState.cameraRenderState.pos;
|
var p=mc.gameRenderer.gameRenderState().levelRenderState.cameraRenderState.pos;
|
||||||
|
boolean wantedDirection=VanillaLight.pbr()&&VanillaLight.pbrIntensity()>0;
|
||||||
int wantedSide=sideFor(VanillaLight.coloredLightDistance());
|
int wantedSide=sideFor(VanillaLight.coloredLightDistance());
|
||||||
var wanted=new BlockPos(anchor(p.x,wantedSide),anchor(p.y,wantedSide),anchor(p.z,wantedSide));
|
var wanted=new BlockPos(anchor(p.x,wantedSide),anchor(p.y,wantedSide),anchor(p.z,wantedSide));
|
||||||
long now=System.nanoTime();
|
long now=System.nanoTime();
|
||||||
if(previous!=null&&blend()>=1){previous.close();previous=null;previousOrigin=null;}
|
if(previous!=null&&blend()>=1){previous.close();previous=null;previousOrigin=null;if(previousDirection!=null){previousDirection.close();previousDirection=null;}}
|
||||||
// Complete a snapshot even if the world keeps changing, then catch up; moving
|
// Complete a snapshot even if the world keeps changing, then catch up; moving
|
||||||
// beyond it cancels immediately. Static worlds do no further scanning or flooding.
|
// beyond it cancels immediately. Static worlds do no further scanning or flooding.
|
||||||
if(build!=null&&(build.side!=wantedSide||!inside(build.origin,BlockPos.containing(p),build.side))){build.close();build=null;}
|
if(build!=null&&(build.directional!=wantedDirection||build.side!=wantedSide||!inside(build.origin,BlockPos.containing(p),build.side))){build.close();build=null;}
|
||||||
if(build==null&&previous==null&&(texture==null||side!=wantedSide||!wanted.equals(origin)||published!=revision)&&now-lastStart>=100_000_000L){
|
if(build==null&&previous==null&&(texture==null||directional!=wantedDirection||side!=wantedSide||!wanted.equals(origin)||published!=revision)&&now-lastStart>=100_000_000L){
|
||||||
build=new Build(level,wanted,wantedSide,revision,materials);lastStart=now;
|
build=new Build(level,wanted,wantedSide,revision,materials,wantedDirection);lastStart=now;
|
||||||
}
|
}
|
||||||
if(build!=null&&build.advance(now+2_000_000L)){
|
if(build!=null&&build.advance(now+2_000_000L)){
|
||||||
previous=texture;previousOrigin=origin;previousSide=side;
|
previous=texture;previousOrigin=origin;previousSide=side;previousDirection=direction;
|
||||||
|
direction=build.directionImage==null?null:new DynamicTexture(()->"Sanctuary light direction",build.takeDirectionImage());directional=build.directional;
|
||||||
texture=new DynamicTexture(()->"Sanctuary coloured light volume",build.takeImage());
|
texture=new DynamicTexture(()->"Sanctuary coloured light volume",build.takeImage());
|
||||||
side=build.side;transitionStarted=System.nanoTime();
|
side=build.side;transitionStarted=System.nanoTime();
|
||||||
origin=build.origin;published=build.revision;build.close();build=null;completed++;
|
origin=build.origin;published=build.revision;build.close();build=null;completed++;
|
||||||
@@ -68,9 +73,10 @@ final class ColoredLightVolume implements AutoCloseable {
|
|||||||
BlockPos origin(){return origin;}
|
BlockPos origin(){return origin;}
|
||||||
boolean settled(){return texture!=null&&build==null&&revision==published&&blend()>=1;}
|
boolean settled(){return texture!=null&&build==null&&revision==published&&blend()>=1;}
|
||||||
int completed(){return completed;}
|
int completed(){return completed;}
|
||||||
|
int directionSample(BlockPos p){if(direction==null||!inside(origin,p,side))return 0;int x=p.getX()-origin.getX(),y=p.getY()-origin.getY(),z=p.getZ()-origin.getZ(),cols=columns(side);return direction.getPixels().getPixel(x+(y%cols)*side,z+(y/cols)*side);}
|
||||||
int cachedStates(){return materials.cachedStates();}
|
int cachedStates(){return materials.cachedStates();}
|
||||||
int sample(BlockPos p){if(texture==null||!inside(origin,p,side))return 0;int x=p.getX()-origin.getX(),y=p.getY()-origin.getY(),z=p.getZ()-origin.getZ(),cols=columns(side);return texture.getPixels().getPixel(x+(y%cols)*side,z+(y/cols)*side);}
|
int sample(BlockPos p){if(texture==null||!inside(origin,p,side))return 0;int x=p.getX()-origin.getX(),y=p.getY()-origin.getY(),z=p.getZ()-origin.getZ(),cols=columns(side);return texture.getPixels().getPixel(x+(y%cols)*side,z+(y/cols)*side);}
|
||||||
public void close(){if(build!=null){build.close();build=null;}if(texture!=null){texture.close();texture=null;}if(previous!=null){previous.close();previous=null;}materials.clear();level=null;models=null;origin=null;previousOrigin=null;side=previousSide=0;published=-1;revision=0;lastStart=0;}
|
public void close(){if(build!=null){build.close();build=null;}if(texture!=null){texture.close();texture=null;}if(previous!=null){previous.close();previous=null;}if(direction!=null){direction.close();direction=null;}if(previousDirection!=null){previousDirection.close();previousDirection=null;}materials.clear();level=null;models=null;origin=null;previousOrigin=null;side=previousSide=0;published=-1;revision=0;lastStart=0;}
|
||||||
|
|
||||||
private static final class Build implements AutoCloseable {
|
private static final class Build implements AutoCloseable {
|
||||||
final int side,count,cols,chunkSide;
|
final int side,count,cols,chunkSide;
|
||||||
@@ -80,11 +86,14 @@ final class ColoredLightVolume implements AutoCloseable {
|
|||||||
final boolean[] queued;final IntArrayFIFOQueue queue=new IntArrayFIFOQueue();
|
final boolean[] queued;final IntArrayFIFOQueue queue=new IntArrayFIFOQueue();
|
||||||
final LevelChunk[] chunks;final int cx,cz;
|
final LevelChunk[] chunks;final int cx,cz;
|
||||||
final BlockPos.MutableBlockPos pos=new BlockPos.MutableBlockPos();
|
final BlockPos.MutableBlockPos pos=new BlockPos.MutableBlockPos();
|
||||||
NativeImage image;int scan,packed;
|
NativeImage image,directionImage;final boolean directional;int scan,packed;
|
||||||
Build(ClientLevel level,BlockPos origin,int side,long revision,ColoredLightMaterials materials){
|
private static final double[] ENERGY=new double[256];
|
||||||
this.side=side;count=side*side*side;cols=columns(side);chunkSide=(side+15)/16+1;
|
static{for(int i=129;i<256;i++)ENERGY[i]=Math.max(0,(Math.pow(.8,31-i/8.)-Math.pow(.8,15))/(1-Math.pow(.8,15)));}
|
||||||
|
Build(ClientLevel level,BlockPos origin,int side,long revision,ColoredLightMaterials materials,boolean directional){
|
||||||
|
this.directional=directional;this.side=side;count=side*side*side;cols=columns(side);chunkSide=(side+15)/16+1;
|
||||||
states=new BlockState[count];red=new byte[count];green=new byte[count];blue=new byte[count];sky=new byte[count];queued=new boolean[count];chunks=new LevelChunk[chunkSide*chunkSide];
|
states=new BlockState[count];red=new byte[count];green=new byte[count];blue=new byte[count];sky=new byte[count];queued=new boolean[count];chunks=new LevelChunk[chunkSide*chunkSide];
|
||||||
image=new NativeImage(cols*side,((side+cols-1)/cols)*side,false);
|
image=new NativeImage(cols*side,((side+cols-1)/cols)*side,false);
|
||||||
|
if(directional)directionImage=new NativeImage(image.getWidth(),image.getHeight(),false);
|
||||||
this.level=level;this.origin=origin;this.revision=revision;this.materials=materials;
|
this.level=level;this.origin=origin;this.revision=revision;this.materials=materials;
|
||||||
cx=origin.getX()>>4;cz=origin.getZ()>>4;
|
cx=origin.getX()>>4;cz=origin.getZ()>>4;
|
||||||
for(int z=0;z<chunkSide;z++)for(int x=0;x<chunkSide;x++)chunks[x+z*chunkSide]=level.getChunkSource().getChunk(cx+x,cz+z,ChunkStatus.FULL,false);
|
for(int z=0;z<chunkSide;z++)for(int x=0;x<chunkSide;x++)chunks[x+z*chunkSide]=level.getChunkSource().getChunk(cx+x,cz+z,ChunkStatus.FULL,false);
|
||||||
@@ -99,6 +108,7 @@ final class ColoredLightVolume implements AutoCloseable {
|
|||||||
while(packed<count){
|
while(packed<count){
|
||||||
int i=packed++,x=i%side,z=i/side%side,y=i/(side*side);
|
int i=packed++,x=i%side,z=i/side%side,y=i/(side*side);
|
||||||
image.setPixel(x+(y%cols)*side,z+(y/cols)*side,(sky[i]&255)<<24|(red[i]&255)<<16|(green[i]&255)<<8|(blue[i]&255));
|
image.setPixel(x+(y%cols)*side,z+(y/cols)*side,(sky[i]&255)<<24|(red[i]&255)<<16|(green[i]&255)<<8|(blue[i]&255));
|
||||||
|
if(directional)directionImage.setPixel(x+(y%cols)*side,z+(y/cols)*side,directionAt(i,x,y,z));
|
||||||
if((packed&511)==0&&System.nanoTime()>=deadline)return false;
|
if((packed&511)==0&&System.nanoTime()>=deadline)return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -124,9 +134,26 @@ final class ColoredLightVolume implements AutoCloseable {
|
|||||||
if(changed)enqueue(n);
|
if(changed)enqueue(n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
double energy(int i){return ENERGY[Math.max(red[i]&255,Math.max(green[i]&255,blue[i]&255))];}
|
||||||
|
int directionAt(int i,int x,int y,int z){
|
||||||
|
double energy=energy(i);if(energy<=0)return 0;
|
||||||
|
double vx=0,vy=0,vz=0,total=0;
|
||||||
|
for(var d:DIRECTIONS){
|
||||||
|
int nx=x+d.getStepX(),ny=y+d.getStepY(),nz=z+d.getStepZ();
|
||||||
|
if(nx<0||nx>=side||ny<0||ny>=side||nz<0||nz>=side)continue;
|
||||||
|
int n=nx+nz*side+ny*side*side;double w=Math.max(0,energy(n)-energy);
|
||||||
|
if(w<=0||net.minecraft.world.level.lighting.LightEngine.getLightDampeningInto(states[n],states[i],d.getOpposite(),Math.max(1,states[i].getLightDampening()))>=15)continue;
|
||||||
|
vx+=d.getStepX()*w;vy+=d.getStepY()*w;vz+=d.getStepZ()*w;total+=w;
|
||||||
|
}
|
||||||
|
// The vector length is confidence: opposing arrivals cancel instead of flipping.
|
||||||
|
double scale=total>1e-12?127/total:0;
|
||||||
|
int a=(int)Math.round(255*energy/(energy+.08));
|
||||||
|
return a<<24|(int)Math.round(128+vx*scale)<<16|(int)Math.round(128+vy*scale)<<8|(int)Math.round(128+vz*scale);
|
||||||
|
}
|
||||||
|
NativeImage takeDirectionImage(){var result=directionImage;directionImage=null;return result;}
|
||||||
void enqueue(int i){if(!queued[i]){queued[i]=true;queue.enqueue(i);}}
|
void enqueue(int i){if(!queued[i]){queued[i]=true;queue.enqueue(i);}}
|
||||||
NativeImage takeImage(){var result=image;image=null;return result;}
|
NativeImage takeImage(){var result=image;image=null;return result;}
|
||||||
public void close(){if(image!=null){image.close();image=null;}}
|
public void close(){if(image!=null){image.close();image=null;}if(directionImage!=null){directionImage.close();directionImage=null;}}
|
||||||
}
|
}
|
||||||
// An offset preserves dim colour channels below native level zero. The shader
|
// An offset preserves dim colour channels below native level zero. The shader
|
||||||
// applies the native cutoff to overall energy, never to each channel separately.
|
// applies the native cutoff to overall energy, never to each channel separately.
|
||||||
|
|||||||
@@ -39,15 +39,15 @@ public final class ColoredLights {
|
|||||||
private static float displayedDistance;
|
private static float displayedDistance;
|
||||||
private static int dynamicCount;
|
private static int dynamicCount;
|
||||||
private static MappableRingBuffer uniforms;
|
private static MappableRingBuffer uniforms;
|
||||||
private static boolean applied;
|
private static boolean applied,prepared;
|
||||||
private static int frames;
|
private static int frames;
|
||||||
private ColoredLights(){}
|
private ColoredLights(){}
|
||||||
public static void captureProjection(Matrix4f matrix){projection.set(matrix);}
|
public static void captureProjection(Matrix4f matrix){projection.set(matrix);}
|
||||||
public static void dirtyBlock(BlockPos pos){VOLUME.dirty(pos);}
|
public static void dirtyBlock(BlockPos pos){VOLUME.dirty(pos);}
|
||||||
public static void dirtyChunk(int x,int z){VOLUME.dirtyChunk(x,z);}
|
public static void dirtyChunk(int x,int z){VOLUME.dirtyChunk(x,z);}
|
||||||
public static void dirtySection(int x,int y,int z){if(!fr.koka.sanctuary.client.DynamicLightClient.invalidatingSections())VOLUME.dirtySection(x,y,z);}
|
public static void dirtySection(int x,int y,int z){if(!fr.koka.sanctuary.client.DynamicLightClient.invalidatingSections())VOLUME.dirtySection(x,y,z);}
|
||||||
public static void render(){
|
public static void prepare(){
|
||||||
applied=false;var mc=Minecraft.getInstance();
|
applied=false;prepared=false;var mc=Minecraft.getInstance();
|
||||||
if(!VanillaLight.active()||!VanillaLight.coloredLights()||VanillaLight.coloredLightIntensity()==0||mc.level==null){release();return;}
|
if(!VanillaLight.active()||!VanillaLight.coloredLights()||VanillaLight.coloredLightIntensity()==0||mc.level==null){release();return;}
|
||||||
if(PanoramaCapture.rendering())return;
|
if(PanoramaCapture.rendering())return;
|
||||||
var camera=mc.gameRenderer.gameRenderState().levelRenderState.cameraRenderState;
|
var camera=mc.gameRenderer.gameRenderState().levelRenderState.cameraRenderState;
|
||||||
@@ -78,6 +78,20 @@ public final class ColoredLights {
|
|||||||
for(int i=0;i<ColoredDynamicLights.LIMIT;i++){if(i<lights.size()){var l=lights.get(i);data.putVec4((float)(l.x-camera.pos.x),(float)(l.y-camera.pos.y),(float)(l.z-camera.pos.z),l.level);}else data.putVec4(0,0,0,0);}
|
for(int i=0;i<ColoredDynamicLights.LIMIT;i++){if(i<lights.size()){var l=lights.get(i);data.putVec4((float)(l.x-camera.pos.x),(float)(l.y-camera.pos.y),(float)(l.z-camera.pos.z),l.level);}else data.putVec4(0,0,0,0);}
|
||||||
for(int i=0;i<ColoredDynamicLights.LIMIT;i++){if(i<lights.size()){var l=lights.get(i);data.putVec4((float)l.r,(float)l.g,(float)l.b,(float)l.visibility);}else data.putVec4(0,0,0,0);}
|
for(int i=0;i<ColoredDynamicLights.LIMIT;i++){if(i<lights.size()){var l=lights.get(i);data.putVec4((float)l.r,(float)l.g,(float)l.b,(float)l.visibility);}else data.putVec4(0,0,0,0);}
|
||||||
}
|
}
|
||||||
|
prepared=true;
|
||||||
|
}
|
||||||
|
static void bindDirections(com.mojang.renderpearl.api.commands.RenderPass pass){
|
||||||
|
var nearest=RenderSystem.getSamplerCache().getClampToEdge(FilterMode.NEAREST);
|
||||||
|
pass.setUniform("ColoredLightScene",uniforms.currentBuffer());
|
||||||
|
pass.setUniform("LightDirection",(VOLUME.direction()==null?black:VOLUME.direction()).getTextureView(),nearest);
|
||||||
|
pass.setUniform("PreviousDirection",(VOLUME.previousDirection()==null?black:VOLUME.previousDirection()).getTextureView(),nearest);
|
||||||
|
}
|
||||||
|
static boolean directionalReady(){return prepared&&VanillaLight.pbr();}
|
||||||
|
public static void render(){
|
||||||
|
if(!prepared)return;
|
||||||
|
var main=Minecraft.getInstance().gameRenderer.mainRenderTarget();
|
||||||
|
var current=VOLUME.texture()==null?black:VOLUME.texture();
|
||||||
|
var previous=VOLUME.previous()==null?black:VOLUME.previous();
|
||||||
var encoder=RenderSystem.getDevice().createCommandEncoder();
|
var encoder=RenderSystem.getDevice().createCommandEncoder();
|
||||||
encoder.copyTextureToTexture(main.getColorTexture(),source.getColorTexture(),0,0,0,0,0,main.width,main.height);
|
encoder.copyTextureToTexture(main.getColorTexture(),source.getColorTexture(),0,0,0,0,0,main.width,main.height);
|
||||||
var nearest=RenderSystem.getSamplerCache().getClampToEdge(FilterMode.NEAREST);
|
var nearest=RenderSystem.getSamplerCache().getClampToEdge(FilterMode.NEAREST);
|
||||||
@@ -87,7 +101,7 @@ public final class ColoredLights {
|
|||||||
}
|
}
|
||||||
applied=true;frames++;
|
applied=true;frames++;
|
||||||
}
|
}
|
||||||
public static void release(){VOLUME.close();DYNAMIC.clear();activated=lastRender=0;dynamicCount=0;if(black!=null){black.close();black=null;}if(source!=null){source.destroyBuffers();source=null;}if(uniforms!=null){uniforms.close();uniforms=null;}applied=false;}
|
public static void release(){prepared=false;VOLUME.close();DYNAMIC.clear();activated=lastRender=0;dynamicCount=0;if(black!=null){black.close();black=null;}if(source!=null){source.destroyBuffers();source=null;}if(uniforms!=null){uniforms.close();uniforms=null;}applied=false;}
|
||||||
static boolean applied(){return applied;}
|
static boolean applied(){return applied;}
|
||||||
static boolean allocated(){return source!=null||VOLUME.texture()!=null;}
|
static boolean allocated(){return source!=null||VOLUME.texture()!=null;}
|
||||||
static boolean settled(){return VOLUME.settled();}
|
static boolean settled(){return VOLUME.settled();}
|
||||||
@@ -98,5 +112,6 @@ public final class ColoredLights {
|
|||||||
static int dynamicCount(){return dynamicCount;}
|
static int dynamicCount(){return dynamicCount;}
|
||||||
static float blend(){return VOLUME.blend();}
|
static float blend(){return VOLUME.blend();}
|
||||||
static int volumeSide(){return VOLUME.side();}
|
static int volumeSide(){return VOLUME.side();}
|
||||||
|
static int directionSample(BlockPos pos){return VOLUME.directionSample(pos);}
|
||||||
static int materials(){return VOLUME.cachedStates();}
|
static int materials(){return VOLUME.cachedStates();}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ public final class ProceduralPbr {
|
|||||||
private static final BindGroupLayout SCENE=BindGroupLayout.builder().withUniform("PbrScene",UniformType.UNIFORM_BUFFER)
|
private static final BindGroupLayout SCENE=BindGroupLayout.builder().withUniform("PbrScene",UniformType.UNIFORM_BUFFER)
|
||||||
.withUniform("PixelShadows",UniformType.UNIFORM_BUFFER).withUniform("SunDepth",UniformType.COMBINED_IMAGE_SAMPLER)
|
.withUniform("PixelShadows",UniformType.UNIFORM_BUFFER).withUniform("SunDepth",UniformType.COMBINED_IMAGE_SAMPLER)
|
||||||
.withUniform("WorldDepth",UniformType.COMBINED_IMAGE_SAMPLER).withUniform("SceneColor",UniformType.COMBINED_IMAGE_SAMPLER)
|
.withUniform("WorldDepth",UniformType.COMBINED_IMAGE_SAMPLER).withUniform("SceneColor",UniformType.COMBINED_IMAGE_SAMPLER)
|
||||||
|
.withUniform("ColoredLightScene",UniformType.UNIFORM_BUFFER).withUniform("LightDirection",UniformType.COMBINED_IMAGE_SAMPLER).withUniform("PreviousDirection",UniformType.COMBINED_IMAGE_SAMPLER)
|
||||||
.withUniform("BlockAtlas",UniformType.COMBINED_IMAGE_SAMPLER).withUniform("MaterialAtlas",UniformType.COMBINED_IMAGE_SAMPLER).build();
|
.withUniform("BlockAtlas",UniformType.COMBINED_IMAGE_SAMPLER).withUniform("MaterialAtlas",UniformType.COMBINED_IMAGE_SAMPLER).build();
|
||||||
private static final RenderPipeline PIPELINE=RenderPipelines.register(RenderPipeline.builder()
|
private static final RenderPipeline PIPELINE=RenderPipelines.register(RenderPipeline.builder()
|
||||||
.withLocation(Identifier.parse("sanctuary:pipeline/pbr")).withVertexShader(Identifier.parse("sanctuary:core/pbr"))
|
.withLocation(Identifier.parse("sanctuary:pipeline/pbr")).withVertexShader(Identifier.parse("sanctuary:core/pbr"))
|
||||||
@@ -52,8 +53,8 @@ public final class ProceduralPbr {
|
|||||||
if(PanoramaCapture.rendering()||camera.fogType!=FogType.NONE||sky.skybox!=DimensionType.Skybox.OVERWORLD)return;
|
if(PanoramaCapture.rendering()||camera.fogType!=FogType.NONE||sky.skybox!=DimensionType.Skybox.OVERWORLD)return;
|
||||||
var main=mc.gameRenderer.mainRenderTarget();
|
var main=mc.gameRenderer.mainRenderTarget();
|
||||||
if(source==null||source.width!=main.width||source.height!=main.height){if(source!=null)source.destroyBuffers();source=new TextureTarget("Sanctuary PBR source",main.width,main.height,GpuFormat.RGBA8_UNORM,null);}
|
if(source==null||source.width!=main.width||source.height!=main.height){if(source!=null)source.destroyBuffers();source=new TextureTarget("Sanctuary PBR source",main.width,main.height,GpuFormat.RGBA8_UNORM,null);}
|
||||||
// The extra padding also supplies a valid inactive PixelShadows binding (192 bytes).
|
// Padding supplies valid inactive PixelShadows and local-light uniform bindings.
|
||||||
if(scene==null)scene=new MappableRingBuffer(()->"Sanctuary PBR scene",GpuBuffer.USAGE_UNIFORM|GpuBuffer.USAGE_MAP_WRITE,192);
|
if(scene==null)scene=new MappableRingBuffer(()->"Sanctuary PBR scene",GpuBuffer.USAGE_UNIFORM|GpuBuffer.USAGE_MAP_WRITE,176+ColoredDynamicLights.LIMIT*32);
|
||||||
var entries=TERRAIN.update();var atlas=TERRAIN.materials.atlas();
|
var entries=TERRAIN.update();var atlas=TERRAIN.materials.atlas();
|
||||||
var vp=new Matrix4f(projection).mul(camera.viewRotationMatrix);var fog=camera.fogData;
|
var vp=new Matrix4f(projection).mul(camera.viewRotationMatrix);var fog=camera.fogData;
|
||||||
var sun=PixelShadows.sunDirection(sky.sunAngle);boolean lunar=sun.y<=.08f;
|
var sun=PixelShadows.sunDirection(sky.sunAngle);boolean lunar=sun.y<=.08f;
|
||||||
@@ -65,7 +66,7 @@ public final class ProceduralPbr {
|
|||||||
Std140Builder.intoBuffer(mapped.data()).putMat4f(new Matrix4f(vp).invert())
|
Std140Builder.intoBuffer(mapped.data()).putMat4f(new Matrix4f(vp).invert())
|
||||||
.putVec4(sun.x,sun.y,sun.z,strength)
|
.putVec4(sun.x,sun.y,sun.z,strength)
|
||||||
.putVec4(VanillaLight.pbrIntensity()/100f,RenderSystem.getDevice().getDeviceInfo().isZZeroToOne()?1:0,shadow?1:0,32)
|
.putVec4(VanillaLight.pbrIntensity()/100f,RenderSystem.getDevice().getDeviceInfo().isZZeroToOne()?1:0,shadow?1:0,32)
|
||||||
.putVec4(fog.renderDistanceStart,fog.renderDistanceEnd,fog.environmentalStart,fog.environmentalEnd).putVec4(fog.color.w,0,0,0)
|
.putVec4(fog.renderDistanceStart,fog.renderDistanceEnd,fog.environmentalStart,fog.environmentalEnd).putVec4(fog.color.w,ColoredLights.directionalReady()?1:0,0,0)
|
||||||
.putVec4(0,0,0,0).putVec4(0,0,0,0).putVec4(0,0,0,0).putVec4(0,0,0,0);
|
.putVec4(0,0,0,0).putVec4(0,0,0,0).putVec4(0,0,0,0).putVec4(0,0,0,0);
|
||||||
}
|
}
|
||||||
var transforms=new ArrayList<GpuBufferSlice>(entries.size());int max=0;
|
var transforms=new ArrayList<GpuBufferSlice>(entries.size());int max=0;
|
||||||
@@ -81,7 +82,10 @@ public final class ProceduralPbr {
|
|||||||
pass.setUniform("SunDepth",shadow?PixelShadows.materialDepth():main.getDepthTextureView(),nearest);
|
pass.setUniform("SunDepth",shadow?PixelShadows.materialDepth():main.getDepthTextureView(),nearest);
|
||||||
pass.setUniform("WorldDepth",main.getDepthTextureView(),nearest);pass.setUniform("SceneColor",source.getColorTextureView(),nearest);
|
pass.setUniform("WorldDepth",main.getDepthTextureView(),nearest);pass.setUniform("SceneColor",source.getColorTextureView(),nearest);
|
||||||
pass.setUniform("BlockAtlas",mc.getTextureManager().getTexture(TextureAtlas.LOCATION_BLOCKS).getTextureView(),nearest);
|
pass.setUniform("BlockAtlas",mc.getTextureManager().getTexture(TextureAtlas.LOCATION_BLOCKS).getTextureView(),nearest);
|
||||||
pass.setUniform("MaterialAtlas",atlas.getTextureView(),nearest);pass.setIndexBuffer(indices.getBuffer(),indices.type());
|
pass.setUniform("MaterialAtlas",atlas.getTextureView(),nearest);
|
||||||
|
if(ColoredLights.directionalReady())ColoredLights.bindDirections(pass);
|
||||||
|
else{pass.setUniform("ColoredLightScene",scene.currentBuffer());pass.setUniform("LightDirection",source.getColorTextureView(),nearest);pass.setUniform("PreviousDirection",source.getColorTextureView(),nearest);}
|
||||||
|
pass.setIndexBuffer(indices.getBuffer(),indices.type());
|
||||||
for(int i=0;i<entries.size();i++){var entry=entries.get(i);pass.setUniform("DynamicTransforms",transforms.get(i));pass.setVertexBuffer(0,entry.vertices().slice());pass.drawIndexed(entry.indices(),1,0,0,0);}
|
for(int i=0;i<entries.size();i++){var entry=entries.get(i);pass.setUniform("DynamicTransforms",transforms.get(i));pass.setVertexBuffer(0,entry.vertices().slice());pass.drawIndexed(entry.indices(),1,0,0,0);}
|
||||||
}
|
}
|
||||||
applied=true;frames++;
|
applied=true;frames++;
|
||||||
|
|||||||
+1
-1
@@ -31,7 +31,7 @@ abstract class PixelShadowRendererMixin {
|
|||||||
return original.call(buffer, matrix);
|
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"))
|
@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(); ColoredLights.render(); ScreenSpaceGI.render(); EdgeHighlights.render(); Bloom.render(); Sunshafts.render(); }
|
private void sanctuary$shadows(CallbackInfo ci) { ColoredLights.prepare(); PixelShadows.render(); ProceduralPbr.render(); ColoredLights.render(); ScreenSpaceGI.render(); EdgeHighlights.render(); Bloom.render(); Sunshafts.render(); }
|
||||||
@Inject(method={"close", "setLevel"}, at=@At("HEAD"))
|
@Inject(method={"close", "setLevel"}, at=@At("HEAD"))
|
||||||
private void sanctuary$close(CallbackInfo ci) { PixelShadows.release(); ProceduralPbr.release(); EdgeHighlights.release(); Bloom.release(); ColoredLights.release(); ScreenSpaceGI.release(); Sunshafts.release(); }
|
private void sanctuary$close(CallbackInfo ci) { PixelShadows.release(); ProceduralPbr.release(); EdgeHighlights.release(); Bloom.release(); ColoredLights.release(); ScreenSpaceGI.release(); Sunshafts.release(); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2295,7 +2295,7 @@
|
|||||||
"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_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.ssgi_intensity_value": "SSGI: %s%%",
|
||||||
"sanctuary.shaders.pbr": "PBR · material relief",
|
"sanctuary.shaders.pbr": "PBR · material relief",
|
||||||
"sanctuary.shaders.pbr_help": "Experimental: procedural block normals and roughness, sun and moon highlights. Range 32 blocks. ON by default.",
|
"sanctuary.shaders.pbr_help": "Experimental: procedural block normals and roughness, sun, moon and local light highlights. Enable Colored lights for local direction. Range 32 blocks. ON by default.",
|
||||||
"sanctuary.shaders.pbr_intensity": "PBR strength",
|
"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_help": "Material relief and highlights. Zero stops rendering and frees the cache. Original textures remain unchanged.",
|
||||||
"sanctuary.shaders.pbr_intensity_value": "PBR: %s%%",
|
"sanctuary.shaders.pbr_intensity_value": "PBR: %s%%",
|
||||||
|
|||||||
@@ -2295,7 +2295,7 @@
|
|||||||
"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_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.ssgi_intensity_value": "SSGI : %s %%",
|
||||||
"sanctuary.shaders.pbr": "PBR · relief des matériaux",
|
"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. Activé par défaut.",
|
"sanctuary.shaders.pbr_help": "Expérimental : normales procédurales et rugosité des blocs, reflets du soleil, de la lune et des lumières locales. Activer Lumières colorées pour la direction locale. Portée 32 blocs. Activé par défaut.",
|
||||||
"sanctuary.shaders.pbr_intensity": "Intensité du PBR",
|
"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_help": "Relief et reflets des matériaux. Zéro coupe le calcul et libère le cache. Ne modifie pas les textures originales.",
|
||||||
"sanctuary.shaders.pbr_intensity_value": "PBR : %s %%",
|
"sanctuary.shaders.pbr_intensity_value": "PBR : %s %%",
|
||||||
|
|||||||
@@ -5,6 +5,20 @@ uniform sampler2D SceneColor;
|
|||||||
uniform sampler2D BlockAtlas;
|
uniform sampler2D BlockAtlas;
|
||||||
uniform sampler2D MaterialAtlas;
|
uniform sampler2D MaterialAtlas;
|
||||||
uniform sampler2D SunDepth;
|
uniform sampler2D SunDepth;
|
||||||
|
uniform sampler2D LightDirection;
|
||||||
|
uniform sampler2D PreviousDirection;
|
||||||
|
layout(std140) uniform ColoredLightScene {
|
||||||
|
mat4 LocalInverseViewProjection;
|
||||||
|
vec4 CameraStrength;
|
||||||
|
vec4 ClipSkyFog;
|
||||||
|
vec4 FogDistance;
|
||||||
|
vec4 VolumeSize;
|
||||||
|
vec4 Atlas;
|
||||||
|
vec4 PreviousCamera;
|
||||||
|
vec4 PreviousAtlas;
|
||||||
|
vec4 DynamicPosition[128];
|
||||||
|
vec4 DynamicColor[128];
|
||||||
|
};
|
||||||
layout(std140) uniform PbrScene {
|
layout(std140) uniform PbrScene {
|
||||||
mat4 PbrInverseVP;
|
mat4 PbrInverseVP;
|
||||||
vec4 PbrSun;
|
vec4 PbrSun;
|
||||||
@@ -36,6 +50,52 @@ float visibility(vec3 p,vec3 n){
|
|||||||
if(any(lessThan(q,vec3(0)))||any(greaterThan(q,vec3(1))))return 1.0;
|
if(any(lessThan(q,vec3(0)))||any(greaterThan(q,vec3(1))))return 1.0;
|
||||||
return q.z>texture(SunDepth,q.xy).r+0.00015?0.0:1.0;
|
return q.z>texture(SunDepth,q.xy).r+0.00015?0.0:1.0;
|
||||||
}
|
}
|
||||||
|
// Cached direction moments; filtering moments before normalization avoids unstable
|
||||||
|
// switches between nearby/opposed sources. Alpha stores the light response.
|
||||||
|
vec4 directionField(sampler2D field,vec3 p,vec4 atlas){
|
||||||
|
if(textureSize(field,0).x<int(atlas.z))return vec4(0);
|
||||||
|
vec3 edges=min(p,vec3(atlas.x)-p);float margin=smoothstep(14.0,18.0,min(edges.x,min(edges.y,edges.z)));
|
||||||
|
if(margin<=0.0)return vec4(0);
|
||||||
|
int side=int(atlas.x),cols=int(atlas.y);vec3 at=p-.5;ivec3 base=ivec3(floor(at));vec3 f=fract(at);vec4 sum=vec4(0);
|
||||||
|
for(int y=0;y<2;y++)for(int z=0;z<2;z++)for(int x=0;x<2;x++){
|
||||||
|
ivec3 q=clamp(base+ivec3(x,y,z),ivec3(0),ivec3(side-1));
|
||||||
|
vec4 cell=texelFetch(field,ivec2(q.x+(q.y%cols)*side,q.z+(q.y/cols)*side),0);
|
||||||
|
float w=(x==0?1.0-f.x:f.x)*(y==0?1.0-f.y:f.y)*(z==0?1.0-f.z:f.z);
|
||||||
|
sum+=vec4((cell.rgb*255.0-128.0)/127.0*cell.a,cell.a)*w;
|
||||||
|
}
|
||||||
|
return sum*margin;
|
||||||
|
}
|
||||||
|
vec4 localArrival(vec3 p,vec3 normal){
|
||||||
|
if(PbrEnvironment.y<.5)return vec4(0);
|
||||||
|
vec3 offset=normal*.51;
|
||||||
|
vec4 field=directionField(LightDirection,p+CameraStrength.xyz+offset,Atlas);
|
||||||
|
if(VolumeSize.y<1.0){
|
||||||
|
vec4 before=PreviousCamera.w>.5?directionField(PreviousDirection,p+PreviousCamera.xyz+offset,PreviousAtlas):vec4(0);
|
||||||
|
field=mix(before,field,smoothstep(0.0,1.0,VolumeSize.y));
|
||||||
|
}
|
||||||
|
for(int i=0;i<int(VolumeSize.w);i++){
|
||||||
|
vec3 delta=DynamicPosition[i].xyz-p;float distance=length(delta),radius=DynamicPosition[i].w;
|
||||||
|
if(distance>=radius||distance<.001)continue;
|
||||||
|
float power=max(0.0,(pow(.8,15.0-radius+distance)-pow(.8,15.0))/(1.0-pow(.8,15.0)));
|
||||||
|
float weight=power/(power+.08)*smoothstep(0.0,1.0,DynamicColor[i].a);
|
||||||
|
field+=vec4(delta/distance*weight,weight);
|
||||||
|
}
|
||||||
|
float magnitude=length(field.xyz);if(magnitude<.00001||field.a<.00001)return vec4(0);
|
||||||
|
float confidence=smoothstep(.02,1.0,magnitude/field.a);
|
||||||
|
float fade=(1.0-smoothstep(VolumeSize.x*.75,VolumeSize.x,length(p)))*smoothstep(0.0,1.0,VolumeSize.z);
|
||||||
|
return vec4(field.xyz/magnitude,confidence*min(field.a,1.0)*fade*clamp(CameraStrength.w/0.3,0.0,1.0));
|
||||||
|
}
|
||||||
|
vec3 localShine(vec3 n,vec3 v,vec3 l,vec4 material,vec3 albedo){
|
||||||
|
vec3 halfVector=l+v;float size=dot(halfVector,halfVector);if(size<.00001)return vec3(0);
|
||||||
|
vec3 h=halfVector*inversesqrt(size);
|
||||||
|
float nl=max(dot(n,l),0.0),nv=max(dot(n,v),.001),nh=max(dot(n,h),0.0),vh=max(dot(v,h),0.0);
|
||||||
|
float roughness=max(material.b,.25),alpha=roughness*roughness,a2=alpha*alpha;
|
||||||
|
float denom=nh*nh*(a2-1.0)+1.0,distribution=a2/max(3.14159265*denom*denom,.00001);
|
||||||
|
float k=(roughness+1.0)*(roughness+1.0)/8.0;
|
||||||
|
float geometry=nv/(nv*(1.0-k)+k)*nl/max(nl*(1.0-k)+k,.00001);
|
||||||
|
vec3 f0=mix(vec3(.04),albedo,material.a),fresnel=f0+(1.0-f0)*pow(1.0-vh,5.0);
|
||||||
|
return distribution*geometry*fresnel/max(4.0*nv*nl,.0001)*nl;
|
||||||
|
}
|
||||||
void main(){
|
void main(){
|
||||||
// Derivatives precede divergent discard; UV-space tangents preserve rotated/mirrored block textures.
|
// Derivatives precede divergent discard; UV-space tangents preserve rotated/mirrored block textures.
|
||||||
vec3 dx=dFdx(relativePosition),dy=dFdy(relativePosition);
|
vec3 dx=dFdx(relativePosition),dy=dFdy(relativePosition);
|
||||||
@@ -67,12 +127,18 @@ void main(){
|
|||||||
clamp((length(relativePosition)-PbrFog.z)/max(PbrFog.w-PbrFog.z,.001),0.0,1.0));
|
clamp((length(relativePosition)-PbrFog.z)/max(PbrFog.w-PbrFog.z,.001),0.0,1.0));
|
||||||
float amount=PbrSun.w*pow(tint.a,8.0)*visibility(relativePosition,baseNormal)*(1.0-fog*PbrEnvironment.x)
|
float amount=PbrSun.w*pow(tint.a,8.0)*visibility(relativePosition,baseNormal)*(1.0-fog*PbrEnvironment.x)
|
||||||
*(1.0-smoothstep(PbrParams.w*.75,PbrParams.w,length(relativePosition)));
|
*(1.0-smoothstep(PbrParams.w*.75,PbrParams.w,length(relativePosition)));
|
||||||
if(amount<=0.0001)discard;
|
vec4 arrival=localArrival(relativePosition,baseNormal);
|
||||||
|
float localAmount=arrival.w*(1.0-fog*PbrEnvironment.x)*(1.0-smoothstep(PbrParams.w*.75,PbrParams.w,length(relativePosition)));
|
||||||
|
if(amount<=0.0001&&localAmount<=0.0001)discard;
|
||||||
vec3 scene=texture(SceneColor,uv).rgb;vec3 linear=scene*scene;
|
vec3 scene=texture(SceneColor,uv).rgb;vec3 linear=scene*scene;
|
||||||
// Native diffuse light already exists. Apply only the perturbed-normal difference, then add GGX.
|
// Native diffuse light already exists. Apply only the perturbed-normal difference, then add GGX.
|
||||||
float relief=(nl-max(dot(baseNormal,l),0.0))*.7;
|
float relief=(nl-max(dot(baseNormal,l),0.0))*.7;
|
||||||
vec3 lit=clamp(linear*(1.0+relief*amount),0.0,1.0);
|
vec3 lit=clamp(linear*(1.0+relief*amount),0.0,1.0);
|
||||||
vec3 shine=min(specular*PbrParams.x*amount*3.0,vec3(0.5));
|
if(localAmount>.0001){
|
||||||
|
float localRelief=(max(dot(n,arrival.xyz),0.0)-max(dot(baseNormal,arrival.xyz),0.0))*.7;
|
||||||
|
lit=clamp(lit*(1.0+localRelief*localAmount),0.0,1.0);
|
||||||
|
}
|
||||||
|
vec3 shine=min((specular*amount+localShine(n,v,arrival.xyz,material,albedo)*localAmount)*PbrParams.x*3.0,vec3(0.5));
|
||||||
lit+=(1.0-lit)*(1.0-exp(-shine));
|
lit+=(1.0-lit)*(1.0-exp(-shine));
|
||||||
fragColor=vec4(sqrt(clamp(lit,0.0,1.0)),1.0);
|
fragColor=vec4(sqrt(clamp(lit,0.0,1.0)),1.0);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
name = "Sanctuary"
|
name = "Sanctuary"
|
||||||
author = "KOKA99CAB"
|
author = "KOKA99CAB"
|
||||||
version = "beta.140"
|
version = "beta.141"
|
||||||
pack-format = "packwiz:1.1.0"
|
pack-format = "packwiz:1.1.0"
|
||||||
|
|
||||||
[index]
|
[index]
|
||||||
|
|||||||
Reference in New Issue
Block a user