feat(shader): experimental local diffuse SSGI in beta.132
Build Sanctuary / build (push) Canceled after 0s
Build Sanctuary / build (push) Canceled after 0s
This commit is contained in:
@@ -20,15 +20,12 @@ La [vision complète](docs/vision.md) conserve les intentions ; le
|
||||
L’économie, les claims et les dimensions décrits dans la vision restent
|
||||
à implémenter.
|
||||
|
||||
La version **beta.131** active par défaut le bloom, les minerais émissifs et
|
||||
le highlight. Couper le bloom ou mettre son intensité à zéro conserve les
|
||||
inclusions émissives sans halo. La précision commune ajoute 64 et 128 pixels
|
||||
par bloc en options ; sa valeur initiale reste 16. Distances initiales :
|
||||
ombres 128 blocs et highlight 32 blocs. **Options → Shaders → Sanctuary ·
|
||||
Vanilla Light**. Les réglages personnalisés sont conservés.
|
||||
Le [ticket de livraison](docs/shader-beta131.md) détaille les vérifications.
|
||||
Release publiée et même instance **Sanctuary Beta** synchronisée, sauvegardes
|
||||
et réglages conservés.
|
||||
La version **beta.132** ajoute le **SSGI · couleurs diffusées** expérimental :
|
||||
les surfaces visibles proches se transmettent une légère teinte. Effet
|
||||
optionnel, intensité réglable, rayon local de 2,5 blocs.
|
||||
**Options → Shaders** ; les réglages existants restent conservés.
|
||||
Le [ticket de livraison](docs/shader-beta132.md) décrit le rendu, les limites
|
||||
et la validation.
|
||||
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**.
|
||||
Les [synchronisations antérieures](docs/git-sync-beta092.md) restent documentées.
|
||||
@@ -39,6 +36,12 @@ La [carte native beta.005](docs/atlas-beta005.md) prend le relais après
|
||||
l’étude de Xaero et des alternatives libres.
|
||||
|
||||
|
||||
## beta.132 — SSGI : diffusion locale des couleurs
|
||||
|
||||
Rebond diffus des couleurs visibles, intensité réglable et reconstruction
|
||||
préservant les silhouettes. Expérimental, initialement désactivé.
|
||||
[Contrat et vérifications](docs/shader-beta132.md).
|
||||
|
||||
## beta.131 — Émissifs indépendants et précision étendue
|
||||
|
||||
Bloom, émissifs et highlight activés initialement ; halo facultatif et cinq
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
# SHADER-132 — diffusion locale SSGI expérimentale
|
||||
|
||||
Demande : légère teinte diffusée des surfaces voisines, sans reflets miroir
|
||||
ni shader d'eau. Branche `codex/ssgi-beta132`, socle beta.131, Minecraft 26.3 /
|
||||
Java 25. Aucun changement de génération, sauvegarde ou lumière serveur.
|
||||
|
||||
## Résultat
|
||||
|
||||
**Options → Shaders → SSGI · couleurs diffusées** active un rebond diffus
|
||||
local. L'effet expérimental est initialement OFF, avec une intensité préparée
|
||||
à **35 %** ; le curseur propose 0–100. À zéro ou OFF, ses cibles et uniformes
|
||||
GPU sont libérés. Les autres effets et leurs préférences sont conservés.
|
||||
|
||||
La couleur d'une surface visible peut teinter les surfaces proches qui lui
|
||||
font face. Le calcul porte à **2,5 blocs** du récepteur et s'estompe entre
|
||||
24 et 32 blocs de la caméra. Il utilise une copie de la couleur native après
|
||||
les ombres, avec la profondeur et des normales reconstruites. Le premier
|
||||
obstacle arrête chaque rayon, même si son épaisseur empêche de retenir une
|
||||
intersection. Les plans coplanaires ne s'éclairent pas eux-mêmes.
|
||||
|
||||
Douze directions hémisphériques fixes, au maximum dix étapes par rayon,
|
||||
sont évaluées à demi-résolution en largeur et hauteur. La reconstruction
|
||||
plein écran est guidée par la profondeur pour conserver les silhouettes et
|
||||
les détails des textures. Le cœur des émissifs, le highlight et le bloom
|
||||
sont composés ensuite : pas de halo réinjecté dans le rebond ni de boucle
|
||||
de rétroaction d'une image sur l'autre. Aucune lecture GPU vers CPU ni
|
||||
recherche de chunks en production. Pas de bruit temporel ou d'historique.
|
||||
|
||||
Deux cibles, RGBA8 plein écran et RGBA16 flottant à demi-résolution, soit
|
||||
environ six octets par pixel de l'écran, plus 176 octets d'uniformes par
|
||||
segment du tampon natif. Redimensionnement, arrêt, déconnexion et
|
||||
rechargement libèrent/recréent les ressources possédées. Ce budget n'est
|
||||
pas une garantie de FPS ; le SSGI coûte davantage qu'un simple réglage de
|
||||
colorimétrie.
|
||||
|
||||
## Limites de cette version
|
||||
|
||||
L'estimation utilise seulement la couleur et la profondeur visibles. Les
|
||||
blocs hors champ ou cachés n'apportent pas leur couleur. Le résultat peut
|
||||
changer lorsqu'ils entrent ou sortent de l'image ; une atténuation près
|
||||
des bords limite la coupure. Les surfaces transparentes absentes de la
|
||||
profondeur ne sont pas reconstruites. Petits objets et intersections plus
|
||||
minces que l'échantillonnage peuvent manquer. Il s'agit d'une approximation
|
||||
diffuse locale, pas d'une simulation complète d'éclairage global ni de
|
||||
reflets spéculaires. Aucune modification de l'eau et aucune source hors
|
||||
champ déduite ou générée.
|
||||
|
||||
## Vérifications
|
||||
|
||||
Test ciblé natif réussi en **1 min** sur macOS / Apple M1 / OpenGL, nouveau
|
||||
monde plat de graine 122. À intensité 90, les échantillons du sol blanc
|
||||
montrent un gain rouge cumulé de 8 968 contre 20 bleu devant le mur rouge,
|
||||
puis 9 168 bleu contre 23 rouge devant le mur bleu. Le sol seul est inchangé.
|
||||
OFF/zéro restituent l'image de base et libèrent les cibles ; intensité 20
|
||||
réduit le gain. Changer la couleur du mur complètement occulté laisse
|
||||
l'image inchangée. Rechargement, caméra fixe, mouvement infinitésimal et
|
||||
retour à la caméra d'origine passent, sans accumulation de couleur.
|
||||
|
||||
Deux ajustements de fixture ont précédé ce résultat : inclure dans les
|
||||
mesures le blanc légèrement bleuté par l'éclairage natif, et formater les
|
||||
petits angles de `/tp` en décimal car la commande refuse la notation
|
||||
scientifique. Aucun comportement du jeu modifié pour ces corrections.
|
||||
|
||||
Log ciblé : `build/shader132-client-ssgi.log`. La suite complète réussit en
|
||||
**4 min 18 s** et vérifie aussi une fenêtre 961 × 541, le retour à sa taille
|
||||
initiale, le SSGI avec ombres/highlight/bloom/émissifs actifs, ainsi que
|
||||
l'arrêt global du shader. Les captures françaises et anglaises passent.
|
||||
Les contrôles précédents des cinq précisions, ombres, lune, feuillage, CTM,
|
||||
bloom et émissifs restent réussis. Log : `build/shader132-client-full.log`.
|
||||
|
||||
Commandes : `./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true
|
||||
-PsanctuaryPixelShadows122ClientTests=true -PsanctuaryClientNoVsync=true
|
||||
-PsanctuaryQuickTests=true`. Pour le test ciblé, remplacer la propriété
|
||||
PixelShadows par `-PsanctuarySsgi132ClientTests=true`.
|
||||
|
||||
`./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest`
|
||||
réussit en **2 min 25 s**, 126 tâches. Les packs normal/Test contiennent le
|
||||
même JAR ; sources archivées et template de ressources correspondent aux
|
||||
sources. Comparaison avec beta.131 : **11 entrées de production** modifiées,
|
||||
limitées au SSGI, à ses options et à ses points d'appel. Gameplay et textures
|
||||
des gemmes inchangés, aucun monde ni test embarqué. Aucun résultat
|
||||
Windows/Vulkan revendiqué.
|
||||
Le serveur GameTest dédié reste exclu conformément au refus antérieur de
|
||||
son EULA. Les captures panoramiques et les milieux de brume sous l'eau/lave
|
||||
ou neige poudreuse suspendent cette passe, comme les autres effets de
|
||||
profondeur Sanctuary.
|
||||
|
||||
## Livraison
|
||||
|
||||
- `Sanctuary-beta.132.mrpack` : 10394026 octets, SHA-256 `a7c0823e00f36925919645e32e7792242ec1d01015b41bee5b4f6285b9fc533d`.
|
||||
- `Sanctuary-Test-beta.132.mrpack` : 10412949 octets, SHA-256 `9bc30a7d6d5834f9323f15f0c39d3f49d200e1a97d739266ab373c9e1ba4a8fd`.
|
||||
|
||||
JAR : `83eb2d091a7acd85eaa8b3b6a00a40ea483f9eeb2a30c033bdf68b4d717c5142`.
|
||||
|
||||
Archives vérifiées recopiées dans `sanctuary-beta/build/`. Publication et
|
||||
synchronisation de la même instance après le commit source.
|
||||
+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.131
|
||||
pack_version=beta.131
|
||||
mod_version=beta.132
|
||||
pack_version=beta.132
|
||||
resource_pack_version=beta.121
|
||||
maven_group=fr.koka.sanctuary
|
||||
jei_version=30.32.0-sanctuary.3
|
||||
|
||||
@@ -93,6 +93,10 @@ tasks.named('runGameTest') {
|
||||
}
|
||||
|
||||
tasks.named('processGametestResources') {
|
||||
def ssgi132Client = providers.gradleProperty('sanctuarySsgi132ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('sanctuarySsgi132ClientTests', ssgi132Client)
|
||||
if (ssgi132Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.shader.Ssgi132ClientChecks') }
|
||||
|
||||
def 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') }
|
||||
|
||||
+10
-6
@@ -36,9 +36,10 @@ 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;
|
||||
private final boolean bloomOnly,ssgiOnly;
|
||||
public PixelShadows122ClientChecks(){this(false);}
|
||||
PixelShadows122ClientChecks(boolean bloomOnly){this.bloomOnly=bloomOnly;}
|
||||
PixelShadows122ClientChecks(boolean bloomOnly){this(bloomOnly,false);}
|
||||
PixelShadows122ClientChecks(boolean bloomOnly,boolean ssgiOnly){this.bloomOnly=bloomOnly;this.ssgiOnly=ssgiOnly;}
|
||||
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);
|
||||
@@ -133,6 +134,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(ssgiOnly){Ssgi132ClientChecks.run(c,server);return;}
|
||||
if(bloomOnly){Bloom130ClientChecks.run(c,server);return;}
|
||||
// Keep VanillaLight enabled for both frames: changing its fog/grade would invalidate this oracle.
|
||||
shadows(c, false);
|
||||
@@ -241,12 +243,13 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
|
||||
RealtimeShadows129ClientChecks.run(c, server);
|
||||
EdgeHighlights129ClientChecks.run(c, server);
|
||||
Bloom130ClientChecks.run(c, server);
|
||||
Ssgi132ClientChecks.run(c, server);
|
||||
c.getInput().pressKey(InputConstants.KEY_F1);
|
||||
for (String locale : new String[]{"fr_fr", "en_us"}) {
|
||||
c.runOnClient(m -> {
|
||||
m.getLanguageManager().setSelected(locale);
|
||||
m.getLanguageManager().onResourceManagerReload(m.getResourceManager());
|
||||
for (String key : new String[]{"pixel_shadows", "shadow_pixels", "shadow_distance", "edge_highlights", "edge_intensity", "edge_distance", "precision", "bloom", "bloom_radius", "ore_emission"}) {
|
||||
for (String key : new String[]{"pixel_shadows", "shadow_pixels", "shadow_distance", "edge_highlights", "edge_intensity", "edge_distance", "precision", "ssgi", "ssgi_intensity", "bloom", "bloom_radius", "ore_emission"}) {
|
||||
String fullKey = "sanctuary.shaders." + key;
|
||||
check(!Component.translatable(fullKey).getString().equals(fullKey), "Translated " + locale + " " + key);
|
||||
}
|
||||
@@ -258,7 +261,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
|
||||
});
|
||||
});
|
||||
c.waitTicks(3);
|
||||
c.takeScreenshot("sanctuary-beta131-shader-options-" + locale);
|
||||
c.takeScreenshot("sanctuary-beta132-shader-options-" + locale);
|
||||
}
|
||||
c.runOnClient(m -> m.gui.setScreen(null));
|
||||
}
|
||||
@@ -423,6 +426,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
|
||||
}
|
||||
|
||||
private static void checkPreferences() {
|
||||
VanillaLight.setSsgi(false);
|
||||
VanillaLight.setBloom(false);
|
||||
VanillaLight.setOreEmission(false);
|
||||
VanillaLight.setEnabled(true);
|
||||
@@ -479,7 +483,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
|
||||
static void look(ClientGameTestContext c, TestServerContext server, float yaw, float pitch) {
|
||||
server.runOnServer(s -> {
|
||||
var p = s.getPlayerList().getPlayers().getFirst();
|
||||
command(s, "tp @a " + p.getX() + " " + p.getY() + " " + p.getZ() + " " + yaw + " " + pitch);
|
||||
command(s, "tp @a " + p.getX() + " " + p.getY() + " " + p.getZ() + String.format(java.util.Locale.ROOT," %.8f %.8f",yaw,pitch));
|
||||
});
|
||||
c.waitFor(m -> Math.abs(m.player.getXRot() - pitch) < .00001
|
||||
&& Math.abs(m.player.getYRot() - yaw) < .00001, 200);
|
||||
@@ -524,7 +528,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
|
||||
}
|
||||
|
||||
static Frame capture(ClientGameTestContext c, String name) {
|
||||
if (name != null) c.takeScreenshot("sanctuary-beta131-shader-" + name);
|
||||
if (name != null) c.takeScreenshot("sanctuary-beta132-shader-" + name);
|
||||
var result = new CompletableFuture<Frame>();
|
||||
c.runOnClient(m -> Screenshot.takeScreenshot(m.gameRenderer.mainRenderTarget(), image -> {
|
||||
try (image) {
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package fr.koka.sanctuary.client.shader;
|
||||
|
||||
import static fr.koka.sanctuary.client.shader.PixelShadows122ClientChecks.*;
|
||||
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.world.level.block.state.BlockState;
|
||||
import java.nio.file.Files;
|
||||
|
||||
public final class Ssgi132ClientChecks implements FabricClientGameTest {
|
||||
public void runTest(ClientGameTestContext c){new PixelShadows122ClientChecks(false,true).runTest(c);}
|
||||
static void run(ClientGameTestContext c,TestServerContext server){
|
||||
c.runOnClient(m->{preferences();VanillaLight.setEnabled(true);VanillaLight.setSaturation(100);VanillaLight.setSsgi(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);VanillaLight.setPixelShadows(false);VanillaLight.setEdgeHighlights(false);});
|
||||
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());
|
||||
}});
|
||||
camera(c,server,10.5,69,5.5,65);look(c,server,0,40);settle(c);
|
||||
var flatOff=capture(c,"ssgi-flat-off");enable(c,true);unchanged(flatOff,capture(c,"ssgi-flat-on"),"Coplanar ground does not bounce light onto itself");
|
||||
wall(server,12,Blocks.CONCRETE.red().defaultBlockState());settle(c);enable(c,false);var redOff=capture(c,"ssgi-red-off");
|
||||
enable(c,true);var redOn=capture(c,"ssgi-red-on");var red=measure(redOff,redOn);System.out.println("SSGI132_RED "+red);
|
||||
check(red.changed>30&&red.red>red.blue*1.5+20,"Visible red wall tints neutral ground: "+red);
|
||||
unchanged(redOn,capture(c,null),"Stationary SSGI has no animated noise or accumulating feedback");
|
||||
look(c,server,0.00002f,40.00002f);frames(c);unchanged(redOn,capture(c,null),"Subpixel motion keeps actual red bounce stable");
|
||||
look(c,server,5,40);frames(c);capture(c,"ssgi-camera-turned");
|
||||
look(c,server,0,40);frames(c);unchanged(redOn,capture(c,null),"Returning the camera leaves no temporal color trails");
|
||||
c.runOnClient(m->VanillaLight.setSsgiIntensity(20));frames(c);var low=measure(redOff,capture(c,"ssgi-strength-20"));
|
||||
check(red.red>low.red*1.5,"Intensity adjusts native color bounce: "+red+" / "+low);
|
||||
c.runOnClient(m->VanillaLight.setSsgiIntensity(0));c.waitTicks(6);unchanged(redOff,capture(c,null),"Zero intensity restores source image");
|
||||
c.runOnClient(m->check(!ScreenSpaceGI.allocated()&&!ScreenSpaceGI.applied(),"Zero frees SSGI targets"));
|
||||
c.runOnClient(m->VanillaLight.setSsgiIntensity(90));frames(c);
|
||||
int oldWidth=c.computeOnClient(m->m.getWindow().getScreenWidth()),oldHeight=c.computeOnClient(m->m.getWindow().getScreenHeight());
|
||||
c.runOnClient(m->m.getWindow().setWindowed(961,541));
|
||||
c.waitFor(m->m.gameRenderer.mainRenderTarget().width!=redOn.width(),200);frames(c);
|
||||
enable(c,false);var resizedOff=capture(c,null);enable(c,true);var resized=measure(resizedOff,capture(c,"ssgi-resized"));
|
||||
check(resized.changed>30&&resized.red>resized.blue*1.5+20,"Resizing reallocates a working SSGI pass: "+resized);
|
||||
c.runOnClient(m->m.getWindow().setWindowed(oldWidth,oldHeight));
|
||||
c.waitFor(m->m.gameRenderer.mainRenderTarget().width==redOn.width(),200);frames(c);
|
||||
wall(server,12,Blocks.CONCRETE.blue().defaultBlockState());settle(c);enable(c,false);var blueOff=capture(c,"ssgi-blue-off");enable(c,true);var blue=measure(blueOff,capture(c,"ssgi-blue-on"));
|
||||
System.out.println("SSGI132_BLUE "+blue);check(blue.changed>30&&blue.blue>blue.red*1.5+20,"Live blue wall changes bounced hue: "+blue);
|
||||
wall(server,11,Blocks.CONCRETE.white().defaultBlockState());settle(c);enable(c,false);var coveredOff=capture(c,null);enable(c,true);var covered=capture(c,"ssgi-covered-blue");
|
||||
wall(server,12,Blocks.CONCRETE.red().defaultBlockState());settle(c);unchanged(covered,capture(c,"ssgi-covered-red"),"Hidden wall color cannot pass through foreground wall");
|
||||
var reload=c.computeOnClient(net.minecraft.client.Minecraft::reloadResourcePacks);c.waitFor(m->reload.isDone()&&m.gui.overlay()==null,1200);reload.join();frames(c);unchanged(covered,capture(c,"ssgi-reloaded"),"Resource reload restores SSGI");
|
||||
look(c,server,0.00002f,40.00002f);frames(c);unchanged(covered,capture(c,null),"Subpixel camera movement keeps bounce stable");
|
||||
look(c,server,0,40);frames(c);enable(c,false);unchanged(coveredOff,capture(c,null),"OFF leaves no residual tint");
|
||||
c.runOnClient(m->check(!ScreenSpaceGI.allocated(),"OFF frees SSGI resources"));
|
||||
c.runOnClient(m->{VanillaLight.setSsgiIntensity(35);VanillaLight.setPixelShadows(true);VanillaLight.setEdgeHighlights(true);VanillaLight.setBloom(true);VanillaLight.setOreEmission(true);});
|
||||
enable(c,true);c.waitFor(m->PixelShadows.applied()&&PixelShadows.ready()&&EdgeHighlights.applied()&&Bloom.applied(),1200);
|
||||
capture(c,"ssgi-combined-shader");
|
||||
c.runOnClient(m->VanillaLight.setEnabled(false));c.waitTicks(8);
|
||||
c.runOnClient(m->check(!ScreenSpaceGI.allocated(),"Main shader OFF releases SSGI too"));
|
||||
c.runOnClient(m->VanillaLight.setEnabled(true));frames(c);enable(c,false);
|
||||
System.out.println("SSGI132_PASS red/blue diffuse bounce; intensity; flat rejection; foreground occlusion; static stability; tiny camera motion; resize; reload; combined shader; OFF/zero; preferences; red="+red+" blue="+blue);
|
||||
}
|
||||
private static void wall(TestServerContext server,int z,BlockState state){server.runOnServer(s->{for(int x=6;x<=15;x++)for(int y=65;y<=(z==11?72:69);y++)s.overworld().setBlockAndUpdate(new BlockPos(x,y,z),state);});}
|
||||
private static void settle(ClientGameTestContext c){c.waitTicks(30);c.waitFor(m->m.levelRenderer.hasRenderedAllSections(),1200);c.waitTicks(8);}
|
||||
private static void enable(ClientGameTestContext c,boolean enabled){c.runOnClient(m->VanillaLight.setSsgi(enabled));if(enabled)frames(c);else c.waitTicks(8);}
|
||||
private static void frames(ClientGameTestContext c){int start=c.computeOnClient(m->ScreenSpaceGI.renderedFrames());c.waitFor(m->ScreenSpaceGI.applied()&&ScreenSpaceGI.renderedFrames()>start+10,1200);c.waitTicks(6);}
|
||||
private static void preferences(){try{
|
||||
var p=FabricLoader.getInstance().getConfigDir().resolve("sanctuary-shaders.json");Files.writeString(p,"{\"bloom\":false,\"shadowPixels\":128}");forgetPreferences();
|
||||
check(!VanillaLight.ssgi()&&VanillaLight.ssgiIntensity()==35&&!VanillaLight.bloom()&&VanillaLight.shadowPixels()==128,"Legacy choices preserved; experimental SSGI defaults OFF at 35");
|
||||
for(int i:new int[]{-10,0,35,90,140}){VanillaLight.setSsgiIntensity(i);VanillaLight.setSsgi(true);forgetPreferences();check(VanillaLight.ssgi()&&VanillaLight.ssgiIntensity()==Math.clamp(i,0,100),"SSGI persists and clamps");}
|
||||
VanillaLight.setSsgiIntensity(90);
|
||||
}catch(Exception e){throw new AssertionError(e);}}
|
||||
private record Gain(int changed,double red,double green,double blue){}
|
||||
private static Gain measure(Frame off,Frame on){int count=0;double r=0,g=0,b=0;for(int i=0;i<off.pixels().length;i++){
|
||||
int a=off.pixels()[i],ar=a>>16&255,ag=a>>8&255,ab=a&255;
|
||||
if(Math.max(ar,Math.max(ag,ab))-Math.min(ar,Math.min(ag,ab))>25||ar<60||ar>245)continue;
|
||||
int z=on.pixels()[i];int dr=(z>>16&255)-ar,dg=(z>>8&255)-ag,db=(z&255)-ab;
|
||||
if(Math.max(dr,Math.max(dg,db))>1)count++;r+=Math.max(0,dr);g+=Math.max(0,dg);b+=Math.max(0,db);
|
||||
}return new Gain(count,r,g,b);}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
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;
|
||||
import net.minecraft.world.level.material.FogType;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
/** Bounded, single-bounce diffuse lighting from this frame's visible surfaces. No GPU readback or history. */
|
||||
public final class ScreenSpaceGI {
|
||||
private static final BindGroupLayout LAYOUT=BindGroupLayout.builder()
|
||||
.withUniform("GIParams",UniformType.UNIFORM_BUFFER)
|
||||
.withUniform("WorldDepth",UniformType.COMBINED_IMAGE_SAMPLER)
|
||||
.withUniform("SceneColor",UniformType.COMBINED_IMAGE_SAMPLER)
|
||||
.withUniform("IndirectLight",UniformType.COMBINED_IMAGE_SAMPLER).build();
|
||||
private static RenderPipeline pipeline(String name,boolean composite){
|
||||
return RenderPipelines.register(RenderPipeline.builder().withLocation(Identifier.parse("sanctuary:pipeline/"+name))
|
||||
.withVertexShader("core/screenquad").withFragmentShader(Identifier.parse("sanctuary:core/"+name))
|
||||
.withBindGroupLayout(LAYOUT).withPrimitiveTopology(PrimitiveTopology.TRIANGLES).withCull(false)
|
||||
.withColorTargetState(new ColorTargetState(composite?Optional.of(new BlendFunction(BlendFactor.ONE,BlendFactor.ONE_MINUS_SRC_COLOR,BlendFactor.ZERO,BlendFactor.ONE)):Optional.empty(),
|
||||
composite?GpuFormat.RGBA8_UNORM:GpuFormat.RGBA16_FLOAT,composite?ColorTargetState.WRITE_COLOR:ColorTargetState.WRITE_ALL))
|
||||
.withDepthStencilState(Optional.empty()).build());
|
||||
}
|
||||
private static final RenderPipeline GATHER=pipeline("ssgi_gather",false),APPLY=pipeline("ssgi_apply",true);
|
||||
private static final Matrix4f projection=new Matrix4f();
|
||||
private static TextureTarget source,indirect;
|
||||
private static MappableRingBuffer uniforms;
|
||||
private static boolean applied;
|
||||
private static int frames;
|
||||
private ScreenSpaceGI(){}
|
||||
public static void captureProjection(Matrix4f matrix){projection.set(matrix);}
|
||||
public static void render(){
|
||||
applied=false;var mc=Minecraft.getInstance();
|
||||
if(!VanillaLight.active()||!VanillaLight.ssgi()||VanillaLight.ssgiIntensity()==0||mc.level==null){release();return;}
|
||||
if(PanoramaCapture.rendering())return;
|
||||
var camera=mc.gameRenderer.gameRenderState().levelRenderState.cameraRenderState;
|
||||
if(camera.fogType!=FogType.NONE)return;
|
||||
var main=mc.gameRenderer.mainRenderTarget();
|
||||
if(source==null||source.width!=main.width||source.height!=main.height){
|
||||
release();source=new TextureTarget("Sanctuary SSGI source",main.width,main.height,GpuFormat.RGBA8_UNORM,null);
|
||||
indirect=new TextureTarget("Sanctuary SSGI indirect",Math.max(1,(main.width+1)/2),Math.max(1,(main.height+1)/2),GpuFormat.RGBA16_FLOAT,null);
|
||||
}
|
||||
if(uniforms==null)uniforms=new MappableRingBuffer(()->"Sanctuary SSGI",GpuBuffer.USAGE_UNIFORM|GpuBuffer.USAGE_MAP_WRITE,176);
|
||||
var vp=new Matrix4f(projection).mul(camera.viewRotationMatrix);var fog=camera.fogData;
|
||||
uniforms.rotate();try(var mapped=uniforms.currentBuffer().map(false,true)){
|
||||
Std140Builder.intoBuffer(mapped.data()).putMat4f(new Matrix4f(vp).invert()).putMat4f(vp)
|
||||
.putVec4(VanillaLight.ssgiIntensity()/100f,RenderSystem.getDevice().getDeviceInfo().isZZeroToOne()?1:0,2.5f,32)
|
||||
.putVec4(fog.renderDistanceStart,fog.renderDistanceEnd,fog.environmentalStart,fog.environmentalEnd)
|
||||
.putVec4(fog.color.w,0,0,0);
|
||||
}
|
||||
// Snapshot once, before adding indirect light: no feedback, no texture sampled while attached for writing.
|
||||
var encoder=RenderSystem.getDevice().createCommandEncoder();
|
||||
encoder.copyTextureToTexture(main.getColorTexture(),source.getColorTexture(),0,0,0,0,0,main.width,main.height);
|
||||
var nearest=RenderSystem.getSamplerCache().getClampToEdge(FilterMode.NEAREST);
|
||||
try(var pass=encoder.createRenderPass(()->"Sanctuary SSGI gather",indirect.getColorTextureView(),Optional.empty())){
|
||||
pass.setPipeline(RenderSystem.getCompiledPipeline(GATHER));pass.setUniform("GIParams",uniforms.currentBuffer());
|
||||
pass.setUniform("WorldDepth",main.getDepthTextureView(),nearest);pass.setUniform("SceneColor",source.getColorTextureView(),nearest);
|
||||
// Unused by gather; bind a separate texture, never its own output.
|
||||
pass.setUniform("IndirectLight",source.getColorTextureView(),nearest);pass.draw(3,1,0,0);
|
||||
}
|
||||
try(var pass=encoder.createRenderPass(()->"Sanctuary SSGI composite",main.getColorTextureView(),Optional.empty())){
|
||||
pass.setPipeline(RenderSystem.getCompiledPipeline(APPLY));pass.setUniform("GIParams",uniforms.currentBuffer());
|
||||
pass.setUniform("WorldDepth",main.getDepthTextureView(),nearest);pass.setUniform("SceneColor",source.getColorTextureView(),nearest);
|
||||
pass.setUniform("IndirectLight",indirect.getColorTextureView(),nearest);pass.draw(3,1,0,0);
|
||||
}
|
||||
applied=true;frames++;
|
||||
}
|
||||
public static void release(){if(source!=null){source.destroyBuffers();indirect.destroyBuffers();source=indirect=null;}if(uniforms!=null){uniforms.close();uniforms=null;}applied=false;}
|
||||
static boolean applied(){return applied;}
|
||||
static boolean allocated(){return source!=null;}
|
||||
static int renderedFrames(){return frames;}
|
||||
}
|
||||
@@ -20,6 +20,9 @@ public final class ShaderOptionsScreen extends OptionsSubScreen {
|
||||
list.addBig(new OptionInstance<Integer>("sanctuary.shaders.precision",OptionInstance.cachedConstantTooltip(
|
||||
Component.translatable("sanctuary.shaders.precision_help")),(caption,value)->Component.translatable("sanctuary.shaders.precision_value",value),
|
||||
new OptionInstance.Enum<>(List.of(8,16,32,64,128),Codec.INT),VanillaLight.shadowPixels(),VanillaLight::setShadowPixels));
|
||||
list.addBig(OptionInstance.createBoolean("sanctuary.shaders.ssgi",OptionInstance.cachedConstantTooltip(
|
||||
Component.translatable("sanctuary.shaders.ssgi_help")),VanillaLight.ssgi(),VanillaLight::setSsgi));
|
||||
slider("ssgi_intensity",VanillaLight.ssgiIntensity(),VanillaLight::setSsgiIntensity);
|
||||
list.addBig(OptionInstance.createBoolean("sanctuary.shaders.bloom",OptionInstance.cachedConstantTooltip(
|
||||
Component.translatable("sanctuary.shaders.bloom_help")),VanillaLight.bloom(),VanillaLight::setBloom));
|
||||
slider("bloom_intensity",VanillaLight.bloomIntensity(),VanillaLight::setBloomIntensity);
|
||||
|
||||
@@ -19,6 +19,8 @@ public final class VanillaLight {
|
||||
int saturation=142;
|
||||
boolean pixelShadows=true;
|
||||
boolean bloom=true;
|
||||
boolean ssgi=false;
|
||||
int ssgiIntensity=35;
|
||||
int bloomIntensity=35;
|
||||
int bloomRadius=50;
|
||||
boolean oreEmission=true;
|
||||
@@ -35,6 +37,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.ssgiIntensity=Math.clamp(preferences.ssgiIntensity,0,100);
|
||||
preferences.bloomIntensity=Math.clamp(preferences.bloomIntensity,0,100);
|
||||
preferences.bloomRadius=Math.clamp(preferences.bloomRadius,0,100);
|
||||
preferences.oreIntensity=Math.clamp(preferences.oreIntensity,0,100);
|
||||
@@ -55,6 +58,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 ssgi(){enabled();return preferences.ssgi;}
|
||||
public static int ssgiIntensity(){enabled();return preferences.ssgiIntensity;}
|
||||
public static void setSsgi(boolean value){enabled();preferences.ssgi=value;save();}
|
||||
public static void setSsgiIntensity(int value){enabled();preferences.ssgiIntensity=Math.clamp(value,0,100);save();}
|
||||
public static boolean bloom(){enabled();return preferences.bloom;}
|
||||
public static int bloomIntensity(){enabled();return preferences.bloomIntensity;}
|
||||
public static int bloomRadius(){enabled();return preferences.bloomRadius;}
|
||||
|
||||
+2
-1
@@ -2,6 +2,7 @@ package fr.koka.sanctuary.mixin.client;
|
||||
|
||||
import fr.koka.sanctuary.client.shader.PixelShadows;
|
||||
import fr.koka.sanctuary.client.shader.Bloom;
|
||||
import fr.koka.sanctuary.client.shader.ScreenSpaceGI;
|
||||
import net.minecraft.client.renderer.extract.LevelExtractor;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
@@ -17,5 +18,5 @@ abstract class PixelShadowDirtyMixin {
|
||||
PixelShadows.dirtyBlock(pos); Bloom.dirtyBlock(pos);
|
||||
}
|
||||
@Inject(method="allChanged", at=@At("HEAD"))
|
||||
private void sanctuary$reload(CallbackInfo ci) { PixelShadows.release(); Bloom.release(); }
|
||||
private void sanctuary$reload(CallbackInfo ci) { PixelShadows.release(); Bloom.release(); ScreenSpaceGI.release(); }
|
||||
}
|
||||
|
||||
+4
-2
@@ -6,6 +6,7 @@ import com.mojang.renderpearl.api.buffers.GpuBufferSlice;
|
||||
import fr.koka.sanctuary.client.shader.PixelShadows;
|
||||
import fr.koka.sanctuary.client.shader.EdgeHighlights;
|
||||
import fr.koka.sanctuary.client.shader.Bloom;
|
||||
import fr.koka.sanctuary.client.shader.ScreenSpaceGI;
|
||||
import net.minecraft.client.renderer.GameRenderer;
|
||||
import net.minecraft.client.renderer.ProjectionMatrixBuffer;
|
||||
import org.joml.Matrix4f;
|
||||
@@ -21,10 +22,11 @@ abstract class PixelShadowRendererMixin {
|
||||
PixelShadows.captureProjection(matrix);
|
||||
EdgeHighlights.captureProjection(matrix);
|
||||
Bloom.captureProjection(matrix);
|
||||
ScreenSpaceGI.captureProjection(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"))
|
||||
private void sanctuary$shadows(CallbackInfo ci) { PixelShadows.render(); EdgeHighlights.render(); Bloom.render(); }
|
||||
private void sanctuary$shadows(CallbackInfo ci) { PixelShadows.render(); ScreenSpaceGI.render(); EdgeHighlights.render(); Bloom.render(); }
|
||||
@Inject(method={"close", "setLevel"}, at=@At("HEAD"))
|
||||
private void sanctuary$close(CallbackInfo ci) { PixelShadows.release(); EdgeHighlights.release(); Bloom.release(); }
|
||||
private void sanctuary$close(CallbackInfo ci) { PixelShadows.release(); EdgeHighlights.release(); Bloom.release(); ScreenSpaceGI.release(); }
|
||||
}
|
||||
|
||||
@@ -2288,5 +2288,10 @@
|
||||
"sanctuary.shaders.ore_emission_help": "Makes ore inclusions glow while preserving their host rock, even with bloom off or at zero. ON by default. Visual effect only.",
|
||||
"sanctuary.shaders.ore_intensity": "Ore emission",
|
||||
"sanctuary.shaders.ore_intensity_help": "Inclusion brightness. Does not change world light levels or mob spawning.",
|
||||
"sanctuary.shaders.ore_intensity_value": "Ores: %s%%"
|
||||
"sanctuary.shaders.ore_intensity_value": "Ores: %s%%",
|
||||
"sanctuary.shaders.ssgi": "SSGI · color bounce",
|
||||
"sanctuary.shaders.ssgi_help": "Experimental: subtle color bounce between nearby visible surfaces. Radius 2.5 blocks. Off-screen surfaces do not contribute. OFF by default.",
|
||||
"sanctuary.shaders.ssgi_intensity": "SSGI strength",
|
||||
"sanctuary.shaders.ssgi_intensity_help": "Strength of diffuse color bounce. Zero stops rendering and frees its resources. Does not change world light.",
|
||||
"sanctuary.shaders.ssgi_intensity_value": "SSGI: %s%%"
|
||||
}
|
||||
|
||||
@@ -2288,5 +2288,10 @@
|
||||
"sanctuary.shaders.ore_emission_help": "Fait briller les inclusions des minerais sans éclairer leur roche, même avec le bloom coupé ou à zéro. Activé par défaut. Effet visuel uniquement.",
|
||||
"sanctuary.shaders.ore_intensity": "Émission des minerais",
|
||||
"sanctuary.shaders.ore_intensity_help": "Luminosité des inclusions. Aucun changement de la lumière du monde ou des apparitions de mobs.",
|
||||
"sanctuary.shaders.ore_intensity_value": "Minerais : %s %%"
|
||||
"sanctuary.shaders.ore_intensity_value": "Minerais : %s %%",
|
||||
"sanctuary.shaders.ssgi": "SSGI · couleurs diffusées",
|
||||
"sanctuary.shaders.ssgi_help": "Expérimental : légère diffusion des couleurs entre surfaces visibles proches. Rayon 2,5 blocs. Les surfaces hors écran ne contribuent pas. Désactivé par défaut.",
|
||||
"sanctuary.shaders.ssgi_intensity": "Intensité du SSGI",
|
||||
"sanctuary.shaders.ssgi_intensity_help": "Force de la teinte diffusée. Zéro coupe le calcul et libère ses ressources. Aucun changement de lumière du monde.",
|
||||
"sanctuary.shaders.ssgi_intensity_value": "SSGI : %s %%"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#version 330
|
||||
#extension GL_ARB_separate_shader_objects : require
|
||||
#include <sanctuary:ssgi.glsl>
|
||||
layout(location=0) in vec2 texCoord;
|
||||
layout(location=0) out vec4 fragColor;
|
||||
void main(){
|
||||
float depth=texture(WorldDepth,texCoord).r;
|
||||
if(depth<=0.0)discard;
|
||||
vec3 p=giPosition(texCoord,depth),normal=giNormal(texCoord,p);
|
||||
vec2 size=vec2(textureSize(IndirectLight,0)),coord=texCoord*size-0.5,base=floor(coord),f=fract(coord);
|
||||
vec3 bounce=vec3(0.0);float total=0.0;
|
||||
for(int y=0;y<2;y++)for(int x=0;x<2;x++){
|
||||
vec2 uv=(base+vec2(x,y)+0.5)/size;
|
||||
vec4 sampleLight=texture(IndirectLight,uv);
|
||||
if(sampleLight.a<=0.0)continue;
|
||||
vec2 surfaceUV=giTexel(uv);vec3 q=giPosition(surfaceUV,texture(WorldDepth,surfaceUV).r);
|
||||
float plane=abs(dot(q-p,normal));
|
||||
// Bilateral reconstruction never blends a wall's light into the foreground silhouette.
|
||||
if(plane>0.06||length(q-p)>0.5)continue;
|
||||
float weight=(x==0?1.0-f.x:f.x)*(y==0?1.0-f.y:f.y)*(1.0-smoothstep(0.01,0.06,plane));
|
||||
bounce+=sampleLight.rgb*weight;total+=weight;
|
||||
}
|
||||
if(total<=0.0001)discard;
|
||||
vec3 albedo=texture(SceneColor,texCoord).rgb;
|
||||
fragColor=vec4(clamp(albedo*bounce/total*GISettings.x*2.0,0.0,0.25),0.0);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#version 330
|
||||
#extension GL_ARB_separate_shader_objects : require
|
||||
#include <sanctuary:ssgi.glsl>
|
||||
layout(location=0) in vec2 texCoord;
|
||||
layout(location=0) out vec4 fragColor;
|
||||
void main(){
|
||||
fragColor=vec4(0.0);
|
||||
vec2 uv=giTexel(texCoord);float depth=texture(WorldDepth,uv).r;
|
||||
if(depth<=0.0)return;
|
||||
vec3 p=giPosition(uv,depth);float distance=length(p);
|
||||
if(distance>=GISettings.w)return;
|
||||
vec3 n=giNormal(uv,p);
|
||||
if(length(n)<0.9)return;
|
||||
vec3 tangent=normalize(cross(n,abs(n.y)<0.9?vec3(0,1,0):vec3(0,0,1)));
|
||||
vec3 bitangent=cross(n,tangent),sum=vec3(0.0);
|
||||
// Fixed hemisphere directions; no per-frame stochastic noise or temporal history.
|
||||
for(int ray=0;ray<12;ray++){
|
||||
float angle=6.2831853*(float(ray<8?ray:ray-8)+0.5)/float(ray<8?8:4);
|
||||
float elevation=ray<8?0.4:0.85;
|
||||
vec3 direction=n*elevation+(tangent*cos(angle)+bitangent*sin(angle))*sqrt(1.0-elevation*elevation);
|
||||
vec3 start=p+n*0.045;
|
||||
for(int step=1;step<=10;step++){
|
||||
float travel=GISettings.z*float(step)/10.0;
|
||||
vec3 probe=start+direction*travel;
|
||||
vec4 clip=ViewProjection*vec4(probe,1.0);
|
||||
if(clip.w<=0.0)break;
|
||||
vec2 hitUV=clip.xy/clip.w*0.5+0.5;
|
||||
if(any(lessThan(hitUV,vec2(0.002)))||any(greaterThan(hitUV,vec2(0.998))))break;
|
||||
hitUV=giTexel(hitUV);float hitDepth=texture(WorldDepth,hitUV).r;
|
||||
if(hitDepth<=0.0)continue;
|
||||
vec3 hit=giPosition(hitUV,hitDepth);float behind=length(probe)-length(hit);
|
||||
if(behind<=0.015)continue;
|
||||
// First occluder terminates the ray even when too thick: never continue behind a wall.
|
||||
vec3 delta=hit-p;float separation=length(delta);
|
||||
if(behind<0.35&&separation>0.08&&separation<GISettings.z&&dot(delta,n)>0.03){
|
||||
vec3 hitNormal=giNormal(hitUV,hit);
|
||||
float facing=max(0.0,dot(hitNormal,-normalize(delta)));
|
||||
float edge=smoothstep(0.0,0.06,min(min(hitUV.x,hitUV.y),min(1.0-hitUV.x,1.0-hitUV.y)));
|
||||
vec3 color=texture(SceneColor,hitUV).rgb;
|
||||
float falloff=1.0-separation/GISettings.z;
|
||||
sum+=color*color*facing*falloff*falloff*edge*giFog(hit);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
fragColor=vec4(sum/12.0*giFog(p)*(1.0-smoothstep(GISettings.w*0.75,GISettings.w,distance)),distance);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
uniform sampler2D WorldDepth;
|
||||
uniform sampler2D SceneColor;
|
||||
uniform sampler2D IndirectLight;
|
||||
layout(std140) uniform GIParams {
|
||||
mat4 InverseViewProjection;
|
||||
mat4 ViewProjection;
|
||||
vec4 GISettings; // strength, zero-to-one clip, local radius, view range
|
||||
vec4 GIFog;
|
||||
vec4 GIEnvironment;
|
||||
};
|
||||
vec3 giPosition(vec2 uv,float depth){
|
||||
vec4 p=InverseViewProjection*vec4(uv*2.0-1.0,GISettings.y>0.5?depth:depth*2.0-1.0,1.0);
|
||||
return p.xyz/p.w;
|
||||
}
|
||||
vec2 giTexel(vec2 uv){vec2 size=vec2(textureSize(WorldDepth,0));return (floor(uv*size)+0.5)/size;}
|
||||
vec3 giNormal(vec2 uv,vec3 p){
|
||||
vec2 pixel=1.0/vec2(textureSize(WorldDepth,0));
|
||||
vec3 l=giPosition(uv-vec2(pixel.x,0),texture(WorldDepth,uv-vec2(pixel.x,0)).r);
|
||||
vec3 r=giPosition(uv+vec2(pixel.x,0),texture(WorldDepth,uv+vec2(pixel.x,0)).r);
|
||||
vec3 d=giPosition(uv-vec2(0,pixel.y),texture(WorldDepth,uv-vec2(0,pixel.y)).r);
|
||||
vec3 u=giPosition(uv+vec2(0,pixel.y),texture(WorldDepth,uv+vec2(0,pixel.y)).r);
|
||||
vec3 dx=length(r-p)<length(p-l)?r-p:p-l;
|
||||
vec3 dy=length(u-p)<length(p-d)?u-p:p-d;
|
||||
vec3 n=cross(dx,dy);n/=max(length(n),0.00001);
|
||||
return dot(n,-p)<0.0?-n:n;
|
||||
}
|
||||
float giFog(vec3 p){
|
||||
float a=clamp((max(length(p.xz),abs(p.y))-GIFog.x)/max(GIFog.y-GIFog.x,0.001),0.0,1.0);
|
||||
float b=clamp((length(p)-GIFog.z)/max(GIFog.w-GIFog.z,0.001),0.0,1.0);
|
||||
return 1.0-max(a,b)*GIEnvironment.x;
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
name = "Sanctuary"
|
||||
author = "KOKA99CAB"
|
||||
version = "beta.131"
|
||||
version = "beta.132"
|
||||
pack-format = "packwiz:1.1.0"
|
||||
|
||||
[index]
|
||||
|
||||
Reference in New Issue
Block a user