Просмотр исходного кода

Added utilities class to compute the Irradiance Map, and Prefiltered Environment Map needed for PBR indirect lighting.

Nehon 10 лет назад
Родитель
Сommit
3135f2f4bf

+ 242 - 0
jme3-core/src/main/java/com/jme3/texture/pbr/CubeMapWrapper.java

@@ -0,0 +1,242 @@
+/*
+ * 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.texture.pbr;
+
+import com.jme3.math.ColorRGBA;
+import static com.jme3.math.FastMath.pow;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.texture.Image;
+import com.jme3.texture.TextureCubeMap;
+import com.jme3.texture.image.DefaultImageRaster;
+import com.jme3.texture.image.MipMapImageRaster;
+import com.jme3.util.BufferUtils;
+
+/**
+ * Wraps a Cube map and allows to read from or write pixels into it.
+ * 
+ * It uses the ImageRaster class to tailor the read write operations.
+ * 
+ * @author Nehon
+ */
+public class CubeMapWrapper {
+
+    private MipMapImageRaster mipMapRaster;
+    private final DefaultImageRaster raster;
+    private int[] sizes;
+    private final Vector2f uvs = new Vector2f();
+    private final Image image;
+
+    /**
+     * Creates a CubeMapWrapper for the given cube map
+     * Note that the cube map must be initialized, and the mipmaps sizes should 
+     * be set if relevant for them to be readable/writable
+     * @param cubeMap the cubemap to wrap.
+     */
+    public CubeMapWrapper(TextureCubeMap cubeMap) {
+        image = cubeMap.getImage();
+        if (image.hasMipmaps()) {
+            int nbMipMaps = image.getMipMapSizes().length;
+            sizes = new int[nbMipMaps];
+            mipMapRaster = new MipMapImageRaster(image, 0);
+
+            for (int i = 0; i < nbMipMaps; i++) {
+                sizes[i] = Math.max(1, image.getWidth() >> i);
+            }
+        } else {
+            sizes = new int[1];
+            sizes[0] = image.getWidth();
+        }
+        raster = new DefaultImageRaster(image, 0);
+    }
+
+    /**
+     * Reads a pixel from the cube map given the coordinate vector
+     * @param vector the direction vector to fetch the texel
+     * @param store the color in which to store the pixel color read.
+     * @return the color of the pixel read.
+     */
+    public ColorRGBA getPixel(Vector3f vector, ColorRGBA store) {
+
+        if (store == null) {
+            store = new ColorRGBA();
+        }
+
+        int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[0], uvs, EnvMapUtils.FixSeamsMethod.Stretch);
+        raster.setSlice(face);
+        return raster.getPixel((int) uvs.x, (int) uvs.y, store);
+    }
+
+    /**
+     * 
+     * Reads a pixel from the cube map given the coordinate vector
+     * @param vector the direction vector to fetch the texel
+     * @param mipLevel the mip level to read from
+     * @param store the color in which to store the pixel color read.
+     * @return the color of the pixel read.
+     */
+    public ColorRGBA getPixel(Vector3f vector, int mipLevel, ColorRGBA store) {
+        if (mipMapRaster == null) {
+            throw new IllegalArgumentException("This cube map has no mip maps");
+        }
+        if (store == null) {
+            store = new ColorRGBA();
+        }
+
+        int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[mipLevel], uvs, EnvMapUtils.FixSeamsMethod.Stretch);
+        mipMapRaster.setSlice(face);
+        mipMapRaster.setMipLevel(mipLevel);
+        return mipMapRaster.getPixel((int) uvs.x, (int) uvs.y, store);
+    }
+
+    /**
+     * Reads a pixel from the cube map given the 2D coordinates and the face to read from
+     * @param x the x tex coordinate (from 0 to width)
+     * @param y the y tex coordinate (from 0 to height)
+     * @param face the face to read from
+     * @param store the color where the result is stored.
+     * @return the color read.
+     */
+    public ColorRGBA getPixel(int x, int y, int face, ColorRGBA store) {
+        if (store == null) {
+            store = new ColorRGBA();
+        }
+        raster.setSlice(face);
+        return raster.getPixel((int) x, (int) y, store);
+    }
+
+     /**
+     * Reads a pixel from the cube map given the 2D coordinates and the face and 
+     * the mip level to read from
+     * @param x the x tex coordinate (from 0 to width)
+     * @param y the y tex coordinate (from 0 to height)
+     * @param face the face to read from
+     * @param mipLevel the miplevel to read from
+     * @param store the color where the result is stored.
+     * @return the color read.
+     */
+    public ColorRGBA getPixel(int x, int y, int face, int mipLevel, ColorRGBA store) {
+        if (mipMapRaster == null) {
+            throw new IllegalArgumentException("This cube map has no mip maps");
+        }
+        if (store == null) {
+            store = new ColorRGBA();
+        }
+        mipMapRaster.setSlice(face);
+        mipMapRaster.setMipLevel(mipLevel);
+        return mipMapRaster.getPixel((int) x, (int) y, store);
+    }
+
+    /**
+     * writes a pixel given the coordinates vector and the color.
+     * @param vector the cooredinates where to write the pixel
+     * @param color the color to write
+     */
+    public void setPixel(Vector3f vector, ColorRGBA color) {
+
+        int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[0], uvs, EnvMapUtils.FixSeamsMethod.Stretch);
+        raster.setSlice(face);
+        raster.setPixel((int) uvs.x, (int) uvs.y, color);
+    }
+    /**
+     * writes a pixel given the coordinates vector, the mip level and the color.
+     * @param vector the cooredinates where to write the pixel
+     * @param mipLevel the miplevel to write to
+     * @param color the color to write
+     */
+    public void setPixel(Vector3f vector, int mipLevel, ColorRGBA color) {
+        if (mipMapRaster == null) {
+            throw new IllegalArgumentException("This cube map has no mip maps");
+        }
+        int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[mipLevel], uvs, EnvMapUtils.FixSeamsMethod.Stretch);
+        mipMapRaster.setSlice(face);
+        mipMapRaster.setMipLevel(mipLevel);
+        mipMapRaster.setPixel((int) uvs.x, (int) uvs.y, color);
+    }
+
+    /**
+     * Writes a pixel given the 2D cordinates and the color
+     * @param x the x tex coord (from 0 to width)
+     * @param y the y tex coord (from 0 to height)
+     * @param face the face to write to
+     * @param color the color to write
+     */
+    public void setPixel(int x, int y, int face, ColorRGBA color) {
+        raster.setSlice(face);
+        raster.setPixel((int) x, (int) y, color);
+    }
+
+    /**
+     * Writes a pixel given the 2D cordinates, the mip level and the color
+     * @param x the x tex coord (from 0 to width)
+     * @param y the y tex coord (from 0 to height)
+     * @param face the face to write to
+     * @param mipLevel the mip level to write to
+     * @param color the color to write
+     */
+    public void setPixel(int x, int y, int face, int mipLevel, ColorRGBA color) {
+        if (mipMapRaster == null) {
+            throw new IllegalArgumentException("This cube map has no mip maps");
+        }
+
+        mipMapRaster.setSlice(face);
+        mipMapRaster.setMipLevel(mipLevel);
+        mipMapRaster.setPixel((int) x, (int) y, color);
+    }
+
+    /**
+     * Inits the mip maps of a cube map witht he given number of mip maps
+     * @param nbMipMaps the number of mip maps to initialize
+     */
+    public void initMipMaps(int nbMipMaps) {
+        int maxMipMap = (int) (Math.log(image.getWidth()) / Math.log(2) + 1);
+        if (nbMipMaps > maxMipMap) {
+            throw new IllegalArgumentException("Max mip map number for a " + image.getWidth() + "x" + image.getHeight() + " cube map is " + maxMipMap);
+        }
+
+        sizes = new int[nbMipMaps];
+
+        int totalSize = 0;
+        for (int i = 0; i < nbMipMaps; i++) {
+            int size = (int) pow(2, maxMipMap - 1 - i);
+            sizes[i] = size * size * image.getFormat().getBitsPerPixel() / 8;
+            totalSize += sizes[i];
+        }
+        
+        image.setMipMapSizes(sizes);        
+        image.getData().clear();
+        for (int i = 0; i < 6; i++) {
+            image.addData(BufferUtils.createByteBuffer(totalSize));
+        }
+        mipMapRaster = new MipMapImageRaster(image, 0);        
+    }
+}

