Переглянути джерело

Accelerated env baker that runs on the GPU.

Riccardo Balbo 2 роки тому
батько
коміт
b88be511f1

+ 98 - 0
jme3-core/src/main/java/com/jme3/environment/LightProbeFactory2.java

@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2009-2019 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.environment;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.environment.baker.IBLGLEnvBakerLight;
+import com.jme3.environment.util.EnvMapUtils;
+import com.jme3.light.LightProbe;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.texture.Image.Format;
+
+
+public class LightProbeFactory2 {
+
+ 
+    public static LightProbe makeProbe(RenderManager rm,
+    AssetManager am, int size,Vector3f pos, float frustumNear,float frustumFar,Spatial scene) {
+        IBLGLEnvBakerLight baker=new IBLGLEnvBakerLight(rm,
+         am, Format.RGB16F, Format.Depth, 
+         size, size);
+
+        baker.bakeEnvironment(scene,pos, frustumNear,frustumFar);
+        baker.bakeSpecularIBL();
+        baker.bakeSphericalHarmonicsCoefficients();
+        
+        LightProbe probe = new LightProbe();
+
+        probe.setPosition(pos);
+        probe.setPrefilteredMap(baker.getSpecularIBL());
+        probe.setNbMipMaps(probe.getPrefilteredEnvMap().getImage().getMipMapSizes().length);
+        probe.setShCoeffs(baker.getSphericalHarmonicsCoefficients());
+        probe.setReady(true);
+
+        baker.clean();
+
+        return probe;
+
+    }
+
+
+
+
+
+    /**
+     * For debuging porpose only
+     * Will return a Node meant to be added to a GUI presenting the 2 cube maps in a cross pattern with all the mip maps.
+     *
+     * @param manager the asset manager
+     * @return a debug node
+     */
+    public static Node getDebugGui(AssetManager manager, LightProbe probe) {
+        if (!probe.isReady()) {
+            throw new UnsupportedOperationException("This EnvProbe is not ready yet, try to test isReady()");
+        }
+
+        Node debugNode = new Node("debug gui probe");
+        Node debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(probe.getPrefilteredEnvMap(), manager);
+        debugNode.attachChild(debugPfemCm);
+        debugPfemCm.setLocalTranslation(520, 0, 0);
+
+        return debugNode;
+    }
+
+
+    
+}

+ 16 - 0
jme3-core/src/main/java/com/jme3/environment/baker/EnvBaker.java

@@ -0,0 +1,16 @@
+package com.jme3.environment.baker;
+
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import com.jme3.texture.TextureCubeMap;
+
+/**
+ * And environment baker. It bakes the environment. ( ͡° ͜ʖ ͡°)
+ *
+ * @author Riccardo Balbo
+ */
+public interface EnvBaker {
+    public void bakeEnvironment(Spatial scene, Vector3f position, float frustumNear, float frustumFar);
+    public TextureCubeMap getEnvMap();
+    public void clean();    
+}

+ 165 - 0
jme3-core/src/main/java/com/jme3/environment/baker/GenericEnvBaker.java

