diff --git a/Common/MatDefs/Light/PBRLighting.j3md b/Common/MatDefs/Light/PBRLighting.j3md new file mode 100644 index 0000000..488c7e5 --- /dev/null +++ b/Common/MatDefs/Light/PBRLighting.j3md @@ -0,0 +1,336 @@ +MaterialDef PBR Lighting { + + MaterialParameters { + Int BoundDrawBuffer + + // Alpha threshold for fragment discarding + Float AlphaDiscardThreshold (AlphaTestFallOff) + + //metallicity of the material + Float Metallic : 1.0 + //Roughness of the material + Float Roughness : 1.0 + // Base material color + Color BaseColor : 1.0 1.0 1.0 1.0 + // The emissive color of the object + Color Emissive + // the emissive power + Float EmissivePower : 3.0 + // the emissive intensity + Float EmissiveIntensity : 2.0 + + // BaseColor map + Texture2D BaseColorMap + + // Metallic map + Texture2D MetallicMap -LINEAR + + // Roughness Map + Texture2D RoughnessMap -LINEAR + + //Metallic and Roughness are packed respectively in the b and g channel of a single map + // r: AO (if AoPackedInMRMap is true) + // g: Roughness + // b: Metallic + Texture2D MetallicRoughnessMap -LINEAR + + // Texture of the emissive parts of the material + Texture2D EmissiveMap + + // Normal map + Texture2D NormalMap -LINEAR + // The scalar parameter applied to each normal vector of the normal map + Float NormalScale + + //The type of normal map: -1.0 (DirectX), 1.0 (OpenGl) + Float NormalType : -1.0 + + // For Spec gloss pipeline + Boolean UseSpecGloss + Texture2D SpecularMap + Texture2D GlossinessMap + Texture2D SpecularGlossinessMap + Color Specular : 1.0 1.0 1.0 1.0 + Float Glossiness : 1.0 + + // Parallax/height map + Texture2D ParallaxMap -LINEAR + + // Specular-AA + Boolean UseSpecularAA : true + // screen space variance,Use the slider to set the strength of the geometric specular anti-aliasing effect between 0 and 1. Higher values produce a blurrier result with less aliasing. + Float SpecularAASigma + // clamping threshold,Use the slider to set a maximum value for the offset that HDRP subtracts from the smoothness value to reduce artifacts. + Float SpecularAAKappa + + //Set to true if parallax map is stored in the alpha channel of the normal map + Boolean PackedNormalParallax + + //Sets the relief height for parallax mapping + Float ParallaxHeight : 0.05 + + //Set to true to activate Steep Parallax mapping + Boolean SteepParallax + + //Horizon fade + Boolean HorizonFade + + // Set to Use Lightmap + Texture2D LightMap + + // A scalar multiplier controlling the amount of occlusion applied. + // A value of `0.0` means no occlusion. A value of `1.0` means full occlusion. + Float AoStrength + + // Set to use TexCoord2 for the lightmap sampling + Boolean SeparateTexCoord + // the light map is a grayscale ao map, only the r channel will be read. + Boolean LightMapAsAOMap + Boolean AoPackedInMRMap + //shadows + Int FilterMode + Boolean HardwareShadows + + Texture2D ShadowMap0 + Texture2D ShadowMap1 + Texture2D ShadowMap2 + Texture2D ShadowMap3 + //pointLights + Texture2D ShadowMap4 + Texture2D ShadowMap5 + + Float ShadowIntensity + Vector4 Splits + Vector2 FadeInfo + + Matrix4 LightViewProjectionMatrix0 + Matrix4 LightViewProjectionMatrix1 + Matrix4 LightViewProjectionMatrix2 + Matrix4 LightViewProjectionMatrix3 + //pointLight + Matrix4 LightViewProjectionMatrix4 + Matrix4 LightViewProjectionMatrix5 + Vector3 LightPos + Vector3 LightDir + + Float PCFEdge + Float ShadowMapSize + + // For hardware skinning + Int NumberOfBones + Matrix4Array BoneMatrices + + // For Morph animation + FloatArray MorphWeights + Int NumberOfMorphTargets + Int NumberOfTargetsBuffers + + // For instancing + Boolean UseInstancing + + // For Vertex Color + Boolean UseVertexColor + + Boolean BackfaceShadows : false + + Boolean UseFog + Color FogColor + Vector2 LinearFog + Float ExpFog + Float ExpSqFog + + Texture2D SunLightExposureMap + Boolean UseVertexColorsAsSunIntensity + Float StaticSunIntensity + Boolean BrightenIndoorShadows //should be set true when shadows are enabled, in order to prevent areas with low SunExposure from being way too dark when shadows are cast + + // debug the final value of the selected layer as a color output + Int DebugValuesMode + // Layers: + // 0 - albedo (unshaded) + // 1 - normals + // 2 - roughness + // 3 - metallic + // 4 - ao + // 5 - emissive + // 6 - exposure + // 7 - alpha + // 8 - geometryNormals + } + + Technique { + LightMode SinglePassAndImageBased + + VertexShader GLSL300 GLSL150 GLSL110: Common/MatDefs/Light/PBRLighting.vert + FragmentShader GLSL300 GLSL150 GLSL110: Common/MatDefs/Light/PBRLighting.frag + + WorldParameters { + WorldViewProjectionMatrix + CameraPosition + WorldMatrix + WorldNormalMatrix + ViewProjectionMatrix + ViewMatrix + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + BASECOLORMAP : BaseColorMap + NORMALMAP : NormalMap + NORMALSCALE : NormalScale + METALLICMAP : MetallicMap + ROUGHNESSMAP : RoughnessMap + EMISSIVEMAP : EmissiveMap + EMISSIVE : Emissive + SPECGLOSSPIPELINE : UseSpecGloss + PARALLAXMAP : ParallaxMap + NORMALMAP_PARALLAX : PackedNormalParallax + STEEP_PARALLAX : SteepParallax + LIGHTMAP : LightMap + SEPARATE_TEXCOORD : SeparateTexCoord + DISCARD_ALPHA : AlphaDiscardThreshold + NUM_BONES : NumberOfBones + INSTANCING : UseInstancing + USE_PACKED_MR: MetallicRoughnessMap + USE_PACKED_SG: SpecularGlossinessMap + SPECULARMAP : SpecularMap + SPECULAR_AA : UseSpecularAA + SPECULAR_AA_SCREEN_SPACE_VARIANCE : SpecularAASigma + SPECULAR_AA_THRESHOLD : SpecularAAKappa + GLOSSINESSMAP : GlossinessMap + NORMAL_TYPE: NormalType + VERTEX_COLOR : UseVertexColor + AO_MAP: LightMapAsAOMap + AO_PACKED_IN_MR_MAP : AoPackedInMRMap + AO_STRENGTH : AoStrength + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers + HORIZON_FADE: HorizonFade + EXPOSUREMAP : SunLightExposureMap + USE_VERTEX_COLORS_AS_SUN_INTENSITY : UseVertexColorsAsSunIntensity + STATIC_SUN_INTENSITY : StaticSunIntensity + BRIGHTEN_INDOOR_SHADOWS : BrightenIndoorShadows + DEBUG_VALUES_MODE : DebugValuesMode + USE_FOG : UseFog + FOG_LINEAR : LinearFog + FOG_EXP : ExpFog + FOG_EXPSQ : ExpSqFog + } + } + + + Technique PreShadow { + + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PreShadow.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PreShadowPBR.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldViewMatrix + ViewProjectionMatrix + ViewMatrix + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + DISCARD_ALPHA : AlphaDiscardThreshold + NUM_BONES : NumberOfBones + INSTANCING : UseInstancing + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers + } + + ForcedRenderState { + FaceCull Off + DepthTest On + DepthWrite On + PolyOffset 5 3 + ColorWrite Off + } + + } + + + Technique PostShadow { + VertexShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadowPBR.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldMatrix + ViewProjectionMatrix + ViewMatrix + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + HARDWARE_SHADOWS : HardwareShadows + FILTER_MODE : FilterMode + PCFEDGE : PCFEdge + DISCARD_ALPHA : AlphaDiscardThreshold + SHADOWMAP_SIZE : ShadowMapSize + FADE : FadeInfo + PSSM : Splits + POINTLIGHT : LightViewProjectionMatrix5 + NUM_BONES : NumberOfBones + INSTANCING : UseInstancing + BACKFACE_SHADOWS: BackfaceShadows + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers + } + + ForcedRenderState { + Blend Modulate + DepthWrite Off + PolyOffset -0.1 0 + } + } + + Technique PreNormalPass { + + VertexShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/SSAO/normal.vert + FragmentShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/SSAO/normal.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldViewMatrix + NormalMatrix + ViewProjectionMatrix + ViewMatrix + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + BASECOLORMAP_ALPHA : BaseColorMap + NUM_BONES : NumberOfBones + INSTANCING : UseInstancing + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers + } + + } + + Technique Glow { + + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Misc/Unshaded.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Light/PBRGlow.frag + + WorldParameters { + WorldViewProjectionMatrix + ViewProjectionMatrix + ViewMatrix + } + + Defines { + HAS_EMISSIVEMAP : EmissiveMap + HAS_EMISSIVECOLOR : Emissive + BOUND_DRAW_BUFFER: BoundDrawBuffer + NEED_TEXCOORD1 + NUM_BONES : NumberOfBones + INSTANCING : UseInstancing + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers + } + } + +} diff --git a/blight-assets/src/main/resources/MatDefs/TerrainArray.j3md b/blight-assets/src/main/resources/MatDefs/TerrainArray.j3md index 29ac22e..2e5d0b0 100644 --- a/blight-assets/src/main/resources/MatDefs/TerrainArray.j3md +++ b/blight-assets/src/main/resources/MatDefs/TerrainArray.j3md @@ -5,9 +5,9 @@ MaterialDef TerrainArray { FloatArray DiffuseScales TextureArray NormalArray Float Shininess : 32.0 - Vector3 LightDir - Vector3 SunColor - Vector3 AmbientColor + Vector3 LightDir : 0.6 -0.8 0.4 + Vector3 SunColor : 0.68 0.65 0.60 + Vector3 AmbientColor : 0.08 0.10 0.16 Boolean DebugNoLight Boolean DebugSlot0Only Texture2D DebugDirectTex @@ -30,8 +30,6 @@ MaterialDef TerrainArray { HAS_NORMAL_ARRAY : NormalArray HAS_ALPHA_1 : AlphaMap_1 HAS_ALPHA_2 : AlphaMap_2 - HAS_LIGHTDIR : LightDir - HAS_SCENE_LIGHT : SunColor DEBUG_NO_LIGHT : DebugNoLight DEBUG_SLOT0_ONLY : DebugSlot0Only DEBUG_DIRECT_TEX : DebugDirectTex diff --git a/blight-assets/src/main/resources/MatDefs/Tree.j3md b/blight-assets/src/main/resources/MatDefs/Tree.j3md index 3fd27ea..c358757 100644 --- a/blight-assets/src/main/resources/MatDefs/Tree.j3md +++ b/blight-assets/src/main/resources/MatDefs/Tree.j3md @@ -7,9 +7,9 @@ MaterialDef Tree { Vector2 WindDir : 0.0 1.0 Texture2D BarkMap Boolean HasBarkMap : false - Vector3 LightDir - Vector3 SunColor - Vector3 AmbientColor + Vector3 LightDir : 0.6 -0.8 0.4 + Vector3 SunColor : 0.68 0.65 0.60 + Vector3 AmbientColor : 0.08 0.10 0.16 } Technique { @@ -23,7 +23,6 @@ MaterialDef Tree { } Defines { - HAS_SCENE_LIGHT : SunColor } } } diff --git a/blight-assets/src/main/resources/MatDefs/TreeLeaf.j3md b/blight-assets/src/main/resources/MatDefs/TreeLeaf.j3md index 7d868ed..3571ac4 100644 --- a/blight-assets/src/main/resources/MatDefs/TreeLeaf.j3md +++ b/blight-assets/src/main/resources/MatDefs/TreeLeaf.j3md @@ -28,9 +28,9 @@ MaterialDef TreeLeaf { Matrix4 LightViewProjectionMatrix4 Matrix4 LightViewProjectionMatrix5 Vector3 LightPos - Vector3 LightDir - Vector3 SunColor - Vector3 AmbientColor + Vector3 LightDir : 0.6 -0.8 0.4 + Vector3 SunColor : 0.68 0.65 0.60 + Vector3 AmbientColor : 0.08 0.10 0.16 Float PCFEdge Float ShadowMapSize Boolean BackfaceShadows : false @@ -51,7 +51,6 @@ MaterialDef TreeLeaf { } Defines { - HAS_SCENE_LIGHT : SunColor } } diff --git a/blight-assets/src/main/resources/Shaders/TerrainArray.frag b/blight-assets/src/main/resources/Shaders/TerrainArray.frag index dc9cd91..e6208fb 100644 --- a/blight-assets/src/main/resources/Shaders/TerrainArray.frag +++ b/blight-assets/src/main/resources/Shaders/TerrainArray.frag @@ -16,14 +16,9 @@ uniform sampler2D m_AlphaMap_2; uniform sampler2DArray m_NormalArray; #endif -#ifdef HAS_LIGHTDIR uniform vec3 m_LightDir; -#endif - -#ifdef HAS_SCENE_LIGHT uniform vec3 m_SunColor; uniform vec3 m_AmbientColor; -#endif in vec2 vSplatUV; in vec3 vWorldPos; @@ -111,18 +106,9 @@ void main() { N = normalize(pertN); #endif -#ifdef HAS_LIGHTDIR vec3 lightDir = normalize(m_LightDir); -#else - vec3 lightDir = normalize(vec3(0.6, 1.0, 0.4)); -#endif float diff = max(dot(N, lightDir), 0.0); - -#ifdef HAS_SCENE_LIGHT vec3 light = m_AmbientColor + m_SunColor * diff; -#else - vec3 light = vec3(diff * 0.65 + 0.35); -#endif const float BRIGHTNESS = 0.80; #ifdef DEBUG_NO_LIGHT diff --git a/blight-assets/src/main/resources/Shaders/Tree.frag b/blight-assets/src/main/resources/Shaders/Tree.frag index 37a8722..22801cc 100644 --- a/blight-assets/src/main/resources/Shaders/Tree.frag +++ b/blight-assets/src/main/resources/Shaders/Tree.frag @@ -3,12 +3,9 @@ uniform vec4 m_Diffuse; uniform sampler2D m_BarkMap; uniform bool m_HasBarkMap; - -#ifdef HAS_SCENE_LIGHT -uniform vec3 m_LightDir; -uniform vec3 m_SunColor; -uniform vec3 m_AmbientColor; -#endif +uniform vec3 m_LightDir; +uniform vec3 m_SunColor; +uniform vec3 m_AmbientColor; in vec2 texCoord; in vec3 worldNormal; @@ -20,19 +17,8 @@ void main() { ? texture2D(m_BarkMap, texCoord).rgb * m_Diffuse.rgb : m_Diffuse.rgb; -#ifdef HAS_SCENE_LIGHT float diff = max(dot(n, normalize(m_LightDir)), 0.0); vec3 light = clamp(m_AmbientColor + m_SunColor * diff, 0.0, 1.0); -#else - vec3 sunDir = normalize(vec3(0.6, 0.7, 0.4)); - vec3 fillDir = normalize(vec3(-0.5, 0.3, -0.4)); - vec3 rimDir = normalize(vec3(-0.3, 0.5, 0.7)); - float sun = max(dot(n, sunDir), 0.0); - float fill = max(dot(n, fillDir), 0.0) * 0.22; - float rim = max(dot(n, rimDir), 0.0) * 0.14; - float sky = dot(n, vec3(0.0, 1.0, 0.0)) * 0.4 + 0.4; - vec3 light = vec3(clamp(sun * 0.75 + fill + rim + sky * 0.09 + 0.05, 0.0, 1.0)); -#endif gl_FragColor = vec4(baseColor * light, m_Diffuse.a); } diff --git a/blight-assets/src/main/resources/Shaders/TreeLeaf.frag b/blight-assets/src/main/resources/Shaders/TreeLeaf.frag index 2e7edbd..5782037 100644 --- a/blight-assets/src/main/resources/Shaders/TreeLeaf.frag +++ b/blight-assets/src/main/resources/Shaders/TreeLeaf.frag @@ -3,12 +3,9 @@ uniform vec4 m_Diffuse; uniform sampler2D m_LeafMap; uniform bool m_HasLeafMap; - -#ifdef HAS_SCENE_LIGHT -uniform vec3 m_LightDir; -uniform vec3 m_SunColor; -uniform vec3 m_AmbientColor; -#endif +uniform vec3 m_LightDir; +uniform vec3 m_SunColor; +uniform vec3 m_AmbientColor; in vec2 texCoord; in vec3 worldNormal; @@ -27,12 +24,8 @@ void main() { baseColor = m_Diffuse.rgb * (0.7 + 0.3 * edge); } -#ifdef HAS_SCENE_LIGHT // Blätter transmittieren Licht — abs() für doppelseitige Beleuchtung float diff = abs(dot(normalize(worldNormal), normalize(m_LightDir))); vec3 light = clamp(m_AmbientColor + m_SunColor * diff * 0.6, 0.0, 1.0); gl_FragColor = vec4(baseColor * light, 1.0); -#else - gl_FragColor = vec4(baseColor * 0.8, 1.0); -#endif } diff --git a/blight-common/src/main/java/de/blight/common/time/DayTime.java b/blight-common/src/main/java/de/blight/common/time/DayTime.java index f2127f6..0a33b0c 100644 --- a/blight-common/src/main/java/de/blight/common/time/DayTime.java +++ b/blight-common/src/main/java/de/blight/common/time/DayTime.java @@ -37,7 +37,10 @@ public class DayTime { /** Tageszeit 0.0–1.0. */ public float getTimeOfDay() { return timeOfDay; } - public void setTimeOfDay(float t) { timeOfDay = ((t % 1f) + 1f) % 1f; } + public void setTimeOfDay(float t) { + timeOfDay = ((t % 1f) + 1f) % 1f; + for (TimeListener l : listeners) l.onTimeChanged(timeOfDay); + } /** @param seconds Echtzeit-Sekunden pro Spieltag */ public void setDayDuration(float seconds) { timeScale = 1f / seconds; } diff --git a/blight-editor/src/main/java/de/blight/editor/EditorApp.java b/blight-editor/src/main/java/de/blight/editor/EditorApp.java index 33ede67..cb6be11 100644 --- a/blight-editor/src/main/java/de/blight/editor/EditorApp.java +++ b/blight-editor/src/main/java/de/blight/editor/EditorApp.java @@ -1137,7 +1137,18 @@ public class EditorApp extends Application { viewMenu.getItems().addAll(resetCam, new SeparatorMenuItem(), viewTexture, viewWireframe, new SeparatorMenuItem(), viewTopology); - menuBar.getMenus().addAll(fileMenu, toolsMenu, viewMenu); + Menu zeitMenu = new Menu("Zeit"); + ToggleGroup zeitGroup = new ToggleGroup(); + int[] hours = {0, 3, 6, 9, 12, 15, 18, 21}; + for (int h : hours) { + RadioMenuItem item = new RadioMenuItem(String.format("%02d:00", h)); + item.setToggleGroup(zeitGroup); + if (h == 12) { item.setSelected(true); } + final float t = h / 24f; + item.setOnAction(e -> input.pendingTimeOfDay = t); + zeitMenu.getItems().add(item); + } + menuBar.getMenus().addAll(fileMenu, toolsMenu, viewMenu, zeitMenu); ToolBar toolBar = new ToolBar(); baseBtn = new ToggleButton("▲▼ Basis-Terrain"); diff --git a/blight-editor/src/main/java/de/blight/editor/JmeEditorApp.java b/blight-editor/src/main/java/de/blight/editor/JmeEditorApp.java index b9c2aa4..6cfb250 100644 --- a/blight-editor/src/main/java/de/blight/editor/JmeEditorApp.java +++ b/blight-editor/src/main/java/de/blight/editor/JmeEditorApp.java @@ -309,6 +309,17 @@ public class JmeEditorApp extends SimpleApplication { input.camPitch = (float) Math.toDegrees( Math.asin(Math.max(-1f, Math.min(1f, dir.y)))); + // Zeit-Menü: Tageszeit setzen + float pendingTime = input.pendingTimeOfDay; + if (!Float.isNaN(pendingTime)) { + input.pendingTimeOfDay = Float.NaN; + DayNightState dns = stateManager.getState(DayNightState.class); + if (dns != null) { + dns.getDayTime().setTimeOfDay(pendingTime); + dns.getDayTime().setPaused(true); + } + } + if (jmeConsole == null) return; // Toggle-Signal von JavaFX diff --git a/blight-editor/src/main/java/de/blight/editor/SharedInput.java b/blight-editor/src/main/java/de/blight/editor/SharedInput.java index 8f2c271..801f001 100644 --- a/blight-editor/src/main/java/de/blight/editor/SharedInput.java +++ b/blight-editor/src/main/java/de/blight/editor/SharedInput.java @@ -359,6 +359,9 @@ public class SharedInput { /** JME setzt diesen Wert; JavaFX liest ihn (z. B. für Eingabesperre). */ public volatile boolean consoleIsOpen = false; + /** JavaFX → JME3: Tageszeit setzen (0.0 = Mitternacht, 0.5 = Mittag, 1.0 = Mitternacht). NaN = kein Auftrag. */ + public volatile float pendingTimeOfDay = Float.NaN; + /** @deprecated Nur noch für Rückwärtskompatibilität – nicht mehr verwenden. */ @Deprecated public volatile String pendingCommand = null; /** Status-/Fehlermeldungen von JME an JavaFX-Statusleiste. */ diff --git a/blight-editor/src/main/java/de/blight/editor/state/SceneObjectState.java b/blight-editor/src/main/java/de/blight/editor/state/SceneObjectState.java index 3fe1dfe..d9f14a8 100644 --- a/blight-editor/src/main/java/de/blight/editor/state/SceneObjectState.java +++ b/blight-editor/src/main/java/de/blight/editor/state/SceneObjectState.java @@ -113,6 +113,11 @@ public class SceneObjectState extends BaseAppState { /** Kleiner Cache: modelPath → ModelMeta, damit nicht pro Frame geladen werden muss. */ private final java.util.Map metaCache = new java.util.HashMap<>(); + // ── Beleuchtungs-Materialien (Tree/TreeLeaf/Fern) ──────────────────────── + private final List sceneLitMaterials = new ArrayList<>(); + private final List windMaterials = new ArrayList<>(); + private final List pbrMaterials = new ArrayList<>(); + // ── Bett-Liegefläche ───────────────────────────────────────────────────── /** Visualisierungspfeil für die Liegefläche (1,8 m); wird nur gezeigt wenn Bett-Objekt gewählt. */ private Node bedArrowNode = null; @@ -183,6 +188,9 @@ public class SceneObjectState extends BaseAppState { objects.clear(); objNodes.clear(); animClips.clear(); + sceneLitMaterials.clear(); + windMaterials.clear(); + pbrMaterials.clear(); selectedIndices.clear(); activeGizmo = -1; if (gizmoNode != null) @@ -222,6 +230,7 @@ public class SceneObjectState extends BaseAppState { objNodes.add(node); objectRoot.attachChild(node); + collectLitMaterials(node); } } @@ -310,6 +319,32 @@ public class SceneObjectState extends BaseAppState { updateGizmoVisibility(); updateGizmoOrientation(); + // Baum- und Farn-Shader mit Sonnen- und Windwerten versorgen; PBR-Emissive setzen + if (!sceneLitMaterials.isEmpty() || !windMaterials.isEmpty() || !pbrMaterials.isEmpty()) { + de.blight.game.state.DayNightState dns = + getApplication().getStateManager().getState(de.blight.game.state.DayNightState.class); + if (dns != null && dns.getSunLight() != null) { + Vector3f lightDir = dns.getSunDirection().negate(); + com.jme3.math.ColorRGBA sc = dns.getSunLight().getColor(); + com.jme3.math.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) { + mat.setVector3("LightDir", lightDir); + mat.setVector3("SunColor", sunVec); + mat.setVector3("AmbientColor", ambVec); + } + for (Material mat : windMaterials) { + mat.setVector3("LightDir", lightDir); + mat.setVector3("SunColor", sunVec); + mat.setVector3("AmbientColor", ambVec); + } + for (Material mat : pbrMaterials) { + mat.setColor("Emissive", new com.jme3.math.ColorRGBA(ac.r, ac.g, ac.b, 1f)); + } + } + } + // Baum-Ordner-Platzierungsmodus String treeFolder = input.treeFolderPath; if (treeFolder != null && !treeFolder.equals(activeFolderPath)) { @@ -687,6 +722,7 @@ public class SceneObjectState extends BaseAppState { } objNodes.add(node); objectRoot.attachChild(node); + collectLitMaterials(node); selectObject(objects.size() - 1, false); input.objectJustPlaced = true; @@ -741,13 +777,73 @@ public class SceneObjectState extends BaseAppState { } } + private void convertUnshadedToPbr(Spatial spatial) { + if (spatial instanceof Geometry geo) { + Material mat = geo.getMaterial(); + if (mat == null) return; + if (!"Unshaded".equals(mat.getMaterialDef().getName())) return; + + com.jme3.material.MatParam colorMap = mat.getParam("ColorMap"); + com.jme3.material.MatParam color = mat.getParam("Color"); + com.jme3.material.RenderState origRS = mat.getAdditionalRenderState(); + + 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) { + pbrMat.setTexture("BaseColorMap", t); + } else if (color != null && color.getValue() instanceof ColorRGBA c) { + pbrMat.setColor("BaseColor", new ColorRGBA(c.r, c.g, c.b, 1f)); + } + + com.jme3.material.RenderState rs = pbrMat.getAdditionalRenderState(); + rs.setBlendMode(origRS.getBlendMode()); + rs.setFaceCullMode(origRS.getFaceCullMode()); + + geo.setMaterial(pbrMat); + } else if (spatial instanceof Node node) { + for (Spatial child : new java.util.ArrayList<>(node.getChildren())) { + convertUnshadedToPbr(child); + } + } + } + + private void collectLitMaterials(Spatial spatial) { + if (spatial instanceof Geometry geo) { + Material mat = geo.getMaterial(); + if (mat == null) return; + String name = mat.getMaterialDef().getName(); + if ("Tree".equals(name) || "TreeLeaf".equals(name)) { + mat.setVector3("SunColor", new com.jme3.math.Vector3f(0.68f, 0.65f, 0.60f)); + mat.setVector3("AmbientColor", new com.jme3.math.Vector3f(0.08f, 0.10f, 0.16f)); + mat.setVector3("LightDir", new com.jme3.math.Vector3f(0f, -1f, 0f)); + sceneLitMaterials.add(mat); + } else if ("Fern".equals(name)) { + windMaterials.add(mat); + } else if ("PBRLighting".equals(name)) { + mat.setFloat("EmissivePower", 1f); + mat.setFloat("EmissiveIntensity", 1f); + mat.setColor("Emissive", new com.jme3.math.ColorRGBA(0.08f, 0.10f, 0.16f, 1f)); + pbrMaterials.add(mat); + } + } else if (spatial instanceof Node node) { + for (Spatial child : node.getChildren()) { + collectLitMaterials(child); + } + } + } + private Node loadModelNode(String modelPath, float wx, float wy, float wz) { Node node = new Node("obj_" + objects.size()); try { Spatial model = modelPath.startsWith("@") ? createPrimitiveSpatial(modelPath.substring(1)) : assets.loadModel(modelPath); - if (!modelPath.startsWith("@")) stripControlsRecursive(model); + if (!modelPath.startsWith("@")) { + stripControlsRecursive(model); + convertUnshadedToPbr(model); + } if (modelPath.startsWith("@")) { Material mat = buildPrimitiveMaterial(); if (model instanceof Geometry g) g.setMaterial(mat); @@ -785,12 +881,13 @@ public class SceneObjectState extends BaseAppState { boolean hasNmap = nmap != null && !nmap.isEmpty(); if (hasTex || hasNmap) { try { - 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(tex); t.setWrap(Texture.WrapMode.Repeat); - mat.setTexture("DiffuseMap", t); + mat.setTexture("BaseColorMap", t); } if (hasNmap) { Texture n = assets.loadTexture(nmap); @@ -800,8 +897,10 @@ public class SceneObjectState extends BaseAppState { return mat; } catch (Exception ignored) {} } - Material mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md"); - mat.setColor("Color", new com.jme3.math.ColorRGBA(0.75f, 0.75f, 0.75f, 1f)); + Material mat = new Material(assets, "Common/MatDefs/Light/PBRLighting.j3md"); + mat.setFloat("Metallic", 0.0f); + mat.setFloat("Roughness", 0.85f); + mat.setColor("BaseColor", new com.jme3.math.ColorRGBA(0.75f, 0.75f, 0.75f, 1f)); return mat; } @@ -1461,7 +1560,9 @@ public class SceneObjectState extends BaseAppState { return; } } else if (hasTex) { - mat = new Material(assets, "Common/MatDefs/Light/Lighting.j3md"); + mat = new Material(assets, "Common/MatDefs/Light/PBRLighting.j3md"); + mat.setFloat("Metallic", 0.0f); + mat.setFloat("Roughness", 0.85f); } else { mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md"); mat.setColor("Color", new ColorRGBA(0.75f, 0.75f, 0.75f, 1f)); diff --git a/blight-editor/src/main/java/de/blight/editor/state/TerrainEditorState.java b/blight-editor/src/main/java/de/blight/editor/state/TerrainEditorState.java index bbe97e4..60f2e8e 100644 --- a/blight-editor/src/main/java/de/blight/editor/state/TerrainEditorState.java +++ b/blight-editor/src/main/java/de/blight/editor/state/TerrainEditorState.java @@ -96,6 +96,7 @@ public class TerrainEditorState extends BaseAppState { private final SharedInput input; private TerrainQuad terrain; + private com.jme3.material.Material terrainMat; private float[] cachedHeightMap; // Einmal geladen, danach manuell synchron gehalten private Geometry brushIndicator; private PlacedObjectState placedObjectState; @@ -157,6 +158,9 @@ public class TerrainEditorState extends BaseAppState { // ── Lifecycle ───────────────────────────────────────────────────────────── private de.blight.game.state.DayNightState dayNightState; + private com.jme3.light.LightProbe probe; + private com.jme3.environment.EnvironmentCamera envCam; + private int envCamFrames = 0; @Override protected void initialize(Application app) { @@ -182,6 +186,16 @@ public class TerrainEditorState extends BaseAppState { dayNightState.getDayTime().setTimeOfDay(0.5f); dayNightState.setPaused(true); app.getStateManager().attach(dayNightState); + + // LightProbe für PBR-IBL – ohne Probe ist envLightContribution immer 0 in JME3 3.9.0. + // Probe backen wenn SkyControl den Himmel gerendert hat (ab Frame 30). + probe = new com.jme3.light.LightProbe(); + probe.getArea().setRadius(8000f); + rootNode.addLight(probe); + envCam = new com.jme3.environment.EnvironmentCamera(256, new com.jme3.math.Vector3f(0f, 5f, 0f)); + app.getStateManager().attach(envCam); + envCamFrames = 0; + } private void loadCameraPrefs() { @@ -391,7 +405,7 @@ public class TerrainEditorState extends BaseAppState { tq.addControl(lod); initSplatmap(); - tq.setMaterial(buildTerrainMaterial()); + tq.setMaterial(terrainMat = buildTerrainMaterial()); return tq; } @@ -594,7 +608,7 @@ public class TerrainEditorState extends BaseAppState { if (dayNightState != null && dayNightState.getSunLight() != null) { mat.setVector3("LightDir", dayNightState.getSunDirection().negate()); com.jme3.math.ColorRGBA sc = dayNightState.getSunLight().getColor(); - com.jme3.math.ColorRGBA ac = dayNightState.getAmbientLight().getColor(); + com.jme3.math.ColorRGBA ac = dayNightState.getCustomAmbient(); mat.setVector3("SunColor", new com.jme3.math.Vector3f(sc.r, sc.g, sc.b)); mat.setVector3("AmbientColor", new com.jme3.math.Vector3f(ac.r, ac.g, ac.b)); } @@ -925,6 +939,30 @@ public class TerrainEditorState extends BaseAppState { @Override public void update(float tpf) { + // LightProbe backen: nach 30 Frames hat SkyControl den Himmel vollständig gerendert. + if (envCam != null) { + envCamFrames++; + if (envCamFrames == 30) { + com.jme3.environment.LightProbeFactory.updateProbe(probe, envCam, rootNode, + new com.jme3.environment.generation.JobProgressAdapter() { + @Override public void done(com.jme3.light.LightProbe result) { + app.getStateManager().detach(envCam); + envCam = null; + log.info("[TerrainEditor] LightProbe fertig."); + } + }); + } + } + + // Terrain-Shader mit DayNightState synchronisieren (reagiert auf Zeit-Menü-Änderungen) + if (terrainMat != null && dayNightState != null && dayNightState.getSunLight() != null) { + terrainMat.setVector3("LightDir", dayNightState.getSunDirection().negate()); + com.jme3.math.ColorRGBA sc = dayNightState.getSunLight().getColor(); + com.jme3.math.ColorRGBA ac = dayNightState.getCustomAmbient(); + terrainMat.setVector3("SunColor", new com.jme3.math.Vector3f(sc.r, sc.g, sc.b)); + terrainMat.setVector3("AmbientColor", new com.jme3.math.Vector3f(ac.r, ac.g, ac.b)); + } + float pgx = input.pendingGotoX; if (!Float.isNaN(pgx)) { input.pendingGotoX = Float.NaN; @@ -987,7 +1025,7 @@ public class TerrainEditorState extends BaseAppState { input.thirdNormalMapsChanged = false; input.normalMapOrderChanged = false; input.diffuseScalesChanged = false; - terrain.setMaterial(buildTerrainMaterial()); + terrain.setMaterial(terrainMat = buildTerrainMaterial()); } if (input.reloadPlacedOther) { diff --git a/blight-editor/src/main/java/de/blight/editor/state/VoxelEditorState.java b/blight-editor/src/main/java/de/blight/editor/state/VoxelEditorState.java index 04b27fa..cf6ab4c 100644 --- a/blight-editor/src/main/java/de/blight/editor/state/VoxelEditorState.java +++ b/blight-editor/src/main/java/de/blight/editor/state/VoxelEditorState.java @@ -219,6 +219,19 @@ public class VoxelEditorState extends BaseAppState { @Override public void update(float tpf) { + // Voxel-Shader mit DayNightState synchronisieren + if (voxelMaterial != null) { + de.blight.game.state.DayNightState dns = + getApplication().getStateManager().getState(de.blight.game.state.DayNightState.class); + if (dns != null && dns.getSunLight() != null) { + voxelMaterial.setVector3("LightDir", dns.getSunDirection().negate()); + com.jme3.math.ColorRGBA sc = dns.getSunLight().getColor(); + com.jme3.math.ColorRGBA ac = dns.getCustomAmbient(); + voxelMaterial.setVector3("SunColor", new com.jme3.math.Vector3f(sc.r, sc.g, sc.b)); + voxelMaterial.setVector3("AmbientColor", new com.jme3.math.Vector3f(ac.r, ac.g, ac.b)); + } + } + // LOD-Ergebnisse aus dem Hintergrund-Thread übernehmen Runnable r; while ((r = lodResultQueue.poll()) != null) r.run(); diff --git a/blight-game/src/main/java/de/blight/game/scene/WorldScene.java b/blight-game/src/main/java/de/blight/game/scene/WorldScene.java index dc8ae37..2517f2a 100644 --- a/blight-game/src/main/java/de/blight/game/scene/WorldScene.java +++ b/blight-game/src/main/java/de/blight/game/scene/WorldScene.java @@ -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() { + @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 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 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); diff --git a/blight-game/src/main/java/de/blight/game/state/DayNightState.java b/blight-game/src/main/java/de/blight/game/state/DayNightState.java index 5c5279a..0971362 100644 --- a/blight-game/src/main/java/de/blight/game/state/DayNightState.java +++ b/blight-game/src/main/java/de/blight/game/state/DayNightState.java @@ -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) { diff --git a/blight-game/src/main/java/de/blight/game/state/LightingConfig.java b/blight-game/src/main/java/de/blight/game/state/LightingConfig.java new file mode 100644 index 0000000..02791b8 --- /dev/null +++ b/blight-game/src/main/java/de/blight/game/state/LightingConfig.java @@ -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 0–1 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.4–0.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.03–0.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.01–0.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() {} +} diff --git a/blight-game/src/main/java/de/blight/game/state/SculptedMeshState.java b/blight-game/src/main/java/de/blight/game/state/SculptedMeshState.java index 459ac35..8fbf55f 100644 --- a/blight-game/src/main/java/de/blight/game/state/SculptedMeshState.java +++ b/blight-game/src/main/java/de/blight/game/state/SculptedMeshState.java @@ -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)); } diff --git a/blight-game/src/main/java/de/blight/game/state/StoneWorldState.java b/blight-game/src/main/java/de/blight/game/state/StoneWorldState.java index 4a108df..01e67a2 100644 --- a/blight-game/src/main/java/de/blight/game/state/StoneWorldState.java +++ b/blight-game/src/main/java/de/blight/game/state/StoneWorldState.java @@ -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); diff --git a/blight-game/src/main/java/de/blight/game/state/WorldNpcsState.java b/blight-game/src/main/java/de/blight/game/state/WorldNpcsState.java index c77fd0a..f2e2f5d 100644 --- a/blight-game/src/main/java/de/blight/game/state/WorldNpcsState.java +++ b/blight-game/src/main/java/de/blight/game/state/WorldNpcsState.java @@ -89,8 +89,9 @@ public class WorldNpcsState extends BaseAppState { private final List spawned = new ArrayList<>(); private static class SpawnedNpc { - final NPC npc; - final Spatial visual; + final NPC npc; + final Spatial visual; + final List 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 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); diff --git a/blight-game/src/main/java/de/blight/game/state/WorldObjectsState.java b/blight-game/src/main/java/de/blight/game/state/WorldObjectsState.java index 4f07594..29ce903 100644 --- a/blight-game/src/main/java/de/blight/game/state/WorldObjectsState.java +++ b/blight-game/src/main/java/de/blight/game/state/WorldObjectsState.java @@ -36,6 +36,8 @@ public class WorldObjectsState extends BaseAppState { private BulletAppState bulletAppState; private final List sceneLitMaterials = new ArrayList<>(); private final List windMaterials = new ArrayList<>(); + private final List pbrMaterials = new ArrayList<>(); + private float debugTimer = 0f; /** RigidBodyControl pro Interactable-ID, damit Kollision während Animationen deaktiviert werden kann. */ private final Map 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()); diff --git a/blight-map/src/main/map/blight_map.blm b/blight-map/src/main/map/blight_map.blm index f23b060..1530377 100644 Binary files a/blight-map/src/main/map/blight_map.blm and b/blight-map/src/main/map/blight_map.blm differ diff --git a/blight-map/src/main/map/blight_objects.blo b/blight-map/src/main/map/blight_objects.blo index ddd005e..5f4db58 100644 --- a/blight-map/src/main/map/blight_objects.blo +++ b/blight-map/src/main/map/blight_objects.blo @@ -3,3 +3,5 @@ Models/imported/wooden+cabin+3d+model.j3o 105.61634 4.85395 58.69108 0.00000 1.0 Models/imported/bank1.j3o 106.96240 2.89958 70.01086 0.00000 1.00000 0.00000 0.00000 false true true 30.00000 80.00000 120.00000 BENCH 5be97ffb-e413-42e5-9815-9d14d1e3b93f Models/trees/pine/medium/pine_medium_20260706_190947.j3o -6.42357 1.22971 -1318.06396 5.19824 1.00000 0.00000 0.00000 false true true 30.00000 80.00000 120.00000 Models/trees/pine/medium/pine_medium_20260706_190953.j3o -17.84356 1.24567 -1316.45410 0.26793 1.00000 0.00000 0.00000 false true true 30.00000 80.00000 120.00000 +Models/plants/misc/heliconia+plant+3d+model.j3o -7.48228 1.22979 -1320.31714 0.00000 1.00000 0.00000 0.00000 false true true 30.00000 80.00000 120.00000 +Models/plants/misc/kaktusfeige.j3o -6.18754 1.22995 -1320.34875 0.00000 2.50000 0.00000 0.00000 true true true 30.00000 80.00000 120.00000