+ 914 - 0
jme3-core/src/main/java/com/jme3/texture/pbr/EnvMapUtils.java

@@ -0,0 +1,914 @@
+/*
+ * 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.texture.pbr;
+
+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.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Quad;
+import com.jme3.texture.Image;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture2D;
+import com.jme3.texture.TextureCubeMap;
+import com.jme3.texture.image.ColorSpace;
+import com.jme3.ui.Picture;
+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;
+
+/**
+ *
+ * This class holds several utility method unseful for Physically Based
+ * Rendering. It alloaws to compute useful pre filtered maps from an env map.
+ *
+ * @author Nehon
+ */
+public class EnvMapUtils {
+
+    public final static int NUM_SH_COEFFICIENT = 9;
+    // See Peter-Pike Sloan paper for these coefficients
+    //http://www.ppsloan.org/publications/StupidSH36.pdf
+    static float[] shBandFactor = {1.0f,
+        2.0f / 3.0f, 2.0f / 3.0f, 2.0f / 3.0f,
+        1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 4.0f};
+
+    public static enum FixSeamsMethod {
+
+        /**
+         * wrap texture coordinates
+         */
+        Wrap,
+        /**
+         * stretch texture coordinates
+         */
+        Stretch,
+        /**
+         * No seams fix
+         */
+        None;
+    }
+
+    /**
+     * Creates a cube map from 6 images
+     *
+     * @param leftImg the west side image, also called negative x (negX) or left
+     * image
+     * @param rightImg the east side image, also called positive x (posX) or
+     * right image
+     * @param downImg the bottom side image, also called negative y (negY) or
+     * down image
+     * @param upImg the up side image, also called positive y (posY) or up image
+     * @param backImg the south side image, also called positive z (posZ) or
+     * back image
+     * @param frontImg the north side image, also called negative z (negZ) or
+     * front image
+     * @param format the format of the image
+     * @return a cube map
+     */
+    public static TextureCubeMap makeCubeMap(Image rightImg, Image leftImg, Image upImg, Image downImg, Image backImg, Image frontImg, Image.Format format) {
+        Image cubeImage = new Image(format, leftImg.getWidth(), leftImg.getHeight(), null, ColorSpace.Linear);
+
+        cubeImage.addData(rightImg.getData(0));
+        cubeImage.addData(leftImg.getData(0));
+
+        cubeImage.addData(upImg.getData(0));
+        cubeImage.addData(downImg.getData(0));
+
+        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);
+        cubeMap.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
+        cubeMap.setWrap(Texture.WrapMode.EdgeClamp);
+
+        return cubeMap;
+    }
+  
+    /**
+     * Make a duplicate of this cube Map. That means that it's another instant
+     * od TextureCubeMap, but the underlying buffers are duplicates of the
+     * original ones. see {@link ByteBuffer#duplicate()}
+     *
+     * Use this if you need to read from the map from multiple threads, it
+     * should garanty the thread safety. Note that if you want to write to the
+     * cube map you have to make sure that the different thread do not write to
+     * the same area of the buffer. The position, limit and mark are not an
+     * issue.
+     *
+     * @param sourceMap
+     * @return
+     */
+    public static TextureCubeMap duplicateCubeMap(TextureCubeMap sourceMap) {
+        Image srcImg = sourceMap.getImage();
+        Image cubeImage = new Image(srcImg.getFormat(), srcImg.getWidth(), srcImg.getHeight(), null, srcImg.getColorSpace());
+
+        for (ByteBuffer d : srcImg.getData()) {
+            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());
+        cubeMap.setMinFilter(sourceMap.getMinFilter());
+        cubeMap.setWrap(sourceMap.getWrap(Texture.WrapAxis.S));
+
+        return cubeMap;
+    }
+
+    /**
+     * Computes the vector coordinates, for the given x,y texture coordinates
+     * and the given cube map face.
+     *
+     * Also computes the solid angle for those coordinates and returns it.
+     *
+     * To know what the solid angle is please read this.
+     * http://www.codinglabs.net/article_physically_based_rendering.aspx
+     *
+     *
+     * Original solid angle calculation code is from Ignacio Castaño. This
+     * formula is from Manne Öhrström's thesis. It takes two coordiantes in the
+     * range [-1, 1] that define a portion of a cube face and return the area of
+     * the projection of that portion on the surface of the sphere.
+     *
+     * @param x texture coordinate from 0 to 1 in the given cube map face
+     * @param y texture coordinate from 0 to 1 in the given cube map face
+     * @param mapSize the size of the cube map
+     * @param face the face id of the cube map
+     * @param store the vector3f where the vector will be stored. don't provide
+     * null for this param
+     * @return the solid angle for the give parameters
+     */
+    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");
+        }
+
+        /* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
+         (+ 0.5f is for texel center addressing) */
+        float u = (2.0f * ((float) x + 0.5f) / (float) mapSize) - 1.0f;
+        float v = (2.0f * ((float) y + 0.5f) / (float) mapSize) - 1.0f;
+
+        getVectorFromCubemapFaceTexCoord(x, y, mapSize, face, store, fixSeamsMethod);
+
+        /* Solid angle weight approximation :
+         * U and V are the -1..1 texture coordinate on the current face.
+         * Get projected area for this texel */
+        float x0, y0, x1, y1;
+        float invRes = 1.0f / (float) mapSize;
+        x0 = u - invRes;
+        y0 = v - invRes;
+        x1 = u + invRes;
+        y1 = v + invRes;
+
+        return areaElement(x0, y0) - areaElement(x0, y1) - areaElement(x1, y0) + areaElement(x1, y1);
+    }
+
+    /**
+     * used to compute the solid angle
+     *
+     * @param x tex coordinates
+     * @param y tex coordinates
+     * @return
+     */
+    private static float areaElement(float x, float y) {
+        return (float) Math.atan2(x * y, sqrt(x * x + y * y + 1));
+    }
+
+    /**
+     *
+     * Computes the 3 component vector coordinates for the given face and coords
+     *
+     * @param x the x texture coordinate
+     * @param y the y texture coordinate
+     * @param mapSize the size of a face of the cube map
+     * @param face the face to consider
+     * @param store a vector3f where the resulting vector will be stored
+     * @param fixSeamsMethod the method to fix the seams
+     * @return
+     */
+    public static Vector3f getVectorFromCubemapFaceTexCoord(int x, int y, int mapSize, int face, Vector3f store, FixSeamsMethod fixSeamsMethod) {
+        if (store == null) {
+            store = new Vector3f();
+        }
+
+        float u;
+        float v;
+
+        if (fixSeamsMethod == FixSeamsMethod.Stretch) {
+            /* Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp		
+             * 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;
+        } else {
+            //Done if any other fix method or no fix method is set
+            /* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
+             * (+ 0.5f is for texel center addressing) */
+            u = (2.0f * ((float) x + 0.5f) / (float) (mapSize)) - 1.0f;
+            v = (2.0f * ((float) y + 0.5f) / (float) (mapSize)) - 1.0f;
+        }
+
+        if (fixSeamsMethod == FixSeamsMethod.Wrap) {
+            // Warp texel centers in the proximity of the edges.
+            float a = pow((float) mapSize, 2.0f) / pow(((float) mapSize - 1f), 3.0f);
+            u = a * pow(u, 3f) + u;
+            v = a * pow(v, 3f) + v;
+        }
+
+        //compute vector depending on the face
+        // Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp	
+        switch (face) {
+            case 0:
+                store.set(1f, -v, -u);
+                break;
+            case 1:
+                store.set(-1f, -v, u);
+                break;
+            case 2:
+                store.set(u, 1f, v);
+                break;
+            case 3:
+                store.set(u, -1f, -v);
+                break;
+            case 4:
+                store.set(u, -v, 1f);
+                break;
+            case 5:
+                store.set(-u, -v, -1.0f);
+                break;
+        }
+
+        return store.normalizeLocal();
+    }
+
+    /**
+     *
+     * Computes the texture coortinates and the face of the cube map from the
+     * given vector
+     *
+     * @param texelVect the vector to fetch texelt from the cube map
+     * @param fixSeamsMethod the method to fix the seams
+     * @param mapSize the size of one face of the cube map
+     * @param store a Vector2f where the texture coordinates will be stored
+     * @return the face from which to fetch the texel
+     */
+    public static int getCubemapFaceTexCoordFromVector(Vector3f texelVect, int mapSize, Vector2f store, FixSeamsMethod fixSeamsMethod) {
+
+        float u = 0, v = 0, bias = 0;
+        int face;
+        float absX = abs(texelVect.x);
+        float absY = abs(texelVect.y);
+        float absZ = abs(texelVect.z);
+        float max = Math.max(Math.max(absX, absY), absZ);
+        if (max == absX) {
+            face = texelVect.x > 0 ? 0 : 1;
+        } else if (max == absY) {
+            face = texelVect.y > 0 ? 2 : 3;
+        } else {
+            face = texelVect.z > 0 ? 4 : 5;
+        }
+
+        //compute vector depending on the face
+        // Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp	
+        switch (face) {
+            case 0:
+                //store.set(1f, -v, -u, 0);
+                bias = 1f / texelVect.x;
+                u = -texelVect.z;
+                v = -texelVect.y;
+                break;
+            case 1:
+                // store.set(-1f, -v, u, 0);
+                bias = -1f / texelVect.x;
+                u = texelVect.z;
+                v = -texelVect.y;
+                break;
+            case 2:
+                //store.set(u, 1f, v, 0);
+                bias = 1f / texelVect.y;
+                u = texelVect.x;
+                v = texelVect.z;
+                break;
+            case 3:
+                //store.set(u, -1f, -v, 0);
+                bias = -1f / texelVect.y;
+                u = texelVect.x;
+                v = -texelVect.z;
+                break;
+            case 4:
+                //store.set(u, -v, 1f, 0);
+                bias = 1f / texelVect.z;
+                u = texelVect.x;
+                v = -texelVect.y;
+                break;
+            case 5:
+                //store.set(-u, -v, -1.0f, 0);
+                bias = -1f / texelVect.z;
+                u = -texelVect.x;
+                v = -texelVect.y;
+                break;
+        }
+        u *= bias;
+        v *= bias;
+
+        if (fixSeamsMethod == FixSeamsMethod.Stretch) {
+            /* Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp		
+             * transform from [0..res - 1] to [-1 .. 1], match up edges exactly. */
+            u = Math.round((u + 1.0f) * ((float) mapSize - 1.0f) * 0.5f);
+            v = Math.round((v + 1.0f) * ((float) mapSize - 1.0f) * 0.5f);
+        } else {
+            //Done if any other fix method or no fix method is set
+            /* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
+             * (+ 0.5f is for texel center addressing) */
+            u = Math.round((u + 1.0f) * ((float) mapSize) * 0.5f - 0.5f);
+            v = Math.round((v + 1.0f) * ((float) mapSize) * 0.5f - 0.5f);
+
+        }
+
+        store.set(u, v);
+        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));
+
+    }*/
+
+    static int getSampleFromMip(int mipLevel, int miptot) {
+        return Math.min(1 << (miptot + mipLevel * 2), 8192);
+    }
+
+    static float getRoughnessFromMip(int miplevel, int miptot) {
+        float mipScale = 1.2f;
+        float mipOffset = 0.0f;
+
+        return pow(2, (float) (miplevel - (miptot - 1) + mipOffset) / mipScale);
+    }
+
+    static float getMipFromRoughness(float roughness, int miptot) {
+        float mipScale = 1.2f;
+        float Lod = (float) (Math.log(roughness) / Math.log(2)) * mipScale + miptot - 1.0f;       
+
+        return (float) Math.max(0.0, Lod);
+    }
+
+    /**
+     * same as
+     * {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap, com.jme3.utils.EnvMapUtils.FixSeamsMethod)}
+     * the fix method used is {@link FixSeamsMethod#Wrap}
+     *
+     * @param cubeMap the environment cube map to compute SH for
+     * @return an array of 9 vector3f representing thos coefficients for each
+     * r,g,b channnel
+     */
+    public static Vector3f[] getSphericalHarmonicsCoefficents(TextureCubeMap cubeMap) {
+        return getSphericalHarmonicsCoefficents(cubeMap, FixSeamsMethod.Wrap);
+    }
+
+    /**
+     * Returns the Spherical Harmonics coefficients for this cube map.
+     *
+     * The method used is the one from this article :
+     * http://graphics.stanford.edu/papers/envmap/envmap.pdf
+     *
+     * Also good resources on spherical harmonics
+     * http://dickyjim.wordpress.com/2013/09/04/spherical-harmonics-for-beginners/
+     *
+     * @param cubeMap the environment cube map to compute SH for
+     * @param fixSeamsMethod method to fix seams when computing the SH
+     * coefficients
+     * @return an array of 9 vector3f representing thos coefficients for each
+     * r,g,b channnel
+     */
+    public static Vector3f[] getSphericalHarmonicsCoefficents(TextureCubeMap cubeMap, FixSeamsMethod fixSeamsMethod) {
+
+        Vector3f[] shCoef = new Vector3f[NUM_SH_COEFFICIENT];
+
+        float[] shDir = new float[9];
+        float weightAccum = 0.0f;
+        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");
+        }
+
+        int width = cubeMap.getImage().getWidth();
+        int height = cubeMap.getImage().getHeight();
+
+        Vector3f texelVect = new Vector3f();
+        ColorRGBA color = new ColorRGBA();
+
+        CubeMapWrapper envMapReader = new CubeMapWrapper(cubeMap);
+        for (int face = 0; face < 6; face++) {
+            for (int y = 0; y < height; y++) {
+                for (int x = 0; x < width; x++) {
+
+                    weight = getSolidAngleAndVector(x, y, width, face, texelVect, fixSeamsMethod);
+
+                    evalShBasis(texelVect, shDir);
+
+                    envMapReader.getPixel(x, y, face, color);
+
+                    for (int i = 0; i < NUM_SH_COEFFICIENT; i++) {
+
+                        if (shCoef[i] == null) {
+                            shCoef[i] = new Vector3f();
+                        }
+
+                        shCoef[i].setX(shCoef[i].x + color.r * shDir[i] * weight);
+                        shCoef[i].setY(shCoef[i].y + color.g * shDir[i] * weight);
+                        shCoef[i].setZ(shCoef[i].z + color.b * shDir[i] * weight);
+                    }
+
+                    weightAccum += weight;
+                }
+            }
+        }
+
+        /* Normalization - The sum of solid angle should be equal to the solid angle of the sphere (4 PI), so
+         * normalize in order our weightAccum exactly match 4 PI. */
+        for (int i = 0; i < NUM_SH_COEFFICIENT; ++i) {
+            shCoef[i].multLocal(4.0f * PI / weightAccum);
+        }
+        return shCoef;
+    }
+
+    /**
+     * Computes SH coefficient for a given textel dir The method used is the one
+     * from this article : http://graphics.stanford.edu/papers/envmap/envmap.pdf
+     *
+     * @param texelVect
+     * @param shDir
+     */
+    public static void evalShBasis(Vector3f texelVect, float[] shDir) {
+
+        float xV = texelVect.x;
+        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;
+
+        shDir[0] = (1f / (2f * sqrtPi));
+        shDir[1] = -(sqrt3Pi * yV) / 2f;
+        shDir[2] = (sqrt3Pi * zV) / 2f;
+        shDir[3] = -(sqrt3Pi * xV) / 2f;
+        shDir[4] = (sqrt15Pi * xV * yV) / 2f;
+        shDir[5] = -(sqrt15Pi * yV * zV) / 2f;
+        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 * store.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;
+    }
+
+    /**
+     * 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) {
+            store = new Vector4f();
+        }
+        float phi;
+        long ui = i;
+        store.setX((float) i / (float) nbrSample);
+
+        /* From http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
+         * Radical Inverse : Van der Corput */
+        ui = (ui << 16) | (ui >> 16);
+        ui = ((ui & 0x55555555) << 1) | ((ui & 0xAAAAAAAA) >>> 1);
+        ui = ((ui & 0x33333333) << 2) | ((ui & 0xCCCCCCCC) >>> 2);
+        ui = ((ui & 0x0F0F0F0F) << 4) | ((ui & 0xF0F0F0F0) >>> 4);
+        ui = ((ui & 0x00FF00FF) << 8) | ((ui & 0xFF00FF00) >>> 8);
+
+        ui = ui & 0xffffffff;
+        store.setY(2.3283064365386963e-10f * (float) (ui)); /* 0x100000000 */
+
+        phi = 2.0f * PI * store.y;
+        store.setZ(cos(phi));
+        store.setW(sin(phi));
+
+        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();
+        }
+
+        float cosTheta = sqrt((1f - xi.x) / (1f + (a2 - 1f) * xi.x));
+        float sinTheta = sqrt(1f - cosTheta * cosTheta);
+
+        float sinThetaCosPhi = sinTheta * xi.z;//xi.z is cos(phi)
+        float sinThetaSinPhi = sinTheta * xi.w;//xi.w is sin(phi)
+
+        Vector3f upVector = Vector3f.UNIT_X;
+
+        if (abs(normal.z) < 0.999) {
+            upVector = Vector3f.UNIT_Y;
+        }
+
+        Vector3f tangentX = vars.vect3.set(upVector).crossLocal(normal).normalizeLocal();
+        Vector3f tangentY = vars.vect4.set(normal).crossLocal(tangentX);
+
+        // Tangent to world space
+        tangentX.multLocal(sinThetaCosPhi);
+        tangentY.multLocal(sinThetaSinPhi);
+        vars.vect5.set(normal).multLocal(cosTheta);
+
+        // Tangent to world space
+        store.set(tangentX).addLocal(tangentY).addLocal(vars.vect5);
+
+        return store;
+    }
+
+    /**
+     * Creates a debug Node of the given cube map to attach to the gui node
+     *
+     * the cube map is layered this way :
+     * <pre>
+     *         _____
+     *        |     |
+     *        | +Y  |
+     *   _____|_____|_____ _____
+     *  |     |     |     |     |
+     *  | -X  | +Z  | +X  | -Z  |
+     *  |_____|_____|_____|_____|
+     *        |     |
+     *        | -Y  |
+     *        |_____|
+     *
+     *</pre>
+     *
+     * @param cubeMap the cube map
+     * @param assetManager the asset Manager
+     * @return
+     */
+    public static Node getCubeMapCrossDebugView(TextureCubeMap cubeMap, AssetManager assetManager) {
+        Node n = new Node("CubeMapDebug" + cubeMap.getName());
+        int size = cubeMap.getImage().getWidth();
+        Picture[] pics = new Picture[6];
+
+        float ratio = 128f / (float) size;
+
+        for (int i = 0; i < 6; i++) {
+            pics[i] = new Picture("bla");
+            Texture2D tex = new Texture2D(new Image(cubeMap.getImage().getFormat(), size, size, cubeMap.getImage().getData(i), cubeMap.getImage().getColorSpace()));
+
+            pics[i].setTexture(assetManager, tex, true);
+            pics[i].setWidth(size);
+            pics[i].setHeight(size);
+            n.attachChild(pics[i]);
+        }
+
+        pics[0].setLocalTranslation(size, size * 2, 1);
+        pics[0].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+        pics[1].setLocalTranslation(size * 3, size * 2, 1);
+        pics[1].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+        pics[2].setLocalTranslation(size * 2, size * 3, 1);
+        pics[2].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+        pics[3].setLocalTranslation(size * 2, size, 1);
+        pics[3].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+        pics[4].setLocalTranslation(size * 2, size * 2, 1);
+        pics[4].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+        pics[5].setLocalTranslation(size * 4, size * 2, 1);
+        pics[5].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+
+        Quad q = new Quad(size * 4, size * 3);
+        Geometry g = new Geometry("bg", q);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setColor("Color", ColorRGBA.Black);
+        g.setMaterial(mat);
+        g.setLocalTranslation(0, 0, 0);
+
+        n.attachChild(g);
+        n.setLocalScale(ratio);
+        return n;
+    }
+
+    public static Node getCubeMapCrossDebugViewWithMipMaps(TextureCubeMap cubeMap, AssetManager assetManager) {
+        Node n = new Node("CubeMapDebug" + cubeMap.getName());
+        int size = cubeMap.getImage().getWidth();
+        int nbMips = cubeMap.getImage().getMipMapSizes().length;
+        Picture[] pics = new Picture[6*nbMips];
+
+        float ratio = 1f;// 128f / (float) size;
+
+        int offset = 0;
+        int guiOffset = 0;
+        for (int mipLevel = 0; mipLevel < nbMips; mipLevel++) {
+            size = Math.max(1, cubeMap.getImage().getWidth() >> mipLevel);
+            int dataSize = cubeMap.getImage().getMipMapSizes()[mipLevel];
+            byte[] dataArray = new byte[dataSize];
+            for (int i = 0; i < 6; i++) {
+                
+                ByteBuffer bb = cubeMap.getImage().getData(i);
+              
+                bb.rewind();
+                bb.position(offset);
+                bb.get(dataArray, 0, dataSize);
+                ByteBuffer data = BufferUtils.createByteBuffer(dataArray);
+
+                pics[i] = new Picture("bla");
+                Texture2D tex = new Texture2D(new Image(cubeMap.getImage().getFormat(), size, size, data, cubeMap.getImage().getColorSpace()));
+
+                pics[i].setTexture(assetManager, tex, true);
+                pics[i].setWidth(size);
+                pics[i].setHeight(size);
+                n.attachChild(pics[i]);
+            }
+            pics[0].setLocalTranslation(guiOffset + size, guiOffset + size * 2, 1);
+            pics[0].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+            pics[1].setLocalTranslation(guiOffset + size * 3, guiOffset + size * 2, 1);
+            pics[1].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+            pics[2].setLocalTranslation(guiOffset + size * 2, guiOffset + size * 3, 1);
+            pics[2].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+            pics[3].setLocalTranslation(guiOffset + size * 2, guiOffset + size, 1);
+            pics[3].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+            pics[4].setLocalTranslation(guiOffset + size * 2, guiOffset + size * 2, 1);
+            pics[4].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+            pics[5].setLocalTranslation(guiOffset + size * 4, guiOffset + size * 2, 1);
+            pics[5].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+            
+            guiOffset+=size *2+1;
+            offset += dataSize;
+            
+        }
+
+        Quad q = new Quad(cubeMap.getImage().getWidth() * 4 + nbMips, guiOffset + size);
+        Geometry g = new Geometry("bg", q);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setColor("Color", ColorRGBA.Black);
+        g.setMaterial(mat);
+        g.setLocalTranslation(0, 0, 0);
+
+        n.attachChild(g);
+        n.setLocalScale(ratio);
+        return n;
+    }
+}