@@ -0,0 +1,165 @@
+package com.jme3.environment.baker;
+
+import java.io.FileOutputStream;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashMap;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.environment.baker.EnvBaker;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture.MagFilter;
+import com.jme3.texture.Texture.MinFilter;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.texture.TextureCubeMap;
+import com.jme3.texture.image.ColorSpace;
+import com.jme3.util.BufferUtils;
+
+
+/**
+ * Render the environment into a cubemap
+ *
+ * @author Riccardo Balbo
+ */
+public abstract class GenericEnvBaker implements EnvBaker{
+
+    protected static Vector3f[] axisX=new Vector3f[6];
+    protected static Vector3f[] axisY=new Vector3f[6];
+    protected static Vector3f[] axisZ=new Vector3f[6];
+    static{
+        //PositiveX axis(left, up, direction)
+        axisX[0]=Vector3f.UNIT_Z.mult(1.0F);
+        axisY[0]=Vector3f.UNIT_Y.mult(-1.0F);
+        axisZ[0]=Vector3f.UNIT_X.mult(1.0F);
+        //NegativeX
+        axisX[1]=Vector3f.UNIT_Z.mult(-1.0F);
+        axisY[1]=Vector3f.UNIT_Y.mult(-1.0F);
+        axisZ[1]=Vector3f.UNIT_X.mult(-1.0F);
+        //PositiveY
+        axisX[2]=Vector3f.UNIT_X.mult(-1.0F);
+        axisY[2]=Vector3f.UNIT_Z.mult(1.0F);
+        axisZ[2]=Vector3f.UNIT_Y.mult(1.0F);
+        //NegativeY
+        axisX[3]=Vector3f.UNIT_X.mult(-1.0F);
+        axisY[3]=Vector3f.UNIT_Z.mult(-1.0F);
+        axisZ[3]=Vector3f.UNIT_Y.mult(-1.0F);
+        //PositiveZ
+        axisX[4]=Vector3f.UNIT_X.mult(-1.0F);
+        axisY[4]=Vector3f.UNIT_Y.mult(-1.0F);
+        axisZ[4]=Vector3f.UNIT_Z;
+        //NegativeZ
+        axisX[5]=Vector3f.UNIT_X.mult(1.0F);
+        axisY[5]=Vector3f.UNIT_Y.mult(-1.0F);
+        axisZ[5]=Vector3f.UNIT_Z.mult(-1.0F);
+    }
+
+    protected TextureCubeMap env;
+    protected Format depthFormat;
+
+
+    protected final RenderManager renderManager;
+    protected final AssetManager assetManager;
+    protected final Camera cam;
+    protected final boolean copyToRam;
+
+
+    public GenericEnvBaker(
+        RenderManager rm,
+        AssetManager am,
+        Format colorFormat,
+        Format depthFormat,
+        int env_size,
+        boolean copyToRam
+    ){
+        this.copyToRam=copyToRam;
+        this.depthFormat=depthFormat;
+
+        renderManager=rm;
+        assetManager=am;
+
+
+        cam=new Camera(128,128);
+        
+        env=new TextureCubeMap(env_size,env_size,colorFormat);
+        env.setMagFilter(MagFilter.Bilinear);
+        env.setMinFilter(MinFilter.BilinearNoMipMaps);
+        env.setWrap(WrapMode.EdgeClamp);
+        env.getImage().setColorSpace(ColorSpace.Linear);
+    }
+
+    public TextureCubeMap getEnvMap(){
+        return env;
+    }   
+
+    Camera getCam(int id,int w,int h,Vector3f position,float frustumNear,float frustumFar){
+        cam.resize(w,h,false);
+        cam.setLocation(position);
+        cam.setFrustumPerspective(90.0F,1F,frustumNear,frustumFar);
+        cam.setRotation(new Quaternion().fromAxes(axisX[id],axisY[id],axisZ[id]));
+        return cam;
+    }    
+
+    @Override
+    public void clean(){
+        env.getImage().dispose();
+        System.gc();
+        System.gc();        
+    }
+
+
+    @Override
+    public void bakeEnvironment(Spatial scene,   Vector3f position, float frustumNear, float frustumFar) {       
+        FrameBuffer envbaker=new FrameBuffer(env.getImage().getWidth(),env.getImage().getHeight(),1);
+        envbaker.setDepthTarget(FrameBuffer.newTarget(depthFormat));
+        envbaker.setSrgb(false);
+
+        for(int i=0;i<6;i++) envbaker.addColorTarget(FrameBuffer.newTarget(env).face(TextureCubeMap.Face.values()[i]));
+            
+          for(int i=0;i<6;i++){
+            envbaker.setTargetIndex(i);
+
+            ViewPort viewPort=new ViewPort("EnvBaker",getCam(i,envbaker.getWidth(),envbaker.getHeight(),position,frustumNear,frustumFar));
+            viewPort.setClearFlags(true,true,true);
+            viewPort.setBackgroundColor(ColorRGBA.Pink);
+        
+            viewPort.setOutputFrameBuffer(envbaker);
+            viewPort.clearScenes();
+            viewPort.attachScene(scene);
+
+            scene.updateLogicalState(0);
+            scene.updateModelBound();
+            scene.updateGeometricState();
+
+            renderManager.renderViewPort(viewPort,0.16f);
+
+            if(copyToRam){
+                ByteBuffer face=BufferUtils.createByteBuffer(
+                    (
+                        env.getImage().getWidth()*env.getImage().getHeight()*(
+                            env.getImage().getFormat().getBitsPerPixel()/8
+                        )
+                    )
+                ); 
+                renderManager.getRenderer().readFrameBufferWithFormat(envbaker, face,env.getImage().getFormat());
+                face.rewind();
+                env.getImage().setData(i,face);
+
+            }
+        }
+
+        env.getImage().clearUpdateNeeded();
+  
+        envbaker.dispose();
+    }
+
+    
+}

