Am Lightning System gearbeitet

This commit is contained in:
2026-07-17 11:23:44 +02:00
parent cdaaba3841
commit 5ed7b4017c
23 changed files with 928 additions and 164 deletions

View File

@@ -9,6 +9,9 @@ import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
import com.jme3.bullet.control.CharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.environment.EnvironmentCamera;
import com.jme3.environment.LightProbeFactory;
import com.jme3.environment.generation.JobProgressAdapter;
import com.jme3.light.*;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
@@ -338,12 +341,35 @@ public class WorldScene extends BaseAppState {
drownState = new de.blight.game.state.DrownState(terrainChunkState, physicsChar, playerInput, drownMc);
app.getStateManager().attach(drownState);
// Maus einfangen keine Klick-Pflicht für Kamerasteuerung
app.getInputManager().setCursorVisible(false);
}
@Override
public void update(float tpf) {
// LightProbe backen: nach 30 Frames auf Mittag stellen (SkyControl braucht mehrere
// Frames um die Himmels-Geometrie zu aktualisieren). Nach dem Backen Uhrzeit zurück.
if (envCam != null) {
envCamFrames++;
if (envCamFrames == 2) {
// Himmel schon jetzt auf Mittag damit SkyControl bis Frame 30 rendern kann
dayNight.getDayTime().setTimeOfDay(0.5f);
} else if (envCamFrames == 30) {
log.info("[WorldScene] Starte LightProbe-Backen bei Mittag (Frame {})...", envCamFrames);
final float restoreTime = 0.25f;
LightProbeFactory.updateProbe(probe, envCam, rootNode,
new JobProgressAdapter<LightProbe>() {
@Override public void done(LightProbe result) {
dayNight.getDayTime().setTimeOfDay(restoreTime);
app.getStateManager().detach(envCam);
envCam = null;
log.info("[WorldScene] LightProbe fertig, Uhrzeit zurück auf {}.", restoreTime);
}
});
}
}
// Physik-Charakter erst aktivieren wenn BulletAppState bereit ist UND
// TerrainChunkState bereits einen Physik-Collider für den Spawn-Chunk hat.
if (physicsCharPending) {
@@ -380,17 +406,16 @@ public class WorldScene extends BaseAppState {
if (ambientSounds != null) ambientSounds.setPlayerPosition(pos);
}
// Terrain-Shader mit DayNightState-Licht synchronisieren (Richtung + Farben)
if (terrainMaterial != null && dayNight != null
&& dayNight.getSunLight() != null) {
terrainMaterial.setVector3("LightDir",
dayNight.getSunDirection().negate());
if (terrainMaterial != null && dayNight != null && dayNight.getSunLight() != null) {
terrainMaterial.setVector3("LightDir", dayNight.getSunDirection().negate());
ColorRGBA sc = dayNight.getSunLight().getColor();
ColorRGBA ac = dayNight.getAmbientLight().getColor();
terrainMaterial.setVector3("SunColor",
new Vector3f(sc.r, sc.g, sc.b));
terrainMaterial.setVector3("AmbientColor",
new Vector3f(ac.r, ac.g, ac.b));
ColorRGBA ac = dayNight.getCustomAmbient();
terrainMaterial.setVector3("SunColor", new Vector3f(sc.r, sc.g, sc.b));
terrainMaterial.setVector3("AmbientColor", new Vector3f(ac.r, ac.g, ac.b));
// Charakter-PBR: Emissive = customAmbient (IBL-Fallback ohne fertige LightProbe)
for (Material mat : characterPbrMaterials) {
mat.setColor("Emissive", new ColorRGBA(ac.r, ac.g, ac.b, 1f));
}
}
}
@@ -507,6 +532,26 @@ public class WorldScene extends BaseAppState {
}
}
/**
* Sammelt alle PBRLighting-Materialien und setzt EmissivePower=1 (Standardwert 3.0
* würde 0.08 → 0.08³=0.0005 zusammenbrechen lassen). Initial mit Tag-Ambient initialisiert.
*/
public static void collectPbrMaterials(Spatial s, List<com.jme3.material.Material> out) {
if (s instanceof Geometry g) {
com.jme3.material.Material m = g.getMaterial();
if (m != null && "PBRLighting".equals(m.getMaterialDef().getName())) {
m.setFloat("EmissivePower", 1f);
m.setFloat("EmissiveIntensity", 1f);
m.setColor("Emissive", new ColorRGBA(0.08f, 0.10f, 0.16f, 1f));
out.add(m);
}
} else if (s instanceof Node n) {
for (Spatial child : n.getChildren()) {
collectPbrMaterials(child, out);
}
}
}
/** Lädt das Hauptcharakter-Modell, falls im character/-Verzeichnis definiert; sonst Platzhalter. */
private Node loadOrBuildCharacter() {
MainCharacter mc = findMainCharacter();
@@ -516,6 +561,8 @@ public class WorldScene extends BaseAppState {
stripEmbeddedClips(loaded, mc.getModelPath());
loaded.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
applyFullAmbient(loaded);
collectPbrMaterials(loaded, characterPbrMaterials);
log.info("[WorldScene] {} PBR-Materialien im Charakter-Modell gefunden.", characterPbrMaterials.size());
// Auf 1.8 m skalieren Höhe aus Vertex-Daten (zuverlässiger als BoundingBox
// bei Skinned-Meshes, die vor der ersten SkinningControl-Runde falsche Bounds liefern)
@@ -628,6 +675,15 @@ public class WorldScene extends BaseAppState {
shadowFilter.setEnabled(true);
dayNight.setShadowFilter(shadowFilter); // bindet sun + übernimmt Intensitäts-Updates
// LightProbe für PBR-IBL: ohne Probe ignoriert JME3-PBR den g_AmbientLightColor.
// EnvironmentCamera backt in den ersten Frames, AmbientLight skaliert dann dynamisch.
probe = new LightProbe();
probe.getArea().setRadius(4000f);
rootNode.addLight(probe);
envCam = new EnvironmentCamera(256, new Vector3f(0f, 5f, 0f));
app.getStateManager().attach(envCam);
envCamFrames = 0;
setupPostProcessing(dayNight.getSunDirection(), shadowFilter);
}
@@ -763,19 +819,17 @@ public class WorldScene extends BaseAppState {
Node node = new Node("gebirge");
Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
Material mat = new Material(assetManager, "Common/MatDefs/Light/PBRLighting.j3md");
mat.setFloat("Metallic", 0.0f);
mat.setFloat("Roughness", 0.9f);
try {
Texture rock = assetManager.loadTexture("Textures/Terrain/Rock2/rock.jpg");
rock.setWrap(Texture.WrapMode.Repeat);
mat.setTexture("DiffuseMap", rock);
mat.setColor("Diffuse", new ColorRGBA(0.45f, 0.32f, 0.25f, 1f));
mat.setTexture("BaseColorMap", rock);
mat.setColor("BaseColor", new ColorRGBA(0.45f, 0.32f, 0.25f, 1f));
} catch (Exception e) {
mat.setBoolean("UseMaterialColors", true);
mat.setColor("Diffuse", new ColorRGBA(0.18f, 0.12f, 0.08f, 1f));
mat.setColor("BaseColor", new ColorRGBA(0.18f, 0.12f, 0.08f, 1f));
}
mat.setColor("Ambient", new ColorRGBA(0.10f, 0.07f, 0.05f, 1f));
mat.setColor("Specular", new ColorRGBA(0.06f, 0.05f, 0.04f, 1f));
mat.setFloat("Shininess", 6f);
mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
mat.getAdditionalRenderState().setPolyOffset(2f, 2f);
@@ -904,6 +958,10 @@ public class WorldScene extends BaseAppState {
private static final int TERRAIN_ARRAY_SIZE = 1024;
private DayNightState dayNight;
private LightProbe probe;
private EnvironmentCamera envCam;
private int envCamFrames = 0;
private final List<Material> characterPbrMaterials = new ArrayList<>();
private int debugMode = 0; // 0=normal, 1=kein Licht, 2=nur Slot0, 3=direkte Tex2D
private String debugSlot0Path = "";
@@ -1118,10 +1176,10 @@ public class WorldScene extends BaseAppState {
mat.setBoolean("useTriPlanarMapping", false);
return mat;
} catch (Exception e) {
Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
mat.setBoolean("UseMaterialColors", true);
mat.setColor("Diffuse", new ColorRGBA(0.28f, 0.58f, 0.18f, 1f));
mat.setColor("Ambient", new ColorRGBA(0.15f, 0.30f, 0.09f, 1f));
Material mat = new Material(assetManager, "Common/MatDefs/Light/PBRLighting.j3md");
mat.setFloat("Metallic", 0.0f);
mat.setFloat("Roughness", 0.9f);
mat.setColor("BaseColor", new ColorRGBA(0.28f, 0.58f, 0.18f, 1f));
return mat;
}
}
@@ -1143,24 +1201,20 @@ public class WorldScene extends BaseAppState {
// -----------------------------------------------------------------------
private void buildDecorations(TerrainQuad terrain) {
Material stoneMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
stoneMat.setBoolean("UseMaterialColors", true);
stoneMat.setColor("Diffuse", new ColorRGBA(0.55f, 0.55f, 0.55f, 1f));
stoneMat.setColor("Ambient", new ColorRGBA(0.3f, 0.3f, 0.3f, 1f));
stoneMat.setColor("Specular", ColorRGBA.White);
stoneMat.setFloat("Shininess", 32f);
Material stoneMat = new Material(assetManager, "Common/MatDefs/Light/PBRLighting.j3md");
stoneMat.setFloat("Metallic", 0.0f);
stoneMat.setFloat("Roughness", 0.8f);
stoneMat.setColor("BaseColor", new ColorRGBA(0.55f, 0.55f, 0.55f, 1f));
Material treeTrunkMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
treeTrunkMat.setBoolean("UseMaterialColors", true);
treeTrunkMat.setColor("Diffuse", new ColorRGBA(0.45f, 0.28f, 0.1f, 1f));
treeTrunkMat.setColor("Ambient", new ColorRGBA(0.2f, 0.12f, 0.04f, 1f));
treeTrunkMat.setColor("Specular", ColorRGBA.Black);
Material treeTrunkMat = new Material(assetManager, "Common/MatDefs/Light/PBRLighting.j3md");
treeTrunkMat.setFloat("Metallic", 0.0f);
treeTrunkMat.setFloat("Roughness", 0.95f);
treeTrunkMat.setColor("BaseColor", new ColorRGBA(0.45f, 0.28f, 0.1f, 1f));
Material treeTopMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
treeTopMat.setBoolean("UseMaterialColors", true);
treeTopMat.setColor("Diffuse", new ColorRGBA(0.1f, 0.55f, 0.15f, 1f));
treeTopMat.setColor("Ambient", new ColorRGBA(0.05f, 0.25f, 0.07f, 1f));
treeTopMat.setColor("Specular", ColorRGBA.Black);
Material treeTopMat = new Material(assetManager, "Common/MatDefs/Light/PBRLighting.j3md");
treeTopMat.setFloat("Metallic", 0.0f);
treeTopMat.setFloat("Roughness", 0.95f);
treeTopMat.setColor("BaseColor", new ColorRGBA(0.1f, 0.55f, 0.15f, 1f));
float[][] treeXZ = {
{12, 8}, {-15, 5}, {20, -10}, {-8, -18},
@@ -1216,19 +1270,15 @@ public class WorldScene extends BaseAppState {
private Node buildCharacter() {
Node character = new Node("character");
Material bodyMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
bodyMat.setBoolean("UseMaterialColors", true);
bodyMat.setColor("Diffuse", new ColorRGBA(0.2f, 0.4f, 0.8f, 1f));
bodyMat.setColor("Ambient", new ColorRGBA(0.1f, 0.2f, 0.4f, 1f));
bodyMat.setColor("Specular", ColorRGBA.White);
bodyMat.setFloat("Shininess", 64f);
Material bodyMat = new Material(assetManager, "Common/MatDefs/Light/PBRLighting.j3md");
bodyMat.setFloat("Metallic", 0.0f);
bodyMat.setFloat("Roughness", 0.7f);
bodyMat.setColor("BaseColor", new ColorRGBA(0.2f, 0.4f, 0.8f, 1f));
Material headMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
headMat.setBoolean("UseMaterialColors", true);
headMat.setColor("Diffuse", new ColorRGBA(0.9f, 0.75f, 0.6f, 1f));
headMat.setColor("Ambient", new ColorRGBA(0.45f, 0.37f, 0.3f, 1f));
headMat.setColor("Specular", new ColorRGBA(0.2f, 0.2f, 0.2f, 1f));
headMat.setFloat("Shininess", 16f);
Material headMat = new Material(assetManager, "Common/MatDefs/Light/PBRLighting.j3md");
headMat.setFloat("Metallic", 0.0f);
headMat.setFloat("Roughness", 0.8f);
headMat.setColor("BaseColor", new ColorRGBA(0.9f, 0.75f, 0.6f, 1f));
Geometry body = new Geometry("body", new Cylinder(8, 16, 0.35f, 1.1f, true));
body.setMaterial(bodyMat);

View File

@@ -26,12 +26,7 @@ public class DayNightState extends BaseAppState implements TimeListener {
private static final Logger log = LoggerFactory.getLogger(DayNightState.class);
// ── Farb-Konstanten ───────────────────────────────────────────────────────
private static final ColorRGBA SUN_DAY = new ColorRGBA(1.00f, 0.95f, 0.88f, 1f);
private static final ColorRGBA SUN_DAWN = new ColorRGBA(1.00f, 0.55f, 0.20f, 1f);
private static final ColorRGBA AMB_DAY = new ColorRGBA(0.08f, 0.10f, 0.16f, 1f);
private static final ColorRGBA AMB_NIGHT = new ColorRGBA(0.04f, 0.04f, 0.12f, 1f);
// Alle Beleuchtungsparameter zentral in LightingConfig.
// ── Konfiguration ─────────────────────────────────────────────────────────
@@ -61,6 +56,7 @@ public class DayNightState extends BaseAppState implements TimeListener {
private float caveCheckTimer = 0f;
private ColorRGBA sunBaseColor = new ColorRGBA(1, 1, 1, 1);
private float shadowBaseIntensity = 0f;
private ColorRGBA customAmbient = new ColorRGBA();
private static final float CAVE_RAY_HEIGHT = 12f;
private static final float CAVE_CHECK_INTERVAL = 0.2f;
@@ -161,6 +157,9 @@ public class DayNightState extends BaseAppState implements TimeListener {
/** Gibt den Shadow-Filter zurück, damit WorldScene ihn in den FPP einhängen kann. */
public DirectionalLightShadowFilter getShadowFilter() { return shadowFilter; }
/** Ambient-Farbe für eigene Shader-Uniforms (AmbientColor). Kleiner als AmbientLight. */
public ColorRGBA getCustomAmbient() { return customAmbient; }
@Override
protected void cleanup(Application app) {
dayTime.removeListener(this);
@@ -234,17 +233,28 @@ public class DayNightState extends BaseAppState implements TimeListener {
float elevation = toSun.y; // +1 = Zenit, 0 = Horizont, -1 = Nadir
float elevC = FastMath.clamp(elevation, 0f, 1f);
// Spiel vs. Editor: unterschiedliche Helligkeitswerte aus LightingConfig
ColorRGBA ambDay = withShadows ? LightingConfig.AMB_DAY : LightingConfig.EDITOR_AMB_DAY;
ColorRGBA ambNight = withShadows ? LightingConfig.AMB_NIGHT : LightingConfig.EDITOR_AMB_NIGHT;
ColorRGBA custDay = withShadows ? LightingConfig.CUSTOM_AMB_DAY : LightingConfig.EDITOR_CUSTOM_AMB_DAY;
ColorRGBA custNight = withShadows ? LightingConfig.CUSTOM_AMB_NIGHT : LightingConfig.EDITOR_CUSTOM_AMB_NIGHT;
float sunIntensity = withShadows ? LightingConfig.SUN_INTENSITY : LightingConfig.EDITOR_SUN_INTENSITY;
// ── Sonnenfarbe: Dämmerung (orange) → Tag (warm-weiß) — jedes Frame ──
float dawnFactor = 1f - FastMath.clamp(elevation * 5f, 0f, 1f);
ColorRGBA sunColor = SUN_DAWN.clone().interpolateLocal(SUN_DAY, 1f - dawnFactor);
sunBaseColor = sunColor.mult(elevC * 0.68f);
float dawnFactor = 1f - FastMath.clamp(elevation * LightingConfig.SUN_DAWN_SLOPE, 0f, 1f);
ColorRGBA sunColor = LightingConfig.SUN_COLOR_DAWN.clone()
.interpolateLocal(LightingConfig.SUN_COLOR_DAY, 1f - dawnFactor);
sunBaseColor = sunColor.mult(elevC * sunIntensity);
sun.setColor(sunBaseColor.mult(1f - caveFactor));
// ── Ambient: Nacht → Tag — jedes Frame ─────────────────────────────
float ambFactor = FastMath.clamp((elevation + 0.2f) / 0.6f, 0f, 1f);
ambient.setColor(AMB_NIGHT.clone().interpolateLocal(AMB_DAY, ambFactor));
float ambFactor = FastMath.clamp(
(elevation - LightingConfig.AMB_ELEV_LOW) / LightingConfig.AMB_ELEV_RANGE, 0f, 1f);
ambient.setColor(ambNight.clone().interpolateLocal(ambDay, ambFactor));
customAmbient.set(custNight.clone().interpolateLocal(custDay, ambFactor));
shadowBaseIntensity = FastMath.clamp(elevation * 0.8f, 0f, 0.5f);
shadowBaseIntensity = FastMath.clamp(
elevation * LightingConfig.SHADOW_SLOPE, 0f, LightingConfig.SHADOW_MAX);
// Lichtrichtung und Schatten nur 4×/Sek aktualisieren → kein Shadow-Map-Flackern
if (sunDirTimer >= SUN_DIR_INTERVAL) {

View File

@@ -0,0 +1,176 @@
package de.blight.game.state;
import com.jme3.math.ColorRGBA;
/**
* Zentrale Beleuchtungsparameter für den Tag/Nacht-Zyklus.
*
* Alle Farbwerte sind im linearen Farbraum (JME3-Standard).
* Werte 01 entsprechen dem sRGB-Bereich; Werte > 1 sind ohne
* Tone-Mapping nicht sinnvoll.
*
* Geändert werden muss nur diese Datei alle States lesen von hier.
*/
public final class LightingConfig {
// ╔══════════════════════════════════════════════════════════════════════════╗
// ║ SONNE ║
// ╚══════════════════════════════════════════════════════════════════════════╝
/**
* Sonnenfarbe bei klarem Mittag.
* Warm-weiß (leichter Gelbstich).
* ↑ R/G/B: Sonne wird weißer / heller; ↓: kühler / trüber.
*/
public static final ColorRGBA SUN_COLOR_DAY = new ColorRGBA(1.00f, 0.95f, 0.88f, 1f);
/**
* Sonnenfarbe bei Sonnenauf- und -untergang.
* Orange-rot, gesättigt. Interpoliert mit SUN_COLOR_DAY
* über die Dämmerungs-Kurve (→ SUN_DAWN_SLOPE).
*/
public static final ColorRGBA SUN_COLOR_DAWN = new ColorRGBA(1.00f, 0.55f, 0.20f, 1f);
/**
* Gesamtintensität der Sonne am Mittag.
* sun.color = sunColor × elevation × SUN_INTENSITY
* ↑ heller (> 0.68 → Überbelichtung möglich); ↓ weicher.
*/
public static final float SUN_INTENSITY = 0.68f;
/**
* Steuert wie schnell die Dämmerungsfarbe (DAWN) in die Tagesfarbe (DAY)
* übergeht. elevation × SUN_DAWN_SLOPE ≥ 1 → voller Tag.
* 5 bedeutet: Übergang endet bei elevation ≈ 0.2 (ca. 12° Sonnenhöhe).
* ↑ Wert → kürzere, schärfere Dämmerung; ↓ → längere, sanftere.
*/
public static final float SUN_DAWN_SLOPE = 5f;
// ╔══════════════════════════════════════════════════════════════════════════╗
// ║ SCHATTEN ║
// ╚══════════════════════════════════════════════════════════════════════════╝
/**
* Maximale Schattenintensität (bei Sonnenhöhe = 1.0, kein Höhlen-Faktor).
* 0 = kein Schatten; 1 = Schatten vollständig schwarz.
* Typisch: 0.40.6. Zu hoch → abrupte, harte Übergänge.
*/
public static final float SHADOW_MAX = 0.5f;
/**
* Steigung der Schattenintensitäts-Kurve (abhängig von Sonnenhöhe).
* shadowIntensity = clamp(elevation × SHADOW_SLOPE, 0, SHADOW_MAX)
* ↑ Schatten werden schneller dunkler wenn Sonne aufgeht;
* ↓ Schatten bleiben länger schwach (z. B. morgens flacher).
*/
public static final float SHADOW_SLOPE = 0.8f;
// ╔══════════════════════════════════════════════════════════════════════════╗
// ║ AMBIENT-LICHT LightProbe-Multiplikator (PBR-IBL) ║
// ╠══════════════════════════════════════════════════════════════════════════╣
// ║ Wirkt NUR auf JME3-PBR-Materialien MIT fertigem LightProbe. ║
// ║ Terrain-, Baum- und Voxel-Shader sind UNBERÜHRT (→ CUSTOM_AMB_*). ║
// ╚══════════════════════════════════════════════════════════════════════════╝
/**
* AmbientLight-Farbe am Mittag.
* Skaliert den LightProbe-IBL: envLight = probeIBL × AMB_DAY.
* Hoch halten (≥ 0.7) damit PBR-Reflexionen sichtbar bleiben.
* Hat keinen Effekt solange kein LightProbe in der Szene ist.
*/
public static final ColorRGBA AMB_DAY = new ColorRGBA(0.90f, 0.90f, 0.90f, 1f);
/**
* AmbientLight-Farbe in der Nacht (LightProbe-Multiplikator).
* Leichter Blaustich simuliert Mondlicht-Reflexionen auf PBR-Flächen.
*/
public static final ColorRGBA AMB_NIGHT = new ColorRGBA(0.10f, 0.10f, 0.25f, 1f);
// ╔══════════════════════════════════════════════════════════════════════════╗
// ║ CUSTOM-AMBIENT Terrain / Bäume / Voxel / PBR-Emissive ║
// ╠══════════════════════════════════════════════════════════════════════════╣
// ║ Direkt-additiv → NIEDRIG halten. ║
// ║ Betrifft: TerrainArray.frag, Tree.frag, Voxel.frag via AmbientColor- ║
// ║ Uniform; Tripo/PBR-Objekte via Emissive-Parameter (EmissivePower=1). ║
// ╚══════════════════════════════════════════════════════════════════════════╝
/**
* Ambient-Farbe am Mittag für Schatten-Bereiche.
* ↑ erhöhen: Schatten heller (lesbarer), beleuchtete Flächen leicht ausgewaschen.
* ↓ senken: mehr Kontrast, tiefere Schatten (wirkt moodiger/dramatischer).
* Empfehlung: 0.030.10 pro Kanal.
*/
public static final ColorRGBA CUSTOM_AMB_DAY = new ColorRGBA(0.04f, 0.05f, 0.08f, 1f);
/**
* Ambient-Farbe in der Nacht.
* Blaustich = dezentes Mondlicht / Sternenlicht auf Oberflächen.
* Zu hoch → Nacht wirkt grau statt dunkel.
* Empfehlung: 0.010.04 pro Kanal, Blau-Kanal darf höher sein.
*/
public static final ColorRGBA CUSTOM_AMB_NIGHT = new ColorRGBA(0.025f, 0.025f, 0.06f, 1f);
// ╔══════════════════════════════════════════════════════════════════════════╗
// ║ ÜBERGANGS-KURVEN ║
// ╚══════════════════════════════════════════════════════════════════════════╝
/**
* Untere Sonnenhöhe für den Tag/Nacht-Übergang der Ambient-Werte.
* Bei elevation ≤ AMB_ELEV_LOW → 100 % Nacht-Ambient.
* Werte: -1 (Mitternacht) … 0 (Horizont) … +1 (Zenit).
*/
public static final float AMB_ELEV_LOW = -0.2f;
/**
* Breite des Übergangs-Bands (Elevation-Einheiten).
* Bei elevation ≥ AMB_ELEV_LOW + AMB_ELEV_RANGE → 100 % Tag-Ambient.
* Aktuell: voller Tag ab elevation = -0.2 + 0.6 = 0.4 (≈ 24° Sonnenhöhe).
* ↑ verbreitern → längere Dämmerungsphase; ↓ verkürzen → abrupter Übergang.
*/
public static final float AMB_ELEV_RANGE = 0.6f;
// ╔══════════════════════════════════════════════════════════════════════════╗
// ║ EDITOR deutlich heller als Spiel für gute Arbeits-Sichtbarkeit ║
// ╠══════════════════════════════════════════════════════════════════════════╣
// ║ Aktiv wenn DayNightState mit withShadows=false erzeugt wird ║
// ║ (Editor-Modus). Spiel-Werte bleiben unverändert. ║
// ╚══════════════════════════════════════════════════════════════════════════╝
/**
* Sonnen-Intensität im Editor.
* Höher als im Spiel damit Geometrien auch bei tiefer Sonne gut erkennbar sind.
*/
public static final float EDITOR_SUN_INTENSITY = 0.90f;
/**
* LightProbe-Multiplikator am Mittag im Editor (AmbientLight).
* Gleich wie im Spiel der LightProbe liefert ohnehin die meiste IBL-Helligkeit.
*/
public static final ColorRGBA EDITOR_AMB_DAY = new ColorRGBA(0.90f, 0.90f, 0.90f, 1f);
/**
* LightProbe-Multiplikator in der Nacht im Editor.
* Deutlich heller als im Spiel damit PBR-Objekte auch bei Nachteinstellung sichtbar bleiben.
*/
public static final ColorRGBA EDITOR_AMB_NIGHT = new ColorRGBA(0.40f, 0.40f, 0.55f, 1f);
/**
* Custom-Ambient am Mittag im Editor (Terrain, Bäume, Voxel, PBR-Emissive).
* Ca. 6× heller als im Spiel: Schatten-Bereiche klar erkennbar beim Modellieren.
* ↑ erhöhen → noch heller / flacher; ↓ senken → mehr Kontrast.
*/
public static final ColorRGBA EDITOR_CUSTOM_AMB_DAY = new ColorRGBA(0.25f, 0.25f, 0.28f, 1f);
/**
* Custom-Ambient in der Nacht im Editor.
* Auch bei Nacht-Einstellung (0:00 Uhr) bleibt die Szene gut arbeitbar.
* Zu hoch → kein spürbarer Tag/Nacht-Unterschied mehr im Editor.
*/
public static final ColorRGBA EDITOR_CUSTOM_AMB_NIGHT = new ColorRGBA(0.18f, 0.18f, 0.25f, 1f);
private LightingConfig() {}
}

View File

@@ -72,7 +72,7 @@ public class SculptedMeshState extends BaseAppState {
if (dns == null || dns.getSunLight() == null) return;
material.setVector3("LightDir", dns.getSunDirection().negate());
ColorRGBA sc = dns.getSunLight().getColor();
ColorRGBA ac = dns.getAmbientLight().getColor();
ColorRGBA ac = dns.getCustomAmbient();
material.setVector3("SunColor", new Vector3f(sc.r, sc.g, sc.b));
material.setVector3("AmbientColor", new Vector3f(ac.r, ac.g, ac.b));
}

View File

@@ -102,11 +102,10 @@ public class StoneWorldState extends BaseAppState {
// ── Materialien ───────────────────────────────────────────────────────────
private Material buildDefaultMat() {
Material m = new Material(assets, "Common/MatDefs/Light/Lighting.j3md");
m.setBoolean("UseMaterialColors", true);
m.setColor("Diffuse", new ColorRGBA(0.55f, 0.52f, 0.48f, 1f));
m.setColor("Ambient", new ColorRGBA(0.15f, 0.14f, 0.13f, 1f));
m.setColor("Specular", ColorRGBA.Black);
Material m = new Material(assets, "Common/MatDefs/Light/PBRLighting.j3md");
m.setFloat("Metallic", 0.0f);
m.setFloat("Roughness", 0.85f);
m.setColor("BaseColor", new ColorRGBA(0.55f, 0.52f, 0.48f, 1f));
return m;
}
@@ -116,12 +115,10 @@ public class StoneWorldState extends BaseAppState {
String p = (paths != null && i < paths.length && paths[i] != null) ? paths[i] : "";
if (!p.isEmpty()) {
try {
Material m = new Material(assets, "Common/MatDefs/Light/Lighting.j3md");
m.setTexture("DiffuseMap", assets.loadTexture(p));
m.setColor("Diffuse", ColorRGBA.White);
m.setColor("Ambient", new ColorRGBA(0.2f, 0.2f, 0.2f, 1f));
m.setColor("Specular", ColorRGBA.Black);
m.setBoolean("UseMaterialColors", true);
Material m = new Material(assets, "Common/MatDefs/Light/PBRLighting.j3md");
m.setFloat("Metallic", 0.0f);
m.setFloat("Roughness", 0.85f);
m.setTexture("BaseColorMap", assets.loadTexture(p));
mats[i] = m;
} catch (Exception e) {
log.warn("[StoneWorld] Textur nicht ladbar: {}", p);

View File

@@ -89,8 +89,9 @@ public class WorldNpcsState extends BaseAppState {
private final List<SpawnedNpc> spawned = new ArrayList<>();
private static class SpawnedNpc {
final NPC npc;
final Spatial visual;
final NPC npc;
final Spatial visual;
final List<Material> pbrMaterials = new ArrayList<>();
float worldX, worldZ;
AnimationAction currentAction = null;
@@ -260,6 +261,16 @@ public class WorldNpcsState extends BaseAppState {
}
spawned.removeAll(toRemove);
// PBR-Emissive der verbleibenden NPCs mit aktuellem Ambient aktualisieren
if (dayNight != null) {
com.jme3.math.ColorRGBA ac = dayNight.getCustomAmbient();
for (SpawnedNpc s : spawned) {
for (Material mat : s.pbrMaterials) {
mat.setColor("Emissive", new com.jme3.math.ColorRGBA(ac.r, ac.g, ac.b, 1f));
}
}
}
// IDs der bereits gespawnten NPCs für schnelle Prüfung
Set<String> spawnedIds = new HashSet<>();
for (SpawnedNpc s : spawned) {
@@ -277,6 +288,13 @@ public class WorldNpcsState extends BaseAppState {
vis.setLocalTranslation(pos);
npcsRoot.attachChild(vis);
SpawnedNpc s = new SpawnedNpc(npc, vis, pos.x, pos.z);
de.blight.game.scene.WorldScene.collectPbrMaterials(vis, s.pbrMaterials);
if (dayNight != null && !s.pbrMaterials.isEmpty()) {
com.jme3.math.ColorRGBA ac = dayNight.getCustomAmbient();
for (Material mat : s.pbrMaterials) {
mat.setColor("Emissive", new com.jme3.math.ColorRGBA(ac.r, ac.g, ac.b, 1f));
}
}
spawned.add(s);
playNpcAction(s, activityActionFor(npc, currentHour));
log.debug("[WorldNpcs] NPC '{}' gespawnt bei ({}, {})", npc.getCharacterId(), pos.x, pos.z);

View File

@@ -36,6 +36,8 @@ public class WorldObjectsState extends BaseAppState {
private BulletAppState bulletAppState;
private final List<Material> sceneLitMaterials = new ArrayList<>();
private final List<Material> windMaterials = new ArrayList<>();
private final List<Material> pbrMaterials = new ArrayList<>();
private float debugTimer = 0f;
/** RigidBodyControl pro Interactable-ID, damit Kollision während Animationen deaktiviert werden kann. */
private final Map<String, RigidBodyControl> interactableRbcs = new HashMap<>();
@@ -110,7 +112,8 @@ public class WorldObjectsState extends BaseAppState {
failed++;
}
}
log.info("[WorldObjects] {} geladen, {} fehlgeschlagen.", loaded, failed);
log.info("[WorldObjects] {} geladen, {} fehlgeschlagen. sceneLitMaterials={}, windMaterials={}, pbrMaterials={}",
loaded, failed, sceneLitMaterials.size(), windMaterials.size(), pbrMaterials.size());
}
@Override protected void cleanup(Application app) {}
@@ -118,12 +121,12 @@ public class WorldObjectsState extends BaseAppState {
@Override
public void update(float tpf) {
if (!sceneLitMaterials.isEmpty()) {
if (!sceneLitMaterials.isEmpty() || !pbrMaterials.isEmpty()) {
DayNightState dns = getApplication().getStateManager().getState(DayNightState.class);
if (dns != null && dns.getSunLight() != null) {
Vector3f lightDir = dns.getSunDirection().negate();
ColorRGBA sc = dns.getSunLight().getColor();
ColorRGBA ac = dns.getAmbientLight().getColor();
ColorRGBA ac = dns.getCustomAmbient();
Vector3f sunVec = new Vector3f(sc.r, sc.g, sc.b);
Vector3f ambVec = new Vector3f(ac.r, ac.g, ac.b);
for (Material mat : sceneLitMaterials) {
@@ -131,6 +134,24 @@ public class WorldObjectsState extends BaseAppState {
mat.setVector3("SunColor", sunVec);
mat.setVector3("AmbientColor", ambVec);
}
// PBR-Ambient via Emissive: ohne LightProbe liefert JME3-PBR kein IBL.
// EmissivePower=1 verhindert, dass 0.08 zu 0.08³=0.0005 zusammenbricht.
for (Material mat : pbrMaterials) {
mat.setColor("Emissive", new ColorRGBA(ac.r, ac.g, ac.b, 1f));
}
debugTimer += tpf;
if (debugTimer >= 5f) {
debugTimer = 0f;
log.info("[WorldObjects] SunColor=({}) AmbColor=({})",
String.format("%.3f,%.3f,%.3f", sunVec.x, sunVec.y, sunVec.z),
String.format("%.3f,%.3f,%.3f", ambVec.x, ambVec.y, ambVec.z));
}
} else {
debugTimer += tpf;
if (debugTimer >= 5f) {
debugTimer = 0f;
log.warn("[WorldObjects] DayNightState nicht gefunden oder sun==null! dns={}", dns);
}
}
}
@@ -164,7 +185,7 @@ public class WorldObjectsState extends BaseAppState {
applyMaterial(spatial, m);
} else {
spatial = assets.loadModel(path);
convertUnshadedToLighting(spatial);
convertUnshadedToPbr(spatial);
collectSceneLitMaterials(spatial);
}
spatial.setName("obj_" + path);
@@ -197,7 +218,7 @@ public class WorldObjectsState extends BaseAppState {
m.lod1Path(), m.lod2Path(),
m.lod1Distance(), m.lod2Distance(), m.cullDistance());
ctrl.setLodLoadCallback(lod -> {
convertUnshadedToLighting(lod);
convertUnshadedToPbr(lod);
collectSceneLitMaterials(lod);
});
lodRoot.addControl(ctrl);
@@ -221,11 +242,7 @@ public class WorldObjectsState extends BaseAppState {
};
}
/**
* Ersetzt Unshaded-Materialien in geladenen j3o-Modellen durch Lighting.j3md,
* damit diese auf den Tag/Nacht-Zyklus reagieren.
*/
private void convertUnshadedToLighting(Spatial spatial) {
private void convertUnshadedToPbr(Spatial spatial) {
if (spatial instanceof Geometry geo) {
Material mat = geo.getMaterial();
if (mat == null) return;
@@ -235,25 +252,24 @@ public class WorldObjectsState extends BaseAppState {
MatParam color = mat.getParam("Color");
RenderState origRS = mat.getAdditionalRenderState();
Material litMat = new Material(assets, "Common/MatDefs/Light/Lighting.j3md");
litMat.setBoolean("UseMaterialColors", false);
Material pbrMat = new Material(assets, "Common/MatDefs/Light/PBRLighting.j3md");
pbrMat.setFloat("Metallic", 0.0f);
pbrMat.setFloat("Roughness", 0.85f);
if (colorMap != null && colorMap.getValue() instanceof Texture t) {
litMat.setTexture("DiffuseMap", t);
pbrMat.setTexture("BaseColorMap", t);
} else if (color != null && color.getValue() instanceof ColorRGBA c) {
litMat.setBoolean("UseMaterialColors", true);
litMat.setColor("Diffuse", new ColorRGBA(c.r, c.g, c.b, 1f));
litMat.setColor("Ambient", new ColorRGBA(c.r * 0.3f, c.g * 0.3f, c.b * 0.3f, 1f));
pbrMat.setColor("BaseColor", new ColorRGBA(c.r, c.g, c.b, 1f));
}
RenderState rs = litMat.getAdditionalRenderState();
RenderState rs = pbrMat.getAdditionalRenderState();
rs.setBlendMode(origRS.getBlendMode());
rs.setFaceCullMode(origRS.getFaceCullMode());
geo.setMaterial(litMat);
geo.setMaterial(pbrMat);
} else if (spatial instanceof Node node) {
for (Spatial child : new ArrayList<>(node.getChildren())) {
convertUnshadedToLighting(child);
convertUnshadedToPbr(child);
}
}
}
@@ -264,9 +280,26 @@ public class WorldObjectsState extends BaseAppState {
if (mat == null) return;
String name = mat.getMaterialDef().getName();
if ("Tree".equals(name) || "TreeLeaf".equals(name)) {
// j3o-Loading setzt keine j3md-Defaults → Uniforms explizit setzen
// (Werte für Mittag; werden sofort in update() durch DayNightState überschrieben)
mat.setVector3("SunColor", new Vector3f(1.00f, 0.95f, 0.88f));
mat.setVector3("AmbientColor", new Vector3f(0.08f, 0.10f, 0.16f));
mat.setVector3("LightDir", new Vector3f(0f, -1f, 0f));
sceneLitMaterials.add(mat);
} else if ("Fern".equals(name)) {
windMaterials.add(mat);
} else if ("PBRLighting".equals(name)) {
// Emissive = Ambient-Ersatz: JME3-PBR ignoriert AmbientLight ohne LightProbe.
// EmissivePower=1 nötig Standard ist 3.0, was 0.08 → 0.0005 zerstört.
mat.setFloat("EmissivePower", 1f);
mat.setFloat("EmissiveIntensity", 1f);
mat.setColor("Emissive", new ColorRGBA(0.08f, 0.10f, 0.16f, 1f));
pbrMaterials.add(mat);
} else if ("Lighting".equals(name)) {
// Phong-Material: Ambient-Multiplikator auf Weiß setzen, damit g_AmbientLightColor voll wirkt.
mat.setColor("Ambient", ColorRGBA.White.clone());
} else {
log.info("[WorldObjects] Material-Def '{}' an '{}' unbekannt, übersprungen.", name, geo.getName());
}
} else if (spatial instanceof Node node) {
for (Spatial child : node.getChildren()) {
@@ -286,12 +319,13 @@ public class WorldObjectsState extends BaseAppState {
return;
}
if (hasTex || hasNmap) {
Material mat = new Material(assets, "Common/MatDefs/Light/Lighting.j3md");
mat.setBoolean("UseMaterialColors", false);
Material mat = new Material(assets, "Common/MatDefs/Light/PBRLighting.j3md");
mat.setFloat("Metallic", 0.0f);
mat.setFloat("Roughness", 0.85f);
if (hasTex) {
Texture t = assets.loadTexture(m.texturePath());
t.setWrap(Texture.WrapMode.Repeat);
mat.setTexture("DiffuseMap", t);
mat.setTexture("BaseColorMap", t);
}
if (hasNmap) {
Texture n = assets.loadTexture(m.normalMapPath());