Jelajahi Sumber

Merge remote-tracking branch 'origin/master' into in-pass-shadows

Kirill Vainer 8 tahun lalu
induk
melakukan
8d125a30ba
19 mengubah file dengan 520 tambahan dan 671 penghapusan
  1. 1 1
      jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java
  2. 12 18
      jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java
  3. 0 176
      jme3-core/src/main/java/com/jme3/environment/generation/IrradianceMapGenerator.java
  4. 117 0
      jme3-core/src/main/java/com/jme3/environment/generation/IrradianceSphericalHarmonicsGenerator.java
  5. 33 32
      jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java
  6. 46 252
      jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java
  7. 1 39
      jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java
  8. 33 28
      jme3-core/src/main/java/com/jme3/light/LightProbe.java
  9. 6 7
      jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java
  10. 10 9
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag
  11. 47 32
      jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib
  12. 19 22
      jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java
  13. 136 0
      jme3-examples/src/main/java/jme3test/light/pbr/TestPBRDirectLighting.java
  14. 30 21
      jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java
  15. 1 10
      jme3-examples/src/main/java/jme3test/light/pbr/TestPbrEnv.java
  16. 28 24
      jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java
  17. TEMPAT SAMPAH
      jme3-examples/src/main/resources/jme3test/light/pbr/ref.png
  18. TEMPAT SAMPAH
      jme3-examples/src/main/resources/jme3test/light/pbr/spheresRefDE.png
  19. TEMPAT SAMPAH
      jme3-examples/src/main/resources/jme3test/light/pbr/spheresRefM.png

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