+ 19 - 0
jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBaker.java

@@ -0,0 +1,19 @@
+package com.jme3.environment.baker;
+
+import com.jme3.texture.Texture2D;
+import com.jme3.texture.TextureCubeMap;
+
+/**
+ * An environment baker, but this one is for Imaged Base Lighting
+ *
+ * @author Riccardo Balbo
+ */
+public interface IBLEnvBaker extends EnvBaker{
+    public Texture2D genBRTF() ;
+    
+    public void bakeIrradiance();
+    public void bakeSpecularIBL() ;
+
+    public TextureCubeMap getSpecularIBL();
+    public TextureCubeMap getIrradiance();
+}

+ 17 - 0
jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBakerLight.java

@@ -0,0 +1,17 @@
+package com.jme3.environment.baker;
+
+import com.jme3.math.Vector3f;
+import com.jme3.texture.TextureCubeMap;
+
+/**
+ * An environment baker for IBL, that uses spherical harmonics for irradiance.
+ *
+ * @author Riccardo Balbo
+ */
+public interface IBLEnvBakerLight extends EnvBaker{   
+    public void bakeSpecularIBL();
+    public void bakeSphericalHarmonicsCoefficients();
+
+    public TextureCubeMap getSpecularIBL();
+    public Vector3f[] getSphericalHarmonicsCoefficients();
+}

+ 182 - 0
jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBaker.java