+ 435 - 0
jme3-core/src/main/java/com/jme3/texture/pbr/EnvironmentCamera.java

@@ -0,0 +1,435 @@
+/*
+ * 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.texture.pbr;
+
+import com.jme3.app.Application;
+import com.jme3.app.state.BaseAppState;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Node;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture2D;
+import com.jme3.texture.TextureCubeMap;
+import com.jme3.texture.image.ColorSpace;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A 360 camera that can capture a cube map of a scene, and then generate the 
+ * Prefiltered Environment cube Map and the Irradiance cube Map needed for PBE 
+ * indirect lighting
+ * 
+ * @author Nehon
+ */
+public class EnvironmentCamera extends BaseAppState {
+    
+    private final static Logger log = Logger.getLogger(EnvironmentCamera.class.getName());
+
+    private static Vector3f[] axisX = new Vector3f[6];
+    private static Vector3f[] axisY = new Vector3f[6];
+    private static Vector3f[] axisZ = new Vector3f[6];
+    private Image.Format imageFormat = Image.Format.RGB16F;
+    private final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(7);
+
+    //Axis for cameras
+    static {
+        //PositiveX axis(left, up, direction)
+        axisX[0] = Vector3f.UNIT_Z.mult(1f);
+        axisY[0] = Vector3f.UNIT_Y.mult(-1f);
+        axisZ[0] = Vector3f.UNIT_X.mult(1f);
+        //NegativeX
+        axisX[1] = Vector3f.UNIT_Z.mult(-1f);
+        axisY[1] = Vector3f.UNIT_Y.mult(-1f);
+        axisZ[1] = Vector3f.UNIT_X.mult(-1f);
+        //PositiveY
+        axisX[2] = Vector3f.UNIT_X.mult(-1f);
+        axisY[2] = Vector3f.UNIT_Z.mult(1f);
+        axisZ[2] = Vector3f.UNIT_Y.mult(1f);
+        //NegativeY
+        axisX[3] = Vector3f.UNIT_X.mult(-1f);
+        axisY[3] = Vector3f.UNIT_Z.mult(-1f);
+        axisZ[3] = Vector3f.UNIT_Y.mult(-1f);
+        //PositiveZ
+        axisX[4] = Vector3f.UNIT_X.mult(-1f);
+        axisY[4] = Vector3f.UNIT_Y.mult(-1f);
+        axisZ[4] = Vector3f.UNIT_Z;
+        //NegativeZ
+        axisX[5] = Vector3f.UNIT_X.mult(1f);
+        axisY[5] = Vector3f.UNIT_Y.mult(-1f);
+        axisZ[5] = Vector3f.UNIT_Z.mult(-1f);
+
+    }
+    private Image images[];
+    ViewPort[] viewports;
+    FrameBuffer[] framebuffers;
+    ByteBuffer[] buffers;
+    private Vector3f position = new Vector3f();
+    private ColorRGBA backGroundColor = null;
+    private boolean snapshotRequested = false;
+    private Node scene;
+    private int size = 128;
+    private TextureCubeMap irradianceMap;
+    private TextureCubeMap prefilteredEnvMap;
+    private Runnable generationCallback;
+    private TextureCubeMap map ;
+    
+    // Generation states
+    private boolean irrMapGenerated = false;
+    private boolean pemGenerated = false;
+    private int faceGenerated = 0;
+    private long time = 0;
+    private boolean generating;
+    private Node debugPfemCm;
+    private Node debugIrrCm;
+    private IrradianceMapGenerator irrMapGenerator;
+    private final PrefilteredEnvMapGenerator[] pemGenerators = new PrefilteredEnvMapGenerator[6];
+
+    /**
+     * Creates an EnvironmentCamera with a size of 128
+     */
+    public EnvironmentCamera() {
+    }
+
+    /**
+     * Creates an EnvironmentCamera with the given size.
+     * @param size the size of the resulting texture.
+     */
+    public EnvironmentCamera(int size) {
+        this.size = size;
+    }
+
+    /**
+     * Creates an EnvironmentCamera with the given size, and the given position
+     * @param size the size of the resulting texture.
+     * @param position the position of the camera.
+     */
+    public EnvironmentCamera(int size, Vector3f position) {
+        this.size = size;
+        this.position = position;
+    }
+
+    /**
+     * Creates an EnvironmentCamera with the given size, and the given position
+     * @param size the size of the resulting texture, and the given ImageFormat.
+     * @param position the position of the camera.
+     * @param imageFormat the ImageFormat to use for the resulting texture.
+     */
+    public EnvironmentCamera(int size, Vector3f position, Image.Format imageFormat) {
+        this.size = size;
+        this.position = position;
+        this.imageFormat = imageFormat;
+    }
+
+    /**
+     * Takes a snapshot of the surrounding scene.
+     * @param scene the scene to snapshot.
+     * @param onDone a callback to call when the snapshot is done.
+     */
+    public void snapshot(Node scene, Runnable onDone) {
+        snapshotRequested = true;
+        this.generationCallback = onDone;
+        this.scene = scene;
+        if (viewports != null) {
+            for (ViewPort viewPort : viewports) {
+                viewPort.clearScenes();
+                viewPort.attachScene(scene);
+            }
+        }
+    }
+    
+    /**
+     * Takes a snapshot of the surrounding scene.
+     * @param scene the scene to snapshot.
+     */
+    public void snapshot(Node scene) {
+        snapshot(scene, null);
+    }
+    
+    @Override
+    public void render(RenderManager renderManager) {
+        if (snapshotRequested) {
+            time = System.currentTimeMillis();
+            snapshotRequested = false;
+            for (int i = 0; i < 6; i++) {
+                renderManager.renderViewPort(viewports[i], 0.16f);
+                renderManager.getRenderer().readFrameBufferWithFormat(framebuffers[i], buffers[i], imageFormat);
+                //renderManager.getRenderer().readFrameBuffer(framebuffers[i], buffers[i]);
+                images[i] = new Image(imageFormat, size, size, buffers[i], ColorSpace.Linear);
+            }
+
+             map = EnvMapUtils.makeCubeMap(images[0], images[1], images[2], images[3], images[4], images[5], imageFormat);
+
+            
+            irrMapGenerator = new IrradianceMapGenerator(getApplication());
+            irrMapGenerator.setGenerationParam(EnvMapUtils.duplicateCubeMap(map), size, EnvMapUtils.FixSeamsMethod.Wrap, irradianceMap);
+            generating = true;           
+            executor.execute(irrMapGenerator);
+            
+            int nbMipMap = (int) (Math.log(size) / Math.log(2) - 1);
+            CubeMapWrapper targetWrapper = new CubeMapWrapper(prefilteredEnvMap);
+            targetWrapper.initMipMaps(nbMipMap);
+
+            for (int i = 0; i < pemGenerators.length; i++) {
+                pemGenerators[i] = new PrefilteredEnvMapGenerator(getApplication(), i);
+                pemGenerators[i].setGenerationParam(EnvMapUtils.duplicateCubeMap(map), size, EnvMapUtils.FixSeamsMethod.Wrap, prefilteredEnvMap);
+                executor.execute(pemGenerators[i]);
+            }
+
+        }
+    }
+
+    /**
+     * Called when the irradiance map is done being generated
+     * @param irrMap 
+     */
+    protected void doneIrradianceMap(TextureCubeMap irrMap) {
+        irradianceMap = irrMap;
+        irrMapGenerated = true;
+    }
+
+    /**
+     * Called when the PEm was generated for the given face
+     * @param face the face of the cube map
+     */
+    protected void donePemForFace(int face) {
+        faceGenerated++;
+        if (faceGenerated == 6) {
+            pemGenerated = true;
+        }
+    }  
+
+    //TODO add a way to plug in a progress reporter that would be external
+    @Override
+    public void update(float tpf) {
+        if (generating) {
+            double progress = 0;
+            progress += irrMapGenerator.getProgress();
+            for (PrefilteredEnvMapGenerator pemGenerator : pemGenerators) {
+                progress += pemGenerator.getProgress();
+            }
+            progress /= 7;
+            log.log(Level.INFO, "progress : {0}%", progress * 100);
+        }
+        if (pemGenerated && irrMapGenerated && generating) {
+            generating = false;
+            long time2 = System.currentTimeMillis();
+            log.log(Level.INFO, "generated in {0} ms", (time2 - time));
+            if(generationCallback != null){
+                generationCallback.run();
+            }
+        }
+    }
+    
+    /**
+     * Displays or cycles through the generated maps.
+     */
+    public void toggleDebug() {
+        if (debugPfemCm == null) {
+            debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(prefilteredEnvMap, getApplication().getAssetManager());
+            debugPfemCm.setLocalTranslation(getApplication().getGuiViewPort().getCamera().getWidth() - 532, 20, 0);
+        }
+        if (debugIrrCm == null) {
+            debugIrrCm = EnvMapUtils.getCubeMapCrossDebugView(irradianceMap, getApplication().getAssetManager());
+            debugIrrCm.setLocalTranslation(getApplication().getGuiViewPort().getCamera().getWidth() - 532, 20, 0);
+        }
+
+        if (debugIrrCm.getParent() != null) {
+            debugIrrCm.removeFromParent();
+            ((Node) (getApplication().getGuiViewPort().getScenes().get(0))).attachChild(debugPfemCm);
+
+        } else if (debugPfemCm.getParent() != null) {
+            debugPfemCm.removeFromParent();
+        } else {
+            ((Node) (getApplication().getGuiViewPort().getScenes().get(0))).attachChild(debugIrrCm);
+        }
+
+    }
+
+    /**
+     * Sets the camera position.
+     * @param position 
+     */
+    public void setPosition(Vector3f position) {
+        this.position.set(position);
+        if (viewports != null) {
+            for (ViewPort viewPort : viewports) {
+                viewPort.getCamera().setLocation(position);
+            }
+        }
+    }
+
+    /**
+     * initialize the Irradiancemap
+     */
+    private void initIrradianceMap() {
+
+        irradianceMap = new TextureCubeMap(size, size, imageFormat);
+        irradianceMap.setMagFilter(Texture.MagFilter.Bilinear);
+        irradianceMap.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
+        irradianceMap.getImage().setColorSpace(ColorSpace.Linear);
+
+    }
+
+    /**
+     * initialize the pem map
+     */
+    private void initPrefilteredEnvMap() {
+
+        prefilteredEnvMap = new TextureCubeMap(size, size, imageFormat);
+        prefilteredEnvMap.setMagFilter(Texture.MagFilter.Bilinear);
+        prefilteredEnvMap.setMinFilter(Texture.MinFilter.Trilinear);
+        prefilteredEnvMap.getImage().setColorSpace(ColorSpace.Linear);
+
+    }
+
+    /**
+     * returns the irradiance map
+     * @return 
+     */
+    public TextureCubeMap getIrradianceMap() {
+        return irradianceMap;
+    }
+    
+    /**
+     * returns the pem map
+     * @return 
+     */
+    public TextureCubeMap getPrefilteredEnvMap() {
+        return prefilteredEnvMap;
+    }
+
+    @Override
+    protected void initialize(Application app) {
+        this.backGroundColor = app.getViewPort().getBackgroundColor();
+        Camera[] cameras = new Camera[6];
+        viewports = new ViewPort[6];
+        framebuffers = new FrameBuffer[6];
+        buffers = new ByteBuffer[6];
+        Texture2D[] textures = new Texture2D[6];
+        images = new Image[6];
+        for (int i = 0; i < 6; i++) {
+            cameras[i] = createOffCamera(size, position, axisX[i], axisY[i], axisZ[i]);
+            viewports[i] = createOffViewPort("EnvView" + i, cameras[i]);
+            framebuffers[i] = createOffScreenFrameBuffer(size, viewports[i]);
+            textures[i] = new Texture2D(size, size, imageFormat);
+            framebuffers[i].setColorTexture(textures[i]);
+            buffers[i] = BufferUtils.createByteBuffer(size * size * imageFormat.getBitsPerPixel() / 8);
+        }
+        initIrradianceMap();
+        initPrefilteredEnvMap();
+    }
+
+    @Override
+    protected void cleanup(Application app) {
+        this.backGroundColor = null;
+        for (FrameBuffer frameBuffer : framebuffers) {
+            app.getRenderManager().getRenderer().deleteFrameBuffer(frameBuffer);
+        }
+        for (Image image : images) {
+            app.getRenderManager().getRenderer().deleteImage(image);
+        }
+
+        executor.shutdownNow();
+    }
+
+    /**
+     * returns the images format used for the generated maps.
+     * @return 
+     */
+    public Image.Format getImageFormat() {
+        return imageFormat;
+    }
+
+    @Override
+    protected void onEnable() {
+    }
+
+    @Override
+    protected void onDisable() {
+    }
+
+    /**
+     * Creates an off camera 
+     * @param mapSize the size 
+     * @param worldPos the position
+     * @param axisX the x axis
+     * @param axisY the y axis
+     * @param axisZ tha z axis
+     * @return 
+     */
+    protected final Camera createOffCamera(int mapSize, Vector3f worldPos, Vector3f axisX, Vector3f axisY, Vector3f axisZ) {
+        Camera offCamera = new Camera(mapSize, mapSize);
+        offCamera.setLocation(worldPos);
+        offCamera.setAxes(axisX, axisY, axisZ);
+        offCamera.setFrustumPerspective(90f, 1f, 1, 1000);
+        offCamera.setLocation(position);
+        return offCamera;
+    }
+
+    /**
+     * creates an offsceen VP
+     * @param name
+     * @param offCamera
+     * @return 
+     */
+    protected final ViewPort createOffViewPort(String name, Camera offCamera) {
+        ViewPort offView = new ViewPort(name, offCamera);
+        offView.setClearFlags(true, true, true);
+        offView.setBackgroundColor(backGroundColor);
+        if (scene != null) {
+            offView.attachScene(scene);
+        }
+        return offView;
+    }
+
+    /**
+     * create an offscreen frame buffer.
+     * @param mapSize
+     * @param offView
+     * @return 
+     */
+    protected final FrameBuffer createOffScreenFrameBuffer(int mapSize, ViewPort offView) {
+        // create offscreen framebuffer
+        FrameBuffer offBuffer = new FrameBuffer(mapSize, mapSize, 1);
+        offBuffer.setDepthBuffer(Image.Format.Depth);
+        offView.setOutputFrameBuffer(offBuffer);
+        return offBuffer;
+    }
+}

