From 6e287d0ef2d619c62524ae5245721d490b168cd7 Mon Sep 17 00:00:00 2001 From: Nehon Date: Sun, 26 Oct 2014 16:04:27 +0100 Subject: [PATCH] Introduced light culling when rendering shadow maps for all light types. When a light is out of the frustum, the shadow maps are not rendered anymore. Also implemented proper Zextend and ZFade for al Light type allowing to cap the shadow distance and to smoothly fade shadows in and out --- .../com/jme3/shadow/AbstractShadowFilter.java | 41 ++++++ .../jme3/shadow/AbstractShadowRenderer.java | 117 ++++++++++++++++-- .../shadow/DirectionalLightShadowFilter.java | 41 ------ .../DirectionalLightShadowRenderer.java | 86 +++---------- .../jme3/shadow/PointLightShadowRenderer.java | 29 +++++ .../jme3/shadow/SpotLightShadowFilter.java | 43 +------ .../jme3/shadow/SpotLightShadowRenderer.java | 95 +++++--------- .../Common/MatDefs/Shadow/PostShadow.frag | 2 +- .../Common/MatDefs/Shadow/PostShadow.vert | 9 +- .../Common/MatDefs/Shadow/PostShadow15.frag | 4 +- .../Common/MatDefs/Shadow/PostShadow15.vert | 13 +- .../MatDefs/Shadow/PostShadowFilter.frag | 7 +- .../MatDefs/Shadow/PostShadowFilter15.frag | 9 +- .../light/TestDirectionalLightShadow.java | 35 +++++- .../jme3test/light/TestPointLightShadows.java | 25 +--- .../jme3test/light/TestSpotLightShadows.java | 17 ++- 16 files changed, 300 insertions(+), 273 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java index ce7e35bc5..8b4e3faf8 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java +++ b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java @@ -122,6 +122,47 @@ public abstract class AbstractShadowFilter ext shadowRenderer.initialize(renderManager, vp); this.viewPort = vp; } + + /** + * How far the shadows are rendered in the view + * + * @see setShadowZExtend(float zFar) + * @return shadowZExtend + */ + public float getShadowZExtend() { + return shadowRenderer.getShadowZExtend(); + } + + /** + * Set the distance from the eye where the shadows will be rendered default + * value is dynamicaly 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 shdaow intensity diff --git a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java index 6a9865da0..ae495b2b6 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java @@ -44,6 +44,7 @@ import com.jme3.export.Savable; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; import com.jme3.math.Matrix4f; +import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; import com.jme3.post.SceneProcessor; import com.jme3.renderer.Camera; @@ -109,11 +110,17 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable protected GeometryList shadowMapOccluders = new GeometryList(new OpaqueComparator()); private String[] shadowMapStringCache; private String[] lightViewStringCache; + /** + * fade shadows at distance + */ + protected float zFarOverride = 0; + protected Vector2f fadeInfo; + protected float fadeLength; + protected Camera frustumCam; /** * true to skip the post pass when there are no shadow casters */ protected boolean skipPostPass; - /** * used for serialization @@ -319,7 +326,15 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable } else { postTechniqueName = "PostShadow"; } + if(zFarOverride>0 && frustumCam == null){ + initFrustumCam(); + } } + + /** + * delegates the initialization of the frustum cam to child renderers + */ + protected abstract void initFrustumCam(); /** * Test whether this shadow renderer has been initialized. @@ -373,25 +388,25 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable GeometryList occluders = rq.getShadowQueueContent(ShadowMode.Cast); sceneReceivers = rq.getShadowQueueContent(ShadowMode.Receive); skipPostPass = false; - if (sceneReceivers.size() == 0 || occluders.size() == 0) { + if (sceneReceivers.size() == 0 || occluders.size() == 0 || !checkCulling(viewPort.getCamera())) { skipPostPass = true; return; } updateShadowCams(viewPort.getCamera()); - + Renderer r = renderManager.getRenderer(); renderManager.setForcedMaterial(preshadowMat); renderManager.setForcedTechnique("PreShadow"); for (int shadowMapIndex = 0; shadowMapIndex < nbShadowMaps; shadowMapIndex++) { - if (debugfrustums) { - doDisplayFrustumDebug(shadowMapIndex); - } - renderShadowMap(shadowMapIndex, occluders, sceneReceivers); + if (debugfrustums) { + doDisplayFrustumDebug(shadowMapIndex); + } + renderShadowMap(shadowMapIndex, occluders, sceneReceivers); - } + } debugfrustums = false; if (flushQueues) { @@ -402,7 +417,7 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable renderManager.setForcedMaterial(null); renderManager.setForcedTechnique(null); renderManager.setCamera(viewPort.getCamera(), false); - + } protected void renderShadowMap(int shadowMapIndex, GeometryList occluders, GeometryList receivers) { @@ -512,6 +527,7 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable for (int j = 1; j < nbShadowMaps; j++) { mat.clearParam(shadowMapStringCache[j]); } + mat.clearParam("FadeInfo"); clearMaterialParameters(mat); } //No need to clear the postShadowMat params as the instance is locale to each renderer @@ -559,7 +575,9 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable mat.setInt("FilterMode", edgeFilteringMode.getMaterialParamValue()); mat.setFloat("PCFEdge", edgesThickness); mat.setFloat("ShadowIntensity", shadowIntensity); - + if (fadeInfo != null) { + mat.setVector2("FadeInfo", fadeInfo); + } setMaterialParameters(mat); } @@ -580,9 +598,86 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable postshadowMat.setMatrix4(lightViewStringCache[j], lightViewProjectionsMatrices[j]); postshadowMat.setTexture(shadowMapStringCache[j], shadowMaps[j]); } + if (fadeInfo != null) { + postshadowMat.setVector2("FadeInfo", fadeInfo); + } + } + + /** + * How far the shadows are rendered in the view + * + * @see #setShadowZExtend(float zFar) + * @return shadowZExtend + */ + public float getShadowZExtend() { + return zFarOverride; } + + /** + * Set the distance from the eye where the shadows will be rendered default + * value is dynamicaly 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) { + this.zFarOverride = zFar; + if(zFarOverride == 0){ + fadeInfo = null; + frustumCam = null; + }else{ + if (fadeInfo != null) { + fadeInfo.set(zFarOverride - fadeLength, 1f / fadeLength); + } + if(frustumCam == null && viewPort != null){ + initFrustumCam(); + } + } + } + + /** + * Define the length over which the shadow will fade out when using a + * shadowZextend This is useful to make dynamic shadows fade into baked + * shadows in the distance. + * + * @param length the fade length in world units + */ + public void setShadowZFadeLength(float length) { + if (length == 0) { + fadeInfo = null; + fadeLength = 0; + postshadowMat.clearParam("FadeInfo"); + } else { + if (zFarOverride == 0) { + fadeInfo = new Vector2f(0, 0); + } else { + fadeInfo = new Vector2f(zFarOverride - length, 1.0f / length); + } + fadeLength = length; + postshadowMat.setVector2("FadeInfo", fadeInfo); + } + } + + /** + * get the length over which the shadow will fade out when using a + * shadowZextend + * + * @return the fade length in world units + */ + public float getShadowZFadeLength() { + if (fadeInfo != null) { + return zFarOverride - fadeInfo.x; + } + return 0f; + } + + /** + * returns true if the light source bounding box is in the view frustum + * @return + */ + protected abstract boolean checkCulling(Camera viewCam); - public void preFrame(float tpf) { + public void preFrame(float tpf) { } public void cleanup() { diff --git a/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowFilter.java b/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowFilter.java index 21b032ec1..844755cb7 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowFilter.java +++ b/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowFilter.java @@ -114,47 +114,6 @@ public class DirectionalLightShadowFilter extends AbstractShadowFilter 0)); + shadowZfarText.setLocalTranslation(10, viewPort.getCamera().getHeight() - 120, 0); + guiNode.attachChild(shadowZfarText); } - private BitmapText shadowStabilizationText ; + private BitmapText shadowStabilizationText; + private BitmapText shadowZfarText; public void onAction(String name, boolean keyPressed, float tpf) { @@ -254,9 +263,25 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act if (name.equals("stabilize") && keyPressed) { dlsr.setEnabledStabilization(!dlsr.isEnabledStabilization()); - dlsf.setEnabledStabilization(!dlsf.isEnabledStabilization()); + dlsf.setEnabledStabilization(!dlsf.isEnabledStabilization()); shadowStabilizationText.setText("(b:on/off) Shadow stabilization : " + dlsr.isEnabledStabilization()); } + if (name.equals("distance") && keyPressed) { + if (dlsr.getShadowZExtend() > 0) { + dlsr.setShadowZExtend(0); + dlsr.setShadowZFadeLength(0); + dlsf.setShadowZExtend(0); + dlsf.setShadowZFadeLength(0); + + } else { + dlsr.setShadowZExtend(500); + dlsr.setShadowZFadeLength(50); + dlsf.setShadowZExtend(500); + dlsf.setShadowZFadeLength(50); + } + shadowZfarText.setText("(n:on/off) Shadow extend to 500 and fade to 50 : " + (dlsr.getShadowZExtend() > 0)); + + } if (name.equals("switchGroundMat") && keyPressed) { if (ground.getMaterial() == matGroundL) { diff --git a/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java b/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java index 114c3b6fb..a98ac03db 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java +++ b/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java @@ -87,33 +87,18 @@ public class TestPointLightShadows extends SimpleApplication { plsr = new PointLightShadowRenderer(assetManager, SHADOWMAP_SIZE); plsr.setLight((PointLight) scene.getLocalLightList().get(0)); plsr.setEdgeFilteringMode(EdgeFilteringMode.PCF4); + plsr.setShadowZExtend(15); + plsr.setShadowZFadeLength(5); // plsr.setFlushQueues(false); //plsr.displayFrustum(); plsr.displayDebug(); viewPort.addProcessor(plsr); -// PointLight pl = new PointLight(); -// pl.setPosition(new Vector3f(0, 0.5f, 0)); -// pl.setRadius(5); -// rootNode.addLight(pl); -// -// Geometry lightMdl2 = new Geometry("Light2", new Sphere(10, 10, 0.1f)); -// //Geometry lightMdl = new Geometry("Light", new Box(.1f,.1f,.1f)); -// lightMdl2.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); -// lightMdl2.setShadowMode(RenderQueue.ShadowMode.Off); -// rootNode.attachChild(lightMdl2); -// lightMdl2.setLocalTranslation(pl.getPosition()); -// PointLightShadowRenderer plsr2 = new PointLightShadowRenderer(assetManager, 512); -// plsr2.setShadowIntensity(0.3f); -// plsr2.setLight(pl); -// plsr2.setEdgeFilteringMode(EdgeFilteringMode.PCF4); -// // plsr.displayDebug(); -// viewPort.addProcessor(plsr2); - - plsf = new PointLightShadowFilter(assetManager, SHADOWMAP_SIZE); - plsf.setLight((PointLight) scene.getLocalLightList().get(0)); + plsf.setLight((PointLight) scene.getLocalLightList().get(0)); + plsf.setShadowZExtend(15); + plsf.setShadowZFadeLength(5); plsf.setEdgeFilteringMode(EdgeFilteringMode.PCF4); plsf.setEnabled(false); diff --git a/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java b/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java index 88f5db1c6..fbf0e1b67 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java +++ b/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java @@ -36,7 +36,6 @@ import com.jme3.input.KeyInput; import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; import com.jme3.light.SpotLight; import com.jme3.material.Material; import com.jme3.math.*; @@ -46,12 +45,11 @@ import com.jme3.scene.Geometry; import com.jme3.scene.Spatial; import com.jme3.scene.shape.Box; import com.jme3.scene.shape.Sphere; -import com.jme3.shader.VarType; -import com.jme3.shadow.CompareMode; import com.jme3.shadow.EdgeFilteringMode; import com.jme3.shadow.SpotLightShadowFilter; import com.jme3.shadow.SpotLightShadowRenderer; import com.jme3.texture.Texture.WrapMode; +import com.jme3.util.MaterialDebugAppState; import com.jme3.util.TangentBinormalGenerator; public class TestSpotLightShadows extends SimpleApplication { @@ -78,7 +76,7 @@ public class TestSpotLightShadows extends SimpleApplication { spot.setSpotInnerAngle(5f * FastMath.DEG_TO_RAD); spot.setSpotOuterAngle(10 * FastMath.DEG_TO_RAD); spot.setPosition(new Vector3f(70.70334f, 34.013165f, 27.1017f)); - spot.setDirection(lightTarget.subtract(spot.getPosition())); + spot.setDirection(lightTarget.subtract(spot.getPosition()).normalizeLocal()); spot.setColor(ColorRGBA.White.mult(2)); rootNode.addLight(spot); @@ -103,12 +101,16 @@ public class TestSpotLightShadows extends SimpleApplication { final SpotLightShadowRenderer slsr = new SpotLightShadowRenderer(assetManager, 512); slsr.setLight(spot); slsr.setShadowIntensity(0.5f); + slsr.setShadowZExtend(100); + slsr.setShadowZFadeLength(5); slsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); viewPort.addProcessor(slsr); SpotLightShadowFilter slsf = new SpotLightShadowFilter(assetManager, 512); slsf.setLight(spot); slsf.setShadowIntensity(0.5f); + slsf.setShadowZExtend(100); + slsf.setShadowZFadeLength(5); slsf.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); slsf.setEnabled(false); @@ -130,7 +132,12 @@ public class TestSpotLightShadows extends SimpleApplication { }, "stop"); inputManager.addMapping("stop", new KeyTrigger(KeyInput.KEY_1)); - + + MaterialDebugAppState s = new MaterialDebugAppState(); + s.registerBinding("Common/MatDefs/Shadow/PostShadow15.frag", rootNode); + s.registerBinding(new KeyTrigger(KeyInput.KEY_R), rootNode); + stateManager.attach(s); + flyCam.setDragToRotate(true); } public void setupFloor() {