@@ -0,0 +1,182 @@
+package com.jme3.environment.baker;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.environment.baker.IBLEnvBaker;
+import com.jme3.material.Material;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture.MagFilter;
+import com.jme3.texture.Texture.MinFilter;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.texture.Texture2D;
+import com.jme3.texture.TextureCubeMap;
+import com.jme3.texture.image.ColorSpace;
+import com.jme3.ui.Picture;
+
+
+/**
+ *  An env baker for IBL that runs on the GPU 
+ * 
+ * @author Riccardo Balbo
+ */
+public class IBLGLEnvBaker extends GenericEnvBaker implements IBLEnvBaker{
+    protected Texture2D brtf;
+    protected TextureCubeMap irradiance;
+    protected TextureCubeMap specular;
+
+    public IBLGLEnvBaker(RenderManager rm,AssetManager am,
+                        Format format,
+                        Format depthFormat,
+                        int env_size,int specular_size,
+                        int irradiance_size,
+                        int brtf_size
+    ){
+        super(rm,am,format,depthFormat,env_size,false);  
+
+        irradiance=new TextureCubeMap(irradiance_size,irradiance_size,format);
+        irradiance.setMagFilter(MagFilter.Bilinear);
+        irradiance.setMinFilter(MinFilter.BilinearNoMipMaps);
+        irradiance.setWrap(WrapMode.EdgeClamp);
+        irradiance.getImage().setColorSpace(ColorSpace.Linear);
+
+        specular=new TextureCubeMap(specular_size,specular_size,format);
+        specular.setMagFilter(MagFilter.Bilinear);
+        specular.setMinFilter(MinFilter.BilinearNoMipMaps);
+        specular.setWrap(WrapMode.EdgeClamp);
+        specular.getImage().setColorSpace(ColorSpace.Linear);
+        int nbMipMaps=(int)(Math.log(specular_size)/Math.log(2)+1);
+        if(nbMipMaps>6)nbMipMaps=6;
+        int[] sizes=new int[nbMipMaps];
+        for(int i=0;i<nbMipMaps;i++){
+            int size=(int)FastMath.pow(2,nbMipMaps-1-i);
+            sizes[i]=size*size*(specular.getImage().getFormat().getBitsPerPixel()/8);
+        }
+        specular.getImage().setMipMapSizes(sizes);
+
+        brtf=new Texture2D(brtf_size,brtf_size,format);
+        brtf.setMagFilter(MagFilter.Bilinear);
+        brtf.setMinFilter(MinFilter.BilinearNoMipMaps);
+        brtf.setWrap(WrapMode.EdgeClamp);
+        brtf.getImage().setColorSpace(ColorSpace.Linear);
+    }
+
+
+    public TextureCubeMap getSpecularIBL(){
+        return specular;
+    }
+       
+    public TextureCubeMap getIrradiance(){
+        return irradiance;
+    }
+
+    @Override
+    public void bakeSpecularIBL() {
+        Box boxm=new Box(1,1,1);
+        Geometry screen=new Geometry("BakeBox",boxm);
+    
+        Material mat=new Material(assetManager,"Common/IBL/IBLKernels.j3md");
+        mat.setBoolean("UseSpecularIBL",true);
+        mat.setTexture("EnvMap",env);
+        screen.setMaterial(mat);
+    
+        for(int mip=0;mip<specular.getImage().getMipMapSizes().length;mip++){
+            int mipWidth=(int)(specular.getImage().getWidth()*FastMath.pow(0.5f,mip));
+            int mipHeight=(int)(specular.getImage().getHeight()*FastMath.pow(0.5f,mip));
+
+            FrameBuffer specularbaker=new FrameBuffer(mipWidth,mipHeight,1);
+            specularbaker.setSrgb(false);
+
+            for(int i=0;i<6;i++)specularbaker.addColorTarget( FrameBuffer.newTarget(specular).level(mip).face(i) );
+            
+            float roughness=(float)mip/(float)(specular.getImage().getMipMapSizes().length-1);
+            mat.setFloat("Roughness",roughness);
+
+            for(int i=0;i<6;i++){
+                specularbaker.setTargetIndex(i);
+                mat.setInt("FaceId",i);
+
+                screen.updateLogicalState(0);
+                screen.updateGeometricState();
+
+                renderManager.setCamera(getCam(i,specularbaker.getWidth(),specularbaker.getHeight(),Vector3f.ZERO,1,1000),false);
+                renderManager.getRenderer().setFrameBuffer(specularbaker);
+                renderManager.renderGeometry(screen);
+            }
+            specularbaker.dispose();
+        }        
+        specular.setMinFilter(MinFilter.Trilinear);        
+    }
+
+    @Override
+    public Texture2D genBRTF() {
+        
+        Picture screen=new Picture("BakeScreen",true);
+        screen.setWidth(1);
+        screen.setHeight(1);
+
+        FrameBuffer brtfbaker=new FrameBuffer(brtf.getImage().getWidth(),brtf.getImage().getHeight(),1);
+        brtfbaker.setSrgb(false);
+        brtfbaker.addColorTarget(FrameBuffer.newTarget(brtf));
+
+        Camera envcam=getCam(0,brtf.getImage().getWidth(),brtf.getImage().getHeight(),Vector3f.ZERO,1,1000);
+
+        Material mat=new Material(assetManager,"Common/IBL/IBLKernels.j3md");
+        mat.setBoolean("UseBRDF",true);
+        screen.setMaterial(mat);
+
+        renderManager.getRenderer().setFrameBuffer(brtfbaker);
+        renderManager.setCamera(envcam,false);
+
+        screen.updateLogicalState(0);
+        screen.updateGeometricState();       
+        renderManager.renderGeometry(screen);
+       
+        brtfbaker.dispose();
+     
+        return brtf;
+    }
+
+    @Override
+    public void bakeIrradiance() {
+     
+        Box boxm=new Box(1,1,1);
+        Geometry screen=new Geometry("BakeBox",boxm);
+    
+
+        FrameBuffer irradiancebaker=new FrameBuffer(irradiance.getImage().getWidth(),irradiance.getImage().getHeight(),1);
+        irradiancebaker.setSrgb(false);
+        
+        for(int i=0;i<6;i++) irradiancebaker.addColorTarget(FrameBuffer.newTarget(irradiance).face(TextureCubeMap.Face.values()[i]));
+
+        Material mat=new Material(assetManager,"Common/IBL/IBLKernels.j3md");
+        mat.setBoolean("UseIrradiance",true);
+        mat.setTexture("EnvMap",env);
+        screen.setMaterial(mat);
+
+        for(int i=0;i<6;i++){
+            irradiancebaker.setTargetIndex(i);
+
+            mat.setInt("FaceId",i);
+
+            screen.updateLogicalState(0);
+            screen.updateGeometricState();
+
+            renderManager.setCamera(
+                getCam(i,irradiancebaker.getWidth(),irradiancebaker.getHeight(),Vector3f.ZERO,1,1000)
+            ,false);
+            renderManager.getRenderer().setFrameBuffer(irradiancebaker);
+            renderManager.renderGeometry(screen);
+        }
+
+        irradiancebaker.dispose();
+
+    }
+
+
+}