@@ -111,7 +111,7 @@ public class EnvironmentCamera extends BaseAppState {
     /**
      * The size of environment cameras.
      */
-    protected int size = 128;
+    protected int size = 256;
 
     private final List<SnapshotJob> jobs = new ArrayList<SnapshotJob>();
 

+ 12 - 18
jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java

@@ -31,12 +31,9 @@
  */
 package com.jme3.environment;
 
+import com.jme3.environment.generation.*;
 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;
@@ -115,7 +112,6 @@ public class LightProbeFactory {
     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>() {
 
@@ -152,16 +148,13 @@ public class LightProbeFactory {
         envCam.setPosition(probe.getPosition());
         
         probe.setReady(false);
-        
-        if(probe.getIrradianceMap() != null) {
-            probe.getIrradianceMap().getImage().dispose();
+
+        if (probe.getPrefilteredEnvMap() != null) {
             probe.getPrefilteredEnvMap().getImage().dispose();
         }
-        
-        probe.setIrradianceMap(EnvMapUtils.createIrradianceMap(envCam.getSize(), envCam.getImageFormat()));
+
         probe.setPrefilteredMap(EnvMapUtils.createPrefilteredEnvMap(envCam.getSize(), envCam.getImageFormat()));
-        
-        
+
         envCam.snapshot(scene, new JobProgressAdapter<TextureCubeMap>() {
 
             @Override
@@ -174,7 +167,7 @@ public class LightProbeFactory {
 
     /**
      * 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).
+     * This method will spawn 7 thread (one for the Irradiance spherical harmonics generator, 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
@@ -183,20 +176,20 @@ public class LightProbeFactory {
      * @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;
+        IrradianceSphericalHarmonicsGenerator irrShGenerator;
         PrefilteredEnvMapFaceGenerator[] pemGenerators = new PrefilteredEnvMapFaceGenerator[6];
 
         final JobState jobState = new JobState(new ScheduledThreadPoolExecutor(7));
 
-        irrMapGenerator = new IrradianceMapGenerator(app, new JobListener(listener, jobState, probe, 6));
+        irrShGenerator = new IrradianceSphericalHarmonicsGenerator(app, new JobListener(listener, jobState, probe, 6));
         int size = envMap.getImage().getWidth();
-        irrMapGenerator.setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), size, EnvMapUtils.FixSeamsMethod.Wrap, probe.getIrradianceMap());
+        irrShGenerator.setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), probe);
 
-        jobState.executor.execute(irrMapGenerator);
+        jobState.executor.execute(irrShGenerator);
 
         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());
+            pemGenerators[i].setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), size, EnvMapUtils.FixSeamsMethod.None, probe.getPrefilteredEnvMap());
             jobState.executor.execute(pemGenerators[i]);
         }
     }
@@ -280,6 +273,7 @@ public class LightProbeFactory {
 
             jobState.done[index] = true;
             if (jobState.isDone()) {
+                probe.setNbMipMaps(probe.getPrefilteredEnvMap().getImage().getMipMapSizes().length);
                 probe.setReady(true);
                 if (globalListener != null) {
                     globalListener.done(probe);

+ 0 - 176
jme3-core/src/main/java/com/jme3/environment/generation/IrradianceMapGenerator.java

@@ -1,176 +0,0 @@
-/*
- * 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;
-
-import com.jme3.environment.util.CubeMapWrapper;
-import com.jme3.environment.util.EnvMapUtils;
-import com.jme3.app.Application;
-import com.jme3.math.ColorRGBA;
-import com.jme3.math.Vector3f;
-import com.jme3.texture.TextureCubeMap;
-import static com.jme3.environment.util.EnvMapUtils.shBandFactor;
-import com.jme3.util.BufferUtils;
-import java.nio.ByteBuffer;
-import java.util.concurrent.Callable;
-
-/**
- *
- * Generates the Irrafiance map for PBR. This job can be lauched from a separate
- * thread.
- *
- * TODO there is a lot of duplicate code here with the EnvMapUtils.
- *
- * @author Nehon
- */
-//TODO there is a lot of duplicate code here with the EnvMapUtils. We should, 
-//either leverage the code from the util class either remove it and only allow 
-//parallel generation using this runnable.
-public class IrradianceMapGenerator extends RunnableWithProgress {
-
-    private int targetMapSize;
-    private EnvMapUtils.FixSeamsMethod fixSeamsMethod;
-    private TextureCubeMap sourceMap;
-    private TextureCubeMap store;
-    private final Application app;
-
-    /**
-     * Creates an Irradiance map generator. The app is needed to enqueue the
-     * call to the EnvironmentCamera when the generation is done, so that this
-     * process is thread safe.
-     *
-     * @param app the Application
-     * @param listener
-     */
-    public IrradianceMapGenerator(Application app, JobProgressListener<Integer> listener) {
-        super(listener);
-        this.app = app;
-    }
-
-    /**
-     * Fills all the genration parameters
-     *
-     * @param sourceMap the source cube map
-     * @param targetMapSize the size of the generated map (width or height in
-     * pixel)
-     * @param fixSeamsMethod the method used to fix seams as described here
-     * {@link EnvMapUtils.FixSeamsMethod}
-     *
-     * @param store The cube map to store the result in.
-     */
-    public void setGenerationParam(TextureCubeMap sourceMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
-        this.sourceMap = sourceMap;
-        this.targetMapSize = targetMapSize;
-        this.fixSeamsMethod = fixSeamsMethod;
-        this.store = store;
-        reset();
-    }
-
-    @Override
-    public void run() {
-        app.enqueue(new Callable<Void>() {
-
-            @Override
-            public Void call() throws Exception {
-                listener.start();
-                return null;
-            }
-        });
-        try {
-            Vector3f[] shCoeffs = EnvMapUtils.getSphericalHarmonicsCoefficents(sourceMap);
-            store = generateIrradianceMap(shCoeffs, targetMapSize, fixSeamsMethod, store);
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-        app.enqueue(new Callable<Void>() {
-
-            @Override
-            public Void call() throws Exception {
-                listener.done(6);
-                return null;
-            }
-        });
-    }
-
-    /**
-     * Generates the Irradiance map (used for image based difuse lighting) from
-     * Spherical Harmonics coefficients previously computed with
-     * {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap)}
-     *
-     * @param shCoeffs the SH coeffs
-     * @param targetMapSize the size of the irradiance map to generate
-     * @param fixSeamsMethod the method to fix seams
-     * @param store
-     * @return The irradiance cube map for the given coefficients
-     */
-    public TextureCubeMap generateIrradianceMap(Vector3f[] shCoeffs, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
-        TextureCubeMap irrCubeMap = store;
-
-        setEnd(6 + 6);
-        for (int i = 0; i < 6; i++) {
-            ByteBuffer buf = BufferUtils.createByteBuffer(targetMapSize * targetMapSize * store.getImage().getFormat().getBitsPerPixel() / 8);
-            irrCubeMap.getImage().setData(i, buf);
-            progress();
-        }
-
-        Vector3f texelVect = new Vector3f();
-        ColorRGBA color = new ColorRGBA(ColorRGBA.Black);
-        float[] shDir = new float[9];
-        CubeMapWrapper envMapWriter = new CubeMapWrapper(irrCubeMap);
-        for (int face = 0; face < 6; face++) {
-
-            for (int y = 0; y < targetMapSize; y++) {
-                for (int x = 0; x < targetMapSize; x++) {
-                    EnvMapUtils.getVectorFromCubemapFaceTexCoord(x, y, targetMapSize, face, texelVect, fixSeamsMethod);
-                    EnvMapUtils.evalShBasis(texelVect, shDir);
-                    color.set(0, 0, 0, 0);
-                    for (int i = 0; i < EnvMapUtils.NUM_SH_COEFFICIENT; i++) {
-                        color.set(color.r + shCoeffs[i].x * shDir[i] * shBandFactor[i],
-                                color.g + shCoeffs[i].y * shDir[i] * shBandFactor[i],
-                                color.b + shCoeffs[i].z * shDir[i] * shBandFactor[i],
-                                1.0f);
-                    }
-
-                    //clamping the color because very low value close to zero produce artifacts
-                    color.r = Math.max(0.0001f, color.r);
-                    color.g = Math.max(0.0001f, color.g);
-                    color.b = Math.max(0.0001f, color.b);
-
-                    envMapWriter.setPixel(x, y, face, color);
-                    
-                }
-            }
-            progress();
-        }
-        return irrCubeMap;
-    }
-
-}

+ 117 - 0
jme3-core/src/main/java/com/jme3/environment/generation/IrradianceSphericalHarmonicsGenerator.java

@@ -0,0 +1,117 @@
+/*
+ * 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;
+
+import com.jme3.app.Application;
+import com.jme3.environment.util.CubeMapWrapper;
+import com.jme3.environment.util.EnvMapUtils;
+import com.jme3.light.LightProbe;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.texture.TextureCubeMap;
+import com.jme3.util.BufferUtils;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.Callable;
+
+import static com.jme3.environment.util.EnvMapUtils.shBandFactor;
+
+/**
+ * Generates the Irradiance map for PBR. This job can be launched from a separate
+ * thread.
+ * <p>
+ * This is not used as we use spherical harmonics directly now, but we may need this code again at some point
+ *
+ * @author Nehon
+ */
+public class IrradianceSphericalHarmonicsGenerator extends RunnableWithProgress {
+
+    private TextureCubeMap sourceMap;
+    private LightProbe store;
+    private final Application app;
+
+    /**
+     * Creates an Irradiance map generator. The app is needed to enqueue the
+     * call to the EnvironmentCamera when the generation is done, so that this
+     * process is thread safe.
+     *
+     * @param app      the Application
+     * @param listener
+     */
+    public IrradianceSphericalHarmonicsGenerator(Application app, JobProgressListener<Integer> listener) {
+        super(listener);
+        this.app = app;
+    }
+
+    /**
+     * Fills all the genration parameters
+     *
+     * @param sourceMap the source cube map
+     *                  {@link EnvMapUtils.FixSeamsMethod}
+     * @param store     The cube map to store the result in.
+     */
+    public void setGenerationParam(TextureCubeMap sourceMap, LightProbe store) {
+        this.sourceMap = sourceMap;
+
+        this.store = store;
+        reset();
+    }
+
+    @Override
+    public void run() {
+        app.enqueue(new Callable<Void>() {
+
+            @Override
+            public Void call() throws Exception {
+                listener.start();
+                return null;
+            }
+        });
+        try {
+            Vector3f[] shCoeffs = EnvMapUtils.getSphericalHarmonicsCoefficents(sourceMap);
+            EnvMapUtils.prepareShCoefs(shCoeffs);
+            store.setShCoeffs(shCoeffs);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        app.enqueue(new Callable<Void>() {
+
+            @Override
+            public Void call() throws Exception {
+                listener.done(6);
+                return null;
+            }
+        });
+    }
+
+}

+ 33 - 32
jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java

@@ -34,27 +34,28 @@ package com.jme3.environment.generation;
 import com.jme3.environment.util.CubeMapWrapper;
 import com.jme3.environment.util.EnvMapUtils;
 import com.jme3.app.Application;
-import com.jme3.math.ColorRGBA;
+import com.jme3.math.*;
+
 import static com.jme3.math.FastMath.abs;
 import static com.jme3.math.FastMath.clamp;
 import static com.jme3.math.FastMath.pow;
 import static com.jme3.math.FastMath.sqrt;
-import com.jme3.math.Vector3f;
-import com.jme3.math.Vector4f;
+
 import com.jme3.texture.TextureCubeMap;
+
 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 java.util.concurrent.Callable;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 /**
- *
  * Generates one face of the prefiltered environnement map for PBR. This job can
  * be lauched from a separate thread.
- *
+ * <p>
  * TODO there is a lot of duplicate code here with the EnvMapUtils.
  *
  * @author Nehon
@@ -85,8 +86,8 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
      * the call to the EnvironmentCamera when the generation is done, so that
      * this process is thread safe.
      *
-     * @param app the Application
-     * @param face the face to generate
+     * @param app      the Application
+     * @param face     the face to generate
      * @param listener
      */
     public PrefilteredEnvMapFaceGenerator(Application app, int face, JobProgressListener<Integer> listener) {
@@ -95,18 +96,16 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
         this.face = face;
     }
 
-    
-    
+
     /**
      * Fills all the genration parameters
      *
-     * @param sourceMap the source cube map
-     * @param targetMapSize the size of the generated map (width or height in
-     * pixel)
+     * @param sourceMap      the source cube map
+     * @param targetMapSize  the size of the generated map (width or height in
+     *                       pixel)
      * @param fixSeamsMethod the method used to fix seams as described here
-     * {@link EnvMapUtils.FixSeamsMethod}
-     *
-     * @param store The cube map to store the result in.
+     *                       {@link EnvMapUtils.FixSeamsMethod}
+     * @param store          The cube map to store the result in.
      */
     public void setGenerationParam(TextureCubeMap sourceMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
         this.sourceMap = sourceMap;
@@ -115,16 +114,16 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
         this.store = store;
         init();
     }
-    
-    private void init(){
-         Xi.set(0, 0, 0, 0);
-         H.set(0, 0, 0);
-         tmp.set(0, 0, 0);
-         c.set(1, 1, 1, 1);
-         tmp1.set(0, 0, 0);
-         tmp2.set(0, 0, 0);
-         tmp3.set(0, 0, 0);
-         reset();
+
+    private void init() {
+        Xi.set(0, 0, 0, 0);
+        H.set(0, 0, 0);
+        tmp.set(0, 0, 0);
+        c.set(1, 1, 1, 1);
+        tmp1.set(0, 0, 0);
+        tmp2.set(0, 0, 0);
+        tmp3.set(0, 0, 0);
+        reset();
 
     }
 
@@ -157,7 +156,7 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
      * Note that the output cube map is in RGBA8 format.
      *
      * @param sourceEnvMap
-     * @param targetMapSize the size of the irradiance map to generate
+     * @param targetMapSize  the size of the irradiance map to generate
      * @param store
      * @param fixSeamsMethod the method to fix seams
      * @return The irradiance cube map for the given coefficients
@@ -168,7 +167,7 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
         int nbMipMap = (int) (Math.log(targetMapSize) / Math.log(2) - 1);
 
         setEnd(nbMipMap);
-        
+
 
         CubeMapWrapper sourceWrapper = new CubeMapWrapper(sourceEnvMap);
         CubeMapWrapper targetWrapper = new CubeMapWrapper(pem);
@@ -184,10 +183,10 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
             for (int y = 0; y < targetMipMapSize; y++) {
                 for (int x = 0; x < targetMipMapSize; x++) {
                     color.set(0, 0, 0);
-                    getVectorFromCubemapFaceTexCoord(x, y, targetMipMapSize, face, texelVect, EnvMapUtils.FixSeamsMethod.Wrap);
+                    getVectorFromCubemapFaceTexCoord(x, y, targetMipMapSize, face, texelVect, fixSeamsMethod);
                     prefilterEnvMapTexel(sourceWrapper, roughness, texelVect, nbSamples, color);
-                    
-                    outColor.set(Math.max(color.x, 0.0001f), Math.max(color.y,0.0001f), Math.max(color.z, 0.0001f), 1);
+
+                    outColor.set(Math.max(color.x, 0.0001f), Math.max(color.y, 0.0001f), Math.max(color.z, 0.0001f), 1);
                     log.log(Level.FINE, "coords {0},{1}", new Object[]{x, y});
                     targetWrapper.setPixel(x, y, face, mipLevel, outColor);
 
@@ -207,7 +206,6 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
         // a = roughness² and a2 = a²
         float a2 = roughness * roughness;
         a2 *= a2;
-        a2 *= 10;
         for (int i = 0; i < numSamples; i++) {
             Xi = getHammersleyPoint(i, numSamples, Xi);
             H = importanceSampleGGX(Xi, a2, N, H);
@@ -227,8 +225,11 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
                 totalWeight += NoL;
             }
         }
+        if (totalWeight > 0) {
+            prefilteredColor.divideLocal(totalWeight);
+        }
 
-        return prefilteredColor.divideLocal(totalWeight);
+        return prefilteredColor;
     }
 
     public Vector3f importanceSampleGGX(Vector4f xi, float a2, Vector3f normal, Vector3f store) {

+ 46 - 252
jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java

@@ -33,9 +33,7 @@ package com.jme3.environment.util;
 
 import com.jme3.asset.AssetManager;
 import com.jme3.material.Material;
-import com.jme3.math.ColorRGBA;
-import com.jme3.math.Vector3f;
-import com.jme3.math.Vector4f;
+import com.jme3.math.*;
 import com.jme3.scene.Geometry;
 import com.jme3.scene.Node;
 import com.jme3.scene.shape.Quad;
@@ -49,8 +47,7 @@ import com.jme3.util.BufferUtils;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import static com.jme3.math.FastMath.*;
-import com.jme3.math.Quaternion;
-import com.jme3.math.Vector2f;
+
 import com.jme3.util.TempVars;
 
 /**
@@ -62,6 +59,12 @@ import com.jme3.util.TempVars;
  */
 public class EnvMapUtils {
 
+
+    private static final float sqrtPi = sqrt(PI);
+    private static final float sqrt3Pi = sqrt(3f / PI);
+    private static final float sqrt5Pi = sqrt(5f / PI);
+    private static final float sqrt15Pi = sqrt(15f / PI);
+
     public final static int NUM_SH_COEFFICIENT = 9;
     // See Peter-Pike Sloan paper for these coefficients
     //http://www.ppsloan.org/publications/StupidSH36.pdf
@@ -82,7 +85,7 @@ public class EnvMapUtils {
         /**
          * No seams fix
          */
-        None;
+        None
     }
 
     /**
@@ -114,18 +117,6 @@ public class EnvMapUtils {
         cubeImage.addData(backImg.getData(0));
         cubeImage.addData(frontImg.getData(0));
 
-        if (leftImg.getEfficentData() != null) {
-            // also consilidate efficient data
-            ArrayList<Object> efficientData = new ArrayList<Object>(6);
-            efficientData.add(rightImg.getEfficentData());
-            efficientData.add(leftImg.getEfficentData());
-            efficientData.add(upImg.getEfficentData());
-            efficientData.add(downImg.getEfficentData());
-            efficientData.add(backImg.getEfficentData());
-            efficientData.add(frontImg.getEfficentData());
-            cubeImage.setEfficentData(efficientData);
-        }
-
         TextureCubeMap cubeMap = new TextureCubeMap(cubeImage);
         cubeMap.setAnisotropicFilter(0);
         cubeMap.setMagFilter(Texture.MagFilter.Bilinear);
@@ -157,13 +148,6 @@ public class EnvMapUtils {
             cubeImage.addData(d.duplicate());
         }
 
-        if (srcImg.getEfficentData() != null) {
-            // also consilidate efficient data
-            ArrayList<Object> efficientData = new ArrayList<Object>(6);
-            efficientData.add(srcImg.getEfficentData());
-            cubeImage.setEfficentData(efficientData);
-        }
-
         TextureCubeMap cubeMap = new TextureCubeMap(cubeImage);
         cubeMap.setAnisotropicFilter(sourceMap.getAnisotropicFilter());
         cubeMap.setMagFilter(sourceMap.getMagFilter());
@@ -199,7 +183,7 @@ public class EnvMapUtils {
     static float getSolidAngleAndVector(int x, int y, int mapSize, int face, Vector3f store, FixSeamsMethod fixSeamsMethod) {
 
         if (store == null) {
-            throw new IllegalArgumentException("the store parameter ust not be null");
+            throw new IllegalArgumentException("the store parameter must not be null");
         }
 
         /* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
@@ -254,7 +238,7 @@ public class EnvMapUtils {
         float v;
 
         if (fixSeamsMethod == FixSeamsMethod.Stretch) {
-            /* Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp		
+            /* Code from Nvtt : https://github.com/castano/nvidia-texture-tools/blob/master/src/nvtt/CubeSurface.cpp#L77
              * transform from [0..res - 1] to [-1 .. 1], match up edges exactly. */
             u = (2.0f * (float) x / ((float) mapSize - 1.0f)) - 1.0f;
             v = (2.0f * (float) y / ((float) mapSize - 1.0f)) - 1.0f;
@@ -274,7 +258,7 @@ public class EnvMapUtils {
         }
 
         //compute vector depending on the face
-        // Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp	
+        // Code from Nvtt : https://github.com/castano/nvidia-texture-tools/blob/master/src/nvtt/CubeSurface.cpp#L101
         switch (face) {
             case 0:
                 store.set(1f, -v, -u);
@@ -387,62 +371,21 @@ public class EnvMapUtils {
         return face;
     }
 
-    /*
-    public static void main(String... argv) {
-
-//        for (int givenFace = 0; givenFace < 6; givenFace++) {
-//
-//            //int givenFace = 1;
-//            for (int x = 0; x < 128; x++) {
-//                for (int y = 0; y < 128; y++) {
-//                    Vector3f v = EnvMapUtils.getVectorFromCubemapFaceTexCoord(x, y, 128, givenFace, null, FixSeamsMethod.None);
-//                    Vector2f uvs = new Vector2f();
-//                    int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(v, 128, uvs, FixSeamsMethod.None);
-//
-//                    if ((int) uvs.x != x || (int) uvs.y != y) {
-//                        System.err.println("error " + uvs + " should be " + x + "," + y + " vect was " + v);
-//                    }
-//                    if (givenFace != face) {
-//                        System.err.println("error face: " + face + " should be " + givenFace);
-//                    }
-//                }
-//            }
-//        }
-//        System.err.println("done ");
-        int total = 0;
-        for (int i = 0; i < 6; i++) {
-            int size = (int) pow(2, 7 - i);
-            int samples = EnvMapUtils.getSampleFromMip(i, 6);
-            int iterations = (samples * size * size);
-            total += iterations;
-            float roughness = EnvMapUtils.getRoughnessFromMip(i, 6);
-            System.err.println("roughness " + i + " : " + roughness + " , map : " + size + " , samples : " + samples + " , iterations : " + iterations);
-            System.err.println("reverse " + EnvMapUtils.getMipFromRoughness(roughness, 6));
-
-        }
-        System.err.println("total " + total);
-        System.err.println(128 * 128 * 1024);
-        System.err.println("test " + EnvMapUtils.getMipFromRoughness(0.9999f, 6));
-        System.err.println("nb mip = " + (Math.log(128) / Math.log(2) - 1));
-
-    }*/
-
-    public static int getSampleFromMip(int mipLevel, int miptot) {        
-        return mipLevel==0?1:Math.min(1 << (miptot - 1 + (mipLevel) * 2 ), 8192);
+    public static int getSampleFromMip(int mipLevel, int miptot) {
+        return mipLevel == 0 ? 1 : Math.min(1 << (miptot - 1 + (mipLevel) * 2), 8192);
     }
 
-    public static float getRoughnessFromMip(int miplevel, int miptot) {
-        float mipScale = 1.0f;
-        float mipOffset = -0.3f;
 
-        return pow(2, (miplevel - (miptot - 1) + mipOffset) / mipScale);
+    //see lagarde's paper https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
+    //linear roughness
+    public static float getRoughnessFromMip(int miplevel, int miptot) {
+        float step = 1f / ((float) miptot - 1);
+        step *= miplevel;
+        return step * step;
     }
 
     public static float getMipFromRoughness(float roughness, int miptot) {
-        float mipScale = 1.0f;
-        float Lod = (float) (Math.log(roughness) / Math.log(2)) * mipScale + miptot - 1.0f;       
-
-        return (float) Math.max(0.0, Lod);
+        return FastMath.sqrt(roughness) * (miptot - 1);
     }
 
     /**
@@ -482,7 +425,7 @@ public class EnvMapUtils {
         float weight;
 
         if (cubeMap.getImage().getData(0) == null) {
-            throw new IllegalStateException("The cube map must contain Efficient data, if you rendered the cube map on the GPU plase use renderer.readFrameBuffer, to create a CPU image");
+            throw new IllegalStateException("The cube map must contain Efficient data, if you rendered the cube map on the GPU please use renderer.readFrameBuffer, to create a CPU image");
         }
 
         int width = cubeMap.getImage().getWidth();
@@ -539,12 +482,6 @@ public class EnvMapUtils {
         float yV = texelVect.y;
         float zV = texelVect.z;
 
-        float pi = PI;
-        float sqrtPi = sqrt(pi);
-        float sqrt3Pi = sqrt(3f / pi);
-        float sqrt5Pi = sqrt(5f / pi);
-        float sqrt15Pi = sqrt(15f / pi);
-
         float x2 = xV * xV;
         float y2 = yV * yV;
         float z2 = zV * zV;
@@ -558,140 +495,31 @@ public class EnvMapUtils {
         shDir[6] = (sqrt5Pi * (-1f + 3f * z2)) / 4f;
         shDir[7] = -(sqrt15Pi * xV * zV) / 2f;
         shDir[8] = sqrt15Pi * (x2 - y2) / 4f;
-        
-//        shDir[0]  = (1f/(2.f*sqrtPi));
-//
-//	shDir[1]  = -(sqrt(3f/pi)*yV)/2.f;
-//	shDir[2]  = (sqrt(3/pi)*zV)/2.f;
-//	shDir[3]  = -(sqrt(3/pi)*xV)/2.f;
-//
-//	shDir[4]  = (sqrt(15f/pi)*xV*yV)/2.f;
-//	shDir[5]  = -(sqrt(15f/pi)*yV*zV)/2.f;
-//	shDir[6]  = (sqrt(5f/pi)*(-1 + 3f*z2))/4.f;
-//	shDir[7]  = -(sqrt(15f/pi)*xV*zV)/2.f;
-//	shDir[8]  = sqrt(15f/pi)*(x2 - y2)/4.f;
-
-
-    }
-
-    /**
-     * {@link EnvMapUtils#generateIrradianceMap(com.jme3.math.Vector3f[], com.jme3.texture.TextureCubeMap, int, com.jme3.utils.EnvMapUtils.FixSeamsMethod)
-     * }
-     *
-     * @param shCoeffs the spherical harmonics coefficients to use
-     * @param targetMapSize the size of the target map
-     * @return the irradiance map.
-     */
-    public static TextureCubeMap generateIrradianceMap(Vector3f[] shCoeffs, int targetMapSize) {
-        return generateIrradianceMap(shCoeffs, targetMapSize, FixSeamsMethod.Wrap, null);
     }
 
-    /**
-     * Generates the Irradiance map (used for image based difuse lighting) from
-     * Spherical Harmonics coefficients previously computed with
-     * {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap)}
-     * Note that the output cube map is in RGBA8 format.
-     *
-     * @param shCoeffs the SH coeffs
-     * @param targetMapSize the size of the irradiance map to generate
-     * @param fixSeamsMethod the method to fix seams
-     * @param store
-     * @return The irradiance cube map for the given coefficients
-     */
-    public static TextureCubeMap generateIrradianceMap(Vector3f[] shCoeffs, int targetMapSize, FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
-        TextureCubeMap irrCubeMap = store;
-        if (irrCubeMap == null) {
-            irrCubeMap = new TextureCubeMap(targetMapSize, targetMapSize, Image.Format.RGB16F);
-            irrCubeMap.setMagFilter(Texture.MagFilter.Bilinear);
-            irrCubeMap.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
-            irrCubeMap.getImage().setColorSpace(ColorSpace.Linear);
-        }
-
-        for (int i = 0; i < 6; i++) {
-            ByteBuffer buf = BufferUtils.createByteBuffer(targetMapSize * targetMapSize * irrCubeMap.getImage().getFormat().getBitsPerPixel()/8);
-            irrCubeMap.getImage().setData(i, buf);
-        }
-
-        Vector3f texelVect = new Vector3f();
-        ColorRGBA color = new ColorRGBA(ColorRGBA.Black);
-        float[] shDir = new float[9];
-        CubeMapWrapper envMapWriter = new CubeMapWrapper(irrCubeMap);
-        for (int face = 0; face < 6; face++) {
-
-            for (int y = 0; y < targetMapSize; y++) {
-                for (int x = 0; x < targetMapSize; x++) {
-                    getVectorFromCubemapFaceTexCoord(x, y, targetMapSize, face, texelVect, fixSeamsMethod);
-                    evalShBasis(texelVect, shDir);
-                    color.set(0, 0, 0, 0);
-                    for (int i = 0; i < NUM_SH_COEFFICIENT; i++) {
-                        color.set(color.r + shCoeffs[i].x * shDir[i] * shBandFactor[i],
-                                color.g + shCoeffs[i].y * shDir[i] * shBandFactor[i],
-                                color.b + shCoeffs[i].z * shDir[i] * shBandFactor[i],
-                                1.0f);
-                    }
-                    
-                    //clamping the color because very low value close to zero produce artifacts
-                    color.r = Math.max(0.0001f, color.r);
-                    color.g = Math.max(0.0001f, color.g);
-                    color.b = Math.max(0.0001f, color.b);
-                    envMapWriter.setPixel(x, y, face, color);
-                }
-            }
-        }
-        return irrCubeMap;
+    public static void prepareShCoefs(Vector3f[] shCoefs) {
+
+        float coef0 = (1f / (2f * sqrtPi));
+        float coef1 = -sqrt3Pi / 2f;
+        float coef2 = -coef1;
+        float coef3 = coef1;
+        float coef4 = sqrt15Pi / 2f;
+        float coef5 = -coef4;
+        float coef6 = sqrt5Pi / 4f;
+        float coef7 = coef5;
+        float coef8 = sqrt15Pi / 4f;
+
+        shCoefs[0].multLocal(coef0);
+        shCoefs[1].multLocal(coef1);
+        shCoefs[2].multLocal(coef2);
+        shCoefs[3].multLocal(coef3);
+        shCoefs[4].multLocal(coef4);
+        shCoefs[5].multLocal(coef5);
+        shCoefs[6].multLocal(coef6);
+        shCoefs[7].multLocal(coef7);
+        shCoefs[8].multLocal(coef8);
     }
 
-    /**
-     * Generates the prefiltered env map (used for image based specular
-     * lighting) With the GGX/Shlick brdf
-     * {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap)}
-     * Note that the output cube map is in RGBA8 format.
-     *
-     * @param sourceEnvMap
-     * @param targetMapSize the size of the irradiance map to generate
-     * @param store
-     * @param fixSeamsMethod the method to fix seams
-     * @return The irradiance cube map for the given coefficients
-     */
-    public static TextureCubeMap generatePrefilteredEnvMap(TextureCubeMap sourceEnvMap, int targetMapSize, FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
-        TextureCubeMap pem = store;
-        if (pem == null) {
-            pem = new TextureCubeMap(targetMapSize, targetMapSize, Image.Format.RGB16F);
-            pem.setMagFilter(Texture.MagFilter.Bilinear);
-            pem.setMinFilter(Texture.MinFilter.Trilinear);
-            pem.getImage().setColorSpace(ColorSpace.Linear);
-        }
-
-        int nbMipMap = (int) (Math.log(targetMapSize) / Math.log(2) - 1);
-
-        CubeMapWrapper sourceWrapper = new CubeMapWrapper(sourceEnvMap);
-        CubeMapWrapper targetWrapper = new CubeMapWrapper(pem);
-        targetWrapper.initMipMaps(nbMipMap);
-
-        Vector3f texelVect = new Vector3f();
-        Vector3f color = new Vector3f();
-        ColorRGBA outColor = new ColorRGBA();
-        for (int mipLevel = 0; mipLevel < nbMipMap; mipLevel++) {
-            System.err.println("mip level " + mipLevel);
-            float roughness = getRoughnessFromMip(mipLevel, nbMipMap);
-            int nbSamples = getSampleFromMip(mipLevel, nbMipMap);
-            int targetMipMapSize = (int) pow(2, nbMipMap + 1 - mipLevel);
-            for (int face = 0; face < 6; face++) {
-                System.err.println("face " + face);
-                for (int y = 0; y < targetMipMapSize; y++) {
-                    for (int x = 0; x < targetMipMapSize; x++) {
-                        color.set(0, 0, 0);
-                        getVectorFromCubemapFaceTexCoord(x, y, targetMipMapSize, face, texelVect, FixSeamsMethod.Wrap);
-                        prefilterEnvMapTexel(sourceWrapper, roughness, texelVect, nbSamples, color);
-                        outColor.set(color.x, color.y, color.z, 1.0f);
-                        // System.err.println("coords " + x + "," + y);
-                        targetWrapper.setPixel(x, y, face, mipLevel, outColor);
-                    }
-                }
-            }
-        }
-        return pem;
-    }
 
     public static Vector4f getHammersleyPoint(int i, final int nbrSample, Vector4f store) {
         if (store == null) {
@@ -719,43 +547,6 @@ public class EnvMapUtils {
         return store;
     }
 
-    private static Vector3f prefilterEnvMapTexel(CubeMapWrapper envMapReader, float roughness, Vector3f N, int numSamples, Vector3f store) {        
-
-        Vector3f prefilteredColor = store;
-        float totalWeight = 0.0f;
-
-        TempVars vars = TempVars.get();
-        Vector4f Xi = vars.vect4f1;
-        Vector3f H = vars.vect1;
-        Vector3f tmp = vars.vect2;
-        ColorRGBA c = vars.color;
-        // a = roughness² and a2 = a²
-        float a2 = roughness * roughness;
-        a2 *= a2;
-        a2 *= 10;
-        for (int i = 0; i < numSamples; i++) {
-            Xi = getHammersleyPoint(i, numSamples, Xi);            
-            H = importanceSampleGGX(Xi, a2, N, H, vars);
-
-            H.normalizeLocal();
-            tmp.set(H);
-            float NoH = N.dot(tmp);
-
-            Vector3f L = tmp.multLocal(NoH * 2).subtractLocal(N);
-            float NoL = clamp(N.dot(L), 0.0f, 1.0f);
-            if (NoL > 0) {
-                envMapReader.getPixel(L, c);
-                prefilteredColor.setX(prefilteredColor.x + c.r * NoL);
-                prefilteredColor.setY(prefilteredColor.y + c.g * NoL);
-                prefilteredColor.setZ(prefilteredColor.z + c.b * NoL);
-
-                totalWeight += NoL;
-            }
-        }
-        vars.release();
-        return prefilteredColor.divideLocal(totalWeight);
-    }
-
     public static Vector3f importanceSampleGGX(Vector4f xi, float a2, Vector3f normal, Vector3f store, TempVars vars) {
         if (store == null) {
             store = new Vector3f();
@@ -945,3 +736,6 @@ public class EnvMapUtils {
         return pem;
     }
 }
+
+
+

+ 1 - 39
jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java

@@ -61,26 +61,10 @@ public class LightsDebugState extends BaseAppState {
     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");
@@ -114,11 +98,7 @@ public class LightsDebugState extends BaseAppState {
                     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());
-                        }
+                        m.setTexture("CubeMap", probe.getPrefilteredEnvMap());
                     }
                     n.setLocalTranslation(probe.getPosition());
                     n.getChild(1).setLocalScale(((BoundingSphere) probe.getBounds()).getRadius());
@@ -161,24 +141,6 @@ public class LightsDebugState extends BaseAppState {
         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

+ 33 - 28
jme3-core/src/main/java/com/jme3/light/LightProbe.java

@@ -50,17 +50,19 @@ import com.jme3.scene.Spatial;
 import com.jme3.texture.TextureCubeMap;
 import com.jme3.util.TempVars;
 import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 /**
  * 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).
+ * There are two environment data structure  held by the LightProbe :
+ * - The irradiance spherical harmonics factors (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)}
+ * Note that when instantiating the LightProbe, both of those structures are null.
+ * To compute 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).
@@ -75,12 +77,15 @@ import java.io.IOException;
  */
 public class LightProbe extends Light implements Savable {
 
-    private TextureCubeMap irradianceMap;
+    private static final Logger logger = Logger.getLogger(LightProbe.class.getName());
+
+    private Vector3f[] shCoeffs;
     private TextureCubeMap prefilteredEnvMap;
     private BoundingVolume bounds = new BoundingSphere(1.0f, Vector3f.ZERO);
     private boolean ready = false;
     private Vector3f position = new Vector3f();
     private Node debugNode;
+    private int nbMipMaps;
 
     /**
      * Empty constructor used for serialization. 
@@ -89,23 +94,6 @@ public class LightProbe extends Light implements Savable {
     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
@@ -127,7 +115,7 @@ public class LightProbe extends Light implements Savable {
     public void write(JmeExporter ex) throws IOException {
         super.write(ex);
         OutputCapsule oc = ex.getCapsule(this);
-        oc.write(irradianceMap, "irradianceMap", null);
+        oc.write(shCoeffs, "shCoeffs", null);
         oc.write(prefilteredEnvMap, "prefilteredEnvMap", null);
         oc.write(position, "position", null);
         oc.write(bounds, "bounds", new BoundingSphere(1.0f, Vector3f.ZERO));
@@ -138,11 +126,15 @@ public class LightProbe extends Light implements Savable {
     public void read(JmeImporter im) throws IOException {
         super.read(im);
         InputCapsule ic = im.getCapsule(this);
-        irradianceMap = (TextureCubeMap) ic.readSavable("irradianceMap", null);
+        shCoeffs = (Vector3f[]) ic.readSavableArray("shCoeffs", null);
         prefilteredEnvMap = (TextureCubeMap) ic.readSavable("prefilteredEnvMap", null);
         position = (Vector3f) ic.readSavable("position", this);
         bounds = (BoundingVolume) ic.readSavable("bounds", new BoundingSphere(1.0f, Vector3f.ZERO));
         ready = ic.readBoolean("ready", false);
+        if (shCoeffs == null) {
+            ready = false;
+            logger.log(Level.WARNING, "LightProbe is missing parameters, it should be recomputed. Please use lightProbeFactory.updateProbe()");
+        }
     }
 
     /**
@@ -194,14 +186,11 @@ public class LightProbe extends Light implements Savable {
      */
     public Node getDebugGui(AssetManager manager) {
         if (!ready) {
-            throw new UnsupportedOperationException("This EnvProbeis not ready yet, try to test isReady()");
+            throw new UnsupportedOperationException("This EnvProbe is 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);
         }
@@ -209,6 +198,14 @@ public class LightProbe extends Light implements Savable {
         return debugNode;
     }
 
+    public Vector3f[] getShCoeffs() {
+        return shCoeffs;
+    }
+
+    public void setShCoeffs(Vector3f[] shCoeffs) {
+        this.shCoeffs = shCoeffs;
+    }
+
     /**
      * Returns the position of the LightProbe in world space
      * @return the wolrd space position
@@ -226,6 +223,14 @@ public class LightProbe extends Light implements Savable {
         getBounds().setCenter(position);
     }
 
+    public int getNbMipMaps() {
+        return nbMipMaps;
+    }
+
+    public void setNbMipMaps(int nbMipMaps) {
+        this.nbMipMaps = nbMipMaps;
+    }
+
     @Override
     public boolean intersectsBox(BoundingBox box, TempVars vars) {
         return getBounds().intersectsBoundingBox(box);

+ 6 - 7
jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java

@@ -118,7 +118,9 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
         Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
         Uniform lightProbeData = shader.getUniform("g_LightProbeData");
         lightProbeData.setVector4Length(1);
-        Uniform lightProbeIrrMap = shader.getUniform("g_IrradianceMap");
+
+        //TODO These 2 uniforms should be packed in an array, to ba able to have several probes and blend between them.
+        Uniform shCoeffs = shader.getUniform("g_ShCoeffs");
         Uniform lightProbePemMap = shader.getUniform("g_PrefEnvMap");
 
         lightProbe = null;
@@ -131,16 +133,13 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
             ambientColor.setValue(VarType.Vector4, ambientLightColor);
         }
 
-        //If there is a lightProbe in the list we force it's render on the first pass
+        //If there is a lightProbe in the list we force its render on the first pass
         if(lightProbe != null){
             BoundingSphere s = (BoundingSphere)lightProbe.getBounds();
-            lightProbeData.setVector4InArray(lightProbe.getPosition().x, lightProbe.getPosition().y, lightProbe.getPosition().z, 1f/s.getRadius(), 0);
+            lightProbeData.setVector4InArray(lightProbe.getPosition().x, lightProbe.getPosition().y, lightProbe.getPosition().z, 1f / s.getRadius() + lightProbe.getNbMipMaps(), 0);
+            shCoeffs.setValue(VarType.Vector3Array, lightProbe.getShCoeffs());
             //assigning new texture indexes
-            int irrUnit = lastTexUnit++;
             int pemUnit = lastTexUnit++;
-
-            rm.getRenderer().setTexture(irrUnit, lightProbe.getIrradianceMap());
-            lightProbeIrrMap.setValue(VarType.Int, irrUnit);
             rm.getRenderer().setTexture(pemUnit, lightProbe.getPrefilteredEnvMap());
             lightProbePemMap.setValue(VarType.Int, pemUnit);
         } else {

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

@@ -24,7 +24,7 @@ varying vec3 wPosition;
 #ifdef INDIRECT_LIGHTING
 //  uniform sampler2D m_IntegrateBRDF;
   uniform samplerCube g_PrefEnvMap;
-  uniform samplerCube g_IrradianceMap;
+  uniform vec3 g_ShCoeffs[9];
   uniform vec4 g_LightProbeData;
 #endif
 
@@ -236,7 +236,7 @@ void main(){
                             lightColor.rgb,specular, Roughness, ndotv,
                             directDiffuse,  directSpecular);
 
-        vec3 directLighting = diffuseColor.rgb *directDiffuse + directSpecular * specularColor.rgb;
+        vec3 directLighting = diffuseColor.rgb *directDiffuse + directSpecular;
         
         gl_FragColor.rgb += directLighting * fallOff;
     }
@@ -244,20 +244,22 @@ void main(){
     #ifdef INDIRECT_LIGHTING
         vec3 rv = reflect(-viewDir.xyz, normal.xyz);
         //prallax fix for spherical bounds from https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/
-        // g_LightProbeData.w is 1/probe radius, g_LightProbeData.xyz is the position of the lightProbe.
-        rv = g_LightProbeData.w * (wPosition - g_LightProbeData.xyz) +rv;
+        // g_LightProbeData.w is 1/probe radius + nbMipMaps, g_LightProbeData.xyz is the position of the lightProbe.
+        float invRadius = fract( g_LightProbeData.w);
+        float nbMipMaps = g_LightProbeData.w - invRadius;
+        rv = invRadius * (wPosition - g_LightProbeData.xyz) +rv;
 
          //horizon fade from http://marmosetco.tumblr.com/post/81245981087
         float horiz = dot(rv, wNormal.xyz);
-        float horizFadePower= 1.0 - Roughness;
+        float horizFadePower = 1.0 - Roughness;
         horiz = clamp( 1.0 + horizFadePower * horiz, 0.0, 1.0 );
         horiz *= horiz;
 
         vec3 indirectDiffuse = vec3(0.0);
         vec3 indirectSpecular = vec3(0.0);
-        indirectDiffuse = textureCube(g_IrradianceMap, normal.xyz).rgb * diffuseColor.rgb;
-
-        indirectSpecular = ApproximateSpecularIBLPolynomial(g_PrefEnvMap, specularColor.rgb, Roughness, ndotv, rv.xyz);
+        indirectDiffuse = sphericalHarmonics(normal.xyz, g_ShCoeffs) * diffuseColor.rgb;
+        vec3 dominantR = getSpecularDominantDir( normal, rv.xyz, Roughness*Roughness );
+        indirectSpecular = ApproximateSpecularIBLPolynomial(g_PrefEnvMap, specularColor.rgb, Roughness, ndotv, dominantR, nbMipMaps);
         indirectSpecular *= vec3(horiz);
 
         vec3 indirectLighting =  indirectDiffuse + indirectSpecular;
@@ -273,7 +275,6 @@ void main(){
         #endif
         gl_FragColor += emissive * pow(emissive.a, m_EmissivePower) * m_EmissiveIntensity;
     #endif
-           
     gl_FragColor.a = alpha;
    
 }

+ 47 - 32
jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib

@@ -53,6 +53,29 @@ void PBR_ComputeDirectLightSpecWF(vec3 normal, vec3 lightDir, vec3 viewDir,
     outSpecular = fresnel * vec3(specular) * lightColor;
 }
 
+vec3 sphericalHarmonics( const in vec3 normal, const vec3 sph[9] ){
+    float x = normal.x;
+    float y = normal.y;
+    float z = normal.z;
+
+    vec3 result = (
+        sph[0] +
+
+        sph[1] * y +
+        sph[2] * z +
+        sph[3] * x +
+
+        sph[4] * y * x +
+        sph[5] * y * z +
+        sph[6] * (3.0 * z * z - 1.0) +
+        sph[7] * (z * x) +
+        sph[8] * (x*x - y*y)
+    );
+
+    return max(result, vec3(0.0));
+}
+
+
 void PBR_ComputeDirectLight(vec3 normal, vec3 lightDir, vec3 viewDir,
                             vec3 lightColor, float fZero, float roughness, float ndotv,
                             out vec3 outDiffuse, out vec3 outSpecular){
@@ -106,46 +129,38 @@ void PBR_ComputeDirectLight(vec3 normal, vec3 lightDir, vec3 viewDir,
     outSpecular = vec3(specular) * lightColor;
 }
 
-//https://knarkowicz.wordpress.com/2014/12/27/analytical-dfg-term-for-ibl/
-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( 2.5 / (roughness) * specularColor.y, 0.0, 1.0 );
-    return specularColor * scale + bias;
+vec3 integrateBRDFApprox( const in vec3 specular, float roughness, float NoV ){
+    const vec4 c0 = vec4( -1, -0.0275, -0.572, 0.022 );
+    const vec4 c1 = vec4( 1, 0.0425, 1.04, -0.04 );
+    vec4 r = roughness * c0 + c1;
+    float a004 = min( r.x * r.x, exp2( -9.28 * NoV ) ) * r.x + r.y;
+    vec2 AB = vec2( -1.04, 1.04 ) * a004 + r.zw;
+    return specular * AB.x + AB.y;
+}
+
+// from Sebastien Lagarde https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf page 69
+vec3 getSpecularDominantDir(const in vec3 N, const in vec3 R, const in float realRoughness){
+    vec3 dominant;
+
+    float smoothness = 1.0 - realRoughness;
+    float lerpFactor = smoothness * (sqrt(smoothness) + realRoughness);
+    // The result is not normalized as we fetch in a cubemap
+    dominant = mix(N, R, lerpFactor);
+
+    return dominant;
 }
 
-vec3 ApproximateSpecularIBL(samplerCube envMap,sampler2D integrateBRDF, vec3 SpecularColor , float Roughness, float ndotv, vec3 refVec){
-    //TODO magic values should be replaced by defines.
-    float Lod = log2(Roughness) * 1.1 + 6.0 - 2.0;
+vec3 ApproximateSpecularIBL(samplerCube envMap,sampler2D integrateBRDF, vec3 SpecularColor , float Roughness, float ndotv, vec3 refVec, float nbMipMaps){
+    float Lod = sqrt( Roughness ) * (nbMipMaps - 1.0);
     vec3 PrefilteredColor =  textureCubeLod(envMap, refVec.xyz,Lod).rgb;
     vec2 EnvBRDF = texture2D(integrateBRDF,vec2(Roughness, ndotv)).rg;
     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.6 + 5.0;
+vec3 ApproximateSpecularIBLPolynomial(samplerCube envMap, vec3 SpecularColor , float Roughness, float ndotv, vec3 refVec, float nbMipMaps){
+    float Lod = sqrt( Roughness ) * (nbMipMaps - 1.0);
     vec3 PrefilteredColor =  textureCubeLod(envMap, refVec.xyz, Lod).rgb;
-    return PrefilteredColor * EnvDFGPolynomial(SpecularColor, Roughness, ndotv);
+    return PrefilteredColor * integrateBRDFApprox(SpecularColor, Roughness, ndotv);
 }
 
 

+ 19 - 22
jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java

@@ -18,7 +18,7 @@ import com.jme3.scene.Geometry;
 import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
 import com.jme3.ui.Picture;
-import com.jme3.util.MaterialDebugAppState;
+import com.jme3.util.SkyFactory;
 
 /**
  * test
@@ -29,8 +29,7 @@ public class RefEnv extends SimpleApplication {
 
     private Node tex;
     private Node ref;
-    private Picture refDE;
-    private Picture refM;
+    private Picture refImg;
 
     public static void main(String[] args) {
         RefEnv app = new RefEnv();
@@ -40,25 +39,25 @@ public class RefEnv extends SimpleApplication {
     @Override
     public void simpleInitApp() {
 
-        cam.setLocation(new Vector3f(-2.3324413f, 2.9567573f, 4.6054406f));
-        cam.setRotation(new Quaternion(0.06310794f, 0.9321281f, -0.29613864f, 0.1986369f));
-        Spatial sc = assetManager.loadModel("Scenes/PBR/spheres.j3o");
+        cam.setLocation(new Vector3f(-17.713732f, 1.8661976f, 17.156784f));
+        cam.setRotation(new Quaternion(0.021403445f, 0.9428821f, -0.06178002f, 0.32664734f));
+        flyCam.setDragToRotate(true);
+        flyCam.setMoveSpeed(5);
+        Spatial sc = assetManager.loadModel("Models/gltf/ref/scene.gltf");
         rootNode.attachChild(sc);
-        rootNode.getChild("Scene").setCullHint(Spatial.CullHint.Always);
+        Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap);
+        rootNode.attachChild(sky);
+        rootNode.getChild(0).setCullHint(Spatial.CullHint.Always);
 
         ref = new Node("reference pictures");
-        refDE = new Picture("refDE");
-        refDE.setHeight(cam.getHeight());
-        refDE.setWidth(cam.getWidth());
-        refDE.setImage(assetManager,"jme3test/light/pbr/spheresRefDE.png", false);
-        refM = new Picture("refM");
-        refM.setImage(assetManager,"jme3test/light/pbr/spheresRefM.png", false);
-        refM.setHeight(cam.getHeight());
-        refM.setWidth(cam.getWidth());
+        refImg = new Picture("refImg");
+        refImg.setHeight(cam.getHeight());
+        refImg.setWidth(cam.getWidth());
+        refImg.setImage(assetManager, "jme3test/light/pbr/ref.png", false);
 
-        ref.attachChild(refDE);
+        ref.attachChild(refImg);
 
-        stateManager.attach(new EnvironmentCamera());
+        stateManager.attach(new EnvironmentCamera(256, Vector3f.ZERO));
 
         inputManager.addMapping("tex", new KeyTrigger(KeyInput.KEY_SPACE));
         inputManager.addMapping("switch", new KeyTrigger(KeyInput.KEY_RETURN));
@@ -105,13 +104,11 @@ public class RefEnv extends SimpleApplication {
             if (((Float) mat.getParam("Metallic").getValue()) == 1f) {
                 mat.setFloat("Metallic", 0);
                 mat.setColor("BaseColor", ColorRGBA.Black);
-                ref.attachChild(refDE);
-                refM.removeFromParent();
+                ref.attachChild(refImg);
             } else {
                 mat.setFloat("Metallic", 1);
                 mat.setColor("BaseColor", ColorRGBA.White);
-                ref.attachChild(refM);
-                refDE.removeFromParent();
+                refImg.removeFromParent();
             }
         }
     }
@@ -130,7 +127,7 @@ public class RefEnv extends SimpleApplication {
                     System.err.println("Done rendering env maps");
                     tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(result.getPrefilteredEnvMap(), assetManager);
                   //  guiNode.attachChild(tex);
-                    rootNode.getChild("Scene").setCullHint(Spatial.CullHint.Dynamic);
+                    rootNode.getChild(0).setCullHint(Spatial.CullHint.Dynamic);
                 }
             });
             ((BoundingSphere) probe.getBounds()).setRadius(100);

+ 136 - 0
jme3-examples/src/main/java/jme3test/light/pbr/TestPBRDirectLighting.java

@@ -0,0 +1,136 @@
+/*
+ * 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.ChaseCameraAppState;
+import com.jme3.app.SimpleApplication;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.environment.EnvironmentCamera;
+import com.jme3.environment.LightProbeFactory;
+import com.jme3.environment.generation.JobProgressAdapter;
+import com.jme3.environment.util.EnvMapUtils;
+import com.jme3.environment.util.LightsDebugState;
+import com.jme3.input.ChaseCamera;
+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.light.LightProbe;
+import com.jme3.material.Material;
+import com.jme3.math.*;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.ToneMapFilter;
+import com.jme3.scene.*;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.texture.plugins.ktx.KTXLoader;
+import com.jme3.util.MaterialDebugAppState;
+import com.jme3.util.SkyFactory;
+import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
+
+/**
+ * A test case for PBR lighting.
+ * Still experimental.
+ *
+ * @author nehon
+ */
+public class TestPBRDirectLighting extends SimpleApplication {
+
+    public static void main(String[] args) {
+        TestPBRDirectLighting app = new TestPBRDirectLighting();
+        app.start();
+    }
+
+    private DirectionalLight dl;
+
+    private float roughness = 0.0f;
+
+    @Override
+    public void simpleInitApp() {
+
+
+        viewPort.setBackgroundColor(ColorRGBA.DarkGray);
+
+        dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
+        rootNode.addLight(dl);
+        dl.setColor(ColorRGBA.White);
+
+        ChaseCameraAppState chaser = new ChaseCameraAppState();
+        chaser.setDragToRotate(true);
+        chaser.setMinVerticalRotation(-FastMath.HALF_PI);
+        chaser.setMaxDistance(1000);
+        chaser.setInvertVerticalAxis(true);
+        getStateManager().attach(chaser);
+        chaser.setTarget(rootNode);
+        flyCam.setEnabled(false);
+
+        Geometry sphere = new Geometry("sphere", new Sphere(32, 32, 1));
+        final Material m = new Material(assetManager, "Common/MatDefs/Light/PBRLighting.j3md");
+        m.setColor("BaseColor", ColorRGBA.Black);
+        m.setFloat("Metallic", 0f);
+        m.setFloat("Roughness", roughness);
+        sphere.setMaterial(m);
+        rootNode.attachChild(sphere);
+
+        inputManager.addListener(new ActionListener() {
+            @Override
+            public void onAction(String name, boolean isPressed, float tpf) {
+
+                if (name.equals("rup") && isPressed) {
+                    roughness = FastMath.clamp(roughness + 0.1f, 0.0f, 1.0f);
+                    m.setFloat("Roughness", roughness);
+                }
+                if (name.equals("rdown") && isPressed) {
+                    roughness = FastMath.clamp(roughness - 0.1f, 0.0f, 1.0f);
+                    m.setFloat("Roughness", roughness);
+                }
+
+                if (name.equals("light") && isPressed) {
+                    dl.setDirection(cam.getDirection().normalize());
+                }
+            }
+        }, "light", "rup", "rdown");
+
+
+        inputManager.addMapping("light", new KeyTrigger(KeyInput.KEY_F));
+        inputManager.addMapping("rup", new KeyTrigger(KeyInput.KEY_UP));
+        inputManager.addMapping("rdown", new KeyTrigger(KeyInput.KEY_DOWN));
+
+
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+    }
+
+}
+

+ 30 - 21
jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java

@@ -73,13 +73,13 @@ public class TestPBRLighting extends SimpleApplication {
     }
 
     private Node tex;
-    private Node tex2;
 
     private Geometry model;
     private DirectionalLight dl;
     private Node modelNode;
-    private int frame = 0;   
-    private Material pbrMat;    
+    private int frame = 0;
+    private Material pbrMat;
+    private float roughness = 1.0f;
 
     @Override
     public void simpleInitApp() {
@@ -97,7 +97,7 @@ public class TestPBRLighting extends SimpleApplication {
         dl.setColor(ColorRGBA.White);
         rootNode.attachChild(modelNode);
 
-      
+
         FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
 //        fpp.addFilter(new FXAAFilter());
         fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(4.0f)));
@@ -114,17 +114,17 @@ public class TestPBRLighting extends SimpleApplication {
         model.setMaterial(pbrMat);
 
 
-        final EnvironmentCamera envCam = new EnvironmentCamera(128, new Vector3f(0, 3f, 0));
+        final EnvironmentCamera envCam = new EnvironmentCamera(256, new Vector3f(0, 3f, 0));
         stateManager.attach(envCam);
-        
+
 //        EnvironmentManager envManager = new EnvironmentManager();
 //        stateManager.attach(envManager);
-        
- //       envManager.setScene(rootNode);
-        
+
+        //       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);
@@ -142,16 +142,23 @@ public class TestPBRLighting extends SimpleApplication {
                     if (tex == null) {
                         return;
                     }
-                    if (tex.getParent() == null && tex2.getParent() == null) {
+                    if (tex.getParent() == null) {
                         guiNode.attachChild(tex);
-                    } else if (tex2.getParent() == null){
-                        tex.removeFromParent();
-                        guiNode.attachChild(tex2);
                     } else {
-                        tex2.removeFromParent();
+                        tex.removeFromParent();
                     }
                 }
 
+                if (name.equals("rup") && isPressed) {
+                    roughness = FastMath.clamp(roughness + 0.1f, 0.0f, 1.0f);
+                    pbrMat.setFloat("Roughness", roughness);
+                }
+                if (name.equals("rdown") && isPressed) {
+                    roughness = FastMath.clamp(roughness - 0.1f, 0.0f, 1.0f);
+                    pbrMat.setFloat("Roughness", roughness);
+                }
+
+
                 if (name.equals("up") && isPressed) {
                     model.move(0, tpf * 100f, 0);
                 }
@@ -169,7 +176,7 @@ public class TestPBRLighting extends SimpleApplication {
                     dl.setDirection(cam.getDirection().normalize());
                 }
             }
-        }, "toggle", "light", "up", "down", "left", "right", "debug");
+        }, "toggle", "light", "up", "down", "left", "right", "debug", "rup", "rdown");
 
         inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_RETURN));
         inputManager.addMapping("light", new KeyTrigger(KeyInput.KEY_F));
@@ -178,10 +185,13 @@ 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));
-        
-        
+        inputManager.addMapping("rup", new KeyTrigger(KeyInput.KEY_T));
+        inputManager.addMapping("rdown", new KeyTrigger(KeyInput.KEY_G));
+
+
         MaterialDebugAppState debug = new MaterialDebugAppState();
         debug.registerBinding("Common/MatDefs/Light/PBRLighting.frag", rootNode);
+        debug.registerBinding("Common/ShaderLib/PBR.glsllib", rootNode);
         getStateManager().attach(debug);
 
     }
@@ -198,13 +208,12 @@ public class TestPBRLighting extends SimpleApplication {
                 public void done(LightProbe result) {
                     System.err.println("Done rendering env maps");
                     tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(result.getPrefilteredEnvMap(), assetManager);
-                    tex2 = EnvMapUtils.getCubeMapCrossDebugView(result.getIrradianceMap(), assetManager);
                 }
             });
-            ((BoundingSphere)probe.getBounds()).setRadius(100);
+            ((BoundingSphere) probe.getBounds()).setRadius(100);
             rootNode.addLight(probe);
             //getStateManager().getState(EnvironmentManager.class).addEnvProbe(probe);
-            
+
         }
         if (frame > 10 && modelNode.getParent() == null) {
             rootNode.attachChild(modelNode);

+ 1 - 10
jme3-examples/src/main/java/jme3test/light/pbr/TestPbrEnv.java

@@ -306,16 +306,7 @@ public class TestPbrEnv extends SimpleApplication implements ActionListener {
         }
 
         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);
-            }
-
+            debugState.setEnabled(!debugState.isEnabled());
         }
         
         if (name.equals("debugTex") && keyPressed) {

+ 28 - 24
jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java

@@ -54,7 +54,7 @@ public class TestGltfLoading extends SimpleApplication {
     Node probeNode;
     float time = 0;
     int assetIndex = 0;
-    boolean useAutoRotate = true;
+    boolean useAutoRotate = false;
     private final static String indentString = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
     int duration = 2;
     boolean playAnim = true;
@@ -105,25 +105,27 @@ public class TestGltfLoading extends SimpleApplication {
 //        PointLight pl1 = new PointLight(new Vector3f(-5.0f, -5.0f, -5.0f), ColorRGBA.White.mult(0.5f), 50);
 //        rootNode.addLight(pl1);
 
-        loadModel("Models/gltf/box/box.gltf", Vector3f.ZERO, 1);
-        loadModel("Models/gltf/duck/Duck.gltf", new Vector3f(0, -1, 0), 1);
-        loadModel("Models/gltf/damagedHelmet/damagedHelmet.gltf", Vector3f.ZERO, 1);
-        loadModel("Models/gltf/hornet/scene.gltf", new Vector3f(0, -0.5f, 0), 0.4f);
-//        loadModel("Models/gltf/adamHead/adamHead.gltf", Vector3f.ZERO, 0.6f);
-        loadModel("Models/gltf/busterDrone/busterDrone.gltf", new Vector3f(0, 0f, 0), 0.8f);
-        loadModel("Models/gltf/animatedCube/AnimatedCube.gltf", Vector3f.ZERO, 0.5f);
-
-        //TODO need to pad tracks that doesn't have the same length than the animation.
-        //loadModel("Models/gltf/BoxAnimated/BoxAnimated.gltf", new Vector3f(0, 0f, 0), 0.8f);
-
-        loadModel("Models/gltf/RiggedFigure/RiggedSimple.gltf", new Vector3f(0, -0.3f, 0), 0.2f);
-        loadModel("Models/gltf/RiggedFigure/RiggedFigure.gltf", new Vector3f(0, -1f, 0), 1f);
-        loadModel("Models/gltf/CesiumMan/CesiumMan.gltf", new Vector3f(0, -1, 0), 1f);
-        loadModel("Models/gltf/BrainStem/BrainStem.gltf", new Vector3f(0, -1, 0), 1f);
+//        loadModel("Models/gltf/box/box.gltf", Vector3f.ZERO, 1);
+//        loadModel("Models/gltf/duck/Duck.gltf", new Vector3f(0, -1, 0), 1);
+//        loadModel("Models/gltf/damagedHelmet/damagedHelmet.gltf", Vector3f.ZERO, 1);
+//        loadModel("Models/gltf/hornet/scene.gltf", new Vector3f(0, -0.5f, 0), 0.4f);
+////        loadModel("Models/gltf/adamHead/adamHead.gltf", Vector3f.ZERO, 0.6f);
+        //      loadModel("Models/gltf/busterDrone/busterDrone.gltf", new Vector3f(0, 0f, 0), 0.8f);
+//        loadModel("Models/gltf/animatedCube/AnimatedCube.gltf", Vector3f.ZERO, 0.5f);
+//
+//        //loadModel("Models/gltf/BoxAnimated/BoxAnimated.gltf", new Vector3f(0, 0f, 0), 0.8f);
+//
+//        loadModel("Models/gltf/RiggedFigure/RiggedSimple.gltf", new Vector3f(0, -0.3f, 0), 0.2f);
+        //loadModel("Models/gltf/RiggedFigure/RiggedFigure.gltf", new Vector3f(0, -1f, 0), 1f);
+        //loadModel("Models/gltf/CesiumMan/CesiumMan.gltf", new Vector3f(0, -1, 0), 1f);
+        //loadModel("Models/gltf/BrainStem/BrainStem.gltf", new Vector3f(0, -1, 0), 1f);
         //loadModel("Models/gltf/Jaime/Jaime.gltf", new Vector3f(0, -1, 0), 1f);
-        //loadModel("Models/gltf/GiantWorm/GiantWorm.gltf", new Vector3f(0, -1, 0), 1f);
-        //loadModel("Models/gltf/RiggedFigure/WalkingLady.gltf", new Vector3f(0, -0.f, 0), 1f);
-        loadModel("Models/gltf/Monster/Monster.gltf", Vector3f.ZERO, 0.03f);
+//        loadModel("Models/gltf/GiantWorm/GiantWorm.gltf", new Vector3f(0, -1, 0), 1f);
+//        //loadModel("Models/gltf/RiggedFigure/WalkingLady.gltf", new Vector3f(0, -0.f, 0), 1f);
+//        loadModel("Models/gltf/Monster/Monster.gltf", Vector3f.ZERO, 0.03f);
+
+//        loadModel("Models/gltf/corset/Corset.gltf", new Vector3f(0, -1, 0), 20f);
+        loadModel("Models/gltf/boxInter/BoxInterleaved.gltf", new Vector3f(0, 0, 0), 1f);
 
 
         probeNode.attachChild(assets.get(0));
@@ -193,12 +195,14 @@ public class TestGltfLoading extends SimpleApplication {
             playFirstAnim(s);
         }
 
-//        SkeletonControl ctrl = findControl(s, SkeletonControl.class);
+        SkeletonControl ctrl = findControl(s, SkeletonControl.class);
+
 //        //ctrl.getSpatial().removeControl(ctrl);
-//        if (ctrl == null) {
-//            return;
-//        }
-//        getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(ctrl, true);
+        if (ctrl == null) {
+            return;
+        }
+        ctrl.setHardwareSkinningPreferred(false);
+        getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(ctrl, true);
 //        AnimControl aCtrl = findControl(s, AnimControl.class);
 //        //ctrl.getSpatial().removeControl(ctrl);
 //        if (aCtrl == null) {

TEMPAT SAMPAH
jme3-examples/src/main/resources/jme3test/light/pbr/ref.png


TEMPAT SAMPAH
jme3-examples/src/main/resources/jme3test/light/pbr/spheresRefDE.png


TEMPAT SAMPAH
jme3-examples/src/main/resources/jme3test/light/pbr/spheresRefM.png