Selaa lähdekoodia

Big refactoring to how PBR is handled.
- Introduced a new Light type : LightProbes that are lights holding Image based Lighting information that are sent to the shader. For now, only the closest LightProbe from a geometry is sent to the shader. This will be enhanced later as it's obviously not the best way to handle this.
- Added a LightProbeFactory for easy creation and rendering of LightPorbes and associated maps. The maps generation process can also be monitored through a Listener class.
- Added various utility classses for debuging purpose.
- Added a new test case for environment with multiple LightProbes.
- Adapted the previous test case to the new system.

Nehon 10 vuotta sitten
vanhempi
commit
a35b499ee7
30 muutettua tiedostoa jossa 2202 lisäystä ja 112 poistoa
  1. 15 38
      jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java
  2. 246 0
      jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java
  3. 59 0
      jme3-core/src/main/java/com/jme3/environment/generation/JobProgressAdapter.java
  4. 67 0
      jme3-core/src/main/java/com/jme3/environment/generation/JobProgressListener.java
  5. 0 1
      jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java
  6. 177 0
      jme3-core/src/main/java/com/jme3/environment/util/BoundingSphereDebug.java
  7. 214 0
      jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java
  8. 11 1
      jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java
  9. 8 1
      jme3-core/src/main/java/com/jme3/light/Light.java
  10. 251 0
      jme3-core/src/main/java/com/jme3/light/LightProbe.java
  11. 50 12
      jme3-core/src/main/java/com/jme3/material/Material.java
  12. 13 10
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag
  13. 3 1
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md
  14. 45 0
      jme3-core/src/main/resources/Common/MatDefs/Misc/reflect.j3md
  15. 22 0
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping.j3sn
  16. 8 0
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping100.frag
  17. 8 0
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping130.frag
  18. 25 0
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect.j3sn
  19. 14 0
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect100.vert
  20. 33 0
      jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib
  21. 90 0
      jme3-examples/src/main/java/jme3test/light/TestColorApp.java
  22. 96 0
      jme3-examples/src/main/java/jme3test/light/TestTangentCube.java
  23. 71 0
      jme3-examples/src/main/java/jme3test/light/pbr/ConsoleProgressReporter.java
  24. 73 48
      jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java
  25. 378 0
      jme3-examples/src/main/java/jme3test/light/pbr/TestPbrEnv.java
  26. 180 0
      jme3-examples/src/main/java/jme3test/post/TestBloomAlphaThreshold.java
  27. 13 0
      jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat.j3m
  28. 12 0
      jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat2.j3m
  29. 9 0
      jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat3.j3m
  30. 11 0
      jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat4.j3m

+ 15 - 38
jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java

@@ -35,12 +35,13 @@ import com.jme3.environment.generation.JobProgressListener;
 import com.jme3.environment.util.EnvMapUtils;
 import com.jme3.app.Application;
 import com.jme3.app.state.BaseAppState;
+import com.jme3.light.LightProbe;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.Vector3f;
 import com.jme3.renderer.Camera;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
-import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
 import com.jme3.texture.FrameBuffer;
 import com.jme3.texture.Image;
 import com.jme3.texture.Texture2D;
@@ -54,8 +55,11 @@ import java.util.concurrent.Callable;
 
 /**
  * A 360 camera that can capture a cube map of a scene, and then generate the
- * Prefiltered Environment cube Map and the Irradiance cube Map needed for PBE
+ * Prefiltered Environment cube Map and the Irradiance cube Map needed for PBR
  * indirect lighting
+ * 
+ * @see LightProbeFactory
+ * @see LightProbe
  *
  * @author Nehon
  */