+ 104 - 0
jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBakerLight.java

@@ -0,0 +1,104 @@
+package com.jme3.environment.baker;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.environment.baker.IBLEnvBakerLight;
+import com.jme3.environment.util.EnvMapUtils;
+import com.jme3.material.Material;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.TextureCubeMap;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture.MagFilter;
+import com.jme3.texture.Texture.MinFilter;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.texture.image.ColorSpace;
+
+/**
+ *  An env baker for IBL that runs on the GPU 
+ * 
+ * @author Riccardo Balbo
+ */
+public class IBLGLEnvBakerLight extends GenericEnvBaker implements IBLEnvBakerLight{
+    protected TextureCubeMap specular;
+    protected Vector3f[] shCoef;
+    public IBLGLEnvBakerLight(RenderManager rm,AssetManager am,
+                        Format format,
+                        Format depthFormat,
+                        int env_size,
+                        int specular_size
+                     
+    ){
+        super(rm,am,format,depthFormat,env_size,true);
+
+        specular=new TextureCubeMap(specular_size,specular_size,format);
+        specular.setMagFilter(MagFilter.Bilinear);
+        specular.setMinFilter(MinFilter.BilinearNoMipMaps);
+        specular.setWrap(WrapMode.EdgeClamp);
+        specular.getImage().setColorSpace(ColorSpace.Linear);
+        int nbMipMaps=(int)(Math.log(specular_size)/Math.log(2)+1);
+        if(nbMipMaps>6)nbMipMaps=6;
+        int[] sizes=new int[nbMipMaps];
+        for(int i=0;i<nbMipMaps;i++){
+            int size=(int)FastMath.pow(2,nbMipMaps-1-i);
+            sizes[i]=size*size*(specular.getImage().getFormat().getBitsPerPixel()/8);
+        }
+        specular.getImage().setMipMapSizes(sizes);
+	}
+
+
+    @Override
+    public void bakeSpecularIBL() {
+        Box boxm=new Box(1,1,1);
+        Geometry screen=new Geometry("BakeBox",boxm);
+    
+        Material mat=new Material(assetManager,"Common/IBL/IBLKernels.j3md");
+        mat.setBoolean("UseSpecularIBL",true);
+        mat.setTexture("EnvMap",env);
+        screen.setMaterial(mat);
+    
+        for(int mip=0;mip<specular.getImage().getMipMapSizes().length;mip++){
+            int mipWidth=(int)(specular.getImage().getWidth()*FastMath.pow(0.5f,mip));
+            int mipHeight=(int)(specular.getImage().getHeight()*FastMath.pow(0.5f,mip));
+
+            FrameBuffer specularbaker=new FrameBuffer(mipWidth,mipHeight,1);
+            specularbaker.setSrgb(false);
+            for(int i=0;i<6;i++)specularbaker.addColorTarget( FrameBuffer.newTarget(specular).level(mip).face(i) );
+            
+            float roughness=(float)mip/(float)(specular.getImage().getMipMapSizes().length-1);
+            mat.setFloat("Roughness",roughness);
+
+            for(int i=0;i<6;i++){
+                specularbaker.setTargetIndex(i);
+                mat.setInt("FaceId",i);
+
+                screen.updateLogicalState(0);
+                screen.updateGeometricState();
+
+                renderManager.setCamera(getCam(i,specularbaker.getWidth(),specularbaker.getHeight(),Vector3f.ZERO,1,1000),false);
+                renderManager.getRenderer().setFrameBuffer(specularbaker);
+                renderManager.renderGeometry(screen);
+            }
+            specularbaker.dispose();
+        }        
+        specular.setMinFilter(MinFilter.Trilinear);        
+    }
+
+    @Override
+    public TextureCubeMap getSpecularIBL() {
+        return specular;
+    }
+    
+    @Override
+    public void bakeSphericalHarmonicsCoefficients() {
+		shCoef=EnvMapUtils.getSphericalHarmonicsCoefficents(getEnvMap());
+	}
+
+    @Override
+    public Vector3f[] getSphericalHarmonicsCoefficients(){
+        return shCoef;
+    }
+}

