Compare commits

...
1 Commits
Author SHA1 Message Date
koka 448e2febff Keep soil matte and align solar wave glints with native sunset in beta.150
Build Sanctuary / build (push) Waiting to run
2026-09-18 21:47:33 +02:00
12 changed files with 227 additions and 18 deletions
+9 -2
View File
@@ -1,5 +1,12 @@
# Sanctuary
## beta.150 — Terre mate et reflets du coucher de soleil (pack local)
Terre mate avec relief conservé. Le soleil et son reflet se colorent ensemble
en suivant la couleur et la transition du coucher de soleil natif de Minecraft.
Les accents lumineux suivent les normales animées des vaguelettes et leur
orientation vers le soleil et la caméra. [Contrat et vérifications](docs/matte-dirt-beta150.md).
## beta.149 — Distances PBR / SSR et feuillage (pack local)
Deux distances séparées, 64 blocs par défaut chacune, jusqu’à 256 blocs.
@@ -2354,7 +2361,7 @@ Depuis beta.090, la cible est **Minecraft 26.3 finale**, publiée le 15 septembr
| Fabric API | 0.160.5+26.3 |
| Fabric Loom | 1.17.20 |
| Gradle Wrapper | 9.5.1, distribution vérifiée par SHA-256 |
| Sanctuary / pack | beta.149 |
| Sanctuary / pack | beta.150 |
Java 25 et Python 3.11 ou plus récent sont nécessaires. Le script pack utilise
uniquement la bibliothèque standard et repère aussi une installation Python
@@ -2383,7 +2390,7 @@ décrits dans [Validation](docs/testing.md).
Résultats :
- `mods/sanctuary/build/libs/sanctuary-beta.149.jar` : mod à installer avec
- `mods/sanctuary/build/libs/sanctuary-beta.150.jar` : mod à installer avec
Fabric API sur la version Minecraft indiquée.
- `build/packwiz/` : pack de développement complet, avec le mod construit et
l'index vérifié. Voir [Installation packwiz](packwiz/README.md).
+96
View File
@@ -0,0 +1,96 @@
# RENDER-150 — Terre mate et reflets solaires colorés
Branche `codex/matte-dirt-beta150`, socle beta.149 `1c23d0c`.
La terre sèche utilise un profil entièrement mat (rugosité 0,98, métal 0),
comme le feuillage corrigé en beta.149. Cela enlève l’éclat blanc/gris diffus
sans supprimer les normales ni le contraste de relief. Le SSR de ces matières
est également atténué par leur rugosité, sans changer son réglage global.
Blocs concernés : dirt, coarse_dirt, rooted_dirt, grass_block, dirt_path,
farmland, podzol et mycelium. Les faces terreuses suivent ainsi le même profil.
Les autres profils de matière sont inchangés.
Le disque du soleil et son reflet utilisent la même couleur native
`SkyRenderState.sunriseAndSunsetColor`, issue de l’attribut environnemental
Minecraft `SUNRISE_SUNSET_COLOR`. Son alpha pilote la transition depuis le
blanc : aucune palette horaire ou altitude artificielle. Le soleil reste
neutre tant que le dégradé natif est absent et se colore avec le ciel.
La teinte solaire est convertie en lumière linéaire pour le PBR. Le mixin
modifie seulement la couleur du disque solaire, préserve l’alpha de pluie,
la texture native et la lune, et respecte la désactivation globale de Vanilla Light.
La fusion diffuse de l’eau et sa normale animée d’origine sont conservées.
Les accents suivent les pentes des vaguelettes qui réfléchissent le soleil
vers la caméra. Leur pulsation vient des normales animées de la texture,
sans oscillateur indépendant ni inversion artificielle du relief.
Une faible variation stable par texel évite des crêtes toutes identiques ;
elle module seulement l’intensité, sans produire des points isolés aléatoires.
Les détails s’atténuent lorsqu’ils deviennent plus petits qu’un pixel écran.
Les accents restent limités au reflet solaire et conservent sa couleur.
Les lampes et la lune gardent leurs
couleurs propres.
PBR 50 %, SSR 20 % et deux portées indépendantes de 64 blocs par défaut conservés.
Minecraft 26.3, Java 25 et dépendances inchangés ; aucune sauvegarde modifiée.
## Vérifications
Tests des profils de terre et scènes GPU comparant PBR OFF/ON sur terre et
feuillage : conservation de la couleur native et présence du relief.
- Suites PBR finales Vulkan/OpenGL réussies, dont profils des huit types de
sol, proximité, intensités, préférences et feuillage.
Journaux : `build/beta150-pbr-vulkan-final.log` et
`build/beta150-pbr-opengl-final.log`.
- Terre : 180 469 pixels contrôlés, saturation relative 0,99877 sur les deux
moteurs ; relief visible sur 145 415 pixels. Captures conservées dans
`build/evidence/beta150-pbr-vulkan-final/` et
`build/evidence/beta150-pbr-opengl-final/`.
Les tests comparent la contribution PBR au rendu natif et capturent aussi
le disque du soleil, avant puis pendant la transition native du ciel.
Le contrôle serveur `./gradlew check build`, exécuté dans une copie isolée
`build/server-validation` avant les derniers ajouts exclusivement client,
compte 229 réussites et 23 échecs sur 252 tests. Les 23 identifiants d’échec
sont identiques à beta.149 ; aucun nouvel identifiant. Le contrôle global
n’est donc pas vert. Journaux : `build/beta150-check-build-isolated.log` et
`build/beta150-server-failures.json`.
Les scènes solaires comparent le rendu PBR au natif, avant et pendant le
coucher de soleil. À 2 000 ticks, la teinte native est blanche ; à 11 500 ticks
sa transition commence légèrement (alpha 0,051) ; à 12 400 ticks l’alpha
natif atteint 0,831 et le disque devient chaud (centre RGB 255/255/135).
Les deux moteurs ont validé cette synchronisation native.
Un lancement sans sélection PBR a également rencontré l’assertion client
Blocodex « The full snapshot must become visible together », avant les scènes
GPU (`build/beta150-water-motion-vulkan.log`). Il ne constitue pas une
validation de la suite client générale.
La suite finale Vulkan des éclats liés aux vaguelettes passe
(`build/beta150-wave-glints-vulkan.log`) : animation native, masquage F5,
distances indépendantes, reflets solaires et lunaires, matériaux transparents,
rechargement des ressources et redimensionnement. Dans la scène solaire,
150 563 pixels sont éclaircis, sans pixel blanc saturé.
La suite OpenGL finale passe également
(`build/beta150-wave-glints-opengl.log`) : 148 180 pixels éclaircis, aucun
pixel blanc saturé. Les captures finales sont conservées dans
`build/evidence/beta150-wave-glints-vulkan/` et
`build/evidence/beta150-wave-glints-opengl/`.
Le ressenti des vaguelettes en mouvement reste à confirmer par le créateur
sur sa configuration ; les tests ne constituent pas une validation esthétique.
## Pack local
Construction et assemblage réussis avec
`./gradlew build assemblePack -x :sanctuary:check`, après le contrôle complet
ci-dessus. L’exclusion ne transforme pas ses échecs connus en réussite.
Export packwiz puis vérification ZIP, version Fabric, Minecraft 26.3,
Loader 0.19.5, JAR unique identique au construit, shader final et mixin solaire.
- Pack : `/Users/koka/Documents/sanctuary/sanctuary-beta/build/Sanctuary-beta.150.mrpack`
- Taille : 10522942 octets.
- SHA-256 pack : `65a4a44948dcdcf0325b5e24c076a03f8832184f25f5c1ff16f69bd3ee641b93`.
- SHA-256 JAR : `4962e6f8cafd6ef3e81e088bf9b0092a264441632cfa095d4fe7394b855f093d`.
- Reçu : `build/beta150-artifacts.json`, également copié à côté du pack.
Aucun canal publié, aucune instance Prism ni sauvegarde personnelle modifiée.
+2 -2
View File
@@ -9,8 +9,8 @@ loom_version=1.17.20
fabric_api_version=0.160.5+26.3
# Release counter: beta.001, .002, .003, ... (see docs/versioning.md).
mod_version=beta.149
pack_version=beta.149
mod_version=beta.150
pack_version=beta.150
resource_pack_version=beta.121
maven_group=fr.koka.sanctuary
jei_version=30.32.0-sanctuary.3
@@ -50,28 +50,29 @@ public final class Pbr134ClientChecks implements FabricClientGameTest {
c.runOnClient(m->m.getWindow().setWindowed(oldWidth,oldHeight));c.waitFor(m->m.gameRenderer.mainRenderTarget().width==on.width(),200);frames(c);
c.runOnClient(m->{VanillaLight.setPixelShadows(true);VanillaLight.setSsgi(true);VanillaLight.setBloom(true);VanillaLight.setOreEmission(true);VanillaLight.setEdgeHighlights(true);});
c.waitFor(m->PixelShadows.applied()&&PixelShadows.ready()&&ScreenSpaceGI.applied()&&Bloom.applied()&&EdgeHighlights.applied()&&ProceduralPbr.applied(),1200);frames(c);capture(c,"pbr-combined");
foliage(c,server);
matteGround(c,server,Blocks.OAK_LEAVES,"leaves",true);
matteGround(c,server,Blocks.DIRT,"dirt",false);
c.runOnClient(m->VanillaLight.setEnabled(false));c.waitTicks(8);c.runOnClient(m->check(!ProceduralPbr.allocated()&&ProceduralPbr.materialCount()==0,"Global OFF releases PBR"));
c.runOnClient(m->{VanillaLight.setEnabled(true);VanillaLight.setPbr(false);VanillaLight.setSsgi(false);});
System.out.println("PBR134_PASS normals; profiles; texture cache; directional lighting; intensity; OFF/zero; tiny camera; reload; resize; combined; default="+delta+" high="+high+" directional="+directions);
}
private static void foliage(ClientGameTestContext c,TestServerContext server){
private static void matteGround(ClientGameTestContext c,TestServerContext server,net.minecraft.world.level.block.Block block,String name,boolean leaves){
c.runOnClient(m->{VanillaLight.setSsr(false);VanillaLight.setPbrIntensity(50);VanillaLight.setPixelShadows(false);VanillaLight.setSsgi(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);VanillaLight.setEdgeHighlights(false);});
server.runOnServer(s->{command(s,"time set 1000");for(int x=-5;x<=24;x++)for(int z=-5;z<=24;z++)
s.overworld().setBlockAndUpdate(new BlockPos(x,64,z),Blocks.OAK_LEAVES.defaultBlockState().setValue(net.minecraft.world.level.block.LeavesBlock.PERSISTENT,true));});
server.runOnServer(s->{if(leaves)command(s,"time set 1000");for(int x=-5;x<=24;x++)for(int z=-5;z<=24;z++)
s.overworld().setBlockAndUpdate(new BlockPos(x,64,z),leaves?block.defaultBlockState().setValue(net.minecraft.world.level.block.LeavesBlock.PERSISTENT,true):block.defaultBlockState());});
camera(c,server,10.5,69,5.5,65);look(c,server,0,40);settle(c);
enable(c,false);var off=capture(c,"pbr149-leaves-off");enable(c,true);var on=capture(c,"pbr149-leaves-on");
enable(c,false);var off=capture(c,"pbr150-"+name+"-off");enable(c,true);var on=capture(c,"pbr150-"+name+"-on");
double nativeChroma=0,pbrChroma=0;int samples=0,relief=0;
for(int y=off.height()/3;y<off.height()*9/10;y++)for(int x=off.width()/10;x<off.width()*9/10;x++){
int i=y*off.width()+x,a=off.pixels()[i],b=on.pixels()[i],r=a>>16&255,g=a>>8&255,blue=a&255;
if(g<30||g<r*1.1||g<blue*1.1)continue;
if(leaves?(g<30||g<r*1.1||g<blue*1.1):(r<30||r<g*1.05||g<blue*1.1))continue;
nativeChroma+=chroma(a);pbrChroma+=chroma(b);samples++;
if(Math.abs((b>>16&255)-r)+Math.abs((b>>8&255)-g)+Math.abs((b&255)-blue)>6)relief++;
}
double retained=pbrChroma/nativeChroma;
System.out.println("PBR149_LEAVES samples="+samples+" chromaRetained="+retained+" relief="+relief);
check(samples>1000&&retained>.985,"Leaves preserve green colour without specular veil: "+retained);
check(relief>100,"Leaf normal relief remains visible");
System.out.println("PBR150_MATTE material="+name+" samples="+samples+" chromaRetained="+retained+" relief="+relief);
check(samples>1000&&retained>.985,name+" preserves native colour without specular veil: "+retained);
check(relief>100,name+" normal relief remains visible");
}
private static double chroma(int pixel){
int r=pixel>>16&255,g=pixel>>8&255,b=pixel&255,max=Math.max(r,Math.max(g,b)),min=Math.min(r,Math.min(g,b));
@@ -105,6 +106,8 @@ public final class Pbr134ClientChecks implements FabricClientGameTest {
check(PbrMaterials.profile("water").metal()==0&&PbrMaterials.profile("glass").metal()==0,"Water and glass stay dielectric");
int[] glass={0x00FFFFFF,0x00000000,0x00000000,0x00FFFFFF};
for(int p:PbrMaterials.generate(glass,2,2,PbrMaterials.profile("glass")))check((p>>8&0xFFFF)==0x8080,"Transparent glass texels fall back to geometric normal");
for(String soil:new String[]{"dirt","coarse_dirt","rooted_dirt","grass_block","dirt_path","farmland","podzol","mycelium"})
check(PbrMaterials.profile(soil).roughness()>=.98f&&PbrMaterials.profile(soil).metal()==0,"Dry soil has a fully matte profile: "+soil);
check(PbrMaterials.profile("oak_planks").roughness()>PbrMaterials.profile("polished_andesite").roughness(),"Wood remains rougher than polished stone");
}
private static void preferences(){try{
@@ -89,6 +89,37 @@ final class PixelReflectionsClientChecks {
var sunLook=c.computeOnClient(m->{var sun=PixelShadows.sunDirection(m.gameRenderer.gameRenderState().levelRenderState.skyRenderState.sunAngle);return new float[]{(float)Math.toDegrees(Math.atan2(-sun.x,sun.z)),(float)Math.toDegrees(Math.asin(sun.y))};});
look(c,server,sunLook[0],sunLook[1]);frames(c);capture(c,"ssr146-solar-path-low-sun");
c.runOnClient(m->VanillaLight.setBloom(true));frames(c);capture(c,"ssr146-solar-path-low-sun-bloom");
for(int time:new int[]{2000,11500,12400}){
c.runOnClient(m->{VanillaLight.setBloom(false);VanillaLight.setSsr(false);});
server.runOnServer(s->command(s,"time set "+time));c.waitTicks(15);
var solarLook=c.computeOnClient(m->{var sun=PixelShadows.sunDirection(m.gameRenderer.gameRenderState().levelRenderState.skyRenderState.sunAngle);return new float[]{(float)Math.toDegrees(Math.atan2(-sun.x,sun.z)),(float)Math.toDegrees(Math.asin(sun.y))};});
look(c,server,solarLook[0],solarLook[1]);pbrFrames(c);
var warm=capture(c,"ssr150-solar-"+time);
c.runOnClient(m->VanillaLight.setPbr(false));c.waitTicks(8);var nativeWater=capture(c,"ssr150-solar-native-"+time);
long red=0,green=0,blue=0;int samples=0;
for(int y=warm.height()/3;y<warm.height()*9/10;y++)for(int x=warm.width()/3;x<warm.width()*2/3;x++){
int i=y*warm.width()+x,a=nativeWater.pixels()[i],b=warm.pixels()[i];
int dr=(b>>16&255)-(a>>16&255),dg=(b>>8&255)-(a>>8&255),db=(b&255)-(a&255);
if(dr>3){red+=dr;green+=dg;blue+=db;samples++;}
}
System.out.println("PBR150_SOLAR_COLOUR time="+time+" pitch="+solarLook[1]+" samples="+samples+" red="+red+" green="+green+" blue="+blue);
var tint=c.computeOnClient(m->SolarColour.tint(m.gameRenderer.gameRenderState().levelRenderState.skyRenderState));
float nativeFade=c.computeOnClient(m->m.gameRenderer.gameRenderState().levelRenderState.skyRenderState.sunriseAndSunsetColor.w());
System.out.println("PBR150_NATIVE_SUNSET time="+time+" fade="+nativeFade+" tint="+tint);
check(samples>500,"Solar reflection remains visible: "+time);
if(nativeFade>.1)check(red>green*1.1&&red>blue*1.1,"Reflection follows warm native horizon colour: "+time);
if(nativeFade==0)check(tint.equals(new org.joml.Vector3f(1)),"No early tint before native sunset");
c.runOnClient(m->{VanillaLight.setPbr(true);VanillaLight.setSsr(true);VanillaLight.setBloom(true);});frames(c);capture(c,"ssr150-solar-bloom-"+time);
c.runOnClient(m->VanillaLight.setBloom(false));
look(c,server,solarLook[0],-solarLook[1]);frames(c);var sunDisc=capture(c,"ssr150-sun-disc-"+time);
long sr=0,sg=0,sb=0;int sunSamples=0;
for(int y=sunDisc.height()/2-5;y<sunDisc.height()/2+5;y++)for(int x=sunDisc.width()/2-5;x<sunDisc.width()/2+5;x++){
int pixel=sunDisc.pixels()[y*sunDisc.width()+x];sr+=pixel>>16&255;sg+=pixel>>8&255;sb+=pixel&255;sunSamples++;
}
System.out.println("PBR150_SUN_DISC time="+time+" red="+sr/sunSamples+" green="+sg/sunSamples+" blue="+sb/sunSamples);
if(nativeFade>.1)check(sr>sb*1.05,"Sun disc follows native warm tint: "+time);
look(c,server,solarLook[0],solarLook[1]);
}
c.runOnClient(m->{VanillaLight.setBloom(false);VanillaLight.setSsrIntensity(100);});
server.runOnServer(s->command(s,"time set 6000"));c.waitTicks(10);
camera(c,server,.5,68,0,70);look(c,server,0,12);settle(c);
@@ -228,6 +259,7 @@ final class PixelReflectionsClientChecks {
int i=y*a.width()+x,aa=a.pixels()[i],bb=b.pixels()[i];
if(Math.abs((aa>>16&255)-(bb>>16&255))+Math.abs((aa>>8&255)-(bb>>8&255))+Math.abs((aa&255)-(bb&255))>8)n++;
}return n;}
private static void pbrFrames(ClientGameTestContext c){int start=c.computeOnClient(m->ProceduralPbr.renderedFrames());c.waitFor(m->ProceduralPbr.applied()&&ProceduralPbr.renderedFrames()>start+8,1200);}
private static void frames(ClientGameTestContext c){int start=c.computeOnClient(m->PixelReflections.frames());c.waitFor(m->PixelReflections.applied()&&ProceduralPbr.ready()&&PixelReflections.frames()>start+8,1200);}
private static void settle(ClientGameTestContext c){c.waitTicks(45);c.waitFor(m->m.levelRenderer.hasRenderedAllSections()&&ProceduralPbr.ready(),1200);c.waitTicks(10);}
}
@@ -32,6 +32,8 @@ final class PbrMaterials implements AutoCloseable {
if(name.equals("iron_block")||name.equals("gold_block")||name.equals("netherite_block")||name.contains("copper")&&!name.contains("ore")&&!name.contains("raw"))
return new Profile(name.contains("oxidized")?.65f:.32f,name.contains("oxidized")?.35f:.85f);
if(name.contains("polished")||name.contains("glazed")||name.equals("diamond_block")||name.equals("emerald_block")||name.equals("ruby_block")||name.equals("sapphire_block"))return new Profile(.42f,0);
// Dry soil keeps its normal relief without a broad white specular coating.
if(java.util.Set.of("dirt","coarse_dirt","rooted_dirt","grass_block","dirt_path","farmland","podzol","mycelium").contains(name))return new Profile(.98f,0);
if(name.contains("wool")||name.contains("leaves"))return new Profile(.98f,0);
if(name.contains("planks")||name.contains("log")||name.contains("wood"))return new Profile(.88f,0);
return new Profile(.82f,0);
@@ -66,13 +66,16 @@ public final class ProceduralPbr {
if(lunar)sun=PixelShadows.sunDirection(sky.moonAngle);
float moon=sky.moonPhase==null?1:(float)(.5+.5*Math.cos(sky.moonPhase.index()*Math.PI/4));
float strength=(lunar?.12f*moon:1)*sky.rainBrightness*Math.clamp((sun.y-.04f)/.16f,0,1);
var sunColour=lunar?new org.joml.Vector3f(1):SolarColour.tint(sky);
boolean shadow=PixelShadows.applied();
scene.rotate();try(var mapped=scene.currentBuffer().map(false,true)){
Std140Builder.intoBuffer(mapped.data()).putMat4f(new Matrix4f(vp).invert())
.putVec4(sun.x,sun.y,sun.z,strength)
.putVec4(VanillaLight.pbrIntensity()/100f,RenderSystem.getDevice().getDeviceInfo().isZZeroToOne()?1:0,shadow?1:0,VanillaLight.pbrDistance())
.putVec4(fog.renderDistanceStart,fog.renderDistanceEnd,fog.environmentalStart,fog.environmentalEnd).putVec4(fog.color.w,ColoredLights.directionalReady()?1:0,lunar?0:1,0)
.putVec4(0,0,0,0).putVec4(0,0,0,0).putVec4(0,0,0,0).putVec4(0,0,0,0);
.putVec4(sunColour.x*sunColour.x,sunColour.y*sunColour.y,sunColour.z*sunColour.z,1)
.putVec4((float)(camera.pos.x%256),(float)(camera.pos.y%256),(float)(camera.pos.z%256),0)
.putVec4(0,0,0,0).putVec4(0,0,0,0);
}
var transforms=new ArrayList<GpuBufferSlice>(entries.size());int max=0;
for(var entry:entries){var p=entry.origin();var offset=new Vector3f((float)(p.getX()-camera.pos.x),(float)(p.getY()-camera.pos.y),(float)(p.getZ()-camera.pos.z));
@@ -0,0 +1,15 @@
package fr.koka.sanctuary.client.shader;
import net.minecraft.client.renderer.state.level.SkyRenderState;
import org.joml.Vector3f;
/** Uses the exact colour and fade already extracted for Minecraft's horizon. */
public final class SolarColour {
private SolarColour(){}
public static Vector3f tint(SkyRenderState sky){
var colour=sky.sunriseAndSunsetColor;
if(colour==null)return new Vector3f(1);
float weight=Math.clamp(colour.w(),0,1);
return new Vector3f(1).lerp(new Vector3f(colour.x(),colour.y(),colour.z()),weight);
}
}
@@ -0,0 +1,28 @@
package fr.koka.sanctuary.mixin.client;
import com.mojang.renderpearl.api.buffers.GpuBufferSlice;
import fr.koka.sanctuary.client.shader.SolarColour;
import fr.koka.sanctuary.client.shader.VanillaLight;
import net.minecraft.client.renderer.SkyRenderer;
import net.minecraft.client.renderer.state.level.SkyRenderState;
import org.joml.Vector3f;
import org.joml.Vector4f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(SkyRenderer.class)
abstract class SolarColourMixin {
@Unique private Vector3f sanctuary$sunTint=new Vector3f(1);
@Inject(method="render",at=@At("HEAD"))
private void sanctuary$nativeSunset(GpuBufferSlice fog,SkyRenderState sky,CallbackInfo ci){
sanctuary$sunTint=VanillaLight.active()?SolarColour.tint(sky):new Vector3f(1);
}
@ModifyArg(method="renderSun",at=@At(value="INVOKE",target="Lnet/minecraft/client/renderer/DynamicGpuData;writeTransform(Lorg/joml/Matrix4f;Lorg/joml/Vector4f;)Lcom/mojang/renderpearl/api/buffers/GpuBufferSlice;"),index=1)
private Vector4f sanctuary$tintSun(Vector4f nativeColour){
return new Vector4f(nativeColour.x*sanctuary$sunTint.x,nativeColour.y*sanctuary$sunTint.y,nativeColour.z*sanctuary$sunTint.z,nativeColour.w);
}
}
@@ -25,6 +25,8 @@ layout(std140) uniform PbrScene {
vec4 PbrParams;
vec4 PbrFog;
vec4 PbrEnvironment;
vec4 PbrSunColour;
vec4 PbrWaterOrigin;
};
layout(std140) uniform PixelShadows {
mat4 InverseViewProjection;
@@ -102,6 +104,8 @@ void main(){
// Derivatives precede divergent discard; UV-space tangents preserve rotated/mirrored block textures.
vec3 dx=dFdx(relativePosition),dy=dFdy(relativePosition);
vec2 tx=dFdx(blockUv),ty=dFdy(blockUv);
vec2 atlasSize=vec2(textureSize(BlockAtlas,0));
float texelFootprint=max(length(tx*atlasSize),length(ty*atlasSize));
float det=tx.x*ty.y-tx.y*ty.x;
vec3 baseNormal=normalize(cross(dx,dy));if(dot(baseNormal,-relativePosition)<0.0)baseNormal=-baseNormal;
// Exact receiver depth is tested by the GPU; final scene depth also masks entities.
@@ -119,6 +123,14 @@ void main(){
// RG stores signed height derivatives; B roughness, A metalness, not transparency.
float intensity=PbrParams.x;
vec2 slope=(material.rg*255.0-128.0)/127.0*intensity*(family>1.5?.24:2.0);
// Slight world-anchored variation avoids identical crests on every water block.
float waterVariation=1.0;
if(family>1.5){
uvec3 cell=uvec3(floor(mod(relativePosition+PbrWaterOrigin.xyz,256.0)*16.0+.001));
uint seed=cell.x*1597334677u^cell.y*3812015801u^cell.z*2798796415u;
seed=(seed^(seed>>16u))*2246822519u;seed^=seed>>13u;
waterVariation=.75+.25*float(seed&65535u)/65535.0;
}
vec3 n=normalize(baseNormal+tangent*slope.x+bitangent*slope.y);
// Reject degenerate/interpolated geometry without replacing the native scene with NaNs.
if(any(isnan(n))||any(isinf(n))||dot(relativePosition,relativePosition)<.000001)discard;
@@ -132,6 +144,8 @@ void main(){
vec3 specular=localShine(n,v,l,solarMaterial,albedo);
// PbrEnvironment.z identifies sunlight: moonlight keeps its modest base radiance.
float solarRadiance=mix(1.0,family>1.5?6.0:4.0,PbrEnvironment.z);
// Same native sunset tint as the sun disc, converted to linear light.
vec3 sunColour=PbrSunColour.rgb;
float fog=max(clamp((max(length(relativePosition.xz),abs(relativePosition.y))-PbrFog.x)/max(PbrFog.y-PbrFog.x,.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)
@@ -157,10 +171,18 @@ void main(){
if(family>1.5&&PbrEnvironment.z>.5){
// Soft-light/screen blend per native texel: broad daylight lifts the water
// without replacing its colour and detail with a saturated white cone.
vec3 weight=skyShine/(1.0+skyShine);
vec3 highlight=mix(vec3(1.0),sqrt(lit),.55);
// Glints follow animated wave facets reflecting the sun towards the eye.
// Their pulse comes from the native wave animation, not an unrelated timer.
vec3 halfSum=l+v;
float alignment=pow(max(dot(n,halfSum*inversesqrt(max(dot(halfSum,halfSum),.00001))),0.0),384.0);
float crest=smoothstep(.025,.18,length((material.rg*255.0-128.0)/127.0));
float detail=1.0-smoothstep(.75,2.0,texelFootprint);
float glint=alignment*crest*waterVariation*detail;
vec3 glintShine=skyShine*(1.0+glint*1.2);
vec3 weight=glintShine/(1.0+glintShine);
vec3 highlight=max(lit,mix(sunColour,sqrt(lit)*sunColour,mix(.55,.30,glint)));
lit=mix(lit,highlight,weight);
}else lit+=(1.0-lit)*(1.0-exp(-skyShine));
}else lit+=(1.0-lit)*(1.0-exp(-skyShine*sunColour));
lit+=(1.0-lit)*(1.0-exp(-lampShine));
if(any(isnan(lit))||any(isinf(lit)))discard;
fragColor=vec4(sqrt(clamp(lit,0.0,1.0)),1.0);
@@ -263,6 +263,7 @@
"client.InventoryPointerAccess",
"client.OperatorWorldOptionsMixin",
"client.StarSkyMixin",
"client.SolarColourMixin",
"client.ProgressiveFogMixin",
"client.ShaderSaturationMixin",
"client.WeatherEnvironmentMixin",
+1 -1
View File
@@ -1,6 +1,6 @@
name = "Sanctuary"
author = "KOKA99CAB"
version = "beta.149"
version = "beta.150"
pack-format = "packwiz:1.1.0"
[index]