Browse Source

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 năm trước cách đây
mục cha
commit
a35b499ee7
30 tập tin đã thay đổi với 2202 bổ sung112 xóa
  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 {
+    }
+}