Implemented stable shadows for DirectionalLightShadowRenderer

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10515 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
rem..om 12 years ago
parent 107b3f2b88
commit b5014c5fbc
  1. 5
      engine/src/core/com/jme3/shadow/BasicShadowRenderer.java
  2. 19
      engine/src/core/com/jme3/shadow/DirectionalLightShadowFilter.java
  3. 21
      engine/src/core/com/jme3/shadow/DirectionalLightShadowRenderer.java
  4. 2
      engine/src/core/com/jme3/shadow/PssmShadowRenderer.java
  5. 29
      engine/src/core/com/jme3/shadow/ShadowUtil.java
  6. 21
      engine/src/test/jme3test/light/TestDirectionalLightShadow.java
  7. 18
      engine/src/test/jme3test/light/TestPointLightShadows.java
  8. 44
      engine/src/test/jme3test/light/TestTransparentShadow.java

@ -69,6 +69,7 @@ public class BasicShadowRenderer implements SceneProcessor {
private Vector3f[] points = new Vector3f[8];
private Vector3f direction = new Vector3f();
protected Texture2D dummyTex;
private float shadowMapSize;
/**
* Creates a BasicShadowRenderer
@ -84,7 +85,7 @@ public class BasicShadowRenderer implements SceneProcessor {
//DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash)
dummyTex = new Texture2D(size, size, Format.RGBA8);
shadowFB.setColorTexture(dummyTex);
shadowMapSize = (float)size;
preshadowMat = new Material(manager, "Common/MatDefs/Shadow/PreShadow.j3md");
postshadowMat = new Material(manager, "Common/MatDefs/Shadow/BasicPostShadow.j3md");
postshadowMat.setTexture("ShadowMap", shadowMap);
@ -177,7 +178,7 @@ public class BasicShadowRenderer implements SceneProcessor {
shadowCam.updateViewProjection();
// render shadow casters to shadow map
ShadowUtil.updateShadowCamera(occluders, receivers, shadowCam, points);
ShadowUtil.updateShadowCamera(occluders, receivers, shadowCam, points, shadowMapSize);
Renderer r = renderManager.getRenderer();
renderManager.setCamera(shadowCam, false);

@ -37,7 +37,6 @@ import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import java.io.IOException;
/**
@ -156,6 +155,24 @@ public class DirectionalLightShadowFilter extends AbstractShadowFilter<Direction
return shadowRenderer.getShadowZFadeLength();
}
/**
* retruns true if stabilization is enabled
* @return
*/
public boolean isEnabledStabilization() {
return shadowRenderer.isEnabledStabilization();
}
/**
* Enables the stabilization of the shadows's edges. (default is true)
* This prevents shadows' edges to flicker when the camera moves
* However it can lead to some shadow quality loss in some particular scenes.
* @param stabilize
*/
public void setEnabledStabilization(boolean stabilize) {
shadowRenderer.setEnabledStabilization(stabilize);
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);

@ -69,6 +69,7 @@ public class DirectionalLightShadowRenderer extends AbstractShadowRenderer {
//Holding the info for fading shadows in the far distance
protected Vector2f fadeInfo;
protected float fadeLength;
private boolean stabilize = true;
/**
* Used for serialzation use
@ -174,7 +175,7 @@ public class DirectionalLightShadowRenderer extends AbstractShadowRenderer {
ShadowUtil.updateFrustumPoints(viewPort.getCamera(), splitsArray[shadowMapIndex], splitsArray[shadowMapIndex + 1], 1.0f, points);
//Updating shadow cam with curent split frustra
ShadowUtil.updateShadowCamera(sceneOccluders, sceneReceivers, shadowCam, points, shadowMapOccluders);
ShadowUtil.updateShadowCamera(sceneOccluders, sceneReceivers, shadowCam, points, shadowMapOccluders, stabilize?shadowMapSize:0);
return shadowMapOccluders;
}
@ -283,6 +284,24 @@ public class DirectionalLightShadowRenderer extends AbstractShadowRenderer {
return 0f;
}
/**
* retruns true if stabilization is enabled
* @return
*/
public boolean isEnabledStabilization() {
return stabilize;
}
/**
* Enables the stabilization of the shadows's edges. (default is true)
* This prevents shadows' edges to flicker when the camera moves
* However it can lead to some shadow quality loss in some particular scenes.
* @param stabilize
*/
public void setEnabledStabilization(boolean stabilize) {
this.stabilize = stabilize;
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);

@ -437,7 +437,7 @@ public class PssmShadowRenderer implements SceneProcessor {
ShadowUtil.updateFrustumPoints(viewCam, splitsArray[i], splitsArray[i + 1], 1.0f, points);
//Updating shadow cam with curent split frustra
ShadowUtil.updateShadowCamera(occluders, receivers, shadowCam, points, splitOccluders);
ShadowUtil.updateShadowCamera(occluders, receivers, shadowCam, points, splitOccluders, shadowMapSize);
//saving light view projection matrix for this split
lightViewProjectionsMatrices[i].set(shadowCam.getViewProjectionMatrix());

@ -33,6 +33,7 @@ package com.jme3.shadow;
import com.jme3.bounding.BoundingBox;
import com.jme3.bounding.BoundingVolume;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix4f;
import com.jme3.math.Transform;
import com.jme3.math.Vector2f;
@ -44,7 +45,6 @@ import com.jme3.util.TempVars;
import static java.lang.Math.max;
import static java.lang.Math.min;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
@ -337,8 +337,9 @@ public class ShadowUtil {
public static void updateShadowCamera(GeometryList occluders,
GeometryList receivers,
Camera shadowCam,
Vector3f[] points) {
updateShadowCamera(occluders, receivers, shadowCam, points, null);
Vector3f[] points,
float shadowMapSize) {
updateShadowCamera(occluders, receivers, shadowCam, points, null, shadowMapSize);
}
/**
@ -353,7 +354,8 @@ public class ShadowUtil {
GeometryList receivers,
Camera shadowCam,
Vector3f[] points,
GeometryList splitOccluders) {
GeometryList splitOccluders,
float shadowMapSize) {
boolean ortho = shadowCam.isParallelProjection();
@ -467,9 +469,28 @@ public class ShadowUtil {
scaleX = (2.0f) / (cropMax.x - cropMin.x);
scaleY = (2.0f) / (cropMax.y - cropMin.y);
//Shadow map stabilization approximation from shaderX 7
//from Practical Cascaded Shadow maps adapted to PSSM
//scale stabilization
float halfTextureSize = shadowMapSize * 0.5f;
if (halfTextureSize != 0) {
float scaleQuantizer = 0.1f;
scaleX = 1.0f / FastMath.ceil(1.0f / scaleX * scaleQuantizer) * scaleQuantizer;
scaleY = 1.0f / FastMath.ceil(1.0f / scaleY * scaleQuantizer) * scaleQuantizer;
}
offsetX = -0.5f * (cropMax.x + cropMin.x) * scaleX;
offsetY = -0.5f * (cropMax.y + cropMin.y) * scaleY;
//Shadow map stabilization approximation from shaderX 7
//from Practical Cascaded Shadow maps adapted to PSSM
//offset stabilization
if (halfTextureSize != 0) {
offsetX = FastMath.ceil(offsetX * halfTextureSize) / halfTextureSize;
offsetY = FastMath.ceil(offsetY * halfTextureSize) / halfTextureSize;
}
scaleZ = 1.0f / (cropMax.z - cropMin.z);
offsetZ = -cropMin.z * scaleZ;

@ -32,6 +32,7 @@
package jme3test.light;
import com.jme3.app.SimpleApplication;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
@ -59,6 +60,7 @@ import com.jme3.util.SkyFactory;
import com.jme3.util.TangentBinormalGenerator;
public class TestDirectionalLightShadow extends SimpleApplication implements ActionListener, AnalogListener {
public static final int SHADOWMAP_SIZE = 1024;
private Spatial[] obj;
private Material[] mat;
@ -161,7 +163,7 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
loadScene();
dlsr = new DirectionalLightShadowRenderer(assetManager, 1024, 3);
dlsr = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE, 3);
dlsr.setLight(l);
dlsr.setLambda(0.55f);
dlsr.setShadowIntensity(0.6f);
@ -169,7 +171,7 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
dlsr.displayDebug();
viewPort.addProcessor(dlsr);
dlsf = new DirectionalLightShadowFilter(assetManager, 1024, 3);
dlsf = new DirectionalLightShadowFilter(assetManager, SHADOWMAP_SIZE, 3);
dlsf.setLight(l);
dlsf.setLambda(0.55f);
dlsf.setShadowIntensity(0.6f);
@ -192,6 +194,7 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
inputManager.addMapping("lambdaDown", new KeyTrigger(KeyInput.KEY_J));
inputManager.addMapping("switchGroundMat", new KeyTrigger(KeyInput.KEY_M));
inputManager.addMapping("debug", new KeyTrigger(KeyInput.KEY_X));
inputManager.addMapping("stabilize", new KeyTrigger(KeyInput.KEY_B));
inputManager.addMapping("up", new KeyTrigger(KeyInput.KEY_NUMPAD8));
@ -204,14 +207,21 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
inputManager.addListener(this, "lambdaUp", "lambdaDown", "ThicknessUp", "ThicknessDown",
"switchGroundMat", "debug", "up", "down", "right", "left", "fwd", "back","pp");
"switchGroundMat", "debug", "up", "down", "right", "left", "fwd", "back", "pp","stabilize");
ShadowTestUIManager uiMan = new ShadowTestUIManager(assetManager, dlsr, dlsf, guiNode, inputManager, viewPort);
inputManager.addListener(this, "Size+", "Size-");
inputManager.addMapping("Size+", new KeyTrigger(KeyInput.KEY_W));
inputManager.addMapping("Size-", new KeyTrigger(KeyInput.KEY_S));
shadowStabilizationText = new BitmapText(guiFont, false);
shadowStabilizationText.setSize(guiFont.getCharSet().getRenderedSize() * 0.75f);
shadowStabilizationText.setText("(b:on/off) Shadow stabilization : " + dlsr.isEnabledStabilization());
shadowStabilizationText.setLocalTranslation(10, viewPort.getCamera().getHeight() - 100, 0);
guiNode.attachChild(shadowStabilizationText);
}
private BitmapText shadowStabilizationText ;
public void onAction(String name, boolean keyPressed, float tpf) {
@ -242,6 +252,11 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
dlsr.displayFrustum();
}
if (name.equals("stabilize") && keyPressed) {
dlsr.setEnabledStabilization(!dlsr.isEnabledStabilization());
dlsf.setEnabledStabilization(!dlsf.isEnabledStabilization());
shadowStabilizationText.setText("(b:on/off) Shadow stabilization : " + dlsr.isEnabledStabilization());
}
if (name.equals("switchGroundMat") && keyPressed) {
if (ground.getMaterial() == matGroundL) {

@ -46,6 +46,7 @@ import com.jme3.shadow.PointLightShadowFilter;
import com.jme3.shadow.PointLightShadowRenderer;
public class TestPointLightShadows extends SimpleApplication {
public static final int SHADOWMAP_SIZE = 512;
public static void main(String[] args) {
TestPointLightShadows app = new TestPointLightShadows();
@ -83,12 +84,12 @@ public class TestPointLightShadows extends SimpleApplication {
box.setLocalTranslation(-1f, 0.5f, -2);
plsr = new PointLightShadowRenderer(assetManager, 512);
plsr = new PointLightShadowRenderer(assetManager, SHADOWMAP_SIZE);
plsr.setLight((PointLight) scene.getLocalLightList().get(0));
plsr.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
plsr.setEdgeFilteringMode(EdgeFilteringMode.PCF4);
// plsr.setFlushQueues(false);
// plsr.displayDebug();
//plsr.displayFrustum();
plsr.displayDebug();
viewPort.addProcessor(plsr);
@ -96,7 +97,7 @@ public class TestPointLightShadows extends SimpleApplication {
// 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"));
@ -104,15 +105,16 @@ public class TestPointLightShadows extends SimpleApplication {
// rootNode.attachChild(lightMdl2);
// lightMdl2.setLocalTranslation(pl.getPosition());
// PointLightShadowRenderer plsr2 = new PointLightShadowRenderer(assetManager, 512);
// plsr2.setShadowIntensity(0.3f);
// plsr2.setLight(pl);
// plsr2.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
// plsr2.setEdgeFilteringMode(EdgeFilteringMode.PCF4);
// // plsr.displayDebug();
// viewPort.addProcessor(plsr2);
plsf = new PointLightShadowFilter(assetManager, 512);
plsf = new PointLightShadowFilter(assetManager, SHADOWMAP_SIZE);
plsf.setLight((PointLight) scene.getLocalLightList().get(0));
plsf.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
plsf.setEdgeFilteringMode(EdgeFilteringMode.PCF4);
plsf.setEnabled(false);
FilterPostProcessor fpp = new FilterPostProcessor(assetManager);

@ -29,12 +29,14 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package jme3test.light;
import com.jme3.app.SimpleApplication;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh;
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.material.Material;
@ -45,9 +47,9 @@ import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Quad;
import com.jme3.scene.shape.Sphere;
import com.jme3.shadow.PssmShadowRenderer;
import com.jme3.shadow.PssmShadowRenderer.CompareMode;
import com.jme3.shadow.PssmShadowRenderer.FilterMode;
import com.jme3.shadow.CompareMode;
import com.jme3.shadow.DirectionalLightShadowRenderer;
import com.jme3.shadow.EdgeFilteringMode;
public class TestTransparentShadow extends SimpleApplication {
@ -99,16 +101,17 @@ public class TestTransparentShadow extends SimpleApplication {
//mat_red.getAdditionalRenderState().setDepthTest(true);
//mat_red.getAdditionalRenderState().setDepthWrite(true);
fire.setMaterial(mat_red);
fire.setImagesX(2); fire.setImagesY(2); // 2x2 texture animation
fire.setImagesX(2);
fire.setImagesY(2); // 2x2 texture animation
fire.setEndColor(new ColorRGBA(1f, 0f, 0f, 1f)); // red
fire.setStartColor(new ColorRGBA(1f, 1f, 0f, 0.5f)); // yellow
fire.setInitialVelocity(new Vector3f(0, 2, 0));
fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 2, 0));
fire.setStartSize(0.6f);
fire.setEndSize(0.1f);
fire.setGravity(0, 0, 0);
fire.setLowLife(0.5f);
fire.setHighLife(1.5f);
fire.setVelocityVariation(0.3f);
fire.getParticleInfluencer().setVelocityVariation(0.3f);
fire.setLocalTranslation(1.0f, 0, 1.0f);
fire.setLocalScale(0.3f);
fire.setQueueBucket(Bucket.Translucent);
@ -125,13 +128,24 @@ public class TestTransparentShadow extends SimpleApplication {
ball.setLocalTranslation(-1.0f, 1.5f, 1.0f);
PssmShadowRenderer pssmRenderer = new PssmShadowRenderer(assetManager, 1024, 1);
pssmRenderer.setDirection(dl1.getDirection());
pssmRenderer.setLambda(0.55f);
pssmRenderer.setShadowIntensity(0.8f);
pssmRenderer.setCompareMode(CompareMode.Software);
pssmRenderer.setFilterMode(FilterMode.PCF4);
//pssmRenderer.displayDebug();
viewPort.addProcessor(pssmRenderer);
final DirectionalLightShadowRenderer dlsRenderer = new DirectionalLightShadowRenderer(assetManager, 1024, 1);
dlsRenderer.setLight(dl1);
dlsRenderer.setLambda(0.55f);
dlsRenderer.setShadowIntensity(0.8f);
dlsRenderer.setShadowCompareMode(CompareMode.Software);
dlsRenderer.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
dlsRenderer.displayDebug();
viewPort.addProcessor(dlsRenderer);
inputManager.addMapping("stabilize", new KeyTrigger(KeyInput.KEY_B));
inputManager.addListener(new ActionListener() {
@Override
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("stabilize") && isPressed) {
dlsRenderer.setEnabledStabilization(!dlsRenderer.isEnabledStabilization()) ;
}
}
}, "stabilize");
}
}

Loading…
Cancel
Save