+ 101 - 0
jme3-core/src/main/resources/Common/IBL/IBLKernels.frag

@@ -0,0 +1,101 @@
+/**
+*   This code is based on the following articles:
+*               https://learnopengl.com/PBR/IBL/Diffuse-irradiance
+*               https://learnopengl.com/PBR/IBL/Specular-IBL
+*   - Riccardo Balbo
+*/
+#import "Common/IBL/Math.glsllib"
+
+out vec4 outFragColor;
+in vec2 TexCoords;
+in vec3 LocalPos;
+
+uniform samplerCube m_EnvMap;
+uniform float m_Roughness;
+uniform int m_FaceId;
+
+void brdfKernel(){
+    float NdotV=TexCoords.x;
+    float m_Roughness=TexCoords.y;
+    vec3 V;
+    V.x = sqrt(1.0 - NdotV*NdotV);
+    V.y = 0.0;
+    V.z = NdotV;
+    float A = 0.0;
+    float B = 0.0;
+    vec3 N = vec3(0.0, 0.0, 1.0);
+    const uint SAMPLE_COUNT = 1024u;
+    for(uint i = 0u; i < SAMPLE_COUNT; i++){
+        vec2 Xi = Hammersley(i, SAMPLE_COUNT);
+        vec3 H  = ImportanceSampleGGX(Xi, N, m_Roughness);
+        vec3 L  = normalize(2.0 * dot(V, H) * H - V);
+        float NdotL = max(L.z, 0.0);
+        float NdotH = max(H.z, 0.0);
+        float VdotH = max(dot(V, H), 0.0);
+        if(NdotL > 0.0){
+            float G = GeometrySmith(N, V, L, m_Roughness);
+            float G_Vis = (G * VdotH) / (NdotH * NdotV);
+            float Fc = pow(1.0 - VdotH, 5.0);
+            A += (1.0 - Fc) * G_Vis;
+            B += Fc * G_Vis;
+        }
+    }
+    A /= float(SAMPLE_COUNT);
+    B /= float(SAMPLE_COUNT);
+    outFragColor.rg=vec2(A, B);
+    outFragColor.ba=vec2(0);
+}
+
+void irradianceKernel(){		
+    // the sample direction equals the hemisphere's orientation 
+    vec3 N = normalize(LocalPos);
+    vec3 irradiance = vec3(0.0); 
+    vec3 up = vec3(0.0, 1.0, 0.0);
+    vec3 right = cross(up, N);
+    up = cross(N, right);
+    float sampleDelta = 0.025;
+    float nrSamples = 0.0; 
+    for(float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta){
+        for(float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta){
+            // spherical to cartesian (in tangent space)
+            vec3 tangentSample = vec3(sin(theta) * cos(phi),  sin(theta) * sin(phi), cos(theta));
+            // tangent space to world
+            vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N;
+            irradiance += texture(m_EnvMap, sampleVec).rgb * cos(theta) * sin(theta);
+            nrSamples++;
+        }
+    }
+    irradiance = PI * irradiance * (1.0 / float(nrSamples));  
+    outFragColor = vec4(irradiance, 1.0);
+}
+
+void prefilteredEnvKernel(){		
+    vec3 N = normalize(LocalPos);    
+    vec3 R = N;
+    vec3 V = R;
+    const uint SAMPLE_COUNT = 1024u;
+    float totalWeight = 0.0;   
+    vec3 prefilteredColor = vec3(0.0);     
+    for(uint i = 0u; i < SAMPLE_COUNT; ++i) {
+        vec2 Xi = Hammersley(i, SAMPLE_COUNT);
+        vec3 H  = ImportanceSampleGGX(Xi, N, m_Roughness);
+        vec3 L  = normalize(2.0 * dot(V, H) * H - V);
+        float NdotL = max(dot(N, L), 0.0);
+        if(NdotL > 0.0) {
+            prefilteredColor += texture(m_EnvMap, L).rgb * NdotL;
+            totalWeight      += NdotL;
+        }
+    }
+    prefilteredColor = prefilteredColor / totalWeight;
+    outFragColor = vec4(prefilteredColor, 1.0);
+}  
+
+void main(){ 
+    #if defined(SIBL)
+        prefilteredEnvKernel();
+    #elif defined(IRRADIANCE)
+        irradianceKernel();
+    #else
+        brdfKernel();
+    #endif
+}

