diff --git a/engine/src/core/com/jme3/shadow/BasicShadowRenderer.java b/engine/src/core/com/jme3/shadow/BasicShadowRenderer.java index ed0ba7ef3..50be4c91d 100644 --- a/engine/src/core/com/jme3/shadow/BasicShadowRenderer.java +++ b/engine/src/core/com/jme3/shadow/BasicShadowRenderer.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); diff --git a/engine/src/core/com/jme3/shadow/DirectionalLightShadowFilter.java b/engine/src/core/com/jme3/shadow/DirectionalLightShadowFilter.java index 460cafa0c..21b032ec1 100644 --- a/engine/src/core/com/jme3/shadow/DirectionalLightShadowFilter.java +++ b/engine/src/core/com/jme3/shadow/DirectionalLightShadowFilter.java @@ -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; /** @@ -155,6 +154,24 @@ public class DirectionalLightShadowFilter extends AbstractShadowFilter visRecvList = new ArrayList(); for (int i = 0; i < receivers.size(); i++) { // convert bounding box to light's viewproj space Geometry receiver = receivers.get(i); BoundingVolume bv = receiver.getWorldBound(); BoundingVolume recvBox = bv.transform(viewProjMatrix, null); - + if (splitBB.intersects(recvBox)) { visRecvList.add(recvBox); } } - + ArrayList visOccList = new ArrayList(); for (int i = 0; i < occluders.size(); i++) { // convert bounding box to light's viewproj space Geometry occluder = occluders.get(i); BoundingVolume bv = occluder.getWorldBound(); BoundingVolume occBox = bv.transform(viewProjMatrix, null); - + boolean intersects = splitBB.intersects(occBox); if (!intersects && occBox instanceof BoundingBox) { BoundingBox occBB = (BoundingBox) occBox; @@ -416,7 +418,7 @@ public class ShadowUtil { } } } - + BoundingBox casterBB = computeUnionBound(visOccList); BoundingBox receiverBB = computeUnionBound(visRecvList); @@ -426,18 +428,18 @@ public class ShadowUtil { casterBB.setYExtent(casterBB.getYExtent() + 2.0f); casterBB.setZExtent(casterBB.getZExtent() + 2.0f); } - + TempVars vars = TempVars.get(); - + Vector3f casterMin = casterBB.getMin(vars.vect1); Vector3f casterMax = casterBB.getMax(vars.vect2); - + Vector3f receiverMin = receiverBB.getMin(vars.vect3); Vector3f receiverMax = receiverBB.getMax(vars.vect4); - + Vector3f splitMin = splitBB.getMin(vars.vect5); Vector3f splitMax = splitBB.getMax(vars.vect6); - + splitMin.z = 0; // if (!ortho) { @@ -445,17 +447,17 @@ public class ShadowUtil { // } Matrix4f projMatrix = shadowCam.getProjectionMatrix(); - + Vector3f cropMin = vars.vect7; Vector3f cropMax = vars.vect8; // IMPORTANT: Special handling for Z values cropMin.x = max(max(casterMin.x, receiverMin.x), splitMin.x); cropMax.x = min(min(casterMax.x, receiverMax.x), splitMax.x); - + cropMin.y = max(max(casterMin.y, receiverMin.y), splitMin.y); cropMax.y = min(min(casterMax.y, receiverMax.y), splitMax.y); - + cropMin.z = min(casterMin.z, splitMin.z); cropMax.z = min(receiverMax.z, splitMax.z); @@ -463,33 +465,52 @@ public class ShadowUtil { // Create the crop matrix. float scaleX, scaleY, scaleZ; float offsetX, offsetY, offsetZ; - + 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; - - - - + + + + Matrix4f cropMatrix = vars.tempMat4; cropMatrix.set(scaleX, 0f, 0f, offsetX, 0f, scaleY, 0f, offsetY, 0f, 0f, scaleZ, offsetZ, 0f, 0f, 0f, 1f); - - + + Matrix4f result = new Matrix4f(); result.set(cropMatrix); result.multLocal(projMatrix); vars.release(); - + shadowCam.setProjectionMatrix(result); - + } /** @@ -514,7 +535,7 @@ public class ShadowUtil { } camera.setPlaneState(planeState); } - + } /** @@ -545,6 +566,6 @@ public class ShadowUtil { outputGeometryList.add(g); } } - + } } diff --git a/engine/src/test/jme3test/light/TestDirectionalLightShadow.java b/engine/src/test/jme3test/light/TestDirectionalLightShadow.java index eb6712390..6ee2b4f6a 100644 --- a/engine/src/test/jme3test/light/TestDirectionalLightShadow.java +++ b/engine/src/test/jme3test/light/TestDirectionalLightShadow.java @@ -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; @@ -135,7 +137,7 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act rootNode.attachChild(ground); l = new DirectionalLight(); - // l.setDirection(new Vector3f(0.5973172f, -0.16583486f, 0.7846725f).normalizeLocal()); + //l.setDirection(new Vector3f(0.5973172f, -0.16583486f, 0.7846725f).normalizeLocal()); l.setDirection(new Vector3f(-1, -1, -1)); rootNode.addLight(l); @@ -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)); + 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) { @@ -223,7 +233,7 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act cam.setParallelProjection(true); float aspect = (float) cam.getWidth() / cam.getHeight(); cam.setFrustum(-1000, 1000, -aspect * frustumSize, aspect * frustumSize, frustumSize, -frustumSize); - + } } @@ -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) { diff --git a/engine/src/test/jme3test/light/TestPointLightShadows.java b/engine/src/test/jme3test/light/TestPointLightShadows.java index 40dbb2838..114c3b6fb 100644 --- a/engine/src/test/jme3test/light/TestPointLightShadows.java +++ b/engine/src/test/jme3test/light/TestPointLightShadows.java @@ -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.setFlushQueues(false); - // plsr.displayDebug(); + plsr.setEdgeFilteringMode(EdgeFilteringMode.PCF4); + // plsr.setFlushQueues(false); + //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,26 +105,27 @@ 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.setLight((PointLight) scene.getLocalLightList().get(0)); - plsf.setEdgeFilteringMode(EdgeFilteringMode.Nearest); + plsf = new PointLightShadowFilter(assetManager, SHADOWMAP_SIZE); + plsf.setLight((PointLight) scene.getLocalLightList().get(0)); + plsf.setEdgeFilteringMode(EdgeFilteringMode.PCF4); plsf.setEnabled(false); FilterPostProcessor fpp = new FilterPostProcessor(assetManager); fpp.addFilter(plsf); viewPort.addProcessor(fpp); - + ShadowTestUIManager uiMan = new ShadowTestUIManager(assetManager, plsr, plsf, guiNode, inputManager, viewPort); } @Override public void simpleUpdate(float tpf) { -// lightNode.move(FastMath.cos(tpf) * 0.4f, 0, FastMath.sin(tpf) * 0.4f); + // lightNode.move(FastMath.cos(tpf) * 0.4f, 0, FastMath.sin(tpf) * 0.4f); } } \ No newline at end of file diff --git a/engine/src/test/jme3test/light/TestTransparentShadow.java b/engine/src/test/jme3test/light/TestTransparentShadow.java index a7cab8efb..caddeb583 100644 --- a/engine/src/test/jme3test/light/TestTransparentShadow.java +++ b/engine/src/test/jme3test/light/TestTransparentShadow.java @@ -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 { @@ -69,7 +71,7 @@ public class TestTransparentShadow extends SimpleApplication { Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); mat.setFloat("Shininess", 0); geom.setMaterial(mat); - + geom.rotate(-FastMath.HALF_PI, 0, 0); geom.center(); geom.setShadowMode(ShadowMode.CastAndReceive); @@ -80,7 +82,7 @@ public class TestTransparentShadow extends SimpleApplication { tree.setQueueBucket(Bucket.Transparent); tree.setShadowMode(ShadowMode.CastAndReceive); - + AmbientLight al = new AmbientLight(); al.setColor(ColorRGBA.White.mult(0.7f)); rootNode.addLight(al); @@ -90,8 +92,8 @@ public class TestTransparentShadow extends SimpleApplication { dl1.setColor(ColorRGBA.White.mult(1.5f)); rootNode.addLight(dl1); - rootNode.attachChild(tree); - + rootNode.attachChild(tree); + /** Uses Texture from jme3-test-data library! */ ParticleEmitter fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 30); Material mat_red = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); @@ -99,39 +101,51 @@ 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.setEndColor( new ColorRGBA(1f, 0f, 0f, 1f)); // red + 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); - // rootNode.attachChild(fire); + // rootNode.attachChild(fire); + + + Material mat2 = assetManager.loadMaterial("Common/Materials/RedColor.j3m"); - - Material mat2 = assetManager.loadMaterial("Common/Materials/RedColor.j3m"); - Geometry ball = new Geometry("sphere", new Sphere(16, 16, 0.5f)); ball.setMaterial(mat2); ball.setShadowMode(ShadowMode.CastAndReceive); rootNode.attachChild(ball); 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"); + + } }