Parcourir la source

Dropped the use of the irradiance Map for LightProbes, only the spherical harmonics coeffs are stored and used to recompose indirect diffuse color in the shader.
Also added a tweak to get the specular dominant direction when sampling the prefiltered env maps. It gives better result on low roughness materials.

Nehon il y a 8 ans
Parent
commit
e6a55e9d3a

+ 11 - 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]);
         }
     }

+ 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 Irradiance 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;
+            }
+        });
+    }
+
+}

+ 26 - 27
jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java

@@ -42,19 +42,20 @@ import static com.jme3.math.FastMath.pow;
 import static com.jme3.math.FastMath.sqrt;
 
 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.None);
+                    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);
 

+ 34 - 29
jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java

@@ -59,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
@@ -79,7 +85,7 @@ public class EnvMapUtils {
         /**
          * No seams fix
          */
-        None;
+        None
     }
 
     /**
@@ -111,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);
@@ -154,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());
@@ -196,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)]
@@ -438,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();
@@ -495,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;
@@ -514,9 +495,32 @@ public class EnvMapUtils {
         shDir[6] = (sqrt5Pi * (-1f + 3f * z2)) / 4f;
         shDir[7] = -(sqrt15Pi * xV * zV) / 2f;
         shDir[8] = sqrt15Pi * (x2 - y2) / 4f;
-        
     }
 
+    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);
+    }
+
+
     public static Vector4f getHammersleyPoint(int i, final int nbrSample, Vector4f store) {
         if (store == null) {
             store = new Vector4f();
@@ -734,3 +738,4 @@ public class EnvMapUtils {
 }
 
 
+

+ 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

+ 24 - 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,7 +77,9 @@ 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;
@@ -90,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
@@ -128,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));
@@ -139,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()");
+        }
     }
 
     /**
@@ -195,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);
         }
@@ -210,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

+ 4 - 5
jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java

@@ -115,7 +115,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;
@@ -132,12 +134,9 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
         if(lightProbe != null){
             BoundingSphere s = (BoundingSphere)lightProbe.getBounds();
             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 {

+ 4 - 4
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
 
@@ -257,9 +257,9 @@ void main(){
 
         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, nbMipMaps);
+        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;

+ 35 - 0
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){
@@ -115,6 +138,18 @@ vec3 integrateBRDFApprox( const in vec3 specular, float roughness, float NoV ){
     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, float nbMipMaps){
     float Lod = sqrt( Roughness ) * (nbMipMaps - 1.0);
     vec3 PrefilteredColor =  textureCubeLod(envMap, refVec.xyz,Lod).rgb;

+ 5 - 9
jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java

@@ -73,7 +73,6 @@ public class TestPBRLighting extends SimpleApplication {
     }
 
     private Node tex;
-    private Node tex2;
 
     private Geometry model;
     private DirectionalLight dl;
@@ -94,7 +93,7 @@ public class TestPBRLighting extends SimpleApplication {
 
         dl = new DirectionalLight();
         dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
-        //       rootNode.addLight(dl);
+        rootNode.addLight(dl);
         dl.setColor(ColorRGBA.White);
         rootNode.attachChild(modelNode);
 
@@ -103,7 +102,7 @@ public class TestPBRLighting extends SimpleApplication {
 //        fpp.addFilter(new FXAAFilter());
         fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(4.0f)));
 //        fpp.addFilter(new SSAOFilter(0.5f, 3, 0.2f, 0.2f));
-        // viewPort.addProcessor(fpp);
+        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);
@@ -143,13 +142,10 @@ 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();
                     }
                 }
 
@@ -195,6 +191,7 @@ public class TestPBRLighting extends SimpleApplication {
 
         MaterialDebugAppState debug = new MaterialDebugAppState();
         debug.registerBinding("Common/MatDefs/Light/PBRLighting.frag", rootNode);
+        debug.registerBinding("Common/ShaderLib/PBR.glsllib", rootNode);
         getStateManager().attach(debug);
 
     }
@@ -211,7 +208,6 @@ 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);

+ 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) {