diff --git a/jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java b/jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java
new file mode 100644
index 0000000000..d07a039995
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (c) 2009-2023 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 java.io.IOException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
+import com.jme3.asset.AssetManager;
+import com.jme3.environment.baker.IBLGLEnvBakerLight;
+import com.jme3.environment.baker.IBLHybridEnvBakerLight;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.light.LightProbe;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.Control;
+import com.jme3.texture.Image.Format;
+
+/**
+ * A control that automatically handles environment bake and rebake including
+ * only tagged spatials.
+ *
+ * Simple usage example:
+ * 1. Load a scene
+ * Node scene=(Node)assetManager.loadModel("Scenes/MyScene.j3o");
+ * 2. Add one or more EnvironmentProbeControl to the root of the scene
+ * EnvironmentProbeControl ec1=new EnvironmentProbeControl(assetManager, 512);
+ * // EnvironmentProbeControl ec2=new EnvironmentProbeControl(assetManager, 512);
+ * 2b. (optional) Set the position of the probes
+ * ec1.setPosition(new Vector3f(0,0,0));
+ * // ec2.setPosition(new Vector3f(0,0,10));
+ * 3. Tag the spatials that are part of the environment
+ * scene.deepFirstTraversal(s->{
+ * if(s.getUserData("isEnvNode")!=null){
+ * EnvironmentProbeControl.tagGlobal(s);
+ * // or ec1.tag(s);
+ * // ec2.tag(s);
+ * }
+ * });
+ *
+ *
+ * @author Riccardo Balbo
+ */
+public class EnvironmentProbeControl extends LightProbe implements Control {
+ private static AtomicInteger instanceCounter = new AtomicInteger(0);
+
+ private AssetManager assetManager;
+ private boolean bakeNeeded = true;
+ private int envMapSize = 256;
+ private Spatial spatial;
+ private boolean requiredSavableResults = false;
+ private float frustumNear = 0.001f, frustumFar = 1000f;
+ private String uuid = "none";
+ private boolean enabled = true;
+
+ private Predicate filter = (s) -> {
+ return s.getUserData("tags.env") != null || s.getUserData("tags.env.env" + uuid) != null;
+ };
+
+ protected EnvironmentProbeControl() {
+ super();
+ uuid = System.currentTimeMillis() + "_" + instanceCounter.getAndIncrement();
+ this.setAreaType(AreaType.Spherical);
+ this.getArea().setRadius(Float.MAX_VALUE);
+ }
+
+ /**
+ * Creates a new environment probe control.
+ *
+ * @param assetManager
+ * the asset manager used to load the shaders needed for the
+ * baking
+ * @param size
+ * the size of side of the resulting cube map (eg. 1024)
+ */
+ public EnvironmentProbeControl(AssetManager assetManager, int size) {
+ this();
+ this.envMapSize = size;
+ this.assetManager = assetManager;
+ }
+
+ /**
+ * Tags the specified spatial as part of the environment for this EnvironmentProbeControl.
+ * Only tagged spatials will be rendered in the environment map.
+ *
+ * @param s
+ * the spatial
+ */
+ public void tag(Spatial s) {
+ if (s instanceof Node) {
+ Node n = (Node) s;
+ for (Spatial sx : n.getChildren()) {
+ tag(sx);
+ }
+ } else if (s instanceof Geometry) {
+ s.setUserData("tags.env.env" + uuid, true);
+ }
+ }
+
+ /**
+ * Untags the specified spatial as part of the environment for this
+ * EnvironmentProbeControl.
+ *
+ * @param s
+ * the spatial
+ */
+ public void untag(Spatial s) {
+ if (s instanceof Node) {
+ Node n = (Node) s;
+ for (Spatial sx : n.getChildren()) {
+ untag(sx);
+ }
+ } else if (s instanceof Geometry) {
+ s.setUserData("tags.env.env" + uuid, null);
+ }
+ }
+
+ /**
+ * Tags the specified spatial as part of the environment for every EnvironmentProbeControl.
+ * Only tagged spatials will be rendered in the environment map.
+ *
+ * @param s
+ * the spatial
+ */
+ public static void tagGlobal(Spatial s) {
+ if (s instanceof Node) {
+ Node n = (Node) s;
+ for (Spatial sx : n.getChildren()) {
+ tagGlobal(sx);
+ }
+ } else if (s instanceof Geometry) {
+ s.setUserData("tags.env", true);
+ }
+ }
+
+ /**
+ * Untags the specified spatial as part of the environment for every
+ * EnvironmentProbeControl.
+ *
+ * @param s the spatial
+ */
+ public static void untagGlobal(Spatial s) {
+ if (s instanceof Node) {
+ Node n = (Node) s;
+ for (Spatial sx : n.getChildren()) {
+ untagGlobal(sx);
+ }
+ } else if (s instanceof Geometry) {
+ s.setUserData("tags.env", null);
+ }
+ }
+
+ @Override
+ public Control cloneForSpatial(Spatial spatial) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Requests savable results from the baking process. This will make the
+ * baking process slower and more memory intensive but will allow to
+ * serialize the results with the control.
+ *
+ * @param v
+ * true to enable (default: false)
+ */
+ public void setRequiredSavableResults(boolean v) {
+ requiredSavableResults = v;
+ }
+
+ /**
+ * Returns true if savable results are required by this control.
+ *
+ * @return true if savable results are required.
+ */
+ public boolean isRequiredSavableResults() {
+ return requiredSavableResults;
+ }
+
+ @Override
+ public void setSpatial(Spatial spatial) {
+ if (this.spatial != null && spatial != null && spatial != this.spatial) {
+ throw new IllegalStateException("This control has already been added to a Spatial");
+ }
+ this.spatial = spatial;
+ if (spatial != null) spatial.addLight(this);
+ }
+
+ @Override
+ public void update(float tpf) {
+
+ }
+
+ @Override
+ public void render(RenderManager rm, ViewPort vp) {
+ if (!isEnabled()) return;
+ if (bakeNeeded) {
+ bakeNeeded = false;
+ rebakeNow(rm);
+ }
+ }
+
+ /**
+ * Schedules a rebake of the environment map.
+ */
+ public void rebake() {
+ bakeNeeded = true;
+ }
+
+ /**
+ * Sets the minimum distance to render.
+ *
+ * @param frustumNear the minimum distance to render
+ */
+ public void setFrustumNear(float frustumNear) {
+ this.frustumNear = frustumNear;
+ }
+
+ /**
+ * Sets the maximum distance to render.
+ *
+ * @param frustumFar the maximum distance to render
+ */
+ public void setFrustumFar(float frustumFar) {
+ this.frustumFar = frustumFar;
+ }
+
+ /**
+ * Gets the minimum distance to render.
+ *
+ * @return frustum near
+ */
+ public float getFrustumNear() {
+ return frustumNear;
+ }
+
+ /**
+ * Gets the maximum distance to render.
+ *
+ * @return frustum far
+ */
+ public float getFrustumFar() {
+ return frustumFar;
+ }
+
+ /**
+ * Sets the asset manager used to load the shaders needed for the baking.
+ *
+ * @param assetManager the asset manager
+ */
+ public void setAssetManager(AssetManager assetManager) {
+ this.assetManager = assetManager;
+ }
+
+ void rebakeNow(RenderManager renderManager) {
+ IBLHybridEnvBakerLight baker = new IBLGLEnvBakerLight(renderManager, assetManager, Format.RGB16F, Format.Depth,
+ envMapSize, envMapSize);
+
+ baker.setTexturePulling(isRequiredSavableResults());
+ baker.bakeEnvironment(spatial, getPosition(), frustumNear, frustumFar, filter);
+ baker.bakeSpecularIBL();
+ baker.bakeSphericalHarmonicsCoefficients();
+
+ setPrefilteredMap(baker.getSpecularIBL());
+
+ int[] mipSizes = getPrefilteredEnvMap().getImage().getMipMapSizes();
+ setNbMipMaps(mipSizes != null ? mipSizes.length : 1);
+
+ setShCoeffs(baker.getSphericalHarmonicsCoefficients());
+ setPosition(Vector3f.ZERO);
+ setReady(true);
+
+ baker.clean();
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public Spatial getSpatial() {
+ return spatial;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(enabled, "enabled", true);
+ oc.write(spatial, "spatial", null);
+ oc.write(envMapSize, "size", 256);
+ oc.write(requiredSavableResults, "requiredSavableResults", false);
+ oc.write(bakeNeeded, "bakeNeeded", true);
+ oc.write(frustumFar, "frustumFar", 1000f);
+ oc.write(frustumNear, "frustumNear", 0.001f);
+ oc.write(uuid, "envProbeControlUUID", "none");
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ enabled = ic.readBoolean("enabled", true);
+ spatial = (Spatial) ic.readSavable("spatial", null);
+ envMapSize = ic.readInt("size", 256);
+ requiredSavableResults = ic.readBoolean("requiredSavableResults", false);
+ bakeNeeded = ic.readBoolean("bakeNeeded", true);
+ assetManager = im.getAssetManager();
+ frustumFar = ic.readFloat("frustumFar", 1000f);
+ frustumNear = ic.readFloat("frustumNear", 0.001f);
+ uuid = ic.readString("envProbeControlUUID", "none");
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/environment/FastLightProbeFactory.java b/jme3-core/src/main/java/com/jme3/environment/FastLightProbeFactory.java
new file mode 100644
index 0000000000..d78edc561e
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/environment/FastLightProbeFactory.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2009-2023 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;
+
+/**
+ * A faster LightProbeFactory that uses GPU accelerated algorithms.
+ * This is the GPU version of @{link LightProbeFactory} and should be generally preferred.
+ *
+ * For common use cases where the probe is baking the scene or part of the scene around it, it
+ * is advised to use the @{link EnvironmentProbeControl} instead since it does automatically most of the
+ * boilerplate work.
+ *
+ *
+ * @author Riccardo Balbo
+ */
+public class FastLightProbeFactory {
+
+ /**
+ * Creates a LightProbe with the given EnvironmentCamera in the given scene.
+ *
+ * @param rm
+ * The RenderManager
+ * @param am
+ * The AssetManager
+ * @param size
+ * The size of the probe
+ * @param pos
+ * The position of the probe
+ * @param frustumNear
+ * The near frustum of the probe
+ * @param frustumFar
+ * The far frustum of the probe
+ * @param scene
+ * The scene to bake
+ * @return The baked LightProbe
+ */
+ 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.setTexturePulling(true);
+ baker.bakeEnvironment(scene, pos, frustumNear, frustumFar, null);
+ baker.bakeSpecularIBL();
+ baker.bakeSphericalHarmonicsCoefficients();
+
+ LightProbe probe = new LightProbe();
+
+ probe.setPosition(pos);
+ probe.setPrefilteredMap(baker.getSpecularIBL());
+
+ int[] mipSizes = probe.getPrefilteredEnvMap().getImage().getMipMapSizes();
+ probe.setNbMipMaps(mipSizes != null ? mipSizes.length : 1);
+
+ probe.setShCoeffs(baker.getSphericalHarmonicsCoefficients());
+ probe.setReady(true);
+
+ baker.clean();
+
+ return probe;
+
+ }
+
+ /**
+ * For debuging purposes 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;
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/environment/baker/EnvBaker.java b/jme3-core/src/main/java/com/jme3/environment/baker/EnvBaker.java
new file mode 100644
index 0000000000..65ee9805f1
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/environment/baker/EnvBaker.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2009-2023 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.baker;
+
+import java.util.function.Predicate;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.texture.TextureCubeMap;
+
+/**
+ * An environment baker to bake a 3d environment into a cubemap
+ *
+ * @author Riccardo Balbo
+ */
+public interface EnvBaker {
+ /**
+ * Bakes the environment.
+ *
+ * @param scene
+ * The scene to bake
+ * @param position
+ * The position of the camera
+ * @param frustumNear
+ * The near frustum
+ * @param frustumFar
+ * The far frustum
+ * @param filter
+ * A filter to select which geometries to bake
+ */
+ public void bakeEnvironment(Spatial scene, Vector3f position, float frustumNear, float frustumFar, Predicate filter);
+
+ /**
+ * Gets the environment map.
+ *
+ * @return The environment map
+ */
+ public TextureCubeMap getEnvMap();
+
+ /**
+ * Cleans the environment baker This method should be called when the baker
+ * is no longer needed It will clean up all the resources.
+ */
+ public void clean();
+
+ /**
+ * Specifies whether textures should be pulled from the GPU.
+ *
+ * @param v
+ */
+ public void setTexturePulling(boolean v);
+
+ /**
+ * Gets if textures should be pulled from the GPU.
+ *
+ * @return
+ */
+ public boolean isTexturePulling();
+}
diff --git a/jme3-core/src/main/java/com/jme3/environment/baker/GenericEnvBaker.java b/jme3-core/src/main/java/com/jme3/environment/baker/GenericEnvBaker.java
new file mode 100644
index 0000000000..6831914945
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/environment/baker/GenericEnvBaker.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (c) 2009-2023 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.baker;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import com.jme3.asset.AssetManager;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Texture;
+import com.jme3.texture.FrameBuffer.FrameBufferTarget;
+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 {
+ private static final Logger LOG = Logger.getLogger(GenericEnvBaker.class.getName());
+
+ 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 envMap;
+ protected Format depthFormat;
+
+ protected final RenderManager renderManager;
+ protected final AssetManager assetManager;
+ protected final Camera cam;
+ protected boolean texturePulling = false;
+ protected List bos = new ArrayList<>();
+
+ protected GenericEnvBaker(RenderManager rm, AssetManager am, Format colorFormat, Format depthFormat, int env_size) {
+ this.depthFormat = depthFormat;
+
+ renderManager = rm;
+ assetManager = am;
+
+ cam = new Camera(128, 128);
+
+ envMap = new TextureCubeMap(env_size, env_size, colorFormat);
+ envMap.setMagFilter(MagFilter.Bilinear);
+ envMap.setMinFilter(MinFilter.BilinearNoMipMaps);
+ envMap.setWrap(WrapMode.EdgeClamp);
+ envMap.getImage().setColorSpace(ColorSpace.Linear);
+ }
+
+ @Override
+ public void setTexturePulling(boolean v) {
+ texturePulling = v;
+ }
+
+ @Override
+ public boolean isTexturePulling() {
+ return texturePulling;
+ }
+
+ public TextureCubeMap getEnvMap() {
+ return envMap;
+ }
+
+ /**
+ * Updates the internal camera to face the given cubemap face
+ * and return it.
+ *
+ * @param faceId
+ * the id of the face (0-5)
+ * @param w
+ * width of the camera
+ * @param h
+ * height of the camera
+ * @param position
+ * position of the camera
+ * @param frustumNear
+ * near frustum
+ * @param frustumFar
+ * far frustum
+ * @return The updated camera
+ */
+ protected Camera updateAndGetInternalCamera(int faceId, 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[faceId], axisY[faceId], axisZ[faceId]));
+ return cam;
+ }
+
+ @Override
+ public void clean() {
+
+ }
+
+ @Override
+ public void bakeEnvironment(Spatial scene, Vector3f position, float frustumNear, float frustumFar, Predicate filter) {
+ FrameBuffer envbakers[] = new FrameBuffer[6];
+ for (int i = 0; i < 6; i++) {
+ envbakers[i] = new FrameBuffer(envMap.getImage().getWidth(), envMap.getImage().getHeight(), 1);
+ envbakers[i].setDepthTarget(FrameBufferTarget.newTarget(depthFormat));
+ envbakers[i].setSrgb(false);
+ envbakers[i].addColorTarget(FrameBufferTarget.newTarget(envMap).face(TextureCubeMap.Face.values()[i]));
+ }
+
+ if (isTexturePulling()) {
+ startPulling();
+ }
+
+ for (int i = 0; i < 6; i++) {
+ FrameBuffer envbaker = envbakers[i];
+
+ ViewPort viewPort = new ViewPort("EnvBaker", updateAndGetInternalCamera(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.updateGeometricState();
+
+ Predicate ofilter = renderManager.getRenderFilter();
+
+ renderManager.setRenderFilter(filter);
+ renderManager.renderViewPort(viewPort, 0.16f);
+ renderManager.setRenderFilter(ofilter);
+
+ if (isTexturePulling()) {
+ pull(envbaker, envMap, i);
+ }
+
+ }
+
+ if (isTexturePulling()) {
+ endPulling(envMap);
+ }
+
+ envMap.getImage().clearUpdateNeeded();
+
+ for (int i = 0; i < 6; i++) {
+ envbakers[i].dispose();
+ }
+ }
+
+ /**
+ * Starts pulling the data from the framebuffer into the texture.
+ */
+ protected void startPulling() {
+ bos.clear();
+ }
+
+ /**
+ * Pulls the data from the framebuffer into the texture Nb. mipmaps must be
+ * pulled sequentially on the same faceId.
+ *
+ * @param fb
+ * the framebuffer to pull from
+ * @param env
+ * the texture to pull into
+ * @param faceId
+ * id of face if cubemap or 0 otherwise
+ * @return the ByteBuffer containing the pulled data
+ */
+ protected ByteBuffer pull(FrameBuffer fb, Texture env, int faceId) {
+
+ if (fb.getColorTarget().getFormat() != env.getImage().getFormat())
+ throw new IllegalArgumentException("Format mismatch: " + fb.getColorTarget().getFormat() + "!=" + env.getImage().getFormat());
+
+ ByteBuffer face = BufferUtils.createByteBuffer(fb.getWidth() * fb.getHeight() * (fb.getColorTarget().getFormat().getBitsPerPixel() / 8));
+ renderManager.getRenderer().readFrameBufferWithFormat(fb, face, fb.getColorTarget().getFormat());
+ face.rewind();
+
+ while (bos.size() <= faceId) {
+ bos.add(null);
+ }
+
+ ByteArrayOutputStream bo = bos.get(faceId);
+ if (bo == null) {
+ bos.set(faceId, bo = new ByteArrayOutputStream());
+ }
+ try {
+ byte array[] = new byte[face.limit()];
+ face.get(array);
+ bo.write(array);
+ } catch (Exception ex) {
+ LOG.log(Level.SEVERE, null, ex);
+ }
+ return face;
+ }
+
+ /**
+ * Ends pulling the data into the texture
+ *
+ * @param tx
+ * the texture to pull into
+ */
+ protected void endPulling(Texture tx) {
+ for (int i = 0; i < bos.size(); i++) {
+ ByteArrayOutputStream bo = bos.get(i);
+ if (bo != null) {
+ ByteBuffer faceMip = ByteBuffer.wrap(bo.toByteArray());
+ tx.getImage().setData(i, faceMip);
+ } else {
+ LOG.log(Level.SEVERE, "Missing face {0}. Pulling incomplete!", i);
+ }
+ }
+ bos.clear();
+ tx.getImage().clearUpdateNeeded();
+ }
+
+ protected int limitMips(int nbMipMaps, int baseW, int baseH, RenderManager rm) {
+ if (nbMipMaps > 6) {
+ nbMipMaps = 6;
+ }
+ return nbMipMaps;
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBaker.java b/jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBaker.java
new file mode 100644
index 0000000000..982ccc79df
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBaker.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2009-2023 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.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 {
+ /**
+ * Generates the BRDF texture.
+ *
+ * @return The BRDF texture
+ */
+ public Texture2D genBRTF();
+
+ /**
+ * Bakes the irradiance map.
+ */
+ public void bakeIrradiance();
+
+ /**
+ * Bakes the specular IBL map.
+ */
+ public void bakeSpecularIBL();
+
+ /**
+ * Gets the specular IBL map.
+ *
+ * @return The specular IBL map
+ */
+ public TextureCubeMap getSpecularIBL();
+
+ /**
+ * Gets the irradiance map.
+ *
+ * @return The irradiance map
+ */
+ public TextureCubeMap getIrradiance();
+}
diff --git a/jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBakerLight.java b/jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBakerLight.java
new file mode 100644
index 0000000000..19275d514e
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBakerLight.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009-2023 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.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();
+}
\ No newline at end of file
diff --git a/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBaker.java b/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBaker.java
new file mode 100644
index 0000000000..0a28664e0d
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBaker.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (c) 2009-2023 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.baker;
+
+import java.util.Arrays;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import com.jme3.asset.AssetManager;
+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.FrameBuffer.FrameBufferTarget;
+import com.jme3.texture.image.ColorSpace;
+import com.jme3.ui.Picture;
+
+/**
+ * Fully accelerated env baker for IBL that runs entirely on the GPU
+ *
+ * @author Riccardo Balbo
+ */
+public class IBLGLEnvBaker extends GenericEnvBaker implements IBLEnvBaker {
+ private static final Logger LOGGER = Logger.getLogger(IBLHybridEnvBakerLight.class.getName());
+
+ protected Texture2D brtf;
+ protected TextureCubeMap irradiance;
+ protected TextureCubeMap specular;
+
+ /**
+ * Create a new IBL env baker
+ * @param rm The render manager used to render the env scene
+ * @param am The asset manager used to load the baking shaders
+ * @param format The format of the color buffers
+ * @param depthFormat The format of the depth buffers
+ * @param env_size The size in pixels of the output environment cube map (eg. 1024)
+ * @param specular_size The size in pixels of the output specular cube map (eg. 1024)
+ * @param irradiance_size The size in pixels of the output irradiance cube map (eg. 512)
+ * @param brtf_size The size in pixels of the output brtf map (eg. 512)
+ */
+ 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);
+
+ 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.Trilinear);
+ specular.setWrap(WrapMode.EdgeClamp);
+ specular.getImage().setColorSpace(ColorSpace.Linear);
+
+ int nbMipMaps = (int) (Math.log(specular_size) / Math.log(2) + 1);
+ nbMipMaps = limitMips(nbMipMaps, specular.getImage().getWidth(), specular.getImage().getHeight(), rm);
+
+ 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;
+ }
+
+ private void bakeSpecularIBL(int mip, float roughness, Material mat, Geometry screen) throws Exception {
+ mat.setFloat("Roughness", roughness);
+
+ int mipWidth = (int) (specular.getImage().getWidth() * FastMath.pow(0.5f, mip));
+ int mipHeight = (int) (specular.getImage().getHeight() * FastMath.pow(0.5f, mip));
+
+ FrameBuffer specularbakers[] = new FrameBuffer[6];
+ for (int i = 0; i < 6; i++) {
+ specularbakers[i] = new FrameBuffer(mipWidth, mipHeight, 1);
+ specularbakers[i].setSrgb(false);
+ specularbakers[i].addColorTarget(FrameBufferTarget.newTarget(specular).level(mip).face(i));
+ specularbakers[i].setMipMapsGenerationHint(false);
+ }
+
+ for (int i = 0; i < 6; i++) {
+ FrameBuffer specularbaker = specularbakers[i];
+ mat.setInt("FaceId", i);
+
+ screen.updateLogicalState(0);
+ screen.updateGeometricState();
+
+ renderManager.setCamera(updateAndGetInternalCamera(i, specularbaker.getWidth(), specularbaker.getHeight(), Vector3f.ZERO, 1, 1000), false);
+ renderManager.getRenderer().setFrameBuffer(specularbaker);
+ renderManager.renderGeometry(screen);
+
+ if (isTexturePulling()) {
+ pull(specularbaker, specular, i);
+ }
+
+ }
+ for (int i = 0; i < 6; i++) {
+ specularbakers[i].dispose();
+ }
+ }
+
+ @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", envMap);
+ screen.setMaterial(mat);
+
+ if (isTexturePulling()) {
+ startPulling();
+ }
+
+ int mip = 0;
+ for (; mip < specular.getImage().getMipMapSizes().length; mip++) {
+ try {
+ float roughness = (float) mip / (float) (specular.getImage().getMipMapSizes().length - 1);
+ bakeSpecularIBL(mip, roughness, mat, screen);
+ } catch (Exception e) {
+ LOGGER.log(Level.WARNING, "Error while computing mip level " + mip, e);
+ break;
+ }
+ }
+
+ if (mip < specular.getImage().getMipMapSizes().length) {
+
+ int[] sizes = specular.getImage().getMipMapSizes();
+ sizes = Arrays.copyOf(sizes, mip);
+ specular.getImage().setMipMapSizes(sizes);
+ specular.getImage().setMipmapsGenerated(true);
+ if (sizes.length <= 1) {
+ try {
+ LOGGER.log(Level.WARNING, "Workaround driver BUG: only one mip level available, regenerate it with higher roughness (shiny fix)");
+ bakeSpecularIBL(0, 1f, mat, screen);
+ } catch (Exception e) {
+ LOGGER.log(Level.FINE, "Error while recomputing mip level 0", e);
+ }
+ }
+ }
+
+ if (isTexturePulling()) {
+ endPulling(specular);
+ }
+ specular.getImage().clearUpdateNeeded();
+
+ }
+
+ @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(FrameBufferTarget.newTarget(brtf));
+
+ if (isTexturePulling()) {
+ startPulling();
+ }
+
+ Camera envcam = updateAndGetInternalCamera(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);
+
+ if (isTexturePulling()) {
+ pull(brtfbaker, brtf, 0);
+ }
+
+ brtfbaker.dispose();
+
+ if (isTexturePulling()) {
+ endPulling(brtf);
+ }
+ brtf.getImage().clearUpdateNeeded();
+
+ 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);
+
+ if (isTexturePulling()) {
+ startPulling();
+ }
+
+ for (int i = 0; i < 6; i++) {
+ irradiancebaker.addColorTarget(
+ FrameBufferTarget.newTarget(irradiance).face(TextureCubeMap.Face.values()[i]));
+ }
+
+ Material mat = new Material(assetManager, "Common/IBL/IBLKernels.j3md");
+ mat.setBoolean("UseIrradiance", true);
+ mat.setTexture("EnvMap", envMap);
+ screen.setMaterial(mat);
+
+ for (int i = 0; i < 6; i++) {
+ irradiancebaker.setTargetIndex(i);
+
+ mat.setInt("FaceId", i);
+
+ screen.updateLogicalState(0);
+ screen.updateGeometricState();
+
+ renderManager.setCamera(updateAndGetInternalCamera(i, irradiancebaker.getWidth(), irradiancebaker.getHeight(), Vector3f.ZERO, 1, 1000), false);
+ renderManager.getRenderer().setFrameBuffer(irradiancebaker);
+ renderManager.renderGeometry(screen);
+
+ if (isTexturePulling()) {
+ pull(irradiancebaker, irradiance, i);
+ }
+ }
+
+ irradiancebaker.dispose();
+
+ if (isTexturePulling()) {
+ endPulling(irradiance);
+ }
+ irradiance.getImage().clearUpdateNeeded();
+
+ }
+
+}
\ No newline at end of file
diff --git a/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBakerLight.java b/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBakerLight.java
new file mode 100644
index 0000000000..8daa62ef40
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBakerLight.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2009-2023 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.baker;
+
+import java.nio.ByteBuffer;
+import java.util.logging.Logger;
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Caps;
+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;
+import com.jme3.texture.Texture2D;
+import com.jme3.texture.FrameBuffer.FrameBufferTarget;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.image.ColorSpace;
+import com.jme3.texture.image.ImageRaster;
+import com.jme3.util.BufferUtils;
+
+/**
+ * Fully accelerated env baker for IBL that bakes the specular map and spherical
+ * harmonics on the GPU.
+ *
+ * This is lighter on VRAM but it is not as parallelized as IBLGLEnvBaker
+ *
+ * @author Riccardo Balbo
+ */
+public class IBLGLEnvBakerLight extends IBLHybridEnvBakerLight {
+ private static final int NUM_SH_COEFFICIENT = 9;
+ private static final Logger LOG = Logger.getLogger(IBLGLEnvBakerLight.class.getName());
+
+ /**
+ * Create a new IBL env baker
+ *
+ * @param rm
+ * The render manager used to render the env scene
+ * @param am
+ * The asset manager used to load the baking shaders
+ * @param format
+ * The format of the color buffers
+ * @param depthFormat
+ * The format of the depth buffers
+ * @param env_size
+ * The size in pixels of the output environment cube map (eg.
+ * 1024)
+ * @param specular_size
+ * The size in pixels of the output specular cube map (eg. 1024)
+ */
+ public IBLGLEnvBakerLight(RenderManager rm, AssetManager am, Format format, Format depthFormat, int env_size, int specular_size) {
+ super(rm, am, format, depthFormat, env_size, specular_size);
+ }
+
+ @Override
+ public boolean isTexturePulling() {
+ return this.texturePulling;
+ }
+
+ @Override
+ public void bakeSphericalHarmonicsCoefficients() {
+ Box boxm = new Box(1, 1, 1);
+ Geometry screen = new Geometry("BakeBox", boxm);
+
+ Material mat = new Material(assetManager, "Common/IBLSphH/IBLSphH.j3md");
+ mat.setTexture("Texture", envMap);
+ mat.setVector2("Resolution", new Vector2f(envMap.getImage().getWidth(), envMap.getImage().getHeight()));
+ screen.setMaterial(mat);
+
+ float remapMaxValue = 0;
+ Format format = Format.RGBA32F;
+ if (!renderManager.getRenderer().getCaps().contains(Caps.FloatColorBufferRGBA)) {
+ LOG.warning("Float textures not supported, using RGB8 instead. This may cause accuracy issues.");
+ format = Format.RGBA8;
+ remapMaxValue = 0.05f;
+ }
+
+ if (remapMaxValue > 0) {
+ mat.setFloat("RemapMaxValue", remapMaxValue);
+ } else {
+ mat.clearParam("RemapMaxValue");
+ }
+
+ Texture2D shCoefTx[] = { new Texture2D(NUM_SH_COEFFICIENT, 1, 1, format), new Texture2D(NUM_SH_COEFFICIENT, 1, 1, format) };
+
+ FrameBuffer shbaker[] = { new FrameBuffer(NUM_SH_COEFFICIENT, 1, 1), new FrameBuffer(NUM_SH_COEFFICIENT, 1, 1) };
+ shbaker[0].setSrgb(false);
+ shbaker[0].addColorTarget(FrameBufferTarget.newTarget(shCoefTx[0]));
+
+ shbaker[1].setSrgb(false);
+ shbaker[1].addColorTarget(FrameBufferTarget.newTarget(shCoefTx[1]));
+
+ int renderOnT = -1;
+
+ for (int faceId = 0; faceId < 6; faceId++) {
+ if (renderOnT != -1) {
+ int s = renderOnT;
+ renderOnT = renderOnT == 0 ? 1 : 0;
+ mat.setTexture("ShCoef", shCoefTx[s]);
+ mat.setInt("FaceId", faceId);
+ } else {
+ renderOnT = 0;
+ }
+
+ screen.updateLogicalState(0);
+ screen.updateGeometricState();
+
+ renderManager.setCamera(updateAndGetInternalCamera(0, shbaker[renderOnT].getWidth(), shbaker[renderOnT].getHeight(), Vector3f.ZERO, 1, 1000), false);
+ renderManager.getRenderer().setFrameBuffer(shbaker[renderOnT]);
+ renderManager.renderGeometry(screen);
+ }
+
+ ByteBuffer shCoefRaw = BufferUtils.createByteBuffer(NUM_SH_COEFFICIENT * 1 * (shbaker[renderOnT].getColorTarget().getFormat().getBitsPerPixel() / 8));
+ renderManager.getRenderer().readFrameBufferWithFormat(shbaker[renderOnT], shCoefRaw, shbaker[renderOnT].getColorTarget().getFormat());
+ shCoefRaw.rewind();
+
+ Image img = new Image(format, NUM_SH_COEFFICIENT, 1, shCoefRaw, ColorSpace.Linear);
+ ImageRaster imgr = ImageRaster.create(img);
+
+ shCoef = new Vector3f[NUM_SH_COEFFICIENT];
+ float weightAccum = 0.0f;
+
+ for (int i = 0; i < shCoef.length; i++) {
+ ColorRGBA c = imgr.getPixel(i, 0);
+ shCoef[i] = new Vector3f(c.r, c.g, c.b);
+ if (weightAccum == 0) weightAccum = c.a;
+ else if (weightAccum != c.a) {
+ LOG.warning("SH weight is not uniform, this may cause issues.");
+ }
+
+ }
+
+ if (remapMaxValue > 0) weightAccum /= remapMaxValue;
+
+ for (int i = 0; i < NUM_SH_COEFFICIENT; ++i) {
+ if (remapMaxValue > 0) shCoef[i].divideLocal(remapMaxValue);
+ shCoef[i].multLocal(4.0f * FastMath.PI / weightAccum);
+ }
+
+ img.dispose();
+
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/environment/baker/IBLHybridEnvBakerLight.java b/jme3-core/src/main/java/com/jme3/environment/baker/IBLHybridEnvBakerLight.java
new file mode 100644
index 0000000000..d72e0dc30f
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/environment/baker/IBLHybridEnvBakerLight.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2009-2023 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.baker;
+
+import java.util.Arrays;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import com.jme3.asset.AssetManager;
+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.FrameBuffer.FrameBufferTarget;
+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 bakes the specular map on the GPU and uses
+ * spherical harmonics generated on the CPU for the irradiance map.
+ *
+ * This is lighter on VRAM but uses the CPU to compute the irradiance map.
+ *
+ * @author Riccardo Balbo
+ */
+public class IBLHybridEnvBakerLight extends GenericEnvBaker implements IBLEnvBakerLight {
+ private static final Logger LOGGER = Logger.getLogger(IBLHybridEnvBakerLight.class.getName());
+ protected TextureCubeMap specular;
+ protected Vector3f[] shCoef;
+
+ /**
+ * Create a new IBL env baker
+ *
+ * @param rm
+ * The render manager used to render the env scene
+ * @param am
+ * The asset manager used to load the baking shaders
+ * @param format
+ * The format of the color buffers
+ * @param depthFormat
+ * The format of the depth buffers
+ * @param env_size
+ * The size in pixels of the output environment cube map (eg.
+ * 1024)
+ * @param specular_size
+ * The size in pixels of the output specular cube map (eg. 1024)
+ */
+ public IBLHybridEnvBakerLight(RenderManager rm, AssetManager am, Format format, Format depthFormat, int env_size, int specular_size) {
+ super(rm, am, format, depthFormat, env_size);
+
+ specular = new TextureCubeMap(specular_size, specular_size, format);
+ specular.setWrap(WrapMode.EdgeClamp);
+ specular.setMagFilter(MagFilter.Bilinear);
+ specular.setMinFilter(MinFilter.Trilinear);
+ specular.getImage().setColorSpace(ColorSpace.Linear);
+
+ int nbMipMaps = (int) (Math.log(specular_size) / Math.log(2) + 1);
+ nbMipMaps = limitMips(nbMipMaps, specular.getImage().getWidth(), specular.getImage().getHeight(), rm);
+
+ 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);
+ specular.getImage().setMipmapsGenerated(true);
+
+ }
+
+ @Override
+ public boolean isTexturePulling() { // always pull textures from gpu
+ return true;
+ }
+
+ private void bakeSpecularIBL(int mip, float roughness, Material mat, Geometry screen) throws Exception {
+ mat.setFloat("Roughness", roughness);
+
+ int mipWidth = (int) (specular.getImage().getWidth() * FastMath.pow(0.5f, mip));
+ int mipHeight = (int) (specular.getImage().getHeight() * FastMath.pow(0.5f, mip));
+
+ FrameBuffer specularbakers[] = new FrameBuffer[6];
+ for (int i = 0; i < 6; i++) {
+ specularbakers[i] = new FrameBuffer(mipWidth, mipHeight, 1);
+ specularbakers[i].setSrgb(false);
+ specularbakers[i].addColorTarget(FrameBufferTarget.newTarget(specular).level(mip).face(i));
+ specularbakers[i].setMipMapsGenerationHint(false);
+ }
+
+ for (int i = 0; i < 6; i++) {
+ FrameBuffer specularbaker = specularbakers[i];
+ mat.setInt("FaceId", i);
+
+ screen.updateLogicalState(0);
+ screen.updateGeometricState();
+
+ renderManager.setCamera(updateAndGetInternalCamera(i, specularbaker.getWidth(), specularbaker.getHeight(), Vector3f.ZERO, 1, 1000), false);
+ renderManager.getRenderer().setFrameBuffer(specularbaker);
+ renderManager.renderGeometry(screen);
+
+ if (isTexturePulling()) {
+ pull(specularbaker, specular, i);
+ }
+
+ }
+ for (int i = 0; i < 6; i++) {
+ specularbakers[i].dispose();
+ }
+ }
+
+ @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", envMap);
+ screen.setMaterial(mat);
+
+ if (isTexturePulling()) {
+ startPulling();
+ }
+
+ int mip = 0;
+ for (; mip < specular.getImage().getMipMapSizes().length; mip++) {
+ try {
+ float roughness = (float) mip / (float) (specular.getImage().getMipMapSizes().length - 1);
+ bakeSpecularIBL(mip, roughness, mat, screen);
+ } catch (Exception e) {
+ LOGGER.log(Level.WARNING, "Error while computing mip level " + mip, e);
+ break;
+ }
+ }
+
+ if (mip < specular.getImage().getMipMapSizes().length) {
+
+ int[] sizes = specular.getImage().getMipMapSizes();
+ sizes = Arrays.copyOf(sizes, mip);
+ specular.getImage().setMipMapSizes(sizes);
+ specular.getImage().setMipmapsGenerated(true);
+ if (sizes.length <= 1) {
+ try {
+ LOGGER.log(Level.WARNING, "Workaround driver BUG: only one mip level available, regenerate it with higher roughness (shiny fix)");
+ bakeSpecularIBL(0, 1f, mat, screen);
+ } catch (Exception e) {
+ LOGGER.log(Level.FINE, "Error while recomputing mip level 0", e);
+ }
+ }
+ }
+
+ if (isTexturePulling()) {
+ endPulling(specular);
+ }
+ specular.getImage().clearUpdateNeeded();
+
+ }
+
+ @Override
+ public TextureCubeMap getSpecularIBL() {
+ return specular;
+ }
+
+ @Override
+ public void bakeSphericalHarmonicsCoefficients() {
+ shCoef = EnvMapUtils.getSphericalHarmonicsCoefficents(getEnvMap());
+ }
+
+ @Override
+ public Vector3f[] getSphericalHarmonicsCoefficients() {
+ return shCoef;
+ }
+}
\ No newline at end of file
diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java
index 5969a1d5bd..aa50608acc 100644
--- a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java
+++ b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java
@@ -66,6 +66,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.function.Predicate;
import java.util.logging.Logger;
/**
@@ -104,6 +105,7 @@ public class RenderManager {
private TechniqueDef.LightMode preferredLightMode = TechniqueDef.LightMode.MultiPass;
private int singlePassLightBatchSize = 1;
private MatParamOverride boundDrawBufferId=new MatParamOverride(VarType.Int,"BoundDrawBuffer",0);
+ private Predicate renderFilter;
/**
@@ -626,6 +628,7 @@ public void updateUniformBindings(Shader shader) {
* @see com.jme3.material.Material#render(com.jme3.scene.Geometry, com.jme3.renderer.RenderManager)
*/
public void renderGeometry(Geometry geom) {
+ if (renderFilter != null && !renderFilter.test(geom)) return;
this.renderer.pushDebugGroup(geom.getName());
if (geom.isIgnoreTransform()) {
setWorldMatrix(Matrix4f.IDENTITY);
@@ -1336,4 +1339,24 @@ public void setPassDrawBufferTargetIdToShaders(boolean v) {
this.forcedOverrides.remove(boundDrawBufferId);
}
}
+ /**
+ * Set a render filter. Every geometry will be tested against this filter
+ * before rendering and will only be rendered if the filter returns true.
+ *
+ * @param filter
+ */
+ public void setRenderFilter(Predicate filter) {
+ renderFilter = filter;
+ }
+
+ /**
+ * Returns the render filter that the RenderManager is currently using
+ *
+ * @param filter
+ * the render filter
+ */
+ public Predicate getRenderFilter() {
+ return renderFilter;
+ }
+
}
diff --git a/jme3-core/src/main/resources/Common/IBL/IBLKernels.frag b/jme3-core/src/main/resources/Common/IBL/IBLKernels.frag
new file mode 100644
index 0000000000..7f32c0ae01
--- /dev/null
+++ b/jme3-core/src/main/resources/Common/IBL/IBLKernels.frag
@@ -0,0 +1,109 @@
+/**
+* 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/ShaderLib/GLSLCompat.glsllib"
+#import "Common/IBL/Math.glsl"
+
+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++){
+ vec4 Xi = Hammersley(i, SAMPLE_COUNT);
+ vec3 H = ImportanceSampleGGX(Xi, m_Roughness, N);
+ 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;
+
+ // float a2 = m_Roughness;
+ float a2 = m_Roughness * m_Roughness; // jme impl, why?
+ a2 *= a2;
+
+ const uint SAMPLE_COUNT = 1024u;
+ float totalWeight = 0.0;
+ vec3 prefilteredColor = vec3(0.0);
+ for(uint i = 0u; i < SAMPLE_COUNT; ++i) {
+ vec4 Xi = Hammersley(i, SAMPLE_COUNT);
+ vec3 H = ImportanceSampleGGX(Xi, a2, N);
+ float VoH = dot(V,H);
+ vec3 L = normalize(2.0 * VoH * H - V);
+ float NdotL = max(dot(N, L), 0.0);
+ if(NdotL > 0.0) {
+ // TODO: use mipmap
+ 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
+}
\ No newline at end of file
diff --git a/jme3-core/src/main/resources/Common/IBL/IBLKernels.j3md b/jme3-core/src/main/resources/Common/IBL/IBLKernels.j3md
new file mode 100644
index 0000000000..147c1bb8a4
--- /dev/null
+++ b/jme3-core/src/main/resources/Common/IBL/IBLKernels.j3md
@@ -0,0 +1,39 @@
+MaterialDef IBLKernels {
+
+ MaterialParameters {
+ Int BoundDrawBuffer
+ TextureCubeMap EnvMap -LINEAR
+ Float Roughness
+ Int FaceId : 0
+ Boolean UseBRDF
+ Boolean UseIrradiance
+ Boolean UseSpecularIBL
+ }
+
+ Technique {
+
+ VertexShader GLSL300 GLSL150 : Common/IBL/IBLKernels.vert
+ FragmentShader GLSL300 GLSL150 : Common/IBL/IBLKernels.frag
+
+ WorldParameters {
+ WorldMatrix
+ ViewMatrix
+ ProjectionMatrix
+ }
+
+ RenderState {
+ DepthWrite Off
+ DepthTest Off
+ DepthFunc Equal
+ FaceCull Off
+ }
+
+ Defines {
+ BOUND_DRAW_BUFFER: BoundDrawBuffer
+ BRDF:UseBRDF
+ IRRADIANCE: UseIrradiance
+ SIBL: UseSpecularIBL
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/jme3-core/src/main/resources/Common/IBL/IBLKernels.vert b/jme3-core/src/main/resources/Common/IBL/IBLKernels.vert
new file mode 100644
index 0000000000..aff3d7eae6
--- /dev/null
+++ b/jme3-core/src/main/resources/Common/IBL/IBLKernels.vert
@@ -0,0 +1,31 @@
+#import "Common/ShaderLib/GLSLCompat.glsllib"
+
+/**
+* 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
+}
\ No newline at end of file
diff --git a/jme3-core/src/main/resources/Common/IBL/Math.glsl b/jme3-core/src/main/resources/Common/IBL/Math.glsl
new file mode 100644
index 0000000000..e7e57240bd
--- /dev/null
+++ b/jme3-core/src/main/resources/Common/IBL/Math.glsl
@@ -0,0 +1,95 @@
+/**
+* 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
+}
+
+vec4 Hammersley(uint i, uint N){
+ vec4 store=vec4(0);
+ store.x = float(i) / float(N);
+ store.y = RadicalInverse_VdC(i);
+
+ float phi = 2.0 * PI *store.x;
+ store.z = cos(phi);
+ store.w = sin(phi);
+
+ return store;
+}
+
+// float VanDerCorput(uint n, uint base){
+// float invBase = 1.0 / float(base);
+// float denom = 1.0;
+// float result = 0.0;
+
+// for(uint i = 0u; i < 32u; ++i)
+// {
+// if(n > 0u)
+// {
+// denom = mod(float(n), 2.0);
+// result += denom * invBase;
+// invBase = invBase / 2.0;
+// n = uint(float(n) / 2.0);
+// }
+// }
+
+// return result;
+// }
+
+// vec2 Hammersley(uint i, uint N){
+// return vec2(float(i)/float(N), VanDerCorput(i, 2u));
+// }
+
+
+vec3 ImportanceSampleGGX(vec4 Xi, float a2, vec3 N){
+
+ float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a2 - 1.0) * Xi.y));
+ float sinTheta = sqrt(1.0 - cosTheta*cosTheta);
+
+ // from spherical coordinates to cartesian coordinates
+ vec3 H;
+ H.x = Xi.z * sinTheta;
+ H.y = Xi.w * 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;
+}
+
\ No newline at end of file
diff --git a/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.frag b/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.frag
new file mode 100644
index 0000000000..6e83dcfde8
--- /dev/null
+++ b/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.frag
@@ -0,0 +1,191 @@
+/**
+
+* - Riccardo Balbo
+*/
+#import "Common/ShaderLib/GLSLCompat.glsllib"
+#import "Common/IBL/Math.glsl"
+
+// #define NUM_SH_COEFFICIENT 9
+#ifndef PI
+ #define PI 3.1415926535897932384626433832795
+#endif
+
+in vec2 TexCoords;
+in vec3 LocalPos;
+
+
+uniform samplerCube m_Texture;
+#ifdef SH_COEF
+ uniform sampler2D m_ShCoef;
+#endif
+uniform vec2 m_Resolution;
+uniform int m_FaceId;
+
+const float sqrtPi = sqrt(PI);
+const float sqrt3Pi = sqrt(3. / PI);
+const float sqrt5Pi = sqrt(5. / PI);
+const float sqrt15Pi = sqrt(15. / PI);
+
+#ifdef REMAP_MAX_VALUE
+ uniform float m_RemapMaxValue;
+#endif
+
+
+vec3 getVectorFromCubemapFaceTexCoord(float x, float y, float mapSize, int face) {
+ float u;
+ float v;
+
+ /* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
+ * (+ 0.5f is for texel center addressing) */
+ u = (2.0 * (x + 0.5) / mapSize) - 1.0;
+ v = (2.0 * (y + 0.5) / mapSize) - 1.0;
+
+
+ // Warp texel centers in the proximity of the edges.
+ float a = pow(mapSize, 2.0) / pow(mapSize - 1., 3.0);
+
+ u = a * pow(u, 3.) + u;
+ v = a * pow(v, 3.) + v;
+ //compute vector depending on the face
+ // Code from Nvtt : https://github.com/castano/nvidia-texture-tools/blob/master/src/nvtt/CubeSurface.cpp#L101
+ vec3 o =vec3(0);
+ switch(face) {
+ case 0:
+ o= normalize(vec3(1., -v, -u));
+ break;
+ case 1:
+ o= normalize(vec3(-1., -v, u));
+ break;
+ case 2:
+ o= normalize(vec3(u, 1., v));
+ break;
+ case 3:
+ o= normalize(vec3(u, -1., -v));
+ break;
+ case 4:
+ o= normalize(vec3(u, -v, 1.));
+ break;
+ case 5:
+ o= normalize(vec3(-u, -v, -1.));
+ break;
+ }
+
+ return o;
+}
+
+float atan2(in float y, in float x) {
+ bool s = (abs(x) > abs(y));
+ return mix(PI / 2.0 - atan(x, y), atan(y, x), s);
+}
+
+float areaElement(float x, float y) {
+ return atan2(x * y, sqrt(x * x + y * y + 1.));
+}
+
+float getSolidAngleAndVector(float x, float y, float mapSize, int face, out vec3 store) {
+ /* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
+ (+ 0.5f is for texel center addressing) */
+ float u = (2.0 * (x + 0.5) / mapSize) - 1.0;
+ float v = (2.0 * (y + 0.5) / mapSize) - 1.0;
+
+ store = getVectorFromCubemapFaceTexCoord(x, y, mapSize, face);
+
+ /* 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.0 / 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);
+}
+
+void evalShBasis(vec3 texelVect, int i, out float shDir) {
+ float xV = texelVect.x;
+ float yV = texelVect.y;
+ float zV = texelVect.z;
+
+ float x2 = xV * xV;
+ float y2 = yV * yV;
+ float z2 = zV * zV;
+
+ if(i==0) shDir = (1. / (2. * sqrtPi));
+ else if(i==1) shDir = -(sqrt3Pi * yV) / 2.;
+ else if(i == 2) shDir = (sqrt3Pi * zV) / 2.;
+ else if(i == 3) shDir = -(sqrt3Pi * xV) / 2.;
+ else if(i == 4) shDir = (sqrt15Pi * xV * yV) / 2.;
+ else if(i == 5) shDir = -(sqrt15Pi * yV * zV) / 2.;
+ else if(i == 6) shDir = (sqrt5Pi * (-1. + 3. * z2)) / 4.;
+ else if(i == 7) shDir = -(sqrt15Pi * xV * zV) / 2.;
+ else shDir = sqrt15Pi * (x2 - y2) / 4.;
+}
+
+vec3 pixelFaceToV(int faceId, float pixelX, float pixelY, float cubeMapSize) {
+ vec2 normalizedCoords = vec2((2.0 * pixelX + 1.0) / cubeMapSize, (2.0 * pixelY + 1.0) / cubeMapSize);
+
+ vec3 direction;
+ if(faceId == 0) {
+ direction = vec3(1.0, -normalizedCoords.y, -normalizedCoords.x);
+ } else if(faceId == 1) {
+ direction = vec3(-1.0, -normalizedCoords.y, normalizedCoords.x);
+ } else if(faceId == 2) {
+ direction = vec3(normalizedCoords.x, 1.0, normalizedCoords.y);
+ } else if(faceId == 3) {
+ direction = vec3(normalizedCoords.x, -1.0, -normalizedCoords.y);
+ } else if(faceId == 4) {
+ direction = vec3(normalizedCoords.x, -normalizedCoords.y, 1.0);
+ } else if(faceId == 5) {
+ direction = vec3(-normalizedCoords.x, -normalizedCoords.y, -1.0);
+ }
+
+ return normalize(direction);
+}
+
+void sphKernel() {
+ int width = int(m_Resolution.x);
+ int height = int(m_Resolution.y);
+ vec3 texelVect=vec3(0);
+ float shDir=0.;
+ float weight=0.;
+ vec4 color=vec4(0);
+
+ int i=int(gl_FragCoord.x);
+
+ #ifdef SH_COEF
+ vec4 r=texelFetch(m_ShCoef, ivec2(i, 0), 0);
+ vec3 shCoef=r.rgb;
+ float weightAccum = r.a;
+ #else
+ vec3 shCoef=vec3(0.0);
+ float weightAccum = 0.0;
+ #endif
+
+ for(int y = 0; y < height; y++) {
+ for(int x = 0; x < width; x++) {
+ weight = getSolidAngleAndVector(float(x), float(y), float(width), m_FaceId, texelVect);
+ evalShBasis(texelVect, i, shDir);
+ color = texture(m_Texture, texelVect);
+ shCoef.x = (shCoef.x + color.r * shDir * weight);
+ shCoef.y = (shCoef.y + color.g * shDir * weight);
+ shCoef.z = (shCoef.z + color.b * shDir * weight);
+ weightAccum += weight;
+ }
+ }
+
+
+
+ #ifdef REMAP_MAX_VALUE
+ shCoef.xyz=shCoef.xyz*m_RemapMaxValue;
+ weightAccum=weightAccum*m_RemapMaxValue;
+ #endif
+
+ outFragColor = vec4(shCoef.xyz,weightAccum);
+
+}
+
+void main() {
+ sphKernel();
+}
\ No newline at end of file
diff --git a/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.j3md b/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.j3md
new file mode 100644
index 0000000000..eaafd2e108
--- /dev/null
+++ b/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.j3md
@@ -0,0 +1,34 @@
+MaterialDef IBLSphH {
+
+ MaterialParameters {
+ Int BoundDrawBuffer
+ TextureCubeMap Texture -LINEAR
+ Int FaceId : 0
+ Texture2D ShCoef -LINEAR
+ Vector2 Resolution
+ Float RemapMaxValue
+ }
+
+ Technique {
+
+ VertexShader GLSL300 GLSL150 : Common/IBLSphH/IBLSphH.vert
+ FragmentShader GLSL300 GLSL150 : Common/IBLSphH/IBLSphH.frag
+
+ WorldParameters {
+ }
+
+ RenderState {
+ DepthWrite Off
+ DepthTest Off
+ DepthFunc Equal
+ FaceCull Off
+ }
+
+ Defines {
+ BOUND_DRAW_BUFFER: BoundDrawBuffer
+ REMAP_MAX_VALUE: RemapMaxValue
+ SH_COEF: ShCoef
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.vert b/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.vert
new file mode 100644
index 0000000000..f7a3c82655
--- /dev/null
+++ b/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.vert
@@ -0,0 +1,18 @@
+#import "Common/ShaderLib/GLSLCompat.glsllib"
+
+/**
+*- Riccardo Balbo
+*/
+in vec3 inPosition;
+in vec2 inTexCoord;
+
+out vec2 TexCoords;
+out vec3 LocalPos;
+
+
+void main() {
+ LocalPos = inPosition.xyz;
+ TexCoords = inTexCoord.xy;
+ vec2 pos = inPosition.xy * 2.0 - 1.0;
+ gl_Position = vec4(pos, 0.0, 1.0);
+}
\ No newline at end of file
diff --git a/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java b/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java
index 7de1e456c7..50fb4a482b 100644
--- a/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java
+++ b/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java
@@ -34,6 +34,7 @@
import com.jme3.app.SimpleApplication;
import com.jme3.environment.EnvironmentCamera;
import com.jme3.environment.LightProbeFactory;
+import com.jme3.environment.FastLightProbeFactory;
import com.jme3.environment.generation.JobProgressAdapter;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.environment.util.LightsDebugState;
@@ -59,7 +60,8 @@
* @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 void simpleInitApp() {
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 void simpleUpdate(float tpf) {
if (frame == 2) {
modelNode.removeFromParent();
- final LightProbe probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter() {
+ 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 = FastLightProbeFactory.makeProbe(renderManager, assetManager, RESOLUTION, Vector3f.ZERO, 1f, 1000f, rootNode);
+ } else {
+ probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter() {
+
+ @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);
diff --git a/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRSimple.java b/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRSimple.java
new file mode 100644
index 0000000000..4070c2a401
--- /dev/null
+++ b/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRSimple.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2009-2023 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package jme3test.light.pbr;
+
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.environment.EnvironmentProbeControl;
+import com.jme3.input.ChaseCamera;
+import com.jme3.material.Material;
+import com.jme3.math.FastMath;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.util.SkyFactory;
+import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
+
+/**
+ * A simpler PBR example that uses EnvironmentProbeControl to bake the environment
+ */
+public class TestPBRSimple extends SimpleApplication {
+ private boolean REALTIME_BAKING = false;
+
+ public static void main(String[] args) {
+ new TestPBRSimple().start();
+ }
+
+ @Override
+ public void simpleInitApp() {
+
+
+ Geometry model = (Geometry) assetManager.loadModel("Models/Tank/tank.j3o");
+ MikktspaceTangentGenerator.generate(model);
+
+ Material pbrMat = assetManager.loadMaterial("Models/Tank/tank.j3m");
+ model.setMaterial(pbrMat);
+ rootNode.attachChild(model);
+
+ ChaseCamera chaseCam = new ChaseCamera(cam, model, inputManager);
+ chaseCam.setDragToRotate(true);
+ chaseCam.setMinVerticalRotation(-FastMath.HALF_PI);
+ chaseCam.setMaxDistance(1000);
+ chaseCam.setSmoothMotion(true);
+ chaseCam.setRotationSensitivity(10);
+ chaseCam.setZoomSensitivity(5);
+ flyCam.setEnabled(false);
+
+ Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap);
+ rootNode.attachChild(sky);
+
+ // Create baker control
+ EnvironmentProbeControl envProbe=new EnvironmentProbeControl(assetManager,256);
+ rootNode.addControl(envProbe);
+
+ // Tag the sky, only the tagged spatials will be rendered in the env map
+ envProbe.tag(sky);
+
+
+
+ }
+
+
+ float lastBake = 0;
+ @Override
+ public void simpleUpdate(float tpf) {
+ if (REALTIME_BAKING) {
+ lastBake += tpf;
+ if (lastBake > 1.4f) {
+ rootNode.getControl(EnvironmentProbeControl.class).rebake();
+ lastBake = 0;
+ }
+ }
+ }
+}
\ No newline at end of file