Compare commits
2 Commits
1b3d9bf0af
...
e651f12475
| Author | SHA1 | Date | |
|---|---|---|---|
| e651f12475 | |||
| 40be463ced |
@@ -186,7 +186,9 @@ public final class ImpostorUtil {
|
||||
// WaterFilter und andere Post-Effects sehen die korrekte Tiefe des Impostors.
|
||||
mat.setFloat("AlphaDiscardThreshold", 0.5f);
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Off);
|
||||
mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Back);
|
||||
// Billboard dreht sich per BillboardControl zur Kamera → FaceCullMode.Off ist korrekt,
|
||||
// da die Winding-Order nach der Rotation nicht feststeht.
|
||||
mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
|
||||
|
||||
Node n = new Node("lod2");
|
||||
for (int d = 0; d < DIRS; d++) {
|
||||
|
||||
@@ -197,6 +197,7 @@ public class BlightGame extends SimpleApplication {
|
||||
keyBindings = KeyBindingStore.load();
|
||||
graphicsSettings = GraphicsStore.load();
|
||||
audioSettings = AudioSettingsStore.load();
|
||||
de.blight.game.state.ModelLodControl.debugLod2 = graphicsSettings.debugLod2;
|
||||
|
||||
startTimeMs = System.currentTimeMillis();
|
||||
initialFixDone = false;
|
||||
@@ -478,6 +479,8 @@ public class BlightGame extends SimpleApplication {
|
||||
rebuildState(HudState.class);
|
||||
rebuildState(HotbarState.class);
|
||||
rebuildState(LoadingScreenState.class);
|
||||
JmeConsole console = stateManager.getState(JmeConsole.class);
|
||||
if (console != null) console.rebuild();
|
||||
}
|
||||
|
||||
private <T extends com.jme3.app.state.BaseAppState> void rebuildState(Class<T> cls) {
|
||||
@@ -550,6 +553,73 @@ public class BlightGame extends SimpleApplication {
|
||||
return "Unbekanntes Wetter. Erlaubt: sunny, cloudy, overcast, storm";
|
||||
}
|
||||
});
|
||||
|
||||
console.registerCommand("set", args -> {
|
||||
if (args.length < 3)
|
||||
return "Syntax: set <schlüssel> <wert>"
|
||||
+ " | debugLod2, width, height, fullscreen, vsync, samples, viewDistance, shadowQuality";
|
||||
String key = args[1].toLowerCase();
|
||||
String val = args[2];
|
||||
return switch (key) {
|
||||
case "debuglod2" -> {
|
||||
boolean v = Boolean.parseBoolean(val);
|
||||
graphicsSettings.debugLod2 = v;
|
||||
de.blight.game.state.ModelLodControl.debugLod2 = v;
|
||||
GraphicsStore.save(graphicsSettings);
|
||||
yield "debugLod2 = " + v;
|
||||
}
|
||||
case "width" -> {
|
||||
try { graphicsSettings.width = Integer.parseInt(val); }
|
||||
catch (NumberFormatException e) { yield "Fehler: ganze Zahl erwartet"; }
|
||||
GraphicsStore.save(graphicsSettings);
|
||||
yield "width = " + graphicsSettings.width + " (Neustart erforderlich)";
|
||||
}
|
||||
case "height" -> {
|
||||
try { graphicsSettings.height = Integer.parseInt(val); }
|
||||
catch (NumberFormatException e) { yield "Fehler: ganze Zahl erwartet"; }
|
||||
GraphicsStore.save(graphicsSettings);
|
||||
yield "height = " + graphicsSettings.height + " (Neustart erforderlich)";
|
||||
}
|
||||
case "fullscreen" -> {
|
||||
graphicsSettings.fullscreen = Boolean.parseBoolean(val);
|
||||
GraphicsStore.save(graphicsSettings);
|
||||
yield "fullscreen = " + graphicsSettings.fullscreen + " (Neustart erforderlich)";
|
||||
}
|
||||
case "vsync" -> {
|
||||
graphicsSettings.vsync = Boolean.parseBoolean(val);
|
||||
GraphicsStore.save(graphicsSettings);
|
||||
yield "vsync = " + graphicsSettings.vsync + " (Neustart erforderlich)";
|
||||
}
|
||||
case "samples" -> {
|
||||
try { graphicsSettings.samples = Integer.parseInt(val); }
|
||||
catch (NumberFormatException e) { yield "Fehler: ganze Zahl erwartet"; }
|
||||
GraphicsStore.save(graphicsSettings);
|
||||
yield "samples = " + graphicsSettings.samples + " (Neustart erforderlich)";
|
||||
}
|
||||
case "viewdistance" -> {
|
||||
try {
|
||||
graphicsSettings.viewDistance =
|
||||
GraphicsSettings.ViewDistance.valueOf(val.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
yield "Unbekannt. Erlaubt: SHORT, NORMAL, FAR";
|
||||
}
|
||||
GraphicsStore.save(graphicsSettings);
|
||||
yield "viewDistance = " + graphicsSettings.viewDistance + " (Neustart erforderlich)";
|
||||
}
|
||||
case "shadowquality" -> {
|
||||
try {
|
||||
graphicsSettings.shadowQuality =
|
||||
GraphicsSettings.ShadowQuality.valueOf(val.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
yield "Unbekannt. Erlaubt: LOW, MEDIUM, HIGH";
|
||||
}
|
||||
GraphicsStore.save(graphicsSettings);
|
||||
yield "shadowQuality = " + graphicsSettings.shadowQuality + " (Neustart erforderlich)";
|
||||
}
|
||||
default -> "Unbekannter Schlüssel: " + args[1]
|
||||
+ " | Erlaubt: debugLod2, width, height, fullscreen, vsync, samples, viewDistance, shadowQuality";
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Vollbild sicherstellen: GLFW ist hier bereits durch JME3 initialisiert.
|
||||
|
||||
@@ -8,6 +8,8 @@ public class GraphicsSettings {
|
||||
public int samples = 4;
|
||||
public ViewDistance viewDistance = ViewDistance.NORMAL;
|
||||
public ShadowQuality shadowQuality = ShadowQuality.MEDIUM;
|
||||
/** Verstecktes Debug-Flag: erzwingt LOD2 (Impostor) für alle Objekte. Nur per Konsole setzbar. */
|
||||
public boolean debugLod2 = false;
|
||||
|
||||
/**
|
||||
* Schattenqualitäts-Stufen.
|
||||
|
||||
@@ -31,6 +31,9 @@ public class ModelLodControl extends AbstractControl {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ModelLodControl.class);
|
||||
|
||||
/** Erzwingt LOD2 für alle Instanzen. Wird per Konsolen-Befehl "set debugLod2 true" gesetzt. */
|
||||
public static volatile boolean debugLod2 = false;
|
||||
|
||||
private final Camera cam;
|
||||
private final float lod1DistSq;
|
||||
private final float lod2DistSq;
|
||||
@@ -119,7 +122,10 @@ public class ModelLodControl extends AbstractControl {
|
||||
// Fehlt LOD1 aber LOD2 vorhanden: bei lod1Distance direkt zu LOD2 wechseln (nicht ausblenden).
|
||||
// Fehlt beides (z.B. Kaktusfeige): ab lod1Distance ausblenden.
|
||||
int targetSlot;
|
||||
if (distSq >= lod2DistSq) {
|
||||
if (debugLod2) {
|
||||
// Debug: erzwingt die niedrigste verfügbare LOD-Stufe
|
||||
targetSlot = hasLod2 ? 2 : (hasLod1 ? 1 : 0);
|
||||
} else if (distSq >= lod2DistSq) {
|
||||
targetSlot = hasLod2 ? 2 : -1;
|
||||
} else if (distSq >= lod1DistSq) {
|
||||
if (hasLod1) targetSlot = 1;
|
||||
|
||||
@@ -41,10 +41,16 @@ public class WorldObjectsState extends BaseAppState {
|
||||
private final List<Material> sceneLitMaterials = new ArrayList<>();
|
||||
private final List<Material> windMaterials = new ArrayList<>();
|
||||
private final List<Material> pbrMaterials = new ArrayList<>();
|
||||
/** Impostor-Unshaded-Materialien: erhalten pro Frame einen Ambient-Tint (Color-Uniform). */
|
||||
/** Impostor-Unshaded-Materialien: erhalten pro Frame einen Tint (Color-Uniform). */
|
||||
private final List<Material> impostorMaterials = new ArrayList<>();
|
||||
/** Capture-Ambient, mit dem der Impostor-Atlas gerendert wurde. Normalisiert den Tint. */
|
||||
private static final float IMPOSTOR_CAP_AMB = 0.30f;
|
||||
// Licht-Werte beim Impostor-Atlas-Capture (aus ImpostorUtil): Haupt-Sonne + Ambient.
|
||||
// Tint = (aktSonne + aktAmbient) / (capSonne + capAmbient) → folgt dem Tageszyklus.
|
||||
private static final float IMPOSTOR_CAP_SUN_R = 0.70f;
|
||||
private static final float IMPOSTOR_CAP_SUN_G = 0.65f;
|
||||
private static final float IMPOSTOR_CAP_SUN_B = 0.52f;
|
||||
private static final float IMPOSTOR_CAP_AMB_R = 0.30f;
|
||||
private static final float IMPOSTOR_CAP_AMB_G = 0.30f;
|
||||
private static final float IMPOSTOR_CAP_AMB_B = 0.32f;
|
||||
private float debugTimer = 0f;
|
||||
private float lodFactor = 1.0f;
|
||||
|
||||
@@ -174,12 +180,12 @@ public class WorldObjectsState extends BaseAppState {
|
||||
for (Material mat : pbrMaterials) {
|
||||
mat.setColor("Emissive", new ColorRGBA(ac.r, ac.g, ac.b, 1f));
|
||||
}
|
||||
// Impostor-Tint: Unshaded-Atlas folgt dem Tag/Nacht-Zyklus über den Color-Multiplikator.
|
||||
// Normiert auf die Capture-Ambient (0.30): Bei gleichem Ambient = 1.0, Nacht ≈ 0.05.
|
||||
// Impostor-Tint: (aktSonne + aktAmbient) / (captureSonne + captureAmbient)
|
||||
// → Impostor folgt Sonne und Ambient; ≈1.0 bei gleichem Licht wie beim Capture.
|
||||
if (!impostorMaterials.isEmpty()) {
|
||||
float tR = Math.min(1.2f, ac.r / IMPOSTOR_CAP_AMB);
|
||||
float tG = Math.min(1.2f, ac.g / IMPOSTOR_CAP_AMB);
|
||||
float tB = Math.min(1.2f, ac.b / IMPOSTOR_CAP_AMB);
|
||||
float tR = Math.min(1.5f, (sc.r + ac.r) / (IMPOSTOR_CAP_SUN_R + IMPOSTOR_CAP_AMB_R));
|
||||
float tG = Math.min(1.5f, (sc.g + ac.g) / (IMPOSTOR_CAP_SUN_G + IMPOSTOR_CAP_AMB_G));
|
||||
float tB = Math.min(1.5f, (sc.b + ac.b) / (IMPOSTOR_CAP_SUN_B + IMPOSTOR_CAP_AMB_B));
|
||||
ColorRGBA tint = new ColorRGBA(tR, tG, tB, 1f);
|
||||
for (Material mat : impostorMaterials) {
|
||||
mat.setColor("Color", tint);
|
||||
@@ -334,23 +340,17 @@ public class WorldObjectsState extends BaseAppState {
|
||||
|
||||
private boolean isImpostorNode(Node n) {
|
||||
if (n.getControl(BillboardControl.class) != null) {
|
||||
log.info("[Impostor] '{}' erkannt via BillboardControl", n.getName());
|
||||
return true;
|
||||
}
|
||||
if (n.getChildren().isEmpty()) {
|
||||
log.info("[Impostor] '{}' hat keine Kinder → kein Impostor", n.getName());
|
||||
return false;
|
||||
}
|
||||
Spatial first = n.getChild(0);
|
||||
log.info("[Impostor] '{}' erstes Kind: {} ({})", n.getName(), first.getName(), first.getClass().getSimpleName());
|
||||
if (!(first instanceof Geometry g)) {
|
||||
log.info("[Impostor] '{}' erstes Kind ist kein Geometry → kein Impostor", n.getName());
|
||||
return false;
|
||||
}
|
||||
Material mat = g.getMaterial();
|
||||
String matName = mat != null ? mat.getMaterialDef().getName() : "null";
|
||||
log.info("[Impostor] '{}' Material: '{}'", n.getName(), matName);
|
||||
return mat != null && "Unshaded".equals(matName);
|
||||
return mat != null && "Unshaded".equals(mat.getMaterialDef().getName());
|
||||
}
|
||||
|
||||
private void initImpostor(Node lod2Node) {
|
||||
@@ -365,7 +365,6 @@ public class WorldObjectsState extends BaseAppState {
|
||||
lod2Node.setQueueBucket(com.jme3.renderer.queue.RenderQueue.Bucket.Opaque);
|
||||
|
||||
java.util.Set<Material> seen = new java.util.HashSet<>();
|
||||
boolean firstMat = true;
|
||||
for (Spatial child : lod2Node.getChildren()) {
|
||||
if (!(child instanceof com.jme3.scene.Geometry geo)) continue;
|
||||
Material mat = geo.getMaterial();
|
||||
@@ -373,32 +372,15 @@ public class WorldObjectsState extends BaseAppState {
|
||||
|
||||
mat.setFloat("AlphaDiscardThreshold", 0.5f);
|
||||
mat.getAdditionalRenderState().setBlendMode(com.jme3.material.RenderState.BlendMode.Off);
|
||||
// FaceCullMode.Off: Billboard dreht sich immer zur Kamera → beide Seiten rendern
|
||||
// ist sicherer als Back, da Winding-Order nach Billboard-Rotation nicht deterministisch ist.
|
||||
// Billboard dreht sich per BillboardControl zur Kamera → FaceCullMode.Off,
|
||||
// da Winding-Order nach Billboard-Rotation nicht feststeht.
|
||||
mat.getAdditionalRenderState().setFaceCullMode(com.jme3.material.RenderState.FaceCullMode.Off);
|
||||
geo.setQueueBucket(com.jme3.renderer.queue.RenderQueue.Bucket.Opaque);
|
||||
|
||||
// Helligkeit-Fix: Atlas wurde ohne Gamma-Korrektur gerendert (kein FPP im Editor).
|
||||
com.jme3.material.MatParam colorMap = mat.getParam("ColorMap");
|
||||
if (colorMap != null && colorMap.getValue() instanceof com.jme3.texture.Texture t) {
|
||||
t.getImage().setColorSpace(com.jme3.texture.image.ColorSpace.sRGB);
|
||||
log.info("[Impostor] Tex '{}': {}×{} fmt={}",
|
||||
t.getName(), t.getImage().getWidth(), t.getImage().getHeight(), t.getImage().getFormat());
|
||||
// Diagnose: Alpha-Wert im Zentrum von Strip 0 prüfen (Pixel 256,256)
|
||||
if (firstMat) {
|
||||
firstMat = false;
|
||||
java.nio.ByteBuffer imgData = t.getImage().getData(0);
|
||||
if (imgData != null && imgData.capacity() >= (256 * 4096 + 256) * 4 + 4) {
|
||||
int off = (256 * 4096 + 256) * 4;
|
||||
int r = imgData.get(off) & 0xFF;
|
||||
int g = imgData.get(off + 1) & 0xFF;
|
||||
int b = imgData.get(off + 2) & 0xFF;
|
||||
int a = imgData.get(off + 3) & 0xFF;
|
||||
log.info("[Impostor] Atlas Pixel(256,256) Strip0: R={} G={} B={} A={}", r, g, b, a);
|
||||
} else {
|
||||
log.warn("[Impostor] Atlas-Daten nicht lesbar (capacity={})",
|
||||
imgData != null ? imgData.capacity() : -1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.warn("[Impostor] Quad '{}' hat KEINE ColorMap!", geo.getName());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user