diff --git a/blight-game/src/main/java/de/blight/game/config/GraphicsScreen.java b/blight-game/src/main/java/de/blight/game/config/GraphicsScreen.java index d9b446b..ffb6028 100644 --- a/blight-game/src/main/java/de/blight/game/config/GraphicsScreen.java +++ b/blight-game/src/main/java/de/blight/game/config/GraphicsScreen.java @@ -20,7 +20,8 @@ public class GraphicsScreen extends MenuScreen { private static final int ROW_VSYNC = 2; private static final int ROW_AA = 3; private static final int ROW_VIEWDIST = 4; - private static final int ROW_COUNT = 5; + private static final int ROW_SHADOW = 5; + private static final int ROW_COUNT = 6; private final GraphicsSettings live; private GraphicsSettings edit; @@ -48,7 +49,8 @@ public class GraphicsScreen extends MenuScreen { edit.fullscreen = live.fullscreen; edit.vsync = live.vsync; edit.samples = live.samples; - edit.viewDistance = live.viewDistance; + edit.viewDistance = live.viewDistance; + edit.shadowQuality = live.shadowQuality; resIdx = 0; for (int i = 0; i < RESOLUTIONS.length; i++) { @@ -71,7 +73,8 @@ public class GraphicsScreen extends MenuScreen { "menu.graphics.row.fullscreen", "menu.graphics.row.vsync", "menu.graphics.row.aa", - "menu.graphics.row.viewdist" + "menu.graphics.row.viewdist", + "menu.graphics.row.shadow" }; float lblX = CONT_X + 30f; @@ -125,6 +128,11 @@ public class GraphicsScreen extends MenuScreen { case NORMAL -> t("menu.graphics.val.viewdist.normal"); case FAR -> t("menu.graphics.val.viewdist.far"); }; + case ROW_SHADOW -> switch (edit.shadowQuality) { + case LOW -> t("menu.graphics.val.shadow.low"); + case MEDIUM -> t("menu.graphics.val.shadow.medium"); + case HIGH -> t("menu.graphics.val.shadow.high"); + }; default -> ""; }; BitmapText vt = valTexts[row]; @@ -154,23 +162,30 @@ public class GraphicsScreen extends MenuScreen { GraphicsSettings.ViewDistance[] vds = GraphicsSettings.ViewDistance.values(); edit.viewDistance = vds[(edit.viewDistance.ordinal() + dir + vds.length) % vds.length]; break; + case ROW_SHADOW: + GraphicsSettings.ShadowQuality[] sqs = GraphicsSettings.ShadowQuality.values(); + edit.shadowQuality = sqs[(edit.shadowQuality.ordinal() + dir + sqs.length) % sqs.length]; + break; } refreshText(row); } private void applyAndSave() { - boolean needsRestart = live.width != edit.width - || live.height != edit.height - || live.fullscreen != edit.fullscreen - || live.vsync != edit.vsync - || live.samples != edit.samples; + boolean shadowChanged = live.shadowQuality != edit.shadowQuality; + boolean needsRestart = live.width != edit.width + || live.height != edit.height + || live.fullscreen != edit.fullscreen + || live.vsync != edit.vsync + || live.samples != edit.samples + || shadowChanged; - live.width = edit.width; - live.height = edit.height; - live.fullscreen = edit.fullscreen; - live.vsync = edit.vsync; - live.samples = edit.samples; - live.viewDistance = edit.viewDistance; + live.width = edit.width; + live.height = edit.height; + live.fullscreen = edit.fullscreen; + live.vsync = edit.vsync; + live.samples = edit.samples; + live.viewDistance = edit.viewDistance; + live.shadowQuality = edit.shadowQuality; GraphicsStore.save(live); WeatherState ws = getApplication().getStateManager().getState(WeatherState.class); diff --git a/blight-game/src/main/java/de/blight/game/config/GraphicsSettings.java b/blight-game/src/main/java/de/blight/game/config/GraphicsSettings.java index 90206da..f79d7e3 100644 --- a/blight-game/src/main/java/de/blight/game/config/GraphicsSettings.java +++ b/blight-game/src/main/java/de/blight/game/config/GraphicsSettings.java @@ -1,12 +1,42 @@ package de.blight.game.config; public class GraphicsSettings { - public int width = 1280; - public int height = 720; - public boolean fullscreen = false; - public boolean vsync = false; - public int samples = 4; - public ViewDistance viewDistance = ViewDistance.NORMAL; + public int width = 1280; + public int height = 720; + public boolean fullscreen = false; + public boolean vsync = false; + public int samples = 4; + public ViewDistance viewDistance = ViewDistance.NORMAL; + public ShadowQuality shadowQuality = ShadowQuality.MEDIUM; + + /** + * Schattenqualitäts-Stufen. + * mapSize/splits sind Konstruktor-Parameter des Shadow-Filters → Neustart nötig. + * lambda/zExtend/zFade/backface können zur Laufzeit angepasst werden. + */ + public enum ShadowQuality { + // mapSize splits lambda zExtend zFade backface + LOW ( 1024, 2, 0.70f, 40f, 5f, false), + MEDIUM ( 2048, 3, 0.75f, 60f, 8f, true ), + HIGH ( 4096, 4, 0.80f, 80f, 10f, true ); + + public final int shadowMapSize; + public final int splits; + public final float lambda; + public final float zExtend; + public final float zFade; + public final boolean backface; + + ShadowQuality(int shadowMapSize, int splits, float lambda, + float zExtend, float zFade, boolean backface) { + this.shadowMapSize = shadowMapSize; + this.splits = splits; + this.lambda = lambda; + this.zExtend = zExtend; + this.zFade = zFade; + this.backface = backface; + } + } public enum ViewDistance { SHORT (0.55f, 0, 2), 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 0ed2bb4..fbaed7f 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 @@ -535,6 +535,15 @@ public class WorldScene extends BaseAppState { * Setzt Ambient=(1,1,1,1) auf allen Lighting.j3md-Materialien eines Modells, * damit es den globalen AmbientLight korrekt empfängt (Blender-Export setzt Ambient oft auf schwarz). */ + private static void applyShadowModeRecursive(Spatial s, RenderQueue.ShadowMode mode) { + s.setShadowMode(mode); + if (s instanceof Node n) { + for (Spatial child : n.getChildren()) { + applyShadowModeRecursive(child, mode); + } + } + } + public static void applyFullAmbient(Spatial s) { if (s instanceof Geometry g) { com.jme3.material.Material m = g.getMaterial(); @@ -575,7 +584,7 @@ public class WorldScene extends BaseAppState { try { Spatial loaded = assetManager.loadModel(mc.getModelPath()); stripEmbeddedClips(loaded, mc.getModelPath()); - loaded.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + applyShadowModeRecursive(loaded, RenderQueue.ShadowMode.CastAndReceive); applyFullAmbient(loaded); collectPbrMaterials(loaded, characterPbrMaterials); log.info("[WorldScene] {} PBR-Materialien im Charakter-Modell gefunden.", characterPbrMaterials.size()); @@ -685,8 +694,15 @@ public class WorldScene extends BaseAppState { // Licht, Ambient und Sky kommen von DayNightState (der wurde in initialize() attached). // Wir erstellen hier nur den Shadow-Filter und übergeben ihn an DayNightState, // damit dieser Intensität und Lichtbindung dynamisch verwaltet. + GraphicsSettings.ShadowQuality sq = graphicsSettings.shadowQuality; DirectionalLightShadowFilter shadowFilter = - new DirectionalLightShadowFilter(assetManager, 2048, 3); + new DirectionalLightShadowFilter(assetManager, sq.shadowMapSize, sq.splits); + shadowFilter.setEdgeFilteringMode( + sq == GraphicsSettings.ShadowQuality.LOW ? EdgeFilteringMode.PCF4 : EdgeFilteringMode.PCFPOISSON); + shadowFilter.setLambda(sq.lambda); + shadowFilter.setShadowZExtend(sq.zExtend); + shadowFilter.setShadowZFadeLength(sq.zFade); + shadowFilter.setRenderBackFacesShadows(sq.backface); shadowFilter.setShadowIntensity(0.4f); shadowFilter.setEnabled(true); dayNight.setShadowFilter(shadowFilter); // bindet sun + übernimmt Intensitäts-Updates 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 9190979..7b9abbf 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 @@ -45,7 +45,7 @@ public class DayNightState extends BaseAppState implements TimeListener { // ── Sonnenrichtungs-Drosselung ──────────────────────────────────────────── - private static final float SUN_DIR_INTERVAL = 0.25f; + private static final float SUN_DIR_INTERVAL = 2.0f; private float sunDirTimer = SUN_DIR_INTERVAL; // sofort bereit beim ersten Aufruf // ── Höhlen-Abdunkelung ──────────────────────────────────────────────────── diff --git a/blight-lang/src/main/resources/lang/messages_de.properties b/blight-lang/src/main/resources/lang/messages_de.properties index b7923f1..58f41a1 100644 --- a/blight-lang/src/main/resources/lang/messages_de.properties +++ b/blight-lang/src/main/resources/lang/messages_de.properties @@ -54,6 +54,10 @@ menu.graphics.val.off=Aus menu.graphics.val.viewdist.short=Kurz menu.graphics.val.viewdist.normal=Normal menu.graphics.val.viewdist.far=Weit +menu.graphics.row.shadow=Schattenqualität +menu.graphics.val.shadow.low=Niedrig +menu.graphics.val.shadow.medium=Mittel +menu.graphics.val.shadow.high=Hoch menu.graphics.btn.apply=Übernehmen menu.graphics.btn.cancel=Abbrechen diff --git a/blight-lang/src/main/resources/lang/messages_en.properties b/blight-lang/src/main/resources/lang/messages_en.properties index 7fd4839..98a1174 100644 --- a/blight-lang/src/main/resources/lang/messages_en.properties +++ b/blight-lang/src/main/resources/lang/messages_en.properties @@ -57,6 +57,10 @@ menu.graphics.val.off=Off menu.graphics.val.viewdist.short=Short menu.graphics.val.viewdist.normal=Normal menu.graphics.val.viewdist.far=Far +menu.graphics.row.shadow=Shadow Quality +menu.graphics.val.shadow.low=Low +menu.graphics.val.shadow.medium=Medium +menu.graphics.val.shadow.high=High menu.graphics.btn.apply=Apply menu.graphics.btn.cancel=Cancel diff --git a/com/jme3/shadow/AbstractShadowFilter.java b/com/jme3/shadow/AbstractShadowFilter.java new file mode 100644 index 0000000..7c76b29 --- /dev/null +++ b/com/jme3/shadow/AbstractShadowFilter.java @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2009-2025 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shadow; + +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.material.RenderState; +import com.jme3.math.Matrix4f; +import com.jme3.math.Vector4f; +import com.jme3.post.Filter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.texture.FrameBuffer; +import com.jme3.util.TempVars; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; + +/** + * Generic abstract filter that holds common implementations for the different + * shadow filters + * + * @author Rémy Bouquet aka Nehon + */ +public abstract class AbstractShadowFilter extends Filter implements JmeCloneable { + + protected T shadowRenderer; + protected ViewPort viewPort; + + private final Vector4f tempVec4 = new Vector4f(); + private final Matrix4f tempMat4 = new Matrix4f(); + + /** + * For serialization only. Do not use. + */ + protected AbstractShadowFilter() { + } + + /** + * Creates an AbstractShadowFilter. Subclasses invoke this constructor. + * + * @param assetManager The application's asset manager. + * @param shadowMapSize The size of the rendered shadow maps (e.g., 512, 1024, 2048). + * @param shadowRenderer The shadowRenderer to use for this Filter + */ + protected AbstractShadowFilter(AssetManager assetManager, int shadowMapSize, T shadowRenderer) { + super("Post Shadow"); + this.shadowRenderer = shadowRenderer; + // this is legacy setting for shadows with backface shadows + this.shadowRenderer.setRenderBackFacesShadows(true); + } + + @Override + protected Material getMaterial() { + return material; + } + + @Override + protected boolean isRequiresDepthTexture() { + return true; + } + + /** + * @deprecated Use {@link #getMaterial()} instead. + * @return The Material used by this filter. + */ + @Deprecated + public Material getShadowMaterial() { + return material; + } + + @Override + protected void preFrame(float tpf) { + shadowRenderer.preFrame(tpf); + Matrix4f m = viewPort.getCamera().getViewProjectionMatrix(); + material.setMatrix4("ViewProjectionMatrixInverse", tempMat4.set(m).invertLocal()); + material.setVector4("ViewProjectionMatrixRow2", tempVec4.set(m.m20, m.m21, m.m22, m.m23)); + } + + @Override + protected void postQueue(RenderQueue queue) { + shadowRenderer.postQueue(queue); + if (shadowRenderer.skipPostPass) { + // removing the shadow map so that the post pass is skipped + material.setTexture("ShadowMap0", null); + } + } + + @Override + protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) { + if (!shadowRenderer.skipPostPass) { + shadowRenderer.setPostShadowParams(); + } + } + + @Override + protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + shadowRenderer.needsfallBackMaterial = true; + material = new Material(manager, "Common/MatDefs/Shadow/PostShadowFilter.j3md"); + shadowRenderer.setPostShadowMaterial(material); + shadowRenderer.initialize(renderManager, vp); + this.viewPort = vp; + } + + /** + * How far the shadows are rendered in the view + * + * @return shadowZExtend + * @see #setShadowZExtend(float zFar) + */ + public float getShadowZExtend() { + return shadowRenderer.getShadowZExtend(); + } + + /** + * Set the distance from the eye where the shadows will be rendered default + * value is dynamically computed to the shadow casters/receivers union bound + * zFar, capped to view frustum far value. + * + * @param zFar the zFar values that override the computed one + */ + public void setShadowZExtend(float zFar) { + shadowRenderer.setShadowZExtend(zFar); + } + + /** + * Define the length over which the shadow will fade out when using a + * shadowZextend + * + * @param length the fade length in world units + */ + public void setShadowZFadeLength(float length) { + shadowRenderer.setShadowZFadeLength(length); + } + + /** + * get the length over which the shadow will fade out when using a + * shadowZextend + * + * @return the fade length in world units + */ + public float getShadowZFadeLength() { + return shadowRenderer.getShadowZFadeLength(); + } + + /** + * returns the shadow intensity + * + * @see #setShadowIntensity(float shadowIntensity) + * @return shadowIntensity + */ + public float getShadowIntensity() { + return shadowRenderer.getShadowIntensity(); + } + + /** + * Set the shadowIntensity, the value should be between 0 and 1, a 0 value + * gives a bright and invisible shadow, a 1 value gives a pitch black + * shadow, default is 0.7 + * + * @param shadowIntensity the darkness of the shadow + */ + final public void setShadowIntensity(float shadowIntensity) { + shadowRenderer.setShadowIntensity(shadowIntensity); + } + + /** + * returns the edges thickness
+ * + * @see #setEdgesThickness(int edgesThickness) + * @return edgesThickness + */ + public int getEdgesThickness() { + return shadowRenderer.getEdgesThickness(); + } + + /** + * Sets the shadow edges thickness. Default is 10. Setting it to lower values + * can help reduce the jagged effect of shadow edges. + * + * @param edgesThickness the desired thickness (in tenths of a pixel, default=10) + */ + public void setEdgesThickness(int edgesThickness) { + shadowRenderer.setEdgesThickness(edgesThickness); + } + + /** + * isFlushQueues does nothing and is kept only for backward compatibility + * + * @return false + */ + @Deprecated + public boolean isFlushQueues() { + return shadowRenderer.isFlushQueues(); + } + + /** + * sets the shadow compare mode see {@link CompareMode} for more info + * + * @param compareMode the desired mode + */ + final public void setShadowCompareMode(CompareMode compareMode) { + shadowRenderer.setShadowCompareMode(compareMode); + } + + /** + * returns the shadow compare mode + * + * @see CompareMode + * @return the shadowCompareMode + */ + public CompareMode getShadowCompareMode() { + return shadowRenderer.getShadowCompareMode(); + } + + /** + * Sets the filtering mode for shadow edges see {@link EdgeFilteringMode} + * for more info + * + * @param filterMode the desired mode + */ + final public void setEdgeFilteringMode(EdgeFilteringMode filterMode) { + shadowRenderer.setEdgeFilteringMode(filterMode); + } + + /** + * + * !! WARNING !! this parameter is defaulted to true for the ShadowFilter. + * Setting it to true, may produce edges artifacts on shadows. + * + * Set to true if you want back faces shadows on geometries. + * Note that back faces shadows will be blended over dark lighten areas and may produce overly dark lighting. + * + * Setting this parameter will override this parameter for ALL materials in the scene. + * This also will automatically adjust the faceCullMode and the PolyOffset of the pre shadow pass. + * You can modify them by using {@link #getPreShadowForcedRenderState()} + * + * If you want to set it differently for each material in the scene you have to use the ShadowRenderer instead + * of the shadow filter. + * + * @param renderBackFacesShadows true or false. + */ + public void setRenderBackFacesShadows(boolean renderBackFacesShadows) { + shadowRenderer.setRenderBackFacesShadows(renderBackFacesShadows); + } + + /** + * if this filter renders back faces shadows + * @return true if this filter renders back faces shadows + */ + public boolean isRenderBackFacesShadows() { + return shadowRenderer.isRenderBackFacesShadows(); + } + + /** + * returns the pre shadows pass render state. + * use it to adjust the RenderState parameters of the pre shadow pass. + * Note that this will be overridden if the preShadow technique in the material has a ForcedRenderState + * @return the pre shadow render state. + */ + public RenderState getPreShadowForcedRenderState() { + return shadowRenderer.getPreShadowForcedRenderState(); + } + + /** + * returns the edge filtering mode + * + * @see EdgeFilteringMode + * @return the enum value + */ + public EdgeFilteringMode getEdgeFilteringMode() { + return shadowRenderer.getEdgeFilteringMode(); + } + + /** + * Read the number of shadow maps rendered by this filter. + * + * @return count + */ + public int getNumShadowMaps() { + return shadowRenderer.getNumShadowMaps(); + } + + /** + * Read the size of each shadow map rendered by this filter. + * + * @return a map's height (which is also its width, in pixels) + */ + public int getShadowMapSize() { + return shadowRenderer.getShadowMapSize(); + } + + @Override + @SuppressWarnings("unchecked") + public AbstractShadowFilter jmeClone() { + try { + return (AbstractShadowFilter) super.clone(); + } catch (final CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + + @Override + public void cloneFields(final Cloner cloner, final Object original) { + material = cloner.clone(material); + shadowRenderer = cloner.clone(shadowRenderer); + shadowRenderer.setPostShadowMaterial(material); + } + +} diff --git a/com/jme3/shadow/DirectionalLightShadowFilter.java b/com/jme3/shadow/DirectionalLightShadowFilter.java new file mode 100644 index 0000000..4438e37 --- /dev/null +++ b/com/jme3/shadow/DirectionalLightShadowFilter.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shadow; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.light.DirectionalLight; +import java.io.IOException; + +/** + * This Filter does basically the same as a DirectionalLightShadowRenderer + * except it renders the post shadow pass as a fullscreen quad pass instead of a + * geometry pass. It's mostly faster than PssmShadowRenderer as long as you have + * more than about ten shadow receiving objects. The expense is the drawback + * that the shadow Receive mode set on spatial is ignored. So basically all and + * only objects that render depth in the scene receive shadows. + * + * API is basically the same as the PssmShadowRenderer. + * + * @author Rémy Bouquet aka Nehon + */ +public class DirectionalLightShadowFilter extends AbstractShadowFilter { + + /** + * For serialization only. Do not use. + * + * @see #DirectionalLightShadowFilter(AssetManager assetManager, int shadowMapSize, int nbSplits) + */ + public DirectionalLightShadowFilter() { + super(); + } + + /** + * Creates a DirectionalLightShadowFilter. + * + * @param assetManager the application's asset manager + * @param shadowMapSize the size of the rendered shadow maps (512, 1024, 2048, etc...) + * @param nbSplits the number of shadow maps rendered (more shadow maps = better quality, but slower) + * + * @throws IllegalArgumentException if the provided 'nbSplits' is not within the valid range of 1 to 4. + */ + public DirectionalLightShadowFilter(AssetManager assetManager, int shadowMapSize, int nbSplits) { + super(assetManager, shadowMapSize, new DirectionalLightShadowRenderer(assetManager, shadowMapSize, nbSplits)); + } + + /** + * Returns the light used to cast shadows. + * + * @return the DirectionalLight + */ + public DirectionalLight getLight() { + return shadowRenderer.getLight(); + } + + /** + * Sets the light to use to cast shadows. + * + * @param light a DirectionalLight + */ + public void setLight(DirectionalLight light) { + shadowRenderer.setLight(light); + } + + /** + * Returns the lambda parameter. + * + * @see #setLambda(float lambda) + * @return lambda + */ + public float getLambda() { + return shadowRenderer.getLambda(); + } + + /** + * Adjusts the partition of the shadow extend into shadow maps. Lambda is + * usually between 0 and 1. + *

+ * A low value gives a more linear partition, resulting in consistent shadow + * quality over the extend, but near shadows could look very jagged. A high + * value gives a more logarithmic partition, resulting in high quality for near + * shadows, but quality decreases rapidly with distance. + *

+ * The default value is 0.65 (the theoretical optimum). + * + * @param lambda the lambda value + */ + public void setLambda(float lambda) { + shadowRenderer.setLambda(lambda); + } + + /** + * Returns true if stabilization is enabled. + * + * @return true if stabilization is enabled + */ + public boolean isEnabledStabilization() { + return shadowRenderer.isEnabledStabilization(); + } + + /** + * Enables the stabilization of the shadow's edges. (default is true) + * This prevents shadow edges from flickering when the camera moves. + * However, it can lead to some loss of shadow quality in particular scenes. + * + * @param stabilize true to stabilize, false to disable stabilization + */ + public void setEnabledStabilization(boolean stabilize) { + shadowRenderer.setEnabledStabilization(stabilize); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(shadowRenderer, "shadowRenderer", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + shadowRenderer = (DirectionalLightShadowRenderer) ic.readSavable("shadowRenderer", null); + } + +}