+ 37 - 0
jme3-core/src/main/resources/Common/IBL/IBLKernels.j3md

@@ -0,0 +1,37 @@
+MaterialDef IBLKernels {
+    
+    MaterialParameters {
+        TextureCubeMap EnvMap -LINEAR
+        Float Roughness
+        Int FaceId : 0
+        Boolean UseBRDF
+        Boolean UseIrradiance
+        Boolean UseSpecularIBL
+    }
+
+    Technique {
+    
+        VertexShader GLSL150:  Common/IBL/IBLKernels.vert
+        FragmentShader GLSL150:  Common/IBL/IBLKernels.frag
+
+        WorldParameters {
+            WorldMatrix
+            ViewMatrix
+            ProjectionMatrix
+        }
+        
+        RenderState {
+            DepthWrite Off
+            DepthTest Off
+            DepthFunc Equal
+            FaceCull Off
+        }
+
+        Defines {
+            BRDF:UseBRDF
+            IRRADIANCE: UseIrradiance
+            SIBL: UseSpecularIBL
+        }
+
+    }
+}

+ 29 - 0
jme3-core/src/main/resources/Common/IBL/IBLKernels.vert

@@ -0,0 +1,29 @@
+/**
+*   This code is based on the following articles:
+*               https://learnopengl.com/PBR/IBL/Diffuse-irradiance
+*               https://learnopengl.com/PBR/IBL/Specular-IBL
+*   - Riccardo Balbo
+*/
+in vec3 inPosition;
+in vec2 inTexCoord;
+in vec3 inNormal;
+
+out vec2 TexCoords;
+out vec3 LocalPos;
+
+uniform mat4 g_ViewMatrix;
+uniform mat4 g_WorldMatrix;
+uniform mat4 g_ProjectionMatrix;
+
+void main() {
+    LocalPos = inPosition.xyz;  
+    TexCoords = inTexCoord.xy;
+    #ifdef BRDF
+        vec2 pos = inPosition.xy * 2.0 - 1.0;      
+        gl_Position = vec4(pos, 0.0, 1.0);
+    #else       
+        mat4 rotView = mat4(mat3(g_ViewMatrix)); // remove translation from the view matrix
+        vec4 clipPos = g_ProjectionMatrix * rotView * vec4(LocalPos, 1.0);
+        gl_Position = clipPos.xyww;
+    #endif
+}

+ 66 - 0
jme3-core/src/main/resources/Common/IBL/Math.glsllib