@@ -105,10 +109,6 @@ public class EnvironmentCamera extends BaseAppState {
 
     private final List<SnapshotJob> jobs = new ArrayList<SnapshotJob>();
 
-    // debug to be removed
-    private Node debugPfemCm;
-    private Node debugIrrCm;
-
     /**
      * Creates an EnvironmentCamera with a size of 128
      */
@@ -154,9 +154,10 @@ public class EnvironmentCamera extends BaseAppState {
      * @param scene the scene to snapshot.
      * @param done a callback to call when the snapshot is done.
      */
-    public void snapshot(final Node scene, final JobProgressListener<TextureCubeMap> done) {
+    public void snapshot(final Spatial scene, final JobProgressListener<TextureCubeMap> done) {
         getApplication().enqueue(new Callable<Void>() {
 
+            @Override
             public Void call() throws Exception {
                 SnapshotJob job = new SnapshotJob(done, scene);
                 jobs.add(job);
@@ -195,37 +196,10 @@ public class EnvironmentCamera extends BaseAppState {
         return position;
     }
     
-    
-
-//    /**
-//     * Displays or cycles through the generated maps.
-//     */
-//    public void toggleDebug() {
-//        if (debugPfemCm == null) {
-//            debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(currentEnvProbe.getPrefilteredEnvMap(), getApplication().getAssetManager());
-//            debugPfemCm.setLocalTranslation(getApplication().getGuiViewPort().getCamera().getWidth() - 532, 20, 0);
-//        }
-//        if (debugIrrCm == null) {
-//            debugIrrCm = EnvMapUtils.getCubeMapCrossDebugView(currentEnvProbe.getIrradianceMap(), getApplication().getAssetManager());
-//            debugIrrCm.setLocalTranslation(getApplication().getGuiViewPort().getCamera().getWidth() - 532, 20, 0);
-//        }
-//
-//        if (debugIrrCm.getParent() != null) {
-//            debugIrrCm.removeFromParent();
-//            ((Node) (getApplication().getGuiViewPort().getScenes().get(0))).attachChild(debugPfemCm);
-//
-//        } else if (debugPfemCm.getParent() != null) {
-//            debugPfemCm.removeFromParent();
-//        } else {
-//            ((Node) (getApplication().getGuiViewPort().getScenes().get(0))).attachChild(debugIrrCm);
-//        }
-//
-//    }
-
     /**
-     * Sets the camera position.
+     * Sets the camera position in world space.
      *
-     * @param position
+     * @param position the position in world space
      */
     public void setPosition(Vector3f position) {
         this.position.set(position);
@@ -336,12 +310,15 @@ public class EnvironmentCamera extends BaseAppState {
         return offBuffer;
     }
 
+    /**
+     * An inner class to keep track on a snapshot job.
+     */
     private class SnapshotJob {
 
         JobProgressListener<TextureCubeMap> callback;
-        Node scene;
+        Spatial scene;
 
-        public SnapshotJob(JobProgressListener callback, Node scene) {
+        public SnapshotJob(JobProgressListener callback, Spatial scene) {
             this.callback = callback;
             this.scene = scene;
         }

+ 246 - 0
jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java

@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 2009-2015 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.environment;
+
+import com.jme3.light.LightProbe;
+import com.jme3.environment.generation.JobProgressListener;
+import com.jme3.environment.generation.PrefilteredEnvMapFaceGenerator;
+import com.jme3.environment.generation.IrradianceMapGenerator;
+import com.jme3.environment.util.EnvMapUtils;
+import com.jme3.environment.generation.JobProgressAdapter;
+import com.jme3.app.Application;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.texture.TextureCubeMap;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+/**
+ * This Factory allows to create LightProbes within a scene given an EnvironmentCamera.
+ * 
+ * Since the process can be long, you can provide a JobProgressListener that 
+ * will be notified of the ongoing generation process when calling the makeProbe method.
+ * 
+ * The process is the folowing : 
+ * 1. Create an EnvironmentCamera
+ * 2. give it a position in the scene
+ * 3. call {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)}
+ * 4. add the created LightProbe to a node with the {@link Node#addLight(com.jme3.light.Light) } method.
+ * 
+ * Optionally for step 3 call {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node, com.jme3.environment.generation.JobProgressListener) }
+ * with a {@link JobProgressListener} to be notified of the progress of the generation process.
+ * 
+ * The generation will be split in several threads for faster generation. 
+ * 
+ * This class is entirely thread safe and can be called from any thread. 
+ * 
+ * Note that in case you are using a {@link JobProgressListener} all the its 
+ * method will be called inside and app.enqueu callable.
+ * This means that it's completely safe to modify the scenegraph within the 
+ * Listener method, but also means that the even will be delayed until next update loop.
+ * 
+ * @see EnvironmentCamera
+ * @author bouquet
+ */
+public class LightProbeFactory {
+
+    /**
+     * Creates a LightProbe with the giver EnvironmentCamera in the given scene.
+     * 
+     * Note that this is an assynchronous process that will run on multiple threads.
+     * The process is thread safe.
+     * The created lightProbe will only be marked as ready when the rendering process is done.
+     * 
+     * If you want to monitor the process use {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node, com.jme3.environment.generation.JobProgressListener) }
+     * 
+     *
+     * 
+     * @see LightProbe
+     * @see EnvironmentCamera
+     * @param envCam the EnvironmentCamera
+     * @param scene the Scene
+     * @return the created LightProbe
+     */
+    public static LightProbe makeProbe(final EnvironmentCamera envCam, Spatial scene) {
+        return makeProbe(envCam, scene, null);
+    }
+
+    /**
+     * Creates a LightProbe with the giver EnvironmentCamera in the given scene.
+     * 
+     * Note that this is an assynchronous process that will run on multiple threads.
+     * The process is thread safe.
+     * The created lightProbe will only be marked as ready when the rendering process is done.
+     *      
+     * The JobProgressListener will be notified of the progress of the generation. 
+     * Note that you can also use a {@link JobProgressAdapter}. 
+     * 
+     * @see LightProbe
+     * @see EnvironmentCamera
+     * @see JobProgressListener
+     
+     * @param envCam the EnvironmentCamera
+     * @param scene the Scene
+     * @param listener the listener of the genration progress.
+     * @return the created LightProbe
+     */
+    public static LightProbe makeProbe(final EnvironmentCamera envCam, Spatial scene, final JobProgressListener<LightProbe> listener) {
+        final LightProbe probe = new LightProbe();
+        probe.setPosition(envCam.getPosition());
+        probe.setIrradianceMap(EnvMapUtils.createIrradianceMap(envCam.getSize(), envCam.getImageFormat()));
+        probe.setPrefilteredMap(EnvMapUtils.createPrefilteredEnvMap(envCam.getSize(), envCam.getImageFormat()));
+        envCam.snapshot(scene, new JobProgressAdapter<TextureCubeMap>() {
+
+            @Override
+            public void done(TextureCubeMap map) {
+                generatePbrMaps(map, probe, envCam.getApplication(), listener);
+            }
+        });
+        return probe;
+    }
+
+    /**
+     * Internally called to generate the maps.
+     * This method will spawn 7 thread (one for the IrradianceMap, and one for each face of the prefiltered env map).
+     * Those threads will be executed in a ScheduledThreadPoolExecutor that will be shutdown when the genration is done.
+     * 
+     * @param envMap the raw env map rendered by the env camera
+     * @param probe the LigthProbe to generate maps for
+     * @param app the Application
+     * @param listener a progress listener. (can be null if no progress reporting is needed)
+     */
+    private static void generatePbrMaps(TextureCubeMap envMap, final LightProbe probe, Application app, final JobProgressListener<LightProbe> listener) {
+        IrradianceMapGenerator irrMapGenerator;
+        PrefilteredEnvMapFaceGenerator[] pemGenerators = new PrefilteredEnvMapFaceGenerator[6];
+
+        final JobState jobState = new JobState(new ScheduledThreadPoolExecutor(7));
+
+        irrMapGenerator = new IrradianceMapGenerator(app, new JobListener(listener, jobState, probe, 6));
+        int size = envMap.getImage().getWidth();
+        irrMapGenerator.setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), size, EnvMapUtils.FixSeamsMethod.Wrap, probe.getIrradianceMap());
+
+        jobState.executor.execute(irrMapGenerator);
+
+        for (int i = 0; i < pemGenerators.length; i++) {
+            pemGenerators[i] = new PrefilteredEnvMapFaceGenerator(app, i, new JobListener(listener, jobState, probe, i));
+            pemGenerators[i].setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), size, EnvMapUtils.FixSeamsMethod.Wrap, probe.getPrefilteredEnvMap());
+            jobState.executor.execute(pemGenerators[i]);
+        }
+    }
+
+    /**
+     * An inner class to keep the state of a generation process
+     */
+    private static class JobState {
+
+        double progress[] = new double[7];
+        boolean done[] = new boolean[7];
+        ScheduledThreadPoolExecutor executor;
+        boolean started = false;
+
+        public JobState(ScheduledThreadPoolExecutor executor) {
+            this.executor = executor;
+        }
+
+        boolean isDone() {
+            for (boolean d : done) {
+                if (d == false) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        float getProgress() {
+            float mean = 0;
+            for (double progres : progress) {
+                mean += progres;
+            }
+            return mean / 7f;
+        }
+    }
+
+    /**
+     * An inner JobProgressListener to controll the genration process and properly clean up when it's done
+     */
+    private static class JobListener extends JobProgressAdapter<Integer> {
+
+        JobProgressListener<LightProbe> globalListener;
+        JobState jobState;
+        LightProbe probe;
+
+        int index;
+
+        public JobListener(JobProgressListener<LightProbe> globalListener, JobState jobState, LightProbe probe, int index) {
+            this.globalListener = globalListener;
+            this.jobState = jobState;
+            this.probe = probe;
+            this.index = index;
+        }
+
+        @Override
+        public void start() {
+            if (globalListener != null && !jobState.started) {
+                jobState.started = true;
+                globalListener.start();
+            }
+        }
+
+        @Override
+        public void progress(double value) {
+            jobState.progress[index] = value;
+            if (globalListener != null) {
+                globalListener.progress(jobState.getProgress());
+            }
+        }
+
+        @Override
+        public void done(Integer result) {
+            if (globalListener != null) {
+                if (result < 6) {
+                    globalListener.step("Prefiltered env map face " + result + " generated");
+                } else {
+                    globalListener.step("Irradiance map generated");
+
+                }
+            }
+
+            jobState.done[index] = true;
+            if (jobState.isDone()) {
+                probe.setReady(true);
+                if (globalListener != null) {
+                    globalListener.done(probe);
+                }
+                jobState.executor.shutdownNow();
+            }
+        }
+    }
+}

+ 59 - 0
jme3-core/src/main/java/com/jme3/environment/generation/JobProgressAdapter.java

@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2009-2015 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.environment.generation;
+
+/**
+ * Abstract Adapter class that implements optional methods of JobProgressListener.
+ * Extends this class instead of implementing a JobProgressListener if you need 
+ * only a subset of method implemented.
+ * 
+ * @author nehon
+ * @param <T>
+ */
+public abstract class JobProgressAdapter<T> implements JobProgressListener<T>{
+
+    @Override
+    public void progress(double value) {        
+    }
+
+    @Override
+    public void start() {
+    }
+
+    @Override
+    public void step(String message) {
+    }
+
+    @Override
+    public abstract void done(T result);
+    
+}

+ 67 - 0
jme3-core/src/main/java/com/jme3/environment/generation/JobProgressListener.java

@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2009-2015 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.environment.generation;
+
+/**
+ * An interface listener that will be notified of the progress of an asynchronous
+ * generation job.
+ *  
+ *
+ * @author nehon
+ * @param <T> The type of object generated.
+ */
+public interface JobProgressListener<T> {
+    
+    /**
+     * Called when the process starts.
+     */
+    public void start();
+    
+    /**
+     * Can be called when a step of the process has been completed with a relevant message.
+     * @param message the message stating of the paricular step completion.
+     */
+    public void step(String message);
+    
+    /**
+     * Called when the process has made some progress.
+     * @param value a value from 0 to 1 representing the percentage of completion of the process.
+     */
+    public void progress(double value);
+    
+    /**
+     * Called when the process is done.
+     * @param result the object generated by the process.
+     */
+    public void done(T result);
+    
+}

+ 0 - 1
jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java

@@ -46,7 +46,6 @@ import static com.jme3.environment.util.EnvMapUtils.getHammersleyPoint;
 import static com.jme3.environment.util.EnvMapUtils.getRoughnessFromMip;
 import static com.jme3.environment.util.EnvMapUtils.getSampleFromMip;
 import static com.jme3.environment.util.EnvMapUtils.getVectorFromCubemapFaceTexCoord;
-import com.jme3.math.FastMath;
 import java.util.concurrent.Callable;
 import java.util.logging.Level;
 import java.util.logging.Logger;

+ 177 - 0
jme3-core/src/main/java/com/jme3/environment/util/BoundingSphereDebug.java

@@ -0,0 +1,177 @@
+ /*
+ * Copyright (c) 2009-2015 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.environment.util;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * 
+ * A debuging shape for a BoundingSphere 
+ * Consists of 3 axis aligned circles.
+ * 
+ * @author nehon
+ */
+public class BoundingSphereDebug extends Mesh {
+
+    protected int vertCount;
+    protected int triCount;
+    protected int radialSamples = 32;
+    protected boolean useEvenSlices;
+    protected boolean interior;
+    /**
+     * the distance from the center point each point falls on
+     */
+    public float radius;
+
+    public float getRadius() {
+        return radius;
+    }
+
+    public BoundingSphereDebug() {
+        setGeometryData();
+        setIndexData();
+    }
+
+    /**
+     * builds the vertices based on the radius
+     */
+    private void setGeometryData() {
+        setMode(Mode.Lines);
+
+        FloatBuffer posBuf = BufferUtils.createVector3Buffer((radialSamples + 1) * 3);
+        FloatBuffer colBuf = BufferUtils.createVector3Buffer((radialSamples + 1) * 4);
+
+        setBuffer(Type.Position, 3, posBuf);
+        setBuffer(Type.Color, 4, colBuf);
+
+        // generate geometry
+        float fInvRS = 1.0f / radialSamples;
+
+        // Generate points on the unit circle to be used in computing the mesh
+        // points on a sphere slice.
+        float[] afSin = new float[(radialSamples + 1)];
+        float[] afCos = new float[(radialSamples + 1)];
+        for (int iR = 0; iR < radialSamples; iR++) {
+            float fAngle = FastMath.TWO_PI * fInvRS * iR;
+            afCos[iR] = FastMath.cos(fAngle);
+            afSin[iR] = FastMath.sin(fAngle);
+        }
+        afSin[radialSamples] = afSin[0];
+        afCos[radialSamples] = afCos[0];
+
+        for (int iR = 0; iR <= radialSamples; iR++) {
+            posBuf.put(afCos[iR])
+                    .put(afSin[iR])
+                    .put(0);
+            colBuf.put(ColorRGBA.Blue.r)
+                    .put(ColorRGBA.Blue.g)
+                    .put(ColorRGBA.Blue.b)
+                    .put(ColorRGBA.Blue.a);
+
+        }
+        for (int iR = 0; iR <= radialSamples; iR++) {
+            posBuf.put(afCos[iR])
+                    .put(0)
+                    .put(afSin[iR]);
+            colBuf.put(ColorRGBA.Green.r)
+                    .put(ColorRGBA.Green.g)
+                    .put(ColorRGBA.Green.b)
+                    .put(ColorRGBA.Green.a);
+        }
+        for (int iR = 0; iR <= radialSamples; iR++) {
+            posBuf.put(0)
+                    .put(afCos[iR])
+                    .put(afSin[iR]);
+            colBuf.put(ColorRGBA.Yellow.r)
+                    .put(ColorRGBA.Yellow.g)
+                    .put(ColorRGBA.Yellow.b)
+                    .put(ColorRGBA.Yellow.a);
+        }
+
+        updateBound();
+        setStatic();
+    }
+
+    /**
+     * sets the indices for rendering the sphere.
+     */
+    private void setIndexData() {
+
+        // allocate connectivity
+        int nbSegments = (radialSamples) * 3;
+
+        ShortBuffer idxBuf = BufferUtils.createShortBuffer(2 * nbSegments);
+        setBuffer(Type.Index, 2, idxBuf);
+
+        int idx = 0;
+        int segDone = 0;
+        while (segDone < nbSegments) {
+            idxBuf.put((short) idx);
+            idxBuf.put((short) (idx + 1));
+            idx++;
+            segDone++;
+            if (segDone == radialSamples || segDone == radialSamples * 2) {
+                idx++;
+            }
+
+        }
+
+    }
+
+    
+    /**
+     * Convenience factory method that creates a debuging bounding sphere geometry
+     * @param assetManager the assetManager
+     * @return the bounding sphere debug geometry.
+     */
+    public static Geometry createDebugSphere(AssetManager assetManager) {
+        BoundingSphereDebug b = new BoundingSphereDebug();
+        Geometry geom = new Geometry("BoundingDebug", b);
+
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setBoolean("VertexColor", true);
+        mat.getAdditionalRenderState().setWireframe(true);
+        
+        geom.setMaterial(mat);
+        return geom;
+
+    }
+}

+ 214 - 0
jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java

@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 2009-2015 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.environment.util;
+
+import com.jme3.app.Application;
+import com.jme3.app.state.BaseAppState;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.material.Material;
+import com.jme3.light.LightProbe;
+import com.jme3.light.Light;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Sphere;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A debug state that will display LIght gizmos on screen.
+ * Still a wip and for now it only displays light probes.
+ * 
+ * @author nehon
+ */
+public class LightsDebugState extends BaseAppState {
+
+    private Node debugNode;
+    private final Map<LightProbe, Node> probeMapping = new HashMap<LightProbe, Node>();
+    private final List<LightProbe> garbage = new ArrayList<LightProbe>();
+    private Geometry debugGeom;
+    private Geometry debugBounds;
+    private Material debugMaterial;
+    private DebugMode debugMode = DebugMode.PrefilteredEnvMap;
+    private float probeScale = 1.0f;
+    private Spatial scene = null;
+    private final List<LightProbe> probes = new ArrayList<LightProbe>();
+
+    /**
+     * Debug mode for light probes
+     */
+    public enum DebugMode {
+
+        /**
+         * Displays the prefiltered env maps on the debug sphere
+         */
+        PrefilteredEnvMap,
+        /**
+         * displays the Irradiance map on the debug sphere
+         */
+        IrradianceMap
+    }
+
+    @Override
+    protected void initialize(Application app) {
+        debugNode = new Node("Environment debug Node");
+        Sphere s = new Sphere(16, 16, 1);
+        debugGeom = new Geometry("debugEnvProbe", s);
+        debugMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/reflect.j3md");
+        debugGeom.setMaterial(debugMaterial);
+        debugBounds = BoundingSphereDebug.createDebugSphere(app.getAssetManager());
+        if (scene == null) {
+            scene = app.getViewPort().getScenes().get(0);
+        }
+    }
+
+    @Override
+    public void update(float tpf) {
+        for (Light light : scene.getWorldLightList()) {
+            switch (light.getType()) {
+
+                case Probe:
+                    LightProbe probe = (LightProbe) light;
+                    probes.add(probe);
+                    Node n = probeMapping.get(probe);
+                    if (n == null) {
+                        n = new Node("DebugProbe");
+                        n.attachChild(debugGeom.clone(true));
+                        n.attachChild(debugBounds.clone(false));
+                        debugNode.attachChild(n);
+                        probeMapping.put(probe, n);
+                    }
+                    Geometry probeGeom = ((Geometry) n.getChild(0));
+                    Material m = probeGeom.getMaterial();
+                    probeGeom.setLocalScale(probeScale);
+                    if (probe.isReady()) {
+                        if (debugMode == DebugMode.IrradianceMap) {
+                            m.setTexture("CubeMap", probe.getIrradianceMap());
+                        } else {
+                            m.setTexture("CubeMap", probe.getPrefilteredEnvMap());
+                        }
+                    }
+                    n.setLocalTranslation(probe.getPosition());
+                    n.getChild(1).setLocalScale(((BoundingSphere) probe.getBounds()).getRadius());
+                    break;
+                default:
+                    break;
+            }
+        }
+        debugNode.updateLogicalState(tpf);
+        debugNode.updateGeometricState();
+        cleanProbes();
+
+    }
+
+    /**
+     * Set the scenes for wich to render light gizmos.
+     * @param scene 
+     */
+    public void setScene(Spatial scene) {
+        this.scene = scene;
+    }
+
+    private void cleanProbes() {
+        if (probes.size() != probeMapping.size()) {
+            for (LightProbe probe : probeMapping.keySet()) {
+                if (!probes.contains(probe)) {
+                    garbage.add(probe);
+                }
+            }
+            for (LightProbe probe : garbage) {
+                probeMapping.remove(probe);
+            }
+            garbage.clear();
+            probes.clear();
+        }
+    }
+
+    @Override
+    public void render(RenderManager rm) {
+        rm.renderScene(debugNode, getApplication().getViewPort());
+    }
+
+    /**
+     * 
+     * @see DebugMode
+     * @return the debug mode
+     */
+    public DebugMode getDebugMode() {
+        return debugMode;
+    }
+
+    /**
+     * sets the debug mode
+     * @see DebugMode
+     * @param debugMode the debug mode
+     */
+    public void setDebugMode(DebugMode debugMode) {
+        this.debugMode = debugMode;
+
+    }
+
+    /**
+     * returns the scale of the probe's debug sphere
+     * @return 
+     */
+    public float getProbeScale() {
+        return probeScale;
+    }
+
+    /**
+     * sets the scale of the probe's debug sphere
+     * @param probeScale 
+     */
+    public void setProbeScale(float probeScale) {
+        this.probeScale = probeScale;
+    }
+
+    @Override
+    protected void cleanup(Application app) {
+
+    }
+
+    @Override
+    protected void onEnable() {
+
+    }
+
+    @Override
+    protected void onDisable() {
+
+    }
+
+}

+ 11 - 1
jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java

@@ -55,6 +55,7 @@ public final class DefaultLightFilter implements LightFilter {
         TempVars vars = TempVars.get();
         try {
             LightList worldLights = geometry.getWorldLightList();
+            boolean probeAdded = false;
             for (int i = 0; i < worldLights.size(); i++) {
                 Light light = worldLights.get(i);
 
@@ -80,8 +81,17 @@ public final class DefaultLightFilter implements LightFilter {
                         throw new UnsupportedOperationException("Only AABB supported for now");
                     }
                 }
+                
+                if (light.getType() == Light.Type.Probe) {
+                    if (!probeAdded && ((LightProbe)light).isReady()) {
+                        //only adding the first probe (the closest to the geom as lights are sorted by the distance to the geom
+                        probeAdded = true;
+                        filteredLightList.add(light);
+                    }
 
-                filteredLightList.add(light);
+                } else {
+                    filteredLightList.add(light);
+                }
             }
         } finally {
             vars.release();

+ 8 - 1
jme3-core/src/main/java/com/jme3/light/Light.java

@@ -77,7 +77,14 @@ public abstract class Light implements Savable, Cloneable {
          * 
          * @see AmbientLight
          */
-        Ambient(3);
+        Ambient(3),
+        
+        /**
+         * Light probe
+         * @see LightProbe
+         */
+        Probe(4);
+                
 
         private int typeId;
 

+ 251 - 0
jme3-core/src/main/java/com/jme3/light/LightProbe.java

@@ -0,0 +1,251 @@
+/*
+ * Copyright (c) 2009-2015 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.light;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.environment.EnvironmentCamera;
+import com.jme3.environment.LightProbeFactory;
+import com.jme3.environment.util.EnvMapUtils;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.texture.TextureCubeMap;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+
+/**
+ * A LightProbe is not exactly a light. It holds environment map information used for Image Based Lighting.
+ * This is used for indirect lighting in the Physically Based Rendering pipeline.
+ * 
+ * A light probe has a position in world space. This is the position from where the Environment Map are rendered.
+ * There are two environment maps held by the LightProbe :
+ * - The irradiance map (used for indirect diffuse lighting in the PBR pipeline).
+ * - The prefiltered environment map (used for indirect specular lighting and reflection in the PBE pipeline).
+ * Note that when instanciating the LightProbe, both those maps are null. 
+ * To render them see {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)}
+ * and {@link EnvironmentCamera}.
+ * 
+ * The light probe has an area of effect that is a bounding volume centered on its position. (for now only Bounding spheres are supported).
+ * 
+ * A LightProbe will only be taken into account when it's marked as ready. 
+ * A light probe is ready when it has valid environment map data set.
+ * Note that you should never call setReady yourself.
+ *
+ * @see LightProbeFactory
+ * @see EnvironmentCamera
+ * @author nehon
+ */
+public class LightProbe extends Light implements Savable {
+
+    private TextureCubeMap irradianceMap;
+    private TextureCubeMap prefilteredEnvMap;
+    private BoundingVolume bounds = new BoundingSphere(1.0f, Vector3f.ZERO);
+    private boolean ready = false;
+    private Vector3f position = new Vector3f();
+    private Node debugNode;
+
+    /**
+     * Empty constructor used for serialization. 
+     * You should never call it, use {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)} instead
+     */
+    public LightProbe() {        
+    }
+
+    /**
+     * returns the irradiance map texture of this Light probe.
+     * Note that this Texture may not have image data yet if the LightProbe is not ready
+     * @return the irradiance map 
+     */
+    public TextureCubeMap getIrradianceMap() {
+        return irradianceMap;
+    }
+
+    /**
+     * Sets the irradiance map
+     * @param irradianceMap the irradiance map
+     */
+    public void setIrradianceMap(TextureCubeMap irradianceMap) {
+        this.irradianceMap = irradianceMap;
+    }
+
+    /**
+     * returns the prefiltered environment map texture of this light probe
+     * Note that this Texture may not have image data yet if the LightProbe is not ready
+     * @return the prefiltered environment map
+     */
+    public TextureCubeMap getPrefilteredEnvMap() {
+        return prefilteredEnvMap;
+    }
+
+    /**
+     * Sets the prefiltered environment map 
+     * @param prefileteredEnvMap the prefiltered environment map 
+     */
+    public void setPrefilteredMap(TextureCubeMap prefileteredEnvMap) {
+        this.prefilteredEnvMap = prefileteredEnvMap;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(irradianceMap, "irradianceMap", null);
+        oc.write(prefilteredEnvMap, "prefilteredEnvMap", null);
+        oc.write(position, "position", null);
+        oc.write(bounds, "bounds", bounds);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        irradianceMap = (TextureCubeMap) ic.readSavable("irradianceMap", null);
+        prefilteredEnvMap = (TextureCubeMap) ic.readSavable("prefilteredEnvMap", null);
+        position = (Vector3f) ic.readSavable("position", this);
+        bounds = (BoundingVolume) ic.readSavable("bounds", bounds);
+    }
+
+    /**
+     * returns the bounding volume of this LightProbe
+     * @return a bounding volume.
+     */
+    public BoundingVolume getBounds() {
+        return bounds;
+    }
+    
+    /**
+     * Sets the bounds of this LightProbe
+     * Note that for now only BoundingSphere is supported and this method will 
+     * throw an UnsupportedOperationException with any other BoundingVolume type
+     * @param bounds the bounds of the LightProbe
+     */
+    public void setBounds(BoundingVolume bounds) {
+        if( bounds.getType()!= BoundingVolume.Type.Sphere){
+            throw new UnsupportedOperationException("For not only BoundingSphere are suported for LightProbe");
+        }
+        this.bounds = bounds;
+    }
+
+    /**
+     * return true if the LightProbe is ready, meaning the Environment maps have
+     * been loaded or rnedered and are ready to be used by a material
+     * @return the LightProbe ready state
+     */
+    public boolean isReady() {
+        return ready;
+    }
+
+    /**
+     * Don't call this method directly.
+     * It's meant to be called by additional systems that will load or render
+     * the Environment maps of the LightProbe
+     * @param ready the ready state of the LightProbe.
+     */
+    public void setReady(boolean ready) {
+        this.ready = ready;
+    }
+
+    /**
+     * For debuging porpose only
+     * Will return a Node meant to be added to a GUI presenting the 2 cube maps in a cross pattern with all the mip maps.
+     * 
+     * @param manager the asset manager
+     * @return a debug node
+     */
+    public Node getDebugGui(AssetManager manager) {
+        if (!ready) {
+            throw new UnsupportedOperationException("This EnvProbeis not ready yet, try to test isReady()");
+        }
+        if (debugNode == null) {
+            debugNode = new Node("debug gui probe");
+            Node debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(getPrefilteredEnvMap(), manager);
+            Node debugIrrCm = EnvMapUtils.getCubeMapCrossDebugView(getIrradianceMap(), manager);
+
+            debugNode.attachChild(debugIrrCm);
+            debugNode.attachChild(debugPfemCm);
+            debugPfemCm.setLocalTranslation(520, 0, 0);
+        }
+
+        return debugNode;
+    }
+
+    /**
+     * Returns the position of the LightProbe in world space
+     * @return the wolrd space position
+     */
+    public Vector3f getPosition() {
+        return position;
+    }
+
+    /**
+     * Sets the position of the LightProbe in world space
+     * @param position the wolrd space position
+     */
+    public void setPosition(Vector3f position) {
+        this.position.set(position);
+    }
+
+    @Override
+    public boolean intersectsBox(BoundingBox box, TempVars vars) {
+        return getBounds().intersectsBoundingBox(box);
+    }
+
+    @Override
+    public boolean intersectsFrustum(Camera camera, TempVars vars) {
+        return camera.contains(bounds) != Camera.FrustumIntersect.Outside;
+    }
+
+    @Override
+    protected void computeLastDistance(Spatial owner) {
+        if (owner.getWorldBound() != null) {
+            BoundingVolume bv = owner.getWorldBound();
+            lastDistance = bv.distanceSquaredTo(position);
+        } else {
+            lastDistance = owner.getWorldTranslation().distanceSquared(position);
+        }
+    }
+
+    @Override
+    public Type getType() {
+        return Type.Probe;
+    }
+
+}

+ 50 - 12
jme3-core/src/main/java/com/jme3/material/Material.java

@@ -34,6 +34,7 @@ package com.jme3.material;
 import com.jme3.asset.AssetKey;
 import com.jme3.asset.AssetManager;
 import com.jme3.asset.CloneableSmartAsset;
+import com.jme3.bounding.BoundingSphere;
 import com.jme3.export.*;
 import com.jme3.light.*;
 import com.jme3.material.RenderState.BlendMode;
@@ -104,6 +105,10 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
     private int sortingId = -1;
     private transient ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
 
+    //Env textures units
+    int irrUnit = -1;
+    int pemUnit = -1;
+    
     public Material(MaterialDef def) {
         if (def == null) {
             throw new NullPointerException("Material definition cannot be null");
@@ -505,22 +510,26 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         paramValues.remove(name);
         if (matParam instanceof MatParamTexture) {
             int texUnit = ((MatParamTexture) matParam).getUnit();
-            nextTexUnit--;
-            for (MatParam param : paramValues.values()) {
-                if (param instanceof MatParamTexture) {
-                    MatParamTexture texParam = (MatParamTexture) param;
-                    if (texParam.getUnit() > texUnit) {
-                        texParam.setUnit(texParam.getUnit() - 1);
-                    }
-                }
-            }
-            sortingId = -1;
+            removeTexUnit(texUnit);
         }
         if (technique != null) {
             technique.notifyParamChanged(name, null, null);
         }
     }
 
+    protected void removeTexUnit(int texUnit) {
+        nextTexUnit--;
+        for (MatParam param : paramValues.values()) {
+            if (param instanceof MatParamTexture) {
+                MatParamTexture texParam = (MatParamTexture) param;
+                if (texParam.getUnit() > texUnit) {
+                    texParam.setUnit(texParam.getUnit() - 1);
+                }
+            }
+        }
+        sortingId = -1;
+    }
+
     /**
      * Set a texture parameter.
      *
@@ -750,8 +759,11 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         Uniform lightData = shader.getUniform("g_LightData");
         lightData.setVector4Length(numLights * 3);//8 lights * max 3
         Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
-
-
+        Uniform lightProbeData = shader.getUniform("g_LightProbeData");
+        Uniform lightProbeIrrMap = shader.getUniform("g_IrradianceMap");
+        Uniform lightProbePemMap = shader.getUniform("g_PrefEnvMap");
+        
+        
         if (startIndex != 0) {
             // apply additive blending for 2nd and future passes
             rm.getRenderer().applyRenderState(additiveLight);
@@ -825,6 +837,32 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
                         lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex);
                         lightDataIndex++;
                         break;
+                    case Probe:
+                        
+                        //There should be a better way to handle these texture units
+                        //for now they are removed and reassign on every frame which is a waste.                        
+                        if (irrUnit != -1) {
+                            lightProbeIrrMap.clearValue();
+                            lightProbePemMap.clearValue();
+                            removeTexUnit(irrUnit);
+                            removeTexUnit(pemUnit);
+                            irrUnit = -1;
+                            pemUnit = -1;
+                        }
+                        endIndex++;
+                        LightProbe probe = (LightProbe)l;
+                        BoundingSphere s = (BoundingSphere)probe.getBounds();
+                        tmpVec.set(probe.getPosition().x, probe.getPosition().y, probe.getPosition().z, 1f/s.getRadius());
+                        lightProbeData.setValue(VarType.Vector4, tmpVec);
+                        if( irrUnit == -1 ){
+                            irrUnit = nextTexUnit++;
+                            pemUnit = nextTexUnit++;
+                        }
+                        rm.getRenderer().setTexture(irrUnit, probe.getIrradianceMap());
+                        lightProbeIrrMap.setValue(VarType.Int, irrUnit);
+                        rm.getRenderer().setTexture(pemUnit, probe.getPrefilteredEnvMap());
+                        lightProbePemMap.setValue(VarType.Int, pemUnit);
+                        break;
                     default:
                         throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
                 }

+ 13 - 10
jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag

@@ -19,11 +19,12 @@ uniform float m_Metallic;
 varying vec3 wPosition;    
 
 
-#ifdef INDIRECT_LIGHTING
-  uniform sampler2D m_IntegrateBRDF;
-  uniform samplerCube m_PrefEnvMap;
-  uniform samplerCube m_IrradianceMap;
-#endif
+//#ifdef INDIRECT_LIGHTING
+//  uniform sampler2D m_IntegrateBRDF;
+  uniform samplerCube g_PrefEnvMap;
+  uniform samplerCube g_IrradianceMap;
+  uniform vec4 g_ProbeData;
+//#endif
 
 #ifdef BASECOLORMAP
   uniform sampler2D m_BaseColorMap;
@@ -201,8 +202,10 @@ void main(){
         gl_FragColor.rgb += directLighting * fallOff;
     }
 
-    #ifdef INDIRECT_LIGHTING
-        vec3 rv = reflect(-viewDir.xyz, normal.xyz);     
+ //   #ifdef INDIRECT_LIGHTING
+        vec3 rv = reflect(-viewDir.xyz, normal.xyz);
+        //prallax fix for spherical bounds.
+        rv = g_ProbeData.w * (wPosition - g_ProbeData.xyz) +rv;
        
          //horizon fade from http://marmosetco.tumblr.com/post/81245981087
         float horiz = dot(rv, wNormal.xyz);
@@ -212,15 +215,15 @@ void main(){
         
         vec3 indirectDiffuse = vec3(0.0);
         vec3 indirectSpecular = vec3(0.0);    
-        indirectDiffuse = textureCube(m_IrradianceMap, rv.xyz).rgb * albedo.rgb;
+        indirectDiffuse = textureCube(g_IrradianceMap, rv.xyz).rgb * albedo.rgb;
         
-        indirectSpecular = ApproximateSpecularIBL(m_PrefEnvMap,m_IntegrateBRDF, specularColor.rgb, Roughness, ndotv, rv.xyz);
+        indirectSpecular = ApproximateSpecularIBLPolynomial(g_PrefEnvMap, specularColor.rgb, Roughness, ndotv, rv.xyz);
         indirectSpecular *= vec3(horiz);
 
         vec3 indirectLighting =  indirectDiffuse + indirectSpecular;
         
         gl_FragColor.rgb = gl_FragColor.rgb + indirectLighting ;        
-    #endif
+  //  #endif
  
     #if defined(EMISSIVE) || defined (EMISSIVEMAP)
         #ifdef EMISSIVEMAP

+ 3 - 1
jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md

@@ -37,6 +37,8 @@ MaterialDef PBR Lighting {
         Texture2D SpecularMap
         Texture2D GlossMap
 
+        Vector4 ProbeData
+
         // Prefiltered Env Map for indirect specular lighting
         TextureCubeMap PrefEnvMap -LINEAR
         
@@ -132,7 +134,7 @@ MaterialDef PBR Lighting {
             DISCARD_ALPHA : AlphaDiscardThreshold                        
             NUM_BONES : NumberOfBones                        
             INSTANCING : UseInstancing
-            INDIRECT_LIGHTING : IntegrateBRDF
+            //INDIRECT_LIGHTING : IntegrateBRDF
             VERTEX_COLOR : UseVertexColor
         }
     }

+ 45 - 0
jme3-core/src/main/resources/Common/MatDefs/Misc/reflect.j3md

@@ -0,0 +1,45 @@
+MaterialDef Simple {
+    MaterialParameters {
+        TextureCubeMap CubeMap
+    }
+    Technique {
+        WorldParameters {
+            WorldViewProjectionMatrix
+            WorldMatrix
+            CameraPosition
+        }
+        VertexShaderNodes {
+            ShaderNode Reflect {
+                Definition : Reflect : Common/MatDefs/ShaderNodes/Environment/reflect.j3sn
+                InputMappings {
+                    normal = Attr.inNormal
+                    position = Global.position.xyz
+                    worldMatrix = WorldParam.WorldMatrix
+                    camPosition = WorldParam.CameraPosition
+                }
+            }
+            ShaderNode CommonVert {
+                Definition : CommonVert : Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn
+                InputMappings {
+                    worldViewProjectionMatrix = WorldParam.WorldViewProjectionMatrix
+                    modelPosition = Global.position.xyz
+                }
+                OutputMappings {
+                    Global.position = projPosition
+                }
+            }
+        }
+        FragmentShaderNodes {
+            ShaderNode EnvMapping {
+                Definition : EnvMapping : Common/MatDefs/ShaderNodes/Environment/envMapping.j3sn
+                InputMappings {
+                    refVec = Reflect.refVec
+                    cubeMap = MatParam.CubeMap
+                }
+                OutputMappings {
+                    Global.color = color
+                }
+            }
+        }
+    }
+}

+ 22 - 0
jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping.j3sn

@@ -0,0 +1,22 @@
+ShaderNodeDefinitions{ 
+    ShaderNodeDefinition EnvMapping {      
+        Type: Fragment
+
+        Shader GLSL100: Common/MatDefs/ShaderNodes/Environment/envMapping100.frag
+        Shader GLSL130: Common/MatDefs/ShaderNodes/Environment/envMapping130.frag
+        
+        Documentation{
+            fetches a texel in a cube map            
+            @input vec3 refVec the reflection vector
+            @input samplerCube cubeMap the cube map
+            @output vec4 color the output color
+        }
+        Input {
+            vec3 refVec
+            samplerCube cubeMap
+        }
+        Output {
+             vec4 color
+        }
+    }
+}

+ 8 - 0
jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping100.frag

@@ -0,0 +1,8 @@
+
+void main(){
+        //@input vec3 refVec the reflection vector
+    //@input samplerCube cubeMap the cube map
+    //@output vec4 color the output color
+
+    color = textureCube(cubeMap, refVec, 0.0);
+}

+ 8 - 0
jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping130.frag

@@ -0,0 +1,8 @@
+
+void main(){
+        //@input vec3 refVec the reflection vector
+    //@input samplerCube cubeMap the cube map
+    //@output vec4 color the output color
+
+        color = texture(cubeMap, refVec, 0.0);
+}

+ 25 - 0
jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect.j3sn

@@ -0,0 +1,25 @@
+ShaderNodeDefinitions{ 
+    ShaderNodeDefinition Reflect {      
+        Type: Vertex
+
+        Shader GLSL100: Common/MatDefs/ShaderNodes/Environment/reflect100.vert
+        
+        Documentation{
+            Computes the relfection vector necessary to do some environment mapping            
+            @input vec3 position position in model space
+            @input vec3 normal the normal of the vertex
+            @input vec3 camPosition camera position in world space
+            @input mat4 worldMatrix the world matrix
+            @output vec3 refVec the reflection vector
+        }
+        Input {
+            vec3 position
+            vec3 normal
+            vec3 camPosition
+            mat4 worldMatrix
+        }
+        Output {
+             vec3 refVec
+        }
+    }
+}

+ 14 - 0
jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect100.vert

@@ -0,0 +1,14 @@
+
+void main(){
+        //@input vec3 position position in model space
+    //@input vec3 normal the normal of the vertex
+    //@input vec3 camPosition camera position in world space
+    //@input mat4 worldMatrix the world view matrix
+    //@output vec3 refVec the reflection vector
+
+    vec3 worldPos = (worldMatrix * vec4(position, 1.0)).xyz;
+    vec3 N = normalize((worldMatrix * vec4(normal, 0.0)).xyz);
+    vec3 I = normalize( camPosition - worldPos  ).xyz;
+    refVec.xyz = reflect(-I, N);
+
+}

+ 33 - 0
jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib

@@ -106,6 +106,31 @@ void PBR_ComputeDirectLight(vec3 normal, vec3 lightDir, vec3 viewDir,
     outSpecular = vec3(specular) * lightColor;
 }
 
+vec3 EnvDFGPolynomial( vec3 specularColor, float roughness, float ndotv ){
+    float x = 1.0 - roughness;
+    float y = ndotv;
+ 
+    float b1 = -0.1688;
+    float b2 = 1.895;
+    float b3 = 0.9903;
+    float b4 = -4.853;
+    float b5 = 8.404;
+    float b6 = -5.069;
+    float bias = clamp( min( b1 * x + b2 * x * x, b3 + b4 * y + b5 * y * y + b6 * y * y * y ), 0.0, 1.0 );
+ 
+    float d0 = 0.6045;
+    float d1 = 1.699;
+    float d2 = -0.5228;
+    float d3 = -3.603;
+    float d4 = 1.404;
+    float d5 = 0.1939;
+    float d6 = 2.661;
+    float delta = clamp(( d0 + d1 * x + d2 * y + d3 * x * x + d4 * x * y + d5 * y * y + d6 * x * x * x ), 0.0, 1.0);
+    float scale = delta - bias;
+ 
+    bias *= clamp( 50.0 * specularColor.y, 0.0, 1.0 );
+    return specularColor * scale + bias;
+}
 
 vec3 ApproximateSpecularIBL(samplerCube envMap,sampler2D integrateBRDF, vec3 SpecularColor , float Roughness, float ndotv, vec3 refVec){
     //TODO magic values should be replaced by defines.
@@ -115,6 +140,14 @@ vec3 ApproximateSpecularIBL(samplerCube envMap,sampler2D integrateBRDF, vec3 Spe
     return PrefilteredColor * ( SpecularColor * EnvBRDF.x+ EnvBRDF.y );    
 }
 
+vec3 ApproximateSpecularIBLPolynomial(samplerCube envMap, vec3 SpecularColor , float Roughness, float ndotv, vec3 refVec){
+    //TODO magic values should be replaced by defines.
+    float Lod = log2(Roughness) * 1.2 + 6.0 - 1.0;
+    vec3 PrefilteredColor =  textureCube(envMap, refVec.xyz,Lod).rgb;    
+    return PrefilteredColor * EnvDFGPolynomial(SpecularColor, Roughness, ndotv);
+}
+
+
 
 
 

+ 90 - 0
jme3-examples/src/main/java/jme3test/light/TestColorApp.java

@@ -0,0 +1,90 @@
+package jme3test.light;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.input.ChaseCamera;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.AnalogListener;
+import com.jme3.light.DirectionalLight;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.shadow.DirectionalLightShadowFilter;
+import com.jme3.shadow.DirectionalLightShadowRenderer;
+ 
+public class TestColorApp extends SimpleApplication {
+    public static void main(String[] args) {
+        TestColorApp app = new TestColorApp();
+        app.start();
+    }
+ 
+    @Override
+    public void simpleInitApp() {
+        // Lights
+        DirectionalLight sun = new DirectionalLight();
+        Vector3f sunPosition = new Vector3f(1, -1, 1);
+        sun.setDirection(sunPosition);
+        sun.setColor(new ColorRGBA(1f,1f,1f,1f));
+        rootNode.addLight(sun);
+ 
+        //DirectionalLightShadowFilter sun_renderer = new DirectionalLightShadowFilter(assetManager, 2048, 4);
+        DirectionalLightShadowRenderer sun_renderer = new DirectionalLightShadowRenderer(assetManager, 2048, 1);
+        sun_renderer.setLight(sun);
+        viewPort.addProcessor(sun_renderer);
+        
+//        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
+//        fpp.addFilter(sun_renderer);
+//        viewPort.addProcessor(fpp);
+        
+        rootNode.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
+ 
+        // Camera
+        viewPort.setBackgroundColor(new ColorRGBA(.6f, .6f, .6f, 1f));
+        ChaseCamera chaseCam = new ChaseCamera(cam, inputManager);
+ 
+ 
+        // Objects
+        // Ground Object
+        final Geometry groundBoxWhite = new Geometry("Box", new Box(7.5f, 7.5f, .25f));
+        float[] f = {-FastMath.PI / 2, 3 * FastMath.PI / 2, 0f};
+        groundBoxWhite.setLocalRotation(new Quaternion(f));
+        groundBoxWhite.move(7.5f, -.75f, 7.5f);
+        final Material groundMaterial = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        groundMaterial.setColor("Diffuse", new ColorRGBA(.9f, .9f, .9f, .9f));
+        groundBoxWhite.setMaterial(groundMaterial);
+        groundBoxWhite.addControl(chaseCam);
+        rootNode.attachChild(groundBoxWhite);
+ 
+        // Planter
+        Geometry planterBox = new Geometry("Box", new Box(.5f, .5f, .5f));
+        final Material planterMaterial = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        planterMaterial.setTexture("DiffuseMap", assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));
+        planterBox.setMaterial(groundMaterial);
+        planterBox.setLocalTranslation(10, 0, 9);
+        rootNode.attachChild(planterBox);
+ 
+        // Action!
+        inputManager.addMapping("on", new KeyTrigger(KeyInput.KEY_Z));
+        inputManager.addMapping("off", new KeyTrigger(KeyInput.KEY_X));
+ 
+        inputManager.addListener(new AnalogListener() {
+            @Override
+            public void onAnalog(String s, float v, float v1) {
+                if (s.equals("on")) {
+                    groundBoxWhite.setMaterial(planterMaterial);
+                }
+                if (s.equals("off")) {
+                    groundBoxWhite.setMaterial(groundMaterial);
+                }
+            }
+        }, "on", "off");
+ 
+        inputEnabled = true;
+    }
+}

+ 96 - 0
jme3-examples/src/main/java/jme3test/light/TestTangentCube.java

@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2009-2015 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 jme3test.light;
+
+import com.jme3.app.ChaseCameraAppState;
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.util.TangentBinormalGenerator;
+
+/**
+ *
+ * @author Nehon
+ */
+public class TestTangentCube extends SimpleApplication {
+
+    public static void main(String... args) {
+        TestTangentCube app = new TestTangentCube();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Box aBox = new Box(1, 1, 1);
+        Geometry aGeometry = new Geometry("Box", aBox);
+        TangentBinormalGenerator.generate(aBox);
+
+        Material aMaterial = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        aMaterial.setTexture("DiffuseMap",
+                assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));
+        aMaterial.setTexture("NormalMap",
+                assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall_normal.jpg"));
+        aMaterial.setBoolean("UseMaterialColors", false);
+        aMaterial.setColor("Diffuse", ColorRGBA.White);
+        aMaterial.setColor("Specular", ColorRGBA.White);
+        aMaterial.setFloat("Shininess", 64f);
+        aGeometry.setMaterial(aMaterial);
+
+        // Rotate 45 degrees to see multiple faces
+        aGeometry.rotate(FastMath.QUARTER_PI, FastMath.QUARTER_PI, 0.0f);
+        rootNode.attachChild(aGeometry);
+
+        /**
+         * Must add a light to make the lit object visible!
+         */
+        PointLight aLight = new PointLight();
+        aLight.setPosition(new Vector3f(0, 3, 3));
+        aLight.setColor(ColorRGBA.Red);
+        rootNode.addLight(aLight);
+//
+//        AmbientLight bLight = new AmbientLight();
+//        bLight.setColor(ColorRGBA.Gray);
+//        rootNode.addLight(bLight);
+
+        
+        ChaseCameraAppState chaser = new ChaseCameraAppState();
+        chaser.setTarget(aGeometry);
+        getStateManager().attach(chaser);
+    }
+
+}

+ 71 - 0
jme3-examples/src/main/java/jme3test/light/pbr/ConsoleProgressReporter.java

@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2009-2015 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 jme3test.light.pbr;
+
+import com.jme3.environment.generation.JobProgressAdapter;
+import com.jme3.light.LightProbe;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A basic logger for environment map rendering progress.
+ * @author nehon
+ */
+public class ConsoleProgressReporter extends JobProgressAdapter<LightProbe>{
+
+    private static final Logger logger = Logger.getLogger(ConsoleProgressReporter.class.getName());
+    
+    long time;
+
+    @Override
+    public void start() {
+        time = System.currentTimeMillis();
+        logger.log(Level.INFO,"Starting generation");
+    }
+
+    @Override
+    public void progress(double value) {       
+        logger.log(Level.INFO, "Progress : {0}%", (value * 100));
+    }
+
+    @Override
+    public void step(String message) {       
+        logger.info(message);
+    }
+    
+    @Override
+    public void done(LightProbe result) {
+        long end = System.currentTimeMillis();
+        logger.log(Level.INFO, "Generation done in {0}", ((float)(end - time) / 1000f));
+    }
+    
+}

+ 73 - 48
jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java

@@ -1,6 +1,43 @@
+/*
+ * Copyright (c) 2009-2015 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 jme3test.light.pbr;
 
 import com.jme3.app.SimpleApplication;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.light.LightProbe;
+import com.jme3.environment.LightProbeFactory;
+import com.jme3.environment.EnvironmentCamera;
+import com.jme3.environment.generation.JobProgressAdapter;
+import com.jme3.environment.util.LightsDebugState;
 import com.jme3.input.ChaseCamera;
 import com.jme3.input.KeyInput;
 import com.jme3.input.controls.ActionListener;
@@ -13,11 +50,12 @@ import com.jme3.math.Vector3f;
 import com.jme3.post.FilterPostProcessor;
 import com.jme3.post.filters.FXAAFilter;
 import com.jme3.post.filters.ToneMapFilter;
+import com.jme3.post.ssao.SSAOFilter;
 import com.jme3.scene.Geometry;
 import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
-import com.jme3.texture.pbr.EnvironmentCamera;
 import com.jme3.texture.plugins.ktx.KTXLoader;
+import com.jme3.util.MaterialDebugAppState;
 import com.jme3.util.SkyFactory;
 
 /**
@@ -35,10 +73,8 @@ public class TestPBRLighting extends SimpleApplication {
     private Geometry model;
     private DirectionalLight dl;
     private Node modelNode;
-    private int frame = 0;
-    private boolean indirectLighting = true;
-    private Material pbrMat;
-    private Material adHocMat;
+    private int frame = 0;   
+    private Material pbrMat;    
 
     @Override
     public void simpleInitApp() {
@@ -55,22 +91,34 @@ public class TestPBRLighting extends SimpleApplication {
         dl.setColor(ColorRGBA.White);
         rootNode.attachChild(modelNode);
 
-        final EnvironmentCamera envCam = new EnvironmentCamera(128, new Vector3f(0, 3f, 0));
-        stateManager.attach(envCam);
+      
         FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
         fpp.addFilter(new FXAAFilter());
-        fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(2.0f)));
+        fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(4.0f)));
+        fpp.addFilter(new SSAOFilter(0.5f, 3, 0.2f, 0.2f));
         viewPort.addProcessor(fpp);
 
         //Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Sky_Cloudy.hdr", SkyFactory.EnvMapType.EquirectMap);
         Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap);
-        //Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Stonewall.hdr", SkyFactory.EnvMapType.EquirectMap);
+        //Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", SkyFactory.EnvMapType.CubeMap);
         //Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/road.hdr", SkyFactory.EnvMapType.EquirectMap);
         rootNode.attachChild(sky);
 
         pbrMat = assetManager.loadMaterial("Models/Tank/tank.j3m");
         model.setMaterial(pbrMat);
 
+
+        final EnvironmentCamera envCam = new EnvironmentCamera(128, new Vector3f(0, 3f, 0));
+        stateManager.attach(envCam);
+        
+//        EnvironmentManager envManager = new EnvironmentManager();
+//        stateManager.attach(envManager);
+        
+ //       envManager.setScene(rootNode);
+        
+        LightsDebugState debugState = new LightsDebugState();
+        stateManager.attach(debugState);
+        
         ChaseCamera chaser = new ChaseCamera(cam, modelNode, inputManager);
         chaser.setDragToRotate(true);
         chaser.setMinVerticalRotation(-FastMath.HALF_PI);
@@ -84,26 +132,8 @@ public class TestPBRLighting extends SimpleApplication {
         inputManager.addListener(new ActionListener() {
             @Override
             public void onAction(String name, boolean isPressed, float tpf) {
-                if (name.equals("toggle") && isPressed) {
-                    if (!indirectLighting) {
-                        toggleIBL();
-
-                    } else {
-                        pbrMat.clearParam("IntegrateBRDF");
-                        indirectLighting = false;
-                    }
-                }
-
-                if (name.equals("switchMats") && isPressed) {
-                    if (model.getMaterial() == pbrMat) {
-                        model.setMaterial(adHocMat);
-                    } else {
-                        model.setMaterial(pbrMat);
-                    }
-                }
-
                 if (name.equals("debug") && isPressed) {
-                    envCam.toggleDebug();
+                    //envCam.toggleDebug();
                 }
 
                 if (name.equals("up") && isPressed) {
@@ -132,41 +162,36 @@ public class TestPBRLighting extends SimpleApplication {
         inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_LEFT));
         inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_RIGHT));
         inputManager.addMapping("debug", new KeyTrigger(KeyInput.KEY_D));
+        
+        
+        MaterialDebugAppState debug = new MaterialDebugAppState();
+        debug.registerBinding("Common/MatDefs/Light/PBRLighting.frag", rootNode);
+        getStateManager().attach(debug);
 
     }
 
-    private void toggleIBL() {
-        ensurePbrMat();
-        pbrMat.setTexture("IrradianceMap", stateManager.getState(EnvironmentCamera.class).getIrradianceMap());
-        pbrMat.setTexture("PrefEnvMap", stateManager.getState(EnvironmentCamera.class).getPrefilteredEnvMap());
-        pbrMat.setTexture("IntegrateBRDF", assetManager.loadTexture("Common/Textures/integrateBRDF.ktx"));
-        indirectLighting = true;
-    }
-
-    private void ensurePbrMat() {
-        if (model.getMaterial() != pbrMat && model.getMaterial() != adHocMat) {
-            pbrMat = model.getMaterial();
-        }
-    }
-
     @Override
     public void simpleUpdate(float tpf) {
         frame++;
 
         if (frame == 2) {
             modelNode.removeFromParent();
-            stateManager.getState(EnvironmentCamera.class).snapshot(rootNode, new Runnable() {
-                 
-                //this code is ensured to be called in the update loop, the run method is called by the EnvCamera app state in it's update cycle
+            final LightProbe probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter<LightProbe>() {
+
                 @Override
-                public void run() {                    
-                  toggleIBL();
+                public void done(LightProbe result) {
+                    System.err.println("Done rendering env maps");
                 }
             });
+            ((BoundingSphere)probe.getBounds()).setRadius(100);
+            rootNode.addLight(probe);
+            //getStateManager().getState(EnvironmentManager.class).addEnvProbe(probe);
+            
         }
-        if (frame > 2 && modelNode.getParent() == null) {
+        if (frame > 10 && modelNode.getParent() == null) {
             rootNode.attachChild(modelNode);
         }
     }
 
 }
+

+ 378 - 0
jme3-examples/src/main/java/jme3test/light/pbr/TestPbrEnv.java

@@ -0,0 +1,378 @@
+/*
+ * Copyright (c) 2009-2015 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 jme3test.light.pbr;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.input.CameraInput;
+import com.jme3.input.KeyInput;
+import com.jme3.input.MouseInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.input.controls.MouseAxisTrigger;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+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.shadow.DirectionalLightShadowRenderer;
+import com.jme3.shadow.EdgeFilteringMode;
+
+import com.jme3.environment.LightProbeFactory;
+import com.jme3.environment.EnvironmentCamera;
+import com.jme3.environment.util.LightsDebugState;
+import com.jme3.light.LightProbe;
+import com.jme3.material.TechniqueDef;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.BloomFilter;
+import com.jme3.post.filters.FXAAFilter;
+import com.jme3.post.filters.ToneMapFilter;
+import com.jme3.post.ssao.SSAOFilter;
+import com.jme3.scene.Node;
+import com.jme3.texture.plugins.ktx.KTXLoader;
+import com.jme3.util.SkyFactory;
+import com.jme3.util.TangentBinormalGenerator;
+
+public class TestPbrEnv extends SimpleApplication implements ActionListener {
+
+    public static final int SHADOWMAP_SIZE = 1024;
+    private Spatial[] obj;
+    private Material[] mat;
+    private DirectionalLightShadowRenderer dlsr;
+    private LightsDebugState debugState;
+
+    private EnvironmentCamera envCam;
+
+    private Geometry ground;
+    private Material matGroundU;
+    private Material matGroundL;
+
+    private Geometry camGeom;
+
+    public static void main(String[] args) {
+        TestPbrEnv app = new TestPbrEnv();
+        app.start();
+    } 
+
+    
+    public void loadScene() {
+        
+        renderManager.setPreferredLightMode(TechniqueDef.LightMode.SinglePass);
+        renderManager.setSinglePassLightBatchSize(3);
+        obj = new Spatial[2];
+        // Setup first view
+
+        mat = new Material[2];
+        mat[0] = assetManager.loadMaterial("jme3test/light/pbr/pbrMat.j3m");
+        //mat[1] = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");
+        mat[1] = assetManager.loadMaterial("jme3test/light/pbr/pbrMat2.j3m");
+//        mat[1].setBoolean("UseMaterialColors", true);
+//        mat[1].setColor("Ambient", ColorRGBA.White.mult(0.5f));
+//        mat[1].setColor("Diffuse", ColorRGBA.White.clone());
+
+        obj[0] = new Geometry("sphere", new Sphere(30, 30, 2));
+        obj[0].setShadowMode(ShadowMode.CastAndReceive);
+        obj[1] = new Geometry("cube", new Box(1.0f, 1.0f, 1.0f));
+        obj[1].setShadowMode(ShadowMode.CastAndReceive);
+        TangentBinormalGenerator.generate(obj[1]);
+        TangentBinormalGenerator.generate(obj[0]);
+
+//        for (int i = 0; i < 60; i++) {
+//            Spatial t = obj[FastMath.nextRandomInt(0, obj.length - 1)].clone(false);
+//            t.setName("Cube" + i);
+//            t.setLocalScale(FastMath.nextRandomFloat() * 10f);
+//            t.setMaterial(mat[FastMath.nextRandomInt(0, mat.length - 1)]);
+//            rootNode.attachChild(t);
+//            t.setLocalTranslation(FastMath.nextRandomFloat() * 200f, FastMath.nextRandomFloat() * 30f + 20, 30f * (i + 2f));
+//        }
+
+        for (int i = 0; i < 2; i++) {
+            Spatial t = obj[0].clone(false);
+            t.setName("Cube" + i);
+            t.setLocalScale( 10f);
+            t.setMaterial(mat[1].clone());
+            rootNode.attachChild(t);
+            t.setLocalTranslation(i * 200f+ 100f, 50, 800f * (i));
+        }
+        
+        Box b = new Box(1000, 2, 1000);
+        b.scaleTextureCoordinates(new Vector2f(20, 20));
+        ground = new Geometry("soil", b);
+        TangentBinormalGenerator.generate(ground);
+        ground.setLocalTranslation(0, 10, 550);
+        matGroundU = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        matGroundU.setColor("Color", ColorRGBA.Green);
+
+//        matGroundL = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+//        Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
+//        grass.setWrap(WrapMode.Repeat);
+//        matGroundL.setTexture("DiffuseMap", grass);
+
+        matGroundL = assetManager.loadMaterial("jme3test/light/pbr/pbrMat4.j3m");
+        
+        ground.setMaterial(matGroundL);
+
+        //ground.setShadowMode(ShadowMode.CastAndReceive);
+        rootNode.attachChild(ground);
+
+        l = new DirectionalLight();
+        l.setColor(ColorRGBA.White);
+        //l.setDirection(new Vector3f(0.5973172f, -0.16583486f, 0.7846725f).normalizeLocal());
+        l.setDirection(new Vector3f(-0.2823181f, -0.41889593f, 0.863031f).normalizeLocal());
+        
+        rootNode.addLight(l);
+
+        AmbientLight al = new AmbientLight();
+        al.setColor(ColorRGBA.White.mult(0.5f));
+      //  rootNode.addLight(al);
+
+        //Spatial sky = SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", SkyFactory.EnvMapType.CubeMap);
+        Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap);
+        sky.setLocalScale(350);
+
+        rootNode.attachChild(sky);
+    }
+    DirectionalLight l;
+
+    @Override
+    public void simpleInitApp() {
+        assetManager.registerLoader(KTXLoader.class, "ktx");
+        
+        
+        // put the camera in a bad position
+        cam.setLocation(new Vector3f(-52.433647f, 68.69636f, -118.60924f));
+        cam.setRotation(new Quaternion(0.10294232f, 0.25269797f, -0.027049713f, 0.96167296f));      
+
+        flyCam.setMoveSpeed(100);
+
+        loadScene();
+
+        dlsr = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE, 4);
+        dlsr.setLight(l);
+        //dlsr.setLambda(0.55f);
+        dlsr.setShadowIntensity(0.5f);
+        dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON);
+        //dlsr.displayDebug();
+ //       viewPort.addProcessor(dlsr);
+        
+        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
+        
+        fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(6.0f)));
+        SSAOFilter ssao = new SSAOFilter();
+        ssao.setIntensity(5);
+                
+        fpp.addFilter(ssao);
+        
+        BloomFilter bloomFilter = new BloomFilter();
+        fpp.addFilter(bloomFilter);
+        fpp.addFilter(new FXAAFilter());
+        //viewPort.addProcessor(fpp);
+
+        initInputs();
+
+//        envManager = new EnvironmentManager();
+//        getStateManager().attach(envManager);
+//        
+        envCam = new EnvironmentCamera();
+        getStateManager().attach(envCam);
+
+        debugState = new LightsDebugState();
+        debugState.setProbeScale(5);        
+        getStateManager().attach(debugState);
+
+        camGeom = new Geometry("camGeom", new Sphere(16, 16, 2));
+//        Material m = new Material(assetManager, "Common/MatDefs/Misc/UnshadedNodes.j3md");
+//        m.setColor("Color", ColorRGBA.Green);
+        Material m = assetManager.loadMaterial("jme3test/light/pbr/pbrMat3.j3m");
+        camGeom.setMaterial(m);
+        camGeom.setLocalTranslation(0, 20, 0);
+        camGeom.setLocalScale(5);
+        rootNode.attachChild(camGeom);
+        
+   //     envManager.setScene(rootNode);
+        
+//        MaterialDebugAppState debug = new MaterialDebugAppState();
+//        debug.registerBinding("MatDefs/PBRLighting.frag", rootNode);
+//        getStateManager().attach(debug);
+        
+        flyCam.setDragToRotate(true);
+        setPauseOnLostFocus(false);
+        
+       // cam.lookAt(camGeom.getWorldTranslation(), Vector3f.UNIT_Y);
+
+    }
+
+    private void fixFLyCamInputs() {
+        inputManager.deleteMapping(CameraInput.FLYCAM_LEFT);
+        inputManager.deleteMapping(CameraInput.FLYCAM_RIGHT);
+        inputManager.deleteMapping(CameraInput.FLYCAM_UP);
+        inputManager.deleteMapping(CameraInput.FLYCAM_DOWN);
+
+        inputManager.addMapping(CameraInput.FLYCAM_LEFT, new MouseAxisTrigger(MouseInput.AXIS_X, true));
+
+        inputManager.addMapping(CameraInput.FLYCAM_RIGHT, new MouseAxisTrigger(MouseInput.AXIS_X, false));
+
+        inputManager.addMapping(CameraInput.FLYCAM_UP, new MouseAxisTrigger(MouseInput.AXIS_Y, false));
+
+        inputManager.addMapping(CameraInput.FLYCAM_DOWN, new MouseAxisTrigger(MouseInput.AXIS_Y, true));
+
+        inputManager.addListener(flyCam, CameraInput.FLYCAM_LEFT, CameraInput.FLYCAM_RIGHT, CameraInput.FLYCAM_UP, CameraInput.FLYCAM_DOWN);
+    }
+
+    private void initInputs() {
+        inputManager.addMapping("switchGroundMat", new KeyTrigger(KeyInput.KEY_M));
+        inputManager.addMapping("snapshot", new KeyTrigger(KeyInput.KEY_SPACE));
+        inputManager.addMapping("fc", new KeyTrigger(KeyInput.KEY_F));
+        inputManager.addMapping("debugProbe", new KeyTrigger(KeyInput.KEY_RETURN));
+        inputManager.addMapping("debugTex", new KeyTrigger(KeyInput.KEY_T));
+        inputManager.addMapping("up", new KeyTrigger(KeyInput.KEY_UP));
+        inputManager.addMapping("down", new KeyTrigger(KeyInput.KEY_DOWN));
+        inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_RIGHT));
+        inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_LEFT));
+
+        inputManager.addListener(this, "switchGroundMat", "snapshot", "debugTex", "debugProbe", "fc", "up", "down", "left", "right");
+    }
+    
+    private LightProbe lastProbe;
+    private Node debugGui ;
+
+    public void onAction(String name, boolean keyPressed, float tpf) {
+
+        if (name.equals("switchGroundMat") && keyPressed) {
+            if (ground.getMaterial() == matGroundL) {
+                ground.setMaterial(matGroundU);
+            } else {
+                
+                ground.setMaterial(matGroundL);
+            }
+        }
+
+        if (name.equals("snapshot") && keyPressed) {
+            envCam.setPosition(camGeom.getWorldTranslation());
+            lastProbe = LightProbeFactory.makeProbe(envCam, rootNode, new ConsoleProgressReporter());            
+            ((BoundingSphere)lastProbe.getBounds()).setRadius(200);
+            rootNode.addLight(lastProbe);
+
+        }
+
+        if (name.equals("fc") && keyPressed) {
+
+            flyCam.setEnabled(true);
+        }
+
+        if (name.equals("debugProbe") && keyPressed) {
+            //getStateManager().getState(EnvironmentCamera.class).toggleDebug();
+            if (!debugState.isEnabled()) {
+                debugState.setEnabled(true);
+                debugState.setDebugMode(LightsDebugState.DebugMode.IrradianceMap);
+            } else if (debugState.getDebugMode() == LightsDebugState.DebugMode.IrradianceMap) {
+                debugState.setDebugMode(LightsDebugState.DebugMode.PrefilteredEnvMap);
+            } else if (debugState.getDebugMode() == LightsDebugState.DebugMode.PrefilteredEnvMap) {
+                debugState.setEnabled(false);
+            }
+
+        }
+        
+        if (name.equals("debugTex") && keyPressed) {
+            if(debugGui == null || debugGui.getParent() == null){
+                debugGui = lastProbe.getDebugGui(assetManager);
+                debugGui.setLocalTranslation(10, 200, 0);
+                guiNode.attachChild(debugGui);
+            } else if(debugGui != null){
+                debugGui.removeFromParent();
+            }
+        }
+
+        if (name.equals("up")) {
+            up = keyPressed;
+        }
+        if (name.equals("down")) {
+            down = keyPressed;
+        }
+        if (name.equals("right")) {
+            right = keyPressed;
+        }
+        if (name.equals("left")) {
+            left = keyPressed;
+        }
+        if (name.equals("fwd")) {
+            fwd = keyPressed;
+        }
+        if (name.equals("back")) {
+            back = keyPressed;
+        }
+
+    }
+    boolean up = false;
+    boolean down = false;
+    boolean left = false;
+    boolean right = false;
+    boolean fwd = false;
+    boolean back = false;
+    float time = 0;
+    float s = 50f;
+    boolean initialized = false;
+
+    @Override
+    public void simpleUpdate(float tpf) {
+
+        if (!initialized) {
+            fixFLyCamInputs();
+            initialized = true;
+        }
+        float val = tpf * s;
+        if (up) {
+            camGeom.move(0, 0, val);
+        }
+        if (down) {
+            camGeom.move(0, 0, -val);
+
+        }
+        if (right) {
+            camGeom.move(-val, 0, 0);
+
+        }
+        if (left) {
+            camGeom.move(val, 0, 0);
+
+        }
+
+    }
+
+}

+ 180 - 0
jme3-examples/src/main/java/jme3test/post/TestBloomAlphaThreshold.java

@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2009-2015 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 jme3test.post;
+
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.BloomFilter;
+import com.jme3.post.filters.BloomFilter.GlowMode;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.WireFrustum;
+import com.jme3.scene.shape.Box;
+import com.jme3.util.SkyFactory;
+
+public class TestBloomAlphaThreshold extends SimpleApplication
+{
+
+	float angle;
+	Spatial lightMdl;
+	Spatial teapot;
+	Geometry frustumMdl;
+	WireFrustum frustum;
+	boolean active = true;
+	FilterPostProcessor fpp;
+
+	public static void main(String[] args)
+	{
+		TestBloomAlphaThreshold app = new TestBloomAlphaThreshold();
+		app.start();
+	}
+
+	@Override
+	public void simpleInitApp()
+	{
+		// put the camera in a bad position
+		cam.setLocation(new Vector3f(-2.336393f, 11.91392f, -10));
+		cam.setRotation(new Quaternion(0.23602544f, 0.11321983f, -0.027698677f, 0.96473104f));
+		// cam.setFrustumFar(1000);
+
+		Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+
+		mat.setFloat("Shininess", 15f);
+		mat.setBoolean("UseMaterialColors", true);
+		mat.setColor("Ambient", ColorRGBA.Yellow.mult(0.2f));
+		mat.setColor("Diffuse", ColorRGBA.Yellow.mult(0.2f));
+		mat.setColor("Specular", ColorRGBA.Yellow.mult(0.8f));
+		mat.setColor("GlowColor", ColorRGBA.Green);
+
+		Material matSoil = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+		matSoil.setFloat("Shininess", 15f);
+		matSoil.setBoolean("UseMaterialColors", true);
+		matSoil.setColor("Ambient", ColorRGBA.Gray);
+		matSoil.setColor("Diffuse", ColorRGBA.Black);
+		matSoil.setColor("Specular", ColorRGBA.Gray);
+
+		teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");
+		teapot.setLocalTranslation(0, 0, 10);
+
+		teapot.setMaterial(mat);
+		teapot.setShadowMode(ShadowMode.CastAndReceive);
+		teapot.setLocalScale(10.0f);
+		rootNode.attachChild(teapot);
+
+		Geometry soil = new Geometry("soil", new Box(new Vector3f(0, -13, 550), 800, 10, 700));
+		soil.setMaterial(matSoil);
+		soil.setShadowMode(ShadowMode.CastAndReceive);
+		rootNode.attachChild(soil);
+
+		Material matBox = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+		matBox.setTexture("ColorMap", assetManager.loadTexture("Textures/ColoredTex/Monkey.png"));
+		matBox.setFloat("AlphaDiscardThreshold", 0.5f);
+    
+		Geometry box = new Geometry("box", new Box(new Vector3f(-3.5f, 10, -2), 2, 2, 2));
+		box.setMaterial(matBox);
+                box.setQueueBucket(RenderQueue.Bucket.Translucent);
+		// box.setShadowMode(ShadowMode.CastAndReceive);
+		rootNode.attachChild(box);
+
+		DirectionalLight light = new DirectionalLight();
+		light.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
+		light.setColor(ColorRGBA.White.mult(1.5f));
+		rootNode.addLight(light);
+
+		// load sky
+		Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Bright/FullskiesBlueClear03.dds", false);
+		sky.setCullHint(Spatial.CullHint.Never);
+		rootNode.attachChild(sky);
+
+		fpp = new FilterPostProcessor(assetManager);
+		int numSamples = getContext().getSettings().getSamples();
+		if (numSamples > 0)
+		{
+			fpp.setNumSamples(numSamples);
+		}
+
+		BloomFilter bloom = new BloomFilter(GlowMode.Objects);
+		bloom.setDownSamplingFactor(2);
+		bloom.setBlurScale(1.37f);
+		bloom.setExposurePower(3.30f);
+		bloom.setExposureCutOff(0.2f);
+		bloom.setBloomIntensity(2.45f);
+		BloomUI ui = new BloomUI(inputManager, bloom);
+
+		viewPort.addProcessor(fpp);
+		fpp.addFilter(bloom);
+		initInputs();
+
+	}
+
+	private void initInputs()
+	{
+		inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE));
+
+		ActionListener acl = new ActionListener()
+		{
+
+			@Override
+			public void onAction(String name, boolean keyPressed, float tpf)
+			{
+				if (name.equals("toggle") && keyPressed)
+				{
+					if (active)
+					{
+						active = false;
+						viewPort.removeProcessor(fpp);
+					}
+					else
+					{
+						active = true;
+						viewPort.addProcessor(fpp);
+					}
+				}
+			}
+		};
+
+		inputManager.addListener(acl, "toggle");
+
+	}
+
+}

+ 13 - 0
jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat.j3m

@@ -0,0 +1,13 @@
+Material My Material : Common/MatDefs/Light/PBRLighting.j3md {
+     MaterialParameters {
+        Roughness : 0.1
+        BaseColor : 1.0 0.2 0.2 1.0
+        Metallic : 0.0
+        EmissivePower : 0.0
+        EmissiveIntensity : 0.0
+        Emissive : 1.0 0.8 0.8 1.0
+        ParallaxHeight : 0.0
+     }
+    AdditionalRenderState {
+    }
+}

+ 12 - 0
jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat2.j3m

@@ -0,0 +1,12 @@
+Material My Material : Common/MatDefs/Light/PBRLighting.j3md {
+     MaterialParameters {
+        Metallic : 0.0
+        Roughness : 0.0
+        BaseColor : 0.8 0.81960785 0.39607844 1.0
+        EmissiveIntensity : 5.0
+        EmissivePower : 2.0
+        Emissive : 1.0 0.0 0.0 1.0
+     }
+    AdditionalRenderState {
+    }
+}

+ 9 - 0
jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat3.j3m

@@ -0,0 +1,9 @@
+Material My Material : Common/MatDefs/Light/PBRLighting.j3md {
+     MaterialParameters {
+        BaseColor : 0.6 0.6 0.6 1.0
+        Metallic : 1.0
+        Roughness : 0.05
+     }
+    AdditionalRenderState {
+    }
+}

+ 11 - 0
jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat4.j3m

@@ -0,0 +1,11 @@
+Material My Material : Common/MatDefs/Light/PBRLighting.j3md {
+     MaterialParameters {
+        BaseColorMap : Repeat Textures/Terrain/BrickWall/BrickWall.jpg
+        Roughness : 0.01
+        NormalMap : Repeat Textures/Terrain/BrickWall/BrickWall_normal.jpg
+        Metallic : 1.0
+        BaseColor : 1.0 1.0 1.0 1.0
+     }
+    AdditionalRenderState {
+    }
+}