+ 160 - 0
jme3-core/src/main/java/com/jme3/texture/pbr/IrradianceMapGenerator.java

@@ -0,0 +1,160 @@
+/*
+ * 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.texture.pbr;
+
+import com.jme3.app.Application;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.texture.TextureCubeMap;
+import static com.jme3.texture.pbr.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 RunableWithProgress{
+    
+    
+    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
+     */
+    public IrradianceMapGenerator(Application app) {
+        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;
+    }
+    
+    
+    @Override
+    public void run() {
+        Vector3f[] shCoeffs = EnvMapUtils.getSphericalHarmonicsCoefficents(sourceMap);
+        store = generateIrradianceMap(shCoeffs, targetMapSize, fixSeamsMethod, store);
+        app.enqueue(new Callable<Void>() {
+
+            @Override
+            public Void call() throws Exception {
+                app.getStateManager().getState(EnvironmentCamera.class).doneIrradianceMap(store);
+                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 + targetMapSize * targetMapSize * 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;
+    }
+    
+}

+ 238 - 0
jme3-core/src/main/java/com/jme3/texture/pbr/PrefilteredEnvMapGenerator.java

@@ -0,0 +1,238 @@
+/*
+ * 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.texture.pbr;
+
+import com.jme3.app.Application;
+import com.jme3.math.ColorRGBA;
+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.texture.pbr.EnvMapUtils.getHammersleyPoint;
+import static com.jme3.texture.pbr.EnvMapUtils.getRoughnessFromMip;
+import static com.jme3.texture.pbr.EnvMapUtils.getSampleFromMip;
+import static com.jme3.texture.pbr.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.
+ *
+ * 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 PrefilteredEnvMapGenerator extends RunableWithProgress {
+
+    private final static Logger log = Logger.getLogger(PrefilteredEnvMapGenerator.class.getName());
+
+    private int targetMapSize;
+    private EnvMapUtils.FixSeamsMethod fixSeamsMethod;
+    private TextureCubeMap sourceMap;
+    private TextureCubeMap store;
+    private final Application app;
+    private int face = 0;
+    Vector4f Xi = new Vector4f();
+    Vector3f H = new Vector3f();
+    Vector3f tmp = new Vector3f();
+    ColorRGBA c = new ColorRGBA();
+    Vector3f tmp1 = new Vector3f();
+    Vector3f tmp2 = new Vector3f();
+    Vector3f tmp3 = new Vector3f();
+
+    /**
+     * Creates a pem generator for the given face. 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 face the face to generate
+     */
+    public PrefilteredEnvMapGenerator(Application app, int face) {
+        this.app = app;
+        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 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;
+    }
+
+    @Override
+    public void run() {
+
+        store = generatePrefilteredEnvMap(sourceMap, targetMapSize, fixSeamsMethod, store);
+        app.enqueue(new Callable<Void>() {
+
+            @Override
+            public Void call() throws Exception {
+                app.getStateManager().getState(EnvironmentCamera.class).donePemForFace(face);
+                return null;
+            }
+        });
+    }
+
+    /**
+     * 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
+     */
+    private TextureCubeMap generatePrefilteredEnvMap(TextureCubeMap sourceEnvMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
+        TextureCubeMap pem = store;
+
+        int nbMipMap = (int) (Math.log(targetMapSize) / Math.log(2) - 1);
+
+        setEnd(nbMipMap);
+        log.log(Level.FINE, "face length {0}", targetMapSize * targetMapSize * nbMipMap);
+
+        CubeMapWrapper sourceWrapper = new CubeMapWrapper(sourceEnvMap);
+        CubeMapWrapper targetWrapper = new CubeMapWrapper(pem);
+
+        Vector3f texelVect = new Vector3f();
+        Vector3f color = new Vector3f();
+        ColorRGBA outColor = new ColorRGBA();
+        for (int mipLevel = 0; mipLevel < nbMipMap; mipLevel++) {
+            float roughness = getRoughnessFromMip(mipLevel, nbMipMap);
+            int nbSamples = getSampleFromMip(mipLevel, nbMipMap);
+            int targetMipMapSize = (int) pow(2, nbMipMap + 1 - mipLevel);
+
+            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);
+                    prefilterEnvMapTexel(sourceWrapper, roughness, texelVect, nbSamples, color);
+                    outColor.set(color.x, color.y, color.z, 1.0f);
+                    log.log(Level.FINE, "coords {0},{1}", new Object[]{x, y});
+                    targetWrapper.setPixel(x, y, face, mipLevel, outColor);
+
+                }
+            }
+            log.log(Level.FINE, "face {0} : {1}", new Object[]{face, getProgress()});
+            progress();
+        }
+
+        return pem;
+    }
+
+    private Vector3f prefilterEnvMapTexel(CubeMapWrapper envMapReader, float roughness, Vector3f N, int numSamples, Vector3f store) {
+
+        Vector3f prefilteredColor = store;
+        float totalWeight = 0.0f;
+
+        // 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);
+
+            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;
+            }
+        }
+
+        return prefilteredColor.divideLocal(totalWeight);
+    }
+
+    public Vector3f importanceSampleGGX(Vector4f xi, float a2, Vector3f normal, Vector3f store) {
+        if (store == null) {
+            store = new Vector3f();
+        }
+
+        float cosTheta = sqrt((1f - xi.x) / (1f + (a2 - 1f) * xi.x));
+        float sinTheta = sqrt(1f - cosTheta * cosTheta);
+
+        float sinThetaCosPhi = sinTheta * xi.z;//xi.z is cos(phi)
+        float sinThetaSinPhi = sinTheta * xi.w;//xi.w is sin(phi)
+
+        Vector3f upVector = Vector3f.UNIT_X;
+
+        if (abs(normal.z) < 0.999) {
+            upVector = Vector3f.UNIT_Y;
+        }
+
+        Vector3f tangentX = tmp1.set(upVector).crossLocal(normal).normalizeLocal();
+        Vector3f tangentY = tmp2.set(normal).crossLocal(tangentX);
+
+        // Tangent to world space
+        tangentX.multLocal(sinThetaCosPhi);
+        tangentY.multLocal(sinThetaSinPhi);
+        tmp3.set(normal).multLocal(cosTheta);
+
+        // Tangent to world space
+        store.set(tangentX).addLocal(tangentY).addLocal(tmp3);
+
+        return store;
+    }
+
+}

+ 74 - 0
jme3-core/src/main/java/com/jme3/texture/pbr/RunableWithProgress.java

@@ -0,0 +1,74 @@
+/*
+ * 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.texture.pbr;
+
+/**
+ *
+ * Abstract runnable that can report its progress
+ * @author Nehon
+ */
+public abstract class RunableWithProgress implements Runnable {
+
+    private int progress;
+    private int end;
+
+    /**
+     * set the end step value of the process.
+     * @param end 
+     */
+    protected void setEnd(int end) {
+        this.end = end;
+    }
+
+    /**
+     * return the curent progress of the process.
+     * @return 
+     */
+    public double getProgress() {
+        return (double) progress / (double) end;
+    }
+
+    /**
+     * adds one progression step to the process.
+     */
+    protected void progress() {
+        progress++;
+    }
+
+    /**
+     * resets the progression of the process.
+     */
+    protected void reset() {
+        progress = 0;
+    }
+
+}