@@ -0,0 +1,66 @@
+/**
+*   This code is based on the following articles:
+*               https://learnopengl.com/PBR/IBL/Diffuse-irradiance
+*               https://learnopengl.com/PBR/IBL/Specular-IBL
+*   - Riccardo Balbo
+*/
+const float PI = 3.14159265359;
+
+float RadicalInverse_VdC(uint bits) {
+    bits = (bits << 16u) | (bits >> 16u);
+    bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+    bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+    bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+    bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+    return float(bits) * 2.3283064365386963e-10; // / 0x100000000
+}
+
+vec2 Hammersley(uint i, uint N){
+    return vec2(float(i)/float(N), RadicalInverse_VdC(i));
+}  
+
+
+vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness){
+    float a = roughness*roughness;
+	
+    float phi = 2.0 * PI * Xi.x;
+    float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y));
+    float sinTheta = sqrt(1.0 - cosTheta*cosTheta);
+	
+    // from spherical coordinates to cartesian coordinates
+    vec3 H;
+    H.x = cos(phi) * sinTheta;
+    H.y = sin(phi) * sinTheta;
+    H.z = cosTheta;
+	
+    // from tangent-space vector to world-space sample vector
+    vec3 up        = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
+    vec3 tangent   = normalize(cross(up, N));
+    vec3 bitangent = cross(N, tangent);
+	
+    vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z;
+    return normalize(sampleVec);
+}  
+
+
+
+
+float GeometrySchlickGGX(float NdotV, float roughness){
+    float a = roughness;
+    float k = (a * a) / 2.0;
+
+    float nom   = NdotV;
+    float denom = NdotV * (1.0 - k) + k;
+
+    return nom / denom;
+}
+// ----------------------------------------------------------------------------
+float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness){
+    float NdotV = max(dot(N, V), 0.0);
+    float NdotL = max(dot(N, L), 0.0);
+    float ggx2 = GeometrySchlickGGX(NdotV, roughness);
+    float ggx1 = GeometrySchlickGGX(NdotL, roughness);
+
+    return ggx1 * ggx2;
+}  
+    

+ 17 - 10
jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java

@@ -34,6 +34,7 @@ package jme3test.light.pbr;
 import com.jme3.app.SimpleApplication;
 import com.jme3.environment.EnvironmentCamera;
 import com.jme3.environment.LightProbeFactory;
+import com.jme3.environment.LightProbeFactory2;
 import com.jme3.environment.generation.JobProgressAdapter;
 import com.jme3.environment.util.EnvMapUtils;
 import com.jme3.environment.util.LightsDebugState;
@@ -59,7 +60,8 @@ import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
  * @author nehon
  */
 public class TestPBRLighting extends SimpleApplication {
-
+    private static final boolean USE_ACCELERATED_BAKING=true;
+    private static final int RESOLUTION=256;
     public static void main(String[] args) {
         TestPBRLighting app = new TestPBRLighting();
         app.start();
@@ -111,7 +113,7 @@ public class TestPBRLighting extends SimpleApplication {
         model.setMaterial(pbrMat);
 
 
-        final EnvironmentCamera envCam = new EnvironmentCamera(256, new Vector3f(0, 3f, 0));
+        final EnvironmentCamera envCam = new EnvironmentCamera(RESOLUTION, new Vector3f(0, 3f, 0));
         stateManager.attach(envCam);
 
 //        EnvironmentManager envManager = new EnvironmentManager();
@@ -199,18 +201,23 @@ public class TestPBRLighting extends SimpleApplication {
 
         if (frame == 2) {
             modelNode.removeFromParent();
-            final LightProbe probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter<LightProbe>() {
+            LightProbe probe;
 
-                @Override
-                public void done(LightProbe result) {
-                    System.err.println("Done rendering env maps");
-                    tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(result.getPrefilteredEnvMap(), assetManager);
-                }
-            });
+            if (USE_ACCELERATED_BAKING) {
+                probe = LightProbeFactory2.makeProbe(renderManager, assetManager, RESOLUTION, Vector3f.ZERO, 1f, 1000f, rootNode);
+            } else {
+                probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter<LightProbe>() {
+
+                    @Override
+                    public void done(LightProbe result) {
+                        System.err.println("Done rendering env maps");
+                        tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(result.getPrefilteredEnvMap(), assetManager);
+                    }
+                });
+            }
             probe.getArea().setRadius(100);
             rootNode.addLight(probe);
             //getStateManager().getState(EnvironmentManager.class).addEnvProbe(probe);
-
         }
         if (frame > 10 && modelNode.getParent() == null) {
             rootNode.attachChild